-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path06_Systems.fs
More file actions
293 lines (263 loc) · 12.6 KB
/
06_Systems.fs
File metadata and controls
293 lines (263 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
module MyGame.Systems
open Raylib_cs
open System.Numerics
open Storage
open Sto2
type Transform = MyGame.Transform
type Parallel = System.Threading.Tasks.Parallel
// Similar to Parrallel.For that I used before. But profiling showed a lot of
// garbage and thread waiting. Don't know how it exactly works, so just rewritten
// it myself. It splits the work across a fixed amount of threads, what usually
// is anyway better in a game to have a fixed amount of workers. Than the main
// thread basically spinlocks until all threads finish. This way i still can use
// a loop to process multiple items instead of calling a function for every
// element. This reduces overhead. F# "inline" feature here is important to
// eleminate function calls. I guess the .Net Parallel.For maybe don't have
// this feature, or maybe JIT inlining does it. Who knows?
let inline runThreaded threadAmount count ([<InlineIfLambda>] f) =
let workPerThread = count / threadAmount
let mutable runningThreads = 4
for currentThread=0 to threadAmount-1 do
let start = currentThread * workPerThread
let stop =
if currentThread < (threadAmount-1)
then ((currentThread+1) * workPerThread) - 1
else count - 1
ignore <| System.Threading.ThreadPool.QueueUserWorkItem(fun _ ->
for i=start to stop do
f i
System.Threading.Interlocked.Decrement(&runningThreads) |> ignore
)
while runningThreads > 0 do
()
// Transform System updates the Global_ fields when a Parent is set
module Transform =
// Calculates position, rotation and scale relative to parent
// returns an voption because it is called recursively on transform. ValueNone
// indicates when a parent has no transform defined and recursion ends.
let rec calculateTransform (me:Transform) =
match me.Parent with
| ValueNone -> ValueSome (struct (me.Position,me.Rotation,me.Scale))
| ValueSome parent ->
match Storage.get parent State.Transform with
| ValueSome parent ->
match calculateTransform parent with
| ValueNone -> ValueNone
| ValueSome (pPos,pRot,pScale) ->
let scale = Vector2.create (pScale.X * me.Scale.X) (pScale.Y * me.Scale.Y)
let pos = Vector2.Transform(
me.Position,
Matrix.CreateScale(scale.X, scale.Y, 0f)
* Matrix.CreateRotationZ(float32 (Rad.fromDeg pRot)) // rotate by parent position
* Matrix.CreateTranslation(pPos.X, pPos.Y, 0f) // translate by parent position
)
ValueSome (struct (pos,pRot+me.Rotation,scale))
| ValueNone -> ValueNone
let inline updateIndex idx =
// Get a Transform
let struct (_,t) = State.Transform.Data.[idx]
match calculateTransform t with
| ValueNone -> ()
| ValueSome (pos,rot,scale) ->
t.GlobalPosition.X <- pos.X
t.GlobalPosition.Y <- pos.Y
t.GlobalRotation <- rot
t.GlobalScale.X <- scale.X
t.GlobalScale.Y <- scale.Y
/// Updates all Global fields of every Transform with a Parent
let update () =
Parallel.For(0, State.Transform.Data.Count, updateIndex)
|> ignore
// View System draws entity
module View =
// Special variables used for object culling. I assume that the camera is
// always centered. Mean a camera that target world position 0,0 shows this
// point at the center of the camera. From this point only objects that
// are half the screen width to any direction away will be rendered. + some
// offset so sprites don't disappear.
// Whenever a new frame starts to render, the (min|max)(X|Y) are updated.
// The offset and halfScreen should be set at program start. halfScreen
// should be the half of the virtualScreen
let mutable halfX = 360f
let mutable halfY = 180f
let mutable offset = 64f
let mutable minX = 0f
let mutable maxX = 0f
let mutable minY = 0f
let mutable maxY = 0f
let inline drawTexture (transform:Transform) view =
let pos = transform.GlobalPosition
let rot = transform.GlobalRotation
let scale = transform.GlobalScale
if pos.X > minX && pos.X < maxX && pos.Y > minY && pos.Y < maxY then
State.drawed <- State.drawed + 1
Raylib.DrawTexturePro(
texture = view.Sprite.Texture,
source = view.Sprite.SrcRect,
dest = Rectangle(pos, view.Sprite.SrcRect.Width * scale.X * view.Scale.X, view.Sprite.SrcRect.Height * scale.Y * view.Scale.Y),
origin = view.Origin,
rotation = float32 (rot + view.Rotation),
tint = view.Tint
)
let draw () =
// Used to track how many objects were really drawn
State.drawed <- 0
// Some simple object culling, just camera center + some offset so objects
// that are on the edge of screen don't dissapear when they hit the edge.
// But would be a cool Retro effect of early 3D games.
let cam = State.camera.Target
let zoom = State.camera.Zoom
minX <- cam.X - ((halfX + offset) * (1f / zoom))
maxX <- cam.X + ((halfX + offset) * (1f / zoom))
minY <- cam.Y - ((halfY + offset) * (1f / zoom))
maxY <- cam.Y + ((halfY + offset) * (1f / zoom))
State.View |> Sto2.iter Layer.BG3 (fun entity v ->
match Entity.getTransform entity with
| ValueSome t -> drawTexture t v
| ValueNone -> ()
)
State.View |> Sto2.iter Layer.BG2 (fun entity v ->
match Entity.getTransform entity with
| ValueSome t -> drawTexture t v
| ValueNone -> ()
)
State.View |> Sto2.iter Layer.BG1 (fun entity v ->
match Entity.getTransform entity with
| ValueSome t -> drawTexture t v
| ValueNone -> ()
)
State.View |> Sto2.iter Layer.FG3 (fun entity v ->
match Entity.getTransform entity with
| ValueSome t -> drawTexture t v
| ValueNone -> ()
)
State.View |> Sto2.iter Layer.FG2 (fun entity v ->
match Entity.getTransform entity with
| ValueSome t -> drawTexture t v
| ValueNone -> ()
)
State.View |> Sto2.iter Layer.FG1 (fun entity v ->
match Entity.getTransform entity with
| ValueSome t -> drawTexture t v
| ValueNone -> ()
)
module AutoMovement =
let update (dt:float32) =
for idx=0 to State.AutoMovement.Data.Count-1 do
let struct (entity,mov) = State.AutoMovement.Data.[idx]
match Storage.get entity State.Transform with
| ValueSome t ->
t.Position <- t.Position + (mov.Direction * dt)
t.Rotation <- t.Rotation + (mov.RotateBy * dt)
| ValueNone -> ()
module AutoTargetPosition =
let update (dt:float32) =
for idx=0 to State.AutoTargetPosition.Data.Count-1 do
let struct (entity,mov) = State.AutoTargetPosition.Data.[idx]
match Storage.get entity State.Transform with
| ValueSome t ->
let direction = Vector2.Normalize(mov.Position - t.Position)
t.Position <- t.Position + direction * mov.Speed * dt
| ValueNone -> ()
module Timer =
let mutable state = ResizeArray<Timed<unit>>()
let addTimer timer =
state.Add (Timed.get timer)
let update (deltaTime:float32) =
let deltaTime = TimeSpan.FromSeconds(float deltaTime)
for idx=0 to state.Count-1 do
match Timed.run deltaTime (state.[idx]) with
| Pending -> ()
| Finished _ -> state.RemoveAt(idx)
module Animations =
let inline updateAnimation dt idx =
let struct (entity, anim) = State.Animation.Data.[idx]
anim.ElapsedTime <- anim.ElapsedTime + dt
if anim.ElapsedTime > anim.CurrentSheet.FrameDuration then
anim.ElapsedTime <- anim.ElapsedTime - anim.CurrentSheet.FrameDuration
Comp.setAnimationNextSprite anim
match Sto2.get entity State.View with
| ValueSome (_,view) ->
match Comp.getCurrentSpriteAnimation anim with
| ValueSome sprite -> view.Sprite <- sprite
| ValueNone -> ()
| ValueNone -> ()
let update (deltaTime:float32) =
Parallel.For(0, State.Animation.Data.Count, updateAnimation deltaTime)
|> ignore
module Drawing =
let mousePosition mousePos fontSize (whereToDraw:Vector2) =
let world = Raylib.GetScreenToWorld2D(mousePos, State.camera)
Raylib.DrawText(
text = System.String.Format("Mouse Screen({0:0.00},{1:0.00}) World({2:0.00},{3:0.00})", mousePos.X, mousePos.Y, world.X, world.Y),
posX = int whereToDraw.X,
posY = int whereToDraw.Y,
fontSize = fontSize,
color = Color.Yellow
)
let trackPosition (entity:Entity) fontSize (whereToDraw:Vector2) =
match Entity.getTransform entity with
| ValueSome t ->
let screen = Raylib.GetWorldToScreen2D(t.Position, State.camera)
Raylib.DrawText(
text =
System.String.Format("World({0:0.00},{1:0.00}) Screen({2:0.00},{3:0.00})",
t.Position.X, t.Position.Y,
screen.X, screen.Y
),
posX = int whereToDraw.X,
posY = int whereToDraw.Y,
fontSize = fontSize,
color = Color.Yellow
)
| ValueNone ->
()
let rectangle (thickness:int) (color:Color) (start:Vector2) (stop:Vector2) =
let rect = Rectangle.fromVectors start stop
Raylib.DrawRectangleRec(rect, Raylib.ColorAlpha(color, 0.1f))
Raylib.DrawRectangleLinesEx(rect, float32 thickness, color)
module PathWalking =
let update (dt:float32) =
let finished = ResizeArray<_>()
for (entity,path) in State.PathWalking.Data do
match Storage.get entity State.Transform with
| ValueNone -> ()
| ValueSome t ->
match path.State with
| PathWalkingState.NotStarted ->
if path.Path.Length = 0 then
path.State <- PathWalkingState.Finished
else
let target = path.Path.[0]
let direction = Vector2.Normalize(target - t.Position) * path.Speed
t.Position <- t.Position + (direction * dt)
path.Current <- 0
path.Direction <- direction
path.State <- PathWalkingState.Walking
| PathWalkingState.Walking ->
let target = path.Path.[path.Current]
let direction = path.Direction
// When does the entity reach its target? Currently i just
// check if a minimum distance is reached. But this is
// prone to error with fast moving objects. Maybe make the
// minimum distance configurable? Or do a better check if
// we move beyond the target point.
let next = path.Current + 1
if Vector2.DistanceSquared(target, t.Position) <= 25f then
if next < path.Path.Length then
let target = path.Path.[next]
let direction = Vector2.Normalize(target - t.Position) * path.Speed
t.Position <- t.Position + (path.Direction * dt)
path.Current <- next
path.Direction <- direction
else
path.State <- PathWalkingState.Finished
else
t.Position <- t.Position + (path.Direction * dt)
| PathWalkingState.Finished ->
finished.Add(entity)
// We cannot delete while iterating
| _ ->
failwithf "New PathWalkingState defined, but no Code for handling this case. Review Systems.PathWalking.update"
for entity in finished do
Storage.remove entity State.PathWalking