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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
|
#+TITLE: Entity Model
#+AUTHOR: Downstroke
#+DESCRIPTION: How to create, update, and manage entities in the Downstroke game engine
* Overview
Entities in Downstroke are plain Scheme **plists** (property lists) — alternating keyword/value pairs with no special structure or classes. An entity is just a list:
#+begin_src scheme
(list #:type 'player
#:x 100 #:y 200
#:width 16 #:height 16
#:vx 0 #:vy 0
#:gravity? #t
#:on-ground? #f
#:tile-id 1)
#+end_src
This minimal approach keeps the engine lean: your game defines whatever keys it needs. The shared keys listed below are *conventions* for physics, rendering, and animation — use them to integrate with the engine's built-in systems. Custom keys are always allowed.
* Creating Entities
There is no ~make-entity~ constructor. Create an entity as a plain list:
#+begin_src scheme
(define my-entity
(list #:type 'enemy
#:x 200 #:y 150
#:width 16 #:height 16
#:vx 0 #:vy 0
#:gravity? #t
#:tile-id 42))
#+end_src
Add whatever additional keys your game needs — tags, state, custom data, anything. Entities are pure data.
* Accessing and Updating Entities
Entities are **immutable**. Use these three functions to read and update them:
** ~entity-ref entity key [default]~
Returns the value associated with ~key~ in the entity plist, or ~default~ if the key is absent.
#+begin_src scheme
(entity-ref player #:x 0) ; → 100
(entity-ref player #:vx) ; → 0 (if #:vx is absent)
(entity-ref player #:custom #f) ; → #f (no #:custom key)
#+end_src
If ~default~ is a procedure, it is called to compute the default:
#+begin_src scheme
(entity-ref player #:x (lambda () (error "x is required")))
#+end_src
** ~entity-type entity~
Shorthand for ~(entity-ref entity #:type #f)~. Returns the entity's ~#:type~ or ~#f~.
#+begin_src scheme
(entity-type player) ; → 'player
#+end_src
** ~entity-set entity key val~
Returns a **new** plist with the key/value pair set (or replaced). The original entity is unchanged — this is functional, immutable update.
#+begin_src scheme
(define updated-player (entity-set player #:vx 5))
;; original player is still unchanged:
(entity-ref player #:vx) ; → 0
(entity-ref updated-player #:vx) ; → 5
#+end_src
** ~entity-update entity key proc [default]~
Returns a new entity with ~key~ set to ~(proc (entity-ref entity key default))~. Useful for incrementing or transforming a value:
#+begin_src scheme
(entity-update player #:x (lambda (x) (+ x 3))) ; move right 3 pixels
#+end_src
** Chaining Updates: The let* Pattern
Since each update returns a new entity, chain updates with ~let*~:
#+begin_src scheme
(let* ((player (entity-set player #:vx 3))
(player (apply-velocity-x player))
(player (resolve-tile-collisions-x player tilemap)))
;; now use the updated player
player)
#+end_src
This is how the platformer demo applies physics in order: each step reads the input, computes the next state, and passes it to the next step.
* Plist Key Reference
The engine recognizes these standard keys. Use them to integrate with the physics pipeline, rendering, and animation systems. Custom keys are always allowed.
| Key | Type | Description |
|---|---|---|
| ~#:type~ | symbol | Entity type, e.g., ~'player~, ~'enemy~, ~'coin~. No built-in enforcement; use for ~entity-type~ checks and scene queries. |
| ~#:x~, ~#:y~ | number | World position in pixels (top-left corner of bounding box). Updated by ~apply-velocity-x~, ~apply-velocity-y~, and collision resolvers. |
| ~#:width~, ~#:height~ | number | Bounding box size in pixels. Used for AABB tile collision checks and entity-entity collision. Required for physics. |
| ~#:vx~, ~#:vy~ | number | Velocity in pixels per frame. ~#:vx~ is updated by ~apply-velocity-x~; ~#:vy~ is updated by ~apply-velocity-y~. Both consumed by collision resolvers. |
| ~#:ay~ | number | Y acceleration (e.g., from jumping or knockback). Consumed by ~apply-acceleration~, which adds it to ~#:vy~. Optional; default is 0. |
| ~#:gravity?~ | boolean | Whether gravity applies to this entity. Set to ~#t~ for platformers (gravity pulls down), ~#f~ for top-down or flying entities. Used by ~apply-gravity~. |
| ~#:on-ground?~ | boolean | Whether the entity is touching a solid tile below (set by ~detect-ground~). Use this to gate jump input: only allow jumping if ~#:on-ground?~ is true. |
| ~#:solid?~ | boolean | Whether this entity participates in entity-entity collision. If ~#t~, ~resolve-entity-collisions~ will check it against other solid entities. |
| ~#:tile-id~ | integer | Sprite index in the tileset (1-indexed). Required for rendering with ~draw-sprite~. Updated automatically by animation (~animate-entity~). |
| ~#:facing~ | number | Horizontal flip direction: ~1~ = right (default), ~-1~ = left. Used by renderer to flip sprite horizontally. Update when changing direction. |
| ~#:tags~ | list of symbols | List of tag symbols, e.g., ~'(player solid)~. Used by ~scene-find-tagged~ and ~scene-find-all-tagged~ for fast lookups. |
| ~#:animations~ | alist | Animation definitions (see Animation section). Keys are animation names (symbols); values are animation specs. |
| ~#:anim-name~ | symbol | Currently active animation name, e.g., ~'walk~, ~'jump~. Set with ~set-animation~; reset by ~animate-entity~. |
| ~#:anim-frame~ | integer | Current frame index within the animation (0-indexed). Updated automatically by ~animate-entity~. |
| ~#:anim-tick~ | integer | Tick counter for frame timing (0 to ~#:duration - 1~). Incremented by ~animate-entity~; resets when frame advances. |
* Entities in Scenes
A **scene** is a level state: it holds a tilemap, a camera, and a list of entities. These functions manipulate scene entities:
** ~scene-entities scene~
Returns the list of all entities in the scene.
#+begin_src scheme
(define all-entities (scene-entities scene))
#+end_src
** ~scene-entities-set! scene entities~
Mutates the scene to replace the entity list. Use after ~scene-update-entities~ or batch operations.
#+begin_src scheme
(scene-entities-set! scene (list updated-player updated-enemy))
#+end_src
** ~scene-add-entity scene entity~
Adds an entity to the scene and returns the scene. Appends to the entity list.
#+begin_src scheme
(scene-add-entity scene new-enemy)
#+end_src
** ~scene-update-entities scene proc1 proc2 ...~
Maps each procedure over the scene's entities, applying them in sequence. Each proc must be a function of one entity, returning a new entity.
#+begin_src scheme
;; Apply physics pipeline to all entities:
(scene-update-entities scene
apply-gravity
apply-velocity-x
apply-velocity-y)
#+end_src
The result is equivalent to:
#+begin_src scheme
(let* ((es (scene-entities scene))
(es (map apply-gravity es))
(es (map apply-velocity-x es))
(es (map apply-velocity-y es)))
(scene-entities-set! scene es)
scene)
#+end_src
** ~scene-filter-entities scene pred~
Removes all entities that do not satisfy the predicate. Use to despawn dead enemies, collected items, etc.
#+begin_src scheme
;; Remove all entities with #:health <= 0:
(scene-filter-entities scene
(lambda (e) (> (entity-ref e #:health 1) 0)))
#+end_src
** ~scene-find-tagged scene tag~
Returns the first entity whose ~#:tags~ list contains ~tag~, or ~#f~ if none found.
#+begin_src scheme
(scene-find-tagged scene 'player) ; → player entity or #f
#+end_src
** ~scene-find-all-tagged scene tag~
Returns a list of all entities whose ~#:tags~ list contains ~tag~.
#+begin_src scheme
(scene-find-all-tagged scene 'enemy) ; → (enemy1 enemy2 enemy3) or ()
#+end_src
* Animation
Entities with the ~#:animations~ key can cycle through sprite frames automatically. Animation is data-driven: define sprite sequences once, then switch between them in your update logic.
** Animation Data Format
The ~#:animations~ key holds an **alist** (association list) of animation entries. Each entry is ~(name #:frames (frame-indices...) #:duration ticks-per-frame)~:
#+begin_src scheme
(list #:type 'player
...
#:animations
'((idle #:frames (28) #:duration 10)
(walk #:frames (27 28) #:duration 6)
(jump #:frames (29) #:duration 10)
(fall #:frames (30) #:duration 10))
#:anim-name 'idle
#:anim-frame 0
#:anim-tick 0)
#+end_src
- ~idle~, ~walk~, ~jump~, ~fall~ are animation **names** (symbols).
- ~#:frames (28)~ means frame 0 of the animation displays sprite tile 28 (1-indexed in tileset).
- ~#:frames (27 28)~ means frame 0 displays tile 27, frame 1 displays tile 28, then loops back to frame 0.
- ~#:duration 6~ means each frame displays for 6 game ticks before advancing to the next frame.
** Switching Animations
Use ~set-animation~ to switch to a new animation, resetting frame and tick counters. If the animation is already active, it is a no-op (avoids restarting mid-loop):
#+begin_src scheme
(set-animation player 'walk) ; switch to walk animation
;; → entity with #:anim-name 'walk, #:anim-frame 0, #:anim-tick 0
#+end_src
** Advancing Animation
Call ~animate-entity~ once per game frame to step the animation. Pass the entity and its animation table:
#+begin_src scheme
(define animations
'((idle #:frames (28) #:duration 10)
(walk #:frames (27 28) #:duration 6)))
(let* ((player (set-animation player 'walk))
(player (animate-entity player animations)))
player)
#+end_src
~animate-entity~ does four things:
1. Increments ~#:anim-tick~ by 1.
2. If ~#:anim-tick~ reaches ~#:duration~, advances ~#:anim-frame~ and resets tick to 0.
3. Updates ~#:tile-id~ to the sprite ID for the current frame.
4. Returns the updated entity (or unchanged entity if ~#:anim-name~ is not set).
** Typical Animation Update Pattern
#+begin_src scheme
(define (update-player-animation player input)
(let* ((anim (if (input-held? input 'left) 'walk 'idle))
(player (set-animation player anim)))
(animate-entity player player-animations)))
#+end_src
* Tags
The ~#:tags~ key is a list of symbols used to label and query entities. Tags are arbitrary — define whatever makes sense for your game.
** Creating Entities with Tags
#+begin_src scheme
(list #:type 'player
#:tags '(player solid)
...)
(list #:type 'enemy
#:tags '(enemy dangerous)
...)
(list #:type 'coin
#:tags '(collectible)
...)
#+end_src
** Querying by Tag
Use the scene tag lookup functions:
#+begin_src scheme
;; Find the first entity tagged 'player:
(define player (scene-find-tagged scene 'player))
;; Find all enemies:
(define enemies (scene-find-all-tagged scene 'enemy))
;; Find all collectibles and remove them:
(scene-filter-entities scene
(lambda (e) (not (member 'collectible (entity-ref e #:tags '())))))
#+end_src
** Tag Conventions
While tags are free-form, consider using these conventions in your game:
- ~player~: the player character
- ~enemy~: hostile entities
- ~solid~: entities that participate in collision
- ~collectible~: items to pick up
- ~projectile~: bullets, arrows, etc.
- ~hazard~: spikes, lava, etc.
* Example: Complete Entity Setup
Here is a full example showing entity creation, initialization in the scene, and update logic:
#+begin_src scheme
(define (create-player-entity)
(list #:type 'player
#:x 100 #:y 200
#:width 16 #:height 16
#:vx 0 #:vy 0
#:ay 0
#:gravity? #t
#:on-ground? #f
#:tile-id 1
#:facing 1
#:tags '(player solid)
#:animations
'((idle #:frames (1) #:duration 10)
(walk #:frames (1 2) #:duration 6)
(jump #:frames (3) #:duration 10))
#:anim-name 'idle
#:anim-frame 0
#:anim-tick 0))
(define (create-hook game)
(let* ((scene (game-load-scene! game "assets/level.tmx"))
(player (create-player-entity)))
(scene-add-entity scene player)))
(define (update-hook game dt)
(let* ((input (game-input game))
(scene (game-scene game))
(player (scene-find-tagged scene 'player))
(tm (scene-tilemap scene)))
;; Update input-driven velocity
(let* ((player (entity-set player #:vx
(cond
((input-held? input 'left) -3)
((input-held? input 'right) 3)
(else 0))))
;; Handle jump
(player (if (and (input-pressed? input 'a)
(entity-ref player #:on-ground? #f))
(entity-set player #:ay -5)
player))
;; Apply physics
(player (apply-acceleration player))
(player (apply-gravity player))
(player (apply-velocity-x player))
(player (resolve-tile-collisions-x player tm))
(player (apply-velocity-y player))
(player (resolve-tile-collisions-y player tm))
(player (detect-ground player tm))
;; Update animation
(player (set-animation player
(cond
((not (entity-ref player #:on-ground? #f)) 'jump)
((not (zero? (entity-ref player #:vx 0))) 'walk)
(else 'idle))))
(player (animate-entity player player-animations)))
;; Update facing direction
(let ((player (if (< (entity-ref player #:vx 0) 0)
(entity-set player #:facing -1)
(entity-set player #:facing 1))))
(scene-entities-set! scene (list player))))))
#+end_src
Note the let*-chaining pattern: each update builds on the previous result, keeping the data flow clear and each step testable.
|