66
77// this determines what state the game is in. To learn about ImSim in Nu, see -
88// https://github.com/bryanedds/Nu/wiki/Immediate-Mode-for-Games-via-ImSim
9- type GameState =
10- | Enclosure
9+ type GameState = Enclosure = 0 | Racecourse = 1
1110
1211// this extends the Game API to expose GameState as a property.
1312[<AutoOpen>]
@@ -16,47 +15,210 @@ module Physics2DExtensions =
1615 member this.GetGameState world : GameState = this.Get ( nameof Game.GameState) world
1716 member this.SetGameState ( value : GameState ) world = this.Set ( nameof Game.GameState) value world
1817 member this.GameState = lens ( nameof Game.GameState) this this.GetGameState this.SetGameState
19-
18+ member this.GetCarAcceleration world : float32 = this.Get ( nameof Game.CarAcceleration) world
19+ member this.SetCarAcceleration ( value : float32 ) world = this.Set ( nameof Game.CarAcceleration) value world
20+ member this.CarAcceleration = lens ( nameof Game.CarAcceleration) this this.GetCarAcceleration this.SetCarAcceleration
21+
2022// this is the dispatcher that customizes the top-level behavior of our game.
2123type Physics2DDispatcher () =
2224 inherit GameDispatcherImSim ()
2325
2426 // here we define default property values
2527 static member Properties =
26- [ define Game.GameState Enclosure]
27-
28- // here we define game initialization
29- override _.Register ( _ , world ) =
30- World.setEye2dCenter ( v2 60 f 0 f) world
28+ [ define Game.GameState GameState.Enclosure
29+ define Game.CarAcceleration 0 f]
3130
3231 // here we define the game's top-level behavior
3332 override this.Process ( game , world ) =
3433
35- // declare PlaygroundDispatcher screen
34+ // declare Enclosure screen
3635 let behavior = Dissolve ( Constants.Dissolve.Default, None)
37- let _ = World.beginScreen< EnclosureDispatcher> Simulants.Enclosure.Name ( game.GetGameState world = Enclosure) behavior [] world
36+ let _ =
37+ World.beginScreen< DemoScreenDispatcher> ( nameof GameState.Enclosure) ( game.GetGameState world = GameState.Enclosure) behavior
38+ [ Screen.CameraPositionDefault .= CameraAbsolute ( v2 60 f 0 f)] world
3839 World.beginGroup Simulants.SceneGroup [] world
40+
41+ // define border
42+ let _ =
43+ World.doBlock2d Simulants.BorderEntity // A block uses static physics by default - it does not react to forces or collisions.
44+ [ Entity.Size .= v3 500 f 350 f 0 f
45+ Entity.BodyShape .= ContourShape // The body shape handles collisions and is independent of how it's displayed.
46+ { Links = // A contour shape, unlike other shapes, is hollow.
47+ [| v3 - 0.5 f 0.5 f 0 f // Zero is the entity's center, one is the entity's size in positive direction.
48+ v3 0.5 f 0.5 f 0 f
49+ v3 0.5 f - 0.5 f 0 f
50+ v3 - 0.5 f - 0.5 f 0 f|]
51+ Closed = true // The last point connects to the first point.
52+ // There can be multiple shapes per body, TransformOpt and PropertiesOpt
53+ TransformOpt = None
54+ PropertiesOpt = None }
55+ // Continuous collision detection adds additional checks between frame positions
56+ // against high velocity objects tunneling through thin borders.
57+ Entity.CollisionDetection .= Continuous
58+ // Collision categories is a binary mask, defaulting to "1" (units place).
59+ // The border is set to be in a different category, "10" (twos place)
60+ // because we define fans later to not collide with the border.
61+ // Meanwhile, unless we change the collision mask (Entity.CollisionMask),
62+ // all entites default to collide with "*" (i.e. all collision categories).
63+ Entity.CollisionCategories .= " 10"
64+ Entity.Elevation .= - 1 f // Draw order of the same elevation prioritizes entities with lower vertical position for 2D games.
65+ Entity.StaticImage .= Assets.Gameplay.SkyBoxFront] world
66+
67+ // define agent
68+ let ( agentBody , _ ) =
69+ World.doCharacter2d " Agent"
70+ [ Entity.Restitution .= 0.333 f // bounciness
71+ Entity.GravityOverride .= None // characters have 3x gravity by default, get rid of it
72+ Entity.Friction .= 0.1 f
73+ ] world
74+
75+ // Keyboard controls for agent
76+ if world.ContextScreen.GetSelected world then
77+ let agentForce = 200 f
78+ let agentTorque = 1 f
79+ if World.isKeyboardKeyDown KeyboardKey.A world then
80+ World.applyBodyForce ( v3 - 1 f 0 f 0 f * agentForce) None agentBody world
81+ if World.isKeyboardKeyDown KeyboardKey.D world then
82+ World.applyBodyForce ( v3 1 f 0 f 0 f * agentForce) None agentBody world
83+ if World.isKeyboardKeyDown KeyboardKey.W world then
84+ // Fly up despite gravity
85+ World.applyBodyForce ( v3 0 f 1 f 0 f * agentForce - World.getGravity2d world) None agentBody world
86+ if World.isKeyboardKeyDown KeyboardKey.S world then
87+ // Glide down despite gravity
88+ World.applyBodyForce ( v3 0 f - 1 f 0 f * agentForce - World.getGravity2d world) None agentBody world
89+ if World.isKeyboardKeyDown KeyboardKey.Q world then
90+ World.applyBodyTorque ( v3 1 f 0 f 0 f * agentTorque) agentBody world
91+ if World.isKeyboardKeyDown KeyboardKey.E world then
92+ World.applyBodyTorque ( v3 - 1 f 0 f 0 f * agentTorque) agentBody world
93+
3994 if World.doButton Simulants.BackEntity [] world && world.Unaccompanied then World.exit world
4095 World.endGroup world
4196 World.endScreen world
4297
43- World.cam
44- // Camera control
45- if World.isKeyboardKeyDown KeyboardKey.Left world then
46- World.setEye2dCenter ( World.getEye2dCenter world - v2 1 f 0 f) world
47- if World.isKeyboardKeyDown KeyboardKey.Right world then
48- World.setEye2dCenter ( World.getEye2dCenter world + v2 1 f 0 f) world
49- if World.isKeyboardKeyDown KeyboardKey.Up world then
50- World.setEye2dCenter ( World.getEye2dCenter world + v2 0 f 1 f) world
51- if World.isKeyboardKeyDown KeyboardKey.Down world then
52- World.setEye2dCenter ( World.getEye2dCenter world - v2 0 f 1 f) world
53- if World.isKeyboardKeyDown KeyboardKey.PageUp world then
54- World.setEye2dSize ( World.getEye2dSize world * 1.01 f) world
55- if World.isKeyboardKeyDown KeyboardKey.PageDown world then
56- World.setEye2dSize ( World.getEye2dSize world * 0.99 f) world
57- if World.isKeyboardKeyDown KeyboardKey.Home world then
58- World.setEye2dCenter ( v2 60 f 0 f) world
59- World.setEye2dSize Constants.Render.DisplayVirtualResolution.V2 world
98+ // declare Racecourse screen
99+ let behavior = Dissolve ( Constants.Dissolve.Default, None)
100+ let _ =
101+ World.beginScreen< DemoScreenDispatcher> ( nameof GameState.Racecourse) ( game.GetGameState world = GameState.Racecourse) behavior
102+ [ Screen.CameraPositionDefault .= CameraTracking ( Relation.makeFromString " Car" )] world
103+ World.beginGroup Simulants.SceneGroup [] world
104+
105+ // define racecourse
106+ let racecourse =
107+ [| v2 - 20 f 5 f
108+ v2 - 20 f 0 f
109+ v2 20 f 0 f
110+ v2 25 f 0.25 f
111+ v2 30 f 1 f
112+ v2 35 f 4 f
113+ v2 40 f 0 f
114+ v2 45 f 0 f
115+ v2 50 f - 1 f
116+ v2 55 f - 2 f
117+ v2 60 f - 2 f
118+ v2 65 f - 1.25 f
119+ v2 70 f 0 f
120+ v2 75 f 0.3 f
121+ v2 80 f 1.5 f
122+ v2 85 f 3.5 f
123+ v2 90 f 0 f
124+ v2 95 f - 0.5 f
125+ v2 100 f - 1 f
126+ v2 105 f - 2 f
127+ v2 110 f - 2.5 f
128+ v2 115 f - 1.3 f
129+ v2 120 f 0 f
130+ v2 160 f 0 f
131+ v2 159 f - 10 f
132+ v2 201 f - 10 f
133+ v2 200 f 0 f
134+ v2 240 f 0 f
135+ v2 250 f 5 f
136+ v2 250 f - 10 f
137+ v2 270 f - 10 f
138+ v2 270 f 0 f
139+ v2 310 f 0 f
140+ v2 310 f 5 f|] |> Array.map ( fun p -> p.V3 * Constants.Engine.Entity2dSizeDefault)
141+ let _ =
142+ World.doBlock2d " Racecourse"
143+ [ Entity.Size .= v3 1 f 1 f 0 f
144+ Entity.BodyShape .= ContourShape { Links = racecourse; Closed = false ; TransformOpt = None; PropertiesOpt = None }]
145+ world
146+ for ( p1, p2) in Array.pairwise racecourse do
147+ World.doStaticSprite $" Racecourse {p1} -> {p2}"
148+ [ Entity.Position .= ( p1 + p2) / 2 f
149+ Entity.Size .= v3 (( p2 - p1) .Magnitude / 2 f) 2 f 0 f
150+ Entity.Rotation .= Quaternion.CreateLookAt2d ( p2 - p1) .V2
151+ Entity.StaticImage .= Assets.Default.Black] world
152+
153+ // define car
154+ let carMaxSpeed = 50 f
155+ let carSpawnPosition = v3 0 f 30 f 0 f
156+ 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
169+ let _ =
170+ World.doBox2d " Car"
171+ [ Entity.BodyShape .= PointsShape {
172+ Points = Array.map carGetRelativePosition carPoints
173+ Profile = Convex
174+ TransformOpt = None
175+ PropertiesOpt = None }
176+ Entity.StaticImage .= Assets.Gameplay.Car
177+ Entity.Position .= carSpawnPosition
178+ Entity.Size .= carSize * Constants.Engine.Entity2dSizeDefault
179+ Entity.Substance .= Density 2 f
180+ ] 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,
183+ let acceleration = game.GetCarAcceleration world
184+ 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 _ =
187+ World.doBall2d $" Wheel {relation}"
188+ [ 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 | _ -> ()
193+ ] world
194+ let _ =
195+ World.doBodyJoint2d $" Wheel {relation} joint"
196+ [ Entity.BodyJoint .= TwoBodyJoint2d {
197+ CreateTwoBodyJoint = fun _ _ car wheel ->
198+ 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)) }
201+ Entity.BodyJointTarget .= Relation.makeFromString " ^/Car"
202+ Entity.BodyJointTarget2Opt .= Some ( Relation.makeFromString $" ^/Wheel {relation}" )
203+
204+ ] world
205+ ()
206+
207+ // 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
218+
219+ if World.doButton Simulants.BackEntity [] world && world.Unaccompanied then World.exit world
220+ World.endGroup world
221+ World.endScreen world
60222
61223 // handle Alt+F4 when not in editor
62224 if World.isKeyboardAltDown world &&
0 commit comments