#+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!