#+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 — 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 ** 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 #+end_src This pulls in the eggs declared by the Downstroke package. If your game uses the =states= egg for AI or other state machines, install it separately (=chicken-install states=). * 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))) ;; 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))))) (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**: 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 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) ;; Game logic only — gravity, collisions, and #:on-ground? run in default-engine-update (let* ((input (game-input game)) (scene (game-scene game)) (player (car (scene-entities scene))) (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))))) (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 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. - **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. * 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?= - 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= 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!