#+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))))) (game-scene-set! game (update-scene scene entities: (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. 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. - **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-on-solid player tm))) (game-scene-set! game (update-scene scene entities: (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. - **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.) See =demo/platformer.scm= in the engine source for a complete working example. * Development Tips ** Pixel Scaling Retro-style games often use a small logical resolution (e.g. 320×240) but need a larger window so players can actually see things. Use ~scale:~ to scale everything uniformly: #+begin_src scheme (make-game title: "Pixel Art" width: 320 height: 240 scale: 2) ;; Creates a 640×480 window; all coordinates remain in 320×240 space #+end_src This is integer-only scaling. All rendering — tiles, sprites, text, debug overlays — is scaled automatically by SDL2. Game logic and coordinates are unaffected. See =demo/scaling.scm= for a complete example. ** Fullscreen Use SDL2 window flags in the ~preload:~ hook to go fullscreen: #+begin_src scheme (make-game title: "Fullscreen Game" width: 320 height: 240 scale: 2 preload: (lambda (game) (sdl2:window-fullscreen-set! (game-window game) 'fullscreen-desktop))) #+end_src ~'fullscreen-desktop~ fills the screen without changing display resolution; SDL2 handles letterboxing and scaling. ~'fullscreen~ uses exclusive fullscreen at the window size. ** Debug Mode During development, enable ~debug?: #t~ on ~make-game~ to visualize collision boxes and the tile grid. This helps you understand what the physics engine "sees" and debug collision problems: #+begin_src scheme (define my-game (make-game title: "My Game" width: 600 height: 400 debug?: #t)) ;; Enable collision visualization (game-run! my-game) #+end_src With debug mode enabled, you'll see: - **Purple outlines** around all non-zero tiles - **Blue boxes** around player entities - **Red boxes** around enemy entities - **Green boxes** around active attack hitboxes This is invaluable for tuning collision geometry and understanding why entities are clipping or not colliding as expected. * Demo Overview Downstroke includes several 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 | | Sprite Font | =demo/spritefont.scm= | Bitmap text rendering using non-contiguous tileset ranges | | Menu | =demo/menu.scm= | State machine menus, keyboard navigation, TTF text rendering | | Tweens | =demo/tweens.scm= | Easing curves, =tween-step=, =#:skip-pipelines= with tile collision | | Scaling | =demo/scaling.scm= | Integer pixel scaling with =scale: 2=, 2× upscaled 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 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. - **Tweens**: See =docs/tweens.org= for time-based property interpolation and combining tweens with physics. Happy coding!