@@ -4,10 +4,6 @@ open System.Numerics
44open Prime
55open Nu
66
7- // this determines what state the game is in. To learn about ImSim in Nu, see -
8- // https://github.com/bryanedds/Nu/wiki/Immediate-Mode-for-Games-via-ImSim
9- type GameState = Enclosure = 0 | Racecourse = 1
10-
117// this extends the Game API to expose GameState as a property.
128[<AutoOpen>]
139module Physics2DExtensions =
@@ -25,17 +21,21 @@ type Physics2DDispatcher () =
2521
2622 // here we define default property values
2723 static member Properties =
28- [ define Game.GameState GameState.Enclosure
29- define Game.CarAcceleration 0 f]
24+ [ define Game.CarAcceleration 0 f]
25+
26+ override this.Register ( game , world ) =
27+ game.SetDesiredScreen ( Desire Simulants.EnclosureScreen) world
3028
3129 // here we define the game's top-level behavior
3230 override this.Process ( game , world ) =
3331
3432 // declare Enclosure screen
3533 let behavior = Dissolve ( Constants.Dissolve.Default, None)
3634 let _ =
37- World.beginScreen< DemoScreenDispatcher> ( nameof GameState.Enclosure) ( game.GetGameState world = GameState.Enclosure) behavior
38- [ Screen.CameraPositionDefault .= CameraAbsolute ( v2 60 f 0 f)] world
35+ World.beginScreen< DemoScreenDispatcher> Simulants.EnclosureScreen.Name
36+ ( Simulants.EnclosureScreen.GetExists world && Simulants.EnclosureScreen.GetSelected world) behavior
37+ [ Screen.CameraPositionDefault .= CameraAbsolute ( v2 60 f 0 f)
38+ Screen.NextScreen .= Desire Simulants.RacecourseScreen] world
3939 World.beginGroup Simulants.SceneGroup [] world
4040
4141 // define border
@@ -98,11 +98,22 @@ type Physics2DDispatcher () =
9898 // declare Racecourse screen
9999 let behavior = Dissolve ( Constants.Dissolve.Default, None)
100100 let _ =
101- World.beginScreen< DemoScreenDispatcher> ( nameof GameState.Racecourse) ( game.GetGameState world = GameState.Racecourse) behavior
102- [ Screen.CameraPositionDefault .= CameraTracking ( Relation.makeFromString " Car" )] world
101+ World.beginScreen< DemoScreenDispatcher> Simulants.RacecourseScreen.Name
102+ ( Simulants.RacecourseScreen.GetExists world && Simulants.RacecourseScreen.GetSelected world) behavior
103+ [ Screen.CameraPositionDefault .= CameraTracking ( Relation.makeFromString $" {Simulants.SceneGroup}/Car" )
104+ Screen.NextScreen .= Desire Simulants.EnclosureScreen] world
103105 World.beginGroup Simulants.SceneGroup [] world
104106
107+ World.doStaticSprite Simulants.BorderEntity
108+ [ Entity.Size .= v3 500 f 350 f 0 f
109+ Entity.Position .= v3 - 60 f 0 f 0 f
110+ // Absolute positioning makes this display at the same screen location regardless of the eye position.
111+ Entity.Absolute .= true
112+ Entity.Elevation .= - 1 f
113+ Entity.StaticImage .= Assets.Gameplay.SkyBoxFront] world
114+
105115 // define racecourse
116+ let objectScale = 16 f
106117 let racecourse =
107118 [| v2 - 20 f 5 f
108119 v2 - 20 f 0 f
@@ -137,84 +148,94 @@ type Physics2DDispatcher () =
137148 v2 270 f - 10 f
138149 v2 270 f 0 f
139150 v2 310 f 0 f
140- v2 310 f 5 f|] |> Array.map ( fun p -> p.V3 * Constants.Engine.Entity2dSizeDefault )
151+ v2 310 f 5 f|] |> Array.map ( fun p -> p.V3 * objectScale )
141152 let _ =
142153 World.doBlock2d " Racecourse"
143154 [ Entity.Size .= v3 1 f 1 f 0 f
144- Entity.BodyShape .= ContourShape { Links = racecourse; Closed = false ; TransformOpt = None; PropertiesOpt = None }]
155+ Entity.BodyShape .= ContourShape { Links = racecourse; Closed = false ; TransformOpt = None; PropertiesOpt = None }
156+ // Don't let the car wheels fall through the ground
157+ Entity.CollisionDetection .= Continuous]
145158 world
146159 for ( p1, p2) in Array.pairwise racecourse do
147160 World.doStaticSprite $" Racecourse {p1} -> {p2}"
148161 [ Entity.Position .= ( p1 + p2) / 2 f
149- Entity.Size .= v3 (( p2 - p1) .Magnitude / 2 f ) 2 f 0 f
162+ Entity.Size .= v3 ( p2 - p1) .Magnitude 2 f 0 f
150163 Entity.Rotation .= Quaternion.CreateLookAt2d ( p2 - p1) .V2
151164 Entity.StaticImage .= Assets.Default.Black] world
152165
153166 // define car
154- let carMaxSpeed = 50 f
167+ let carMaxSpeed = 50 f * objectScale
168+ let carMass = 10 f
155169 let carSpawnPosition = v3 0 f 30 f 0 f
156170 let carPoints = [|
157- v3 - 2.5 f - 0.08 f 0 f
158- v3 - 2.375 f 0.46 f 0 f
159- v3 - 0.58 f 0.92 f 0 f
160- v3 0.46 f 0.92 f 0 f
161- v3 2.5 f 0.17 f 0 f
162- v3 2.5 f - 0.205 f 0 f
163- v3 2.3 f - 0.33 f 0 f
164- v3 - 2.25 f - 0.35 f 0 f|]
165- let carBottomLeft = carPoints |> Array.reduce ( fun a b -> v3 ( min a.X b.X) ( min a.Y b.Y) 0 f)
166- let carTopRight = carPoints |> Array.reduce ( fun a b -> v3 ( max a.X b.X) ( max a.Y b.Y) 0 f)
167- let carSize = carTopRight - carBottomLeft
168- let carGetRelativePosition p = ( p - carBottomLeft) / carSize - v3Dup 0.5 f
171+ v2 - 2.5 f 0.92 f
172+ v2 - 2.375 f 1.46 f
173+ v2 - 0.58 f 1.92 f
174+ v2 0.46 f 1.92 f
175+ v2 2.5 f 1.17 f
176+ v2 2.5 f 0.795 f
177+ v2 2.3 f 0.67 f
178+ v2 - 2.25 f 0.65 f|]
179+ let carPointsBox = Box2.Enclose carPoints
180+ let carGetRelativePosition p = ( p - carPointsBox.Center) / carPointsBox.Size
169181 let _ =
170182 World.doBox2d " Car"
171183 [ Entity.BodyShape .= PointsShape {
172- Points = Array.map carGetRelativePosition carPoints
184+ Points = Array.map ( carGetRelativePosition >> _. V3 ) carPoints
173185 Profile = Convex
174186 TransformOpt = None
175187 PropertiesOpt = None }
176188 Entity.StaticImage .= Assets.Gameplay.Car
177189 Entity.Position .= carSpawnPosition
178- Entity.Size .= carSize * Constants.Engine.Entity2dSizeDefault
179- Entity.Substance .= Density 2 f
190+ Entity.Size .= carPointsBox.Size.V3 * objectScale
191+ Entity.Substance .= Mass carMass
192+ Entity.Friction .= 0.2 f
180193 ] world
181- for ( relation, position, density , frequency, friction, maxTorque, motorSpeed) in
182- [( " Back" , v3 - 1.709 f 0.78 f 0 f , 0.8 f, 5 f, Some 0.9 f, 20 f,
194+ for ( relation, position, mass , frequency, friction, maxTorque, motorSpeed) in
195+ [( " Back" , v2 - 1.709 f 0.78 f, 0.8 f, 5 f, 0.9 f * carMass , 20 f,
183196 let acceleration = game.GetCarAcceleration world
184197 float32 ( sign acceleration) * Math.SmoothStep( 0 f, carMaxSpeed, abs acceleration))
185- ( " Front" , v3 1.54 f 0.8 f 0 f, 1 f, 8.5 f, None, 10 f, 0 f)] do
186- let _ =
198+ ( " Front" , v2 1.54 f 0.8 f, 1 f, 8.5 f, 0.2 f, 10 f, 0 f)] do
199+ let wheelRelativePosition = ( carGetRelativePosition position * carPointsBox.Size) .V3 * objectScale
200+ let ( wheel , _ ) =
187201 World.doBall2d $" Wheel {relation}"
188202 [ Entity.StaticImage .= Assets.Gameplay.Wheel
189- Entity.Position .= carSpawnPosition + carGetRelativePosition position * Constants.Engine.Entity2dSizeDefault
190- Entity.Size .= 0.5 f * Constants.Engine.Entity2dSizeDefault
191- Entity.Substance .= Density density
192- match friction with Some f -> Entity.Friction .= f | _ -> ()
203+ Entity.Position .= carSpawnPosition + wheelRelativePosition
204+ Entity.Size .= v3Dup 0.5 f * objectScale
205+ Entity.Substance .= Mass ( mass * carMass )
206+ Entity.Friction .= friction
193207 ] world
208+ if world.ContextScreen.GetSelected world then
209+ World.applyBodyTorque
210+ ( v3 ( if abs motorSpeed >= carMaxSpeed * 0.06 f then min maxTorque motorSpeed else 0 f) 0 f 0 f)
211+ wheel world
194212 let _ =
195213 World.doBodyJoint2d $" Wheel {relation} joint"
196214 [ Entity.BodyJoint .= TwoBodyJoint2d {
197215 CreateTwoBodyJoint = fun _ _ car wheel ->
216+ // NOTE: We cannot use MotorEnabled / MotorSpeed / MaxMotorTorque of Aether's WheelJoint
217+ // without resetting the wheel suspension position each frame! Therefore, we apply motor
218+ // torque ourselves.
198219 nkast.Aether.Physics2D.Dynamics.Joints.WheelJoint ( car, wheel, wheel.Position, new _( 0 f, 1.2 f), true ,
199- Frequency = frequency, DampingRatio = 0.85 f, MaxMotorTorque = maxTorque,
200- MotorSpeed = motorSpeed, MotorEnabled = ( abs motorSpeed >= carMaxSpeed * 0.06 f)) }
220+ Frequency = frequency, DampingRatio = 0.85 f) }
201221 Entity.BodyJointTarget .= Relation.makeFromString " ^/Car"
202222 Entity.BodyJointTarget2Opt .= Some ( Relation.makeFromString $" ^/Wheel {relation}" )
203-
223+ Entity.CollideConnected .= false
204224 ] world
205225 ()
206226
207227 // Keyboard controls for car
208- let isAJustReleased =
209- World.doSubscription " SubscribeARelease" Game.KeyboardKeyChangeEvent world
210- |> Seq.exists ( fun buttonChange -> buttonChange.KeyboardKey = KeyboardKey.A && not buttonChange.Down)
211- if World.isKeyboardKeyDown KeyboardKey.A world then
212- game.CarAcceleration.Map ( fun a -> min ( a + 2.0 f * world.ClockTime) 1 f) world
213- elif World.isKeyboardKeyDown KeyboardKey.D world then
214- game.CarAcceleration.Map ( fun a -> max ( a - 2.0 f * world.ClockTime) - 1 f) world
215- elif World.isKeyboardKeyPressed KeyboardKey.D world || isAJustReleased then
216- game.SetCarAcceleration 0 f world
217- else game.CarAcceleration.Map ( fun a -> a - float32 ( sign a) * 2.0 f * world.ClockTime) world
228+ if world.ContextScreen.GetSelected world then
229+ let isAJustReleased =
230+ World.doSubscription " SubscribeARelease" Game.KeyboardKeyChangeEvent world
231+ |> Seq.exists ( fun buttonChange -> buttonChange.KeyboardKey = KeyboardKey.A && not buttonChange.Down)
232+ if World.isKeyboardKeyDown KeyboardKey.A world then
233+ game.CarAcceleration.Map ( fun a -> min ( a + 2.0 f * world.ClockTime) 1 f) world
234+ elif World.isKeyboardKeyDown KeyboardKey.D world then
235+ game.CarAcceleration.Map ( fun a -> max ( a - 2.0 f * world.ClockTime) - 1 f) world
236+ elif World.isKeyboardKeyPressed KeyboardKey.D world || isAJustReleased then
237+ game.SetCarAcceleration 0 f world
238+ else game.CarAcceleration.Map ( fun a -> a - float32 ( sign a) * 2.0 f * world.ClockTime) world
218239
219240 if World.doButton Simulants.BackEntity [] world && world.Unaccompanied then World.exit world
220241 World.endGroup world
0 commit comments