From b99ada53b715def5492c7d04c0d327fa7048e5d3 Mon Sep 17 00:00:00 2001 From: Gene Pasquet Date: Sun, 5 Apr 2026 23:12:54 +0100 Subject: Complete implementation --- docs/guide.org | 261 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 docs/guide.org (limited to 'docs/guide.org') diff --git a/docs/guide.org b/docs/guide.org new file mode 100644 index 0000000..5119cea --- /dev/null +++ b/docs/guide.org @@ -0,0 +1,261 @@ +#+TITLE: Downstroke — Getting Started +#+AUTHOR: Downstroke Project + +* Introduction + +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. + +* Installation + +** System Dependencies + +Downstroke requires the following system libraries: + +- =SDL2= +- =SDL2_mixer= +- =SDL2_ttf= +- =SDL2_image= +- =expat= + +On Debian/Ubuntu: +#+begin_src bash +sudo apt-get install libsdl2-dev libsdl2-mixer-dev libsdl2-ttf-dev libsdl2-image-dev libexpat1-dev +#+end_src + +On macOS with Homebrew: +#+begin_src bash +brew install sdl2 sdl2_mixer sdl2_ttf sdl2_image expat +#+end_src + +** Chicken Eggs + +Install the Downstroke egg along with its dependencies: + +#+begin_src bash +chicken-install downstroke sdl2 sdl2-image defstruct matchable states +#+end_src + +* Hello World — Your First Game + +Create a file called =mygame.scm=: + +#+begin_src scheme +(import downstroke/engine) + +(define *game* + (make-game title: "Hello World" width: 640 height: 480)) + +(game-run! *game*) +#+end_src + +Build and run: +#+begin_src bash +csc mygame.scm -o mygame +./mygame +#+end_src + +You should see a black window titled "Hello World". Press =Escape= or close the window to quit. The =game-run!= function handles SDL2 initialization, window creation, the event loop, and cleanup automatically. The engine also provides a default =quit= action (Escape key and window close button). + +* Moving Square — First Entity + +Now let's add an entity you can move with the keyboard. Create =square.scm=: + +#+begin_src scheme +(import scheme + (chicken base) + (prefix sdl2 "sdl2:") + downstroke/engine + downstroke/world + downstroke/entity + downstroke/input) + +(define *game* + (make-game + title: "Moving Square" width: 640 height: 480 + + create: (lambda (game) + (game-scene-set! game + (make-scene + entities: (list (list #:type 'box #:x 300 #:y 200 + #:width 32 #:height 32 + #:vx 0 #:vy 0)) + tilemap: #f + camera: (make-camera x: 0 y: 0) + tileset-texture: #f))) + + update: (lambda (game dt) + (let* ((input (game-input game)) + (scene (game-scene game)) + (box (car (scene-entities scene))) + ;; Read input and update velocity + (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))))) + (scene-entities-set! scene (list box)))) + + render: (lambda (game) + (let* ((scene (game-scene game)) + (box (car (scene-entities scene))) + (x (inexact->exact (floor (entity-ref box #:x 0)))) + (y (inexact->exact (floor (entity-ref box #:y 0)))) + (w (entity-ref box #:width 32)) + (h (entity-ref box #:height 32))) + (sdl2:set-render-draw-color! (game-renderer game) 255 200 0 255) + (sdl2:render-fill-rect! (game-renderer game) + (sdl2:make-rect x y w h)))))) + +(game-run! *game*) +#+end_src + +Run it: +#+begin_src bash +csc square.scm -o square +./square +#+end_src + +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. +- **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. +- **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 + +For a real game, you probably want tilemaps, gravity, and collision detection. Downstroke includes a full physics pipeline. Here's the pattern: + +#+begin_src scheme +(import scheme + (chicken base) + downstroke/engine + downstroke/world + downstroke/entity + downstroke/input + downstroke/physics + downstroke/scene-loader + downstroke/sound) + +(define *game* + (make-game + title: "Platformer Demo" width: 600 height: 400 + + preload: (lambda (game) + ;; Initialize audio and load sounds (optional) + (init-audio!) + (load-sounds! '((jump . "assets/jump.wav")))) + + create: (lambda (game) + ;; Load the tilemap from a TMX file (made with Tiled editor) + (let* ((scene (game-load-scene! game "assets/level.tmx")) + ;; Create a player entity + (player (list #:type 'player + #:x 100 #:y 50 + #:width 16 #:height 16 + #:vx 0 #:vy 0 + #:gravity? #t + #:on-ground? #f + #:tile-id 1))) + ;; Add player to the scene + (scene-add-entity scene player))) + + update: (lambda (game dt) + ;; Typical pattern for platformer physics + (let* ((input (game-input game)) + (scene (game-scene game)) + (tm (scene-tilemap scene)) + (player (car (scene-entities scene))) + ;; Set horizontal velocity from input + (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-ground player tm))) + ;; Update camera to follow player + (let ((cam-x (max 0 (- (entity-ref player #:x 0) 300)))) + (camera-x-set! (scene-camera scene) cam-x)) + ;; Replace entities in scene + (scene-entities-set! scene (list player)))))) + +(game-run! *game*) +#+end_src + +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. +- **Ground detection**: =detect-ground= sets the =#:on-ground?= flag so you know if a jump is valid. + +See =demo/platformer.scm= in the engine source for a complete working example. + +* Demo Overview + +Downstroke includes five complete demo games that showcase different features: + +| Demo | File | What it shows | +|------|------|--------------| +| Platformer | =demo/platformer.scm= | Gravity, jump, tile collision, camera follow, sound effects | +| Top-down | =demo/topdown.scm= | 8-directional movement, no gravity, tilemap, camera follow | +| Physics Sandbox | =demo/sandbox.scm= | Entity-entity collision, multi-entity physics, auto-respawn | +| Shoot-em-up | =demo/shmup.scm= | No tilemap, entity spawning/removal, manual AABB collision | +| Audio | =demo/audio.scm= | Sound effects, music toggle, text rendering | + +Each demo is self-contained and serves as a working reference for a particular game mechanic. + +* Build & Run + +If you cloned the downstroke source, you can build everything: + +#+begin_src bash +cd /path/to/downstroke +make # Compile all engine modules +make demos # Build all 5 demo executables +./bin/demo-platformer +./bin/demo-topdown +# etc. +#+end_src + +* Next Steps + +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 +- Load tilemaps with =game-load-scene!= +- Play sounds with =load-sounds!= and =play-sound= + +For more details: + +- **Full API reference**: See =docs/api.org= for all functions and keyword arguments. +- **Entity model**: See =docs/entities.org= to learn about plist keys, tags, prefabs, and mixins. +- **Physics pipeline**: See =docs/physics.org= for the full physics specification and collision model. + +Happy coding! -- cgit v1.2.3