diff options
| author | Gene Pasquet <dev@etenil.net> | 2026-04-08 07:08:54 +0100 |
|---|---|---|
| committer | Gene Pasquet <dev@etenil.net> | 2026-04-08 07:08:54 +0100 |
| commit | afc30a12e25215ff5e9226c3b4f8fd127d9a4d68 (patch) | |
| tree | f736393fb8ebfd8982a4b79310a08c57ee430ff0 /docs/guide.org | |
| parent | 9e8b75f9949259ef01942cd3717b79b044efddf7 (diff) | |
Move the engine-update to the scene
Diffstat (limited to 'docs/guide.org')
| -rw-r--r-- | docs/guide.org | 50 |
1 files changed, 18 insertions, 32 deletions
diff --git a/docs/guide.org b/docs/guide.org index 96663ea..d98b00e 100644 --- a/docs/guide.org +++ b/docs/guide.org @@ -5,7 +5,7 @@ Downstroke is a 2D tile-driven game engine for Chicken Scheme, built on SDL2. It is inspired by Phaser 2: a minimal game is about 20 lines of Scheme. -The engine handles SDL2 initialization, the event loop, input, rendering, and physics. You provide lifecycle hooks to customize behavior. This guide walks you through building your first game with Downstroke. +The engine handles SDL2 initialization, the event loop, input, rendering, and — by default — a full physics pipeline each frame (~default-engine-update~). You provide lifecycle hooks to customize behavior; ~update:~ is where you usually set movement *intent* (~#:vx~, ~#:vy~, ~#:ay~) and game rules. This guide walks you through building your first game with Downstroke. * Installation @@ -89,18 +89,13 @@ Now let's add an entity you can move with the keyboard. Create =square.scm=: (let* ((input (game-input game)) (scene (game-scene game)) (box (car (scene-entities scene))) - ;; Read input and update velocity + ;; Intent only: default engine-update integrates #:vx / #:vy each frame (box (entity-set box #:vx (cond ((input-held? input 'left) -3) ((input-held? input 'right) 3) (else 0)))) (box (entity-set box #:vy (cond ((input-held? input 'up) -3) ((input-held? input 'down) 3) - (else 0)))) - ;; Apply velocity to position - (box (entity-set box #:x (+ (entity-ref box #:x 0) - (entity-ref box #:vx 0)))) - (box (entity-set box #:y (+ (entity-ref box #:y 0) - (entity-ref box #:vy 0))))) + (else 0))))) (game-scene-set! game (update-scene scene entities: (list box))))) @@ -129,7 +124,7 @@ Press arrow keys to move the yellow square around. Here are the key ideas: - **Scenes**: =make-scene= creates a container for entities, tilemaps, and the camera. It holds the game state each frame. Optional =background:= ~(r g b)~ or ~(r g b a)~ sets the color used to clear the window each frame (default is black). - **Entities**: Entities are plists (property lists). They have no class; they're pure data. Access properties with =entity-ref=, and update with =entity-set= (which returns a *new* plist — always bind the result). - **Input**: =input-held?= returns =#t= if an action is currently pressed. Actions are symbols like ='left=, ='right=, ='up=, ='down= (from the default input config). -- **Update & Render**: The =update:= hook runs first and updates entities. The =render:= hook runs after the default rendering pipeline and is used for custom drawing like this colored rectangle. +- **Update & Render**: Each frame, after input, the built-in =engine-update= integrates =#:vx= / =#:vy= into position (gravity and tile steps no-op without =#:gravity?= or a tilemap). Your =update:= hook runs *after* that and sets velocities for the following frame. The =render:= hook runs after the default scene render and is used for custom drawing like this colored rectangle. - **Rendering**: Since our tilemap is =#f=, the default renderer draws nothing; all drawing happens in the =render:= hook using SDL2 functions. * Adding a Tilemap and Physics @@ -171,32 +166,23 @@ For a real game, you probably want tilemaps, gravity, and collision detection. D (scene-add-entity scene player))) update: (lambda (game dt) - ;; Typical pattern for platformer physics + ;; Game logic only — gravity, collisions, and #:on-ground? run in default-engine-update (let* ((input (game-input game)) (scene (game-scene game)) - (tm (scene-tilemap scene)) (player (car (scene-entities scene))) - ;; Set horizontal velocity from input + (jump? (and (input-pressed? input 'a) + (entity-ref player #:on-ground? #f))) (player (entity-set player #:vx (cond ((input-held? input 'left) -3) ((input-held? input 'right) 3) - (else 0)))) - ;; Jump on button press if on ground - (_ (when (and (input-pressed? input 'a) - (entity-ref player #:on-ground? #f)) - (play-sound 'jump))) - (player (apply-jump player (input-pressed? input 'a))) - ;; Run physics pipeline - (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-on-solid player tm))) - (game-scene-set! game - (update-scene scene entities: (list player))))))) + (else 0))))) + (when jump? (play-sound 'jump)) + (let ((player (if jump? + (entity-set player #:ay (- *jump-force*)) + player))) + (game-scene-set! game + (update-scene scene entities: (list player))))))) (game-run! *game*) #+end_src @@ -206,9 +192,9 @@ Key points: - **=game-load-scene!=** loads a TMX tilemap file (created with the Tiled editor), creates the tileset texture, and builds the scene. It returns the scene so you can add more entities. - **=init-audio!=** and **=load-sounds!=** initialize the audio subsystem and load sound files. Call these in the =preload:= hook. - **=play-sound=** plays a loaded sound effect. -- **Physics pipeline**: Functions like =apply-gravity=, =apply-velocity-x=, =resolve-tile-collisions-x= form the physics pipeline. Apply them in order each frame to get correct platformer behavior. -- **Tile collisions**: =resolve-tile-collisions-x= and =resolve-tile-collisions-y= snap entities to tile edges, preventing clipping. -- **On-solid check**: =detect-on-solid= sets the =#:on-ground?= flag from tiles below the feet and, if you pass other scene entities, from standing on solids (moving platforms, crates). Call it after collisions; use the flag next frame to gate jumps. (Despite the =?=-suffix, it returns an updated entity, not a boolean.) +- **Automatic physics**: By default, =make-game= uses =default-engine-update=, which runs the full pipeline (tweens, acceleration, gravity, velocity, tile and entity collisions, ground detection, group sync) *before* your =update:= hook. Your =update:= sets =#:vx= and a one-frame =#:ay= for jumps; =*jump-force*= (from =downstroke-physics=, default 15) is a conventional jump impulse. +- **=engine-update: #f=** disables that pipeline — use this for games that move entities entirely in =update:= (see =demo/shmup.scm=, =demo/scaling.scm=) or supply your own =engine-update:= procedure to replace =default-engine-update=. +- **=detect-on-solid=** (inside the default pipeline) sets =#:on-ground?= from tiles below the feet and from other solids in the scene entity list. When your =update:= runs, that flag already reflects the current frame. See =demo/platformer.scm= in the engine source for a complete working example. @@ -300,7 +286,7 @@ You now know how to: - Create a game with =make-game= and =game-run!= - Add entities to scenes - Read input with =input-held?= and =input-pressed?= -- Apply physics to entities +- Rely on the default physics pipeline (or turn it off with =engine-update: #f=) - Load tilemaps with =game-load-scene!= - Play sounds with =load-sounds!= and =play-sound= |
