#+TITLE: Downstroke API Reference This document describes the public API for the Downstroke game engine. All exported functions are organized by module. * Engine (~downstroke-engine~) #+begin_src scheme (import downstroke-engine) #+end_src The engine module provides the top-level game lifecycle and state management. ** ~make-game~ #+begin_src scheme (make-game #!key (title "Downstroke Game") (width 640) (height 480) (frame-delay 16) (input-config *default-input-config*) (preload #f) (create #f) (update #f) (render #f) (debug? #f)) #+end_src Creates and initializes a game object. All parameters are optional keywords. | Parameter | Type | Default | Description | |-----------+------+---------+-------------| | ~title~ | string | "Downstroke Game" | Window title | | ~width~ | integer | 640 | Game window width in pixels | | ~height~ | integer | 480 | Game window height in pixels | | ~frame-delay~ | integer | 16 | Delay between frames in milliseconds (30 FPS ≈ 33) | | ~input-config~ | input-config | *default-input-config* | Keyboard/controller mappings | | ~preload~ | procedure/false | #f | Hook: ~(lambda (game) ...)~ called once before create | | ~create~ | procedure/false | #f | Hook: ~(lambda (game) ...)~ called once at startup | | ~update~ | procedure/false | #f | Hook: ~(lambda (game dt) ...)~ called each frame | | ~render~ | procedure/false | #f | Hook: ~(lambda (game) ...)~ called after render-scene! | | ~debug?~ | boolean | #f | Enable debug overlay drawing (collision boxes) | The game object is the central hub. Use it to store/retrieve assets, manage scenes, and access the current input state. ** ~game-run!~ #+begin_src scheme (game-run! game) #+end_src Starts the main event loop. Initializes SDL2, opens the window, and runs the frame loop indefinitely until the user quits or the ~quit~ action is pressed. Never returns. Lifecycle order within each frame: 1. Collect SDL2 events 2. Update input state 3. Call ~update:~ hook (or active state's ~update~) 4. Clear renderer 5. Call ~render-scene!~ (if scene is set) 6. Call ~render:~ hook (or active state's ~render~) 7. Present renderer 8. Apply frame delay ** ~game-camera~ #+begin_src scheme (game-camera game) #+end_src Returns the current scene's camera struct. Only valid after ~create:~ runs. Returns a camera record with ~x~ and ~y~ fields for the top-left viewport corner in world coordinates. ** ~game-asset~ #+begin_src scheme (game-asset game key) #+end_src Retrieves an asset from the game's registry by key. Returns ~#f~ if the key is not found. ** ~game-asset-set!~ #+begin_src scheme (game-asset-set! game key value) #+end_src Stores an asset in the game's registry. Overwrites any existing value at the key. ** ~make-game-state~ #+begin_src scheme (make-game-state #!key (create #f) (update #f) (render #f)) #+end_src Creates a state record (plist) with optional lifecycle hooks. Used with ~game-add-state!~ and ~game-start-state!~ to build a state machine within the game. | Parameter | Type | Default | Description | |-----------+------+---------+-------------| | ~create~ | procedure/false | #f | Called when entering this state | | ~update~ | procedure/false | #f | Called each frame while active | | ~render~ | procedure/false | #f | Called each frame after rendering (overlay) | ** ~game-add-state!~ #+begin_src scheme (game-add-state! game name state) #+end_src Registers a named state (created with ~make-game-state~) in the game's state table. ~name~ must be a symbol. ** ~game-start-state!~ #+begin_src scheme (game-start-state! game name) #+end_src Transitions to a named state, activating its lifecycle hooks. Calls the state's ~create:~ hook (if present) immediately. ** Example: Game with State Machine #+begin_src scheme (define my-game (make-game title: "My Game")) (game-add-state! my-game 'playing (make-game-state create: (lambda (game) (print "Game started")) update: (lambda (game dt) (print "Updating...")) render: (lambda (game) (print "Rendering overlay")))) (game-add-state! my-game 'paused (make-game-state create: (lambda (game) (print "Paused")))) ; Start the game in 'playing state (game-start-state! my-game 'playing) ; Later, transition to paused (game-start-state! my-game 'paused) #+end_src * World (~downstroke-world~) #+begin_src scheme (import downstroke-world) #+end_src The world module provides the scene (level) abstraction and camera management. ** ~make-scene~ Auto-generated by defstruct. Use keyword arguments: #+begin_src scheme (make-scene #!key (entities '()) (tilemap #f) (camera #f) (tileset-texture #f) (camera-target #f)) #+end_src Creates a scene record representing the current level state. | Parameter | Type | Default | Description | |-----------+------+---------+-------------| | ~entities~ | list | ~'()~ | List of entity plists | | ~tilemap~ | tilemap/false | #f | Tile grid and collisions | | ~camera~ | camera/false | #f | Viewport position | | ~tileset-texture~ | SDL2 texture/false | #f | Rendered tileset image | | ~camera-target~ | symbol/false | #f | Tag symbol of the entity to follow (see ~scene-camera-target-set!~) | ** ~make-camera~ Auto-generated by defstruct. Use keyword arguments: #+begin_src scheme (make-camera #!key (x 0) (y 0)) #+end_src Creates a camera record. ~x~ and ~y~ are the pixel coordinates of the viewport's top-left corner in world space. ** ~camera-x~, ~camera-y~ #+begin_src scheme (camera-x camera) (camera-y camera) #+end_src Accessors for camera position. ** ~camera-x-set!~, ~camera-y-set!~ #+begin_src scheme (camera-x-set! camera x) (camera-y-set! camera y) #+end_src Mutate camera position (in-place). ** ~camera-follow!~ #+begin_src scheme (camera-follow! camera entity viewport-w viewport-h) #+end_src Centers the camera on an entity, clamping to stay within world bounds (never negative). ~viewport-w~ and ~viewport-h~ are the game window dimensions. ** ~scene-add-entity~ #+begin_src scheme (scene-add-entity scene entity) #+end_src Appends an entity to the scene's entity list. Returns the modified scene. ** ~scene-update-entities~ #+begin_src scheme (scene-update-entities scene proc1 proc2 ...) #+end_src Applies each procedure in sequence to all entities in the scene. Each procedure takes a single entity and returns a modified entity. The scene's entity list is updated once with the final result. Returns the modified scene. Example: #+begin_src scheme (define (increment-x entity) (entity-set entity #:x (+ 1 (entity-ref entity #:x 0)))) (define (apply-gravity entity) (entity-set entity #:vy (+ 1 (entity-ref entity #:vy 0)))) (scene-update-entities scene increment-x apply-gravity) ; Each entity is passed through increment-x, then through apply-gravity #+end_src ** ~scene-filter-entities~ #+begin_src scheme (scene-filter-entities scene predicate) #+end_src Removes all entities that do not satisfy the predicate. Returns the modified scene. Example: #+begin_src scheme ; Remove all entities with type 'enemy (scene-filter-entities scene (lambda (e) (not (eq? (entity-type e) 'enemy)))) #+end_src ** ~scene-find-tagged~ #+begin_src scheme (scene-find-tagged scene tag) #+end_src Returns the first entity whose ~#:tags~ list (a list of symbols) contains the given tag, or ~#f~ if not found. ** ~scene-find-all-tagged~ #+begin_src scheme (scene-find-all-tagged scene tag) #+end_src Returns a list of all entities whose ~#:tags~ list contains the given tag. Returns ~'()~ if none found. ** Accessor functions (auto-generated by defstruct) - ~scene-entities~, ~scene-entities-set!~ - ~scene-tilemap~, ~scene-tilemap-set!~ - ~scene-camera~, ~scene-camera-set!~ - ~scene-tileset-texture~, ~scene-tileset-texture-set!~ - ~scene-camera-target~, ~scene-camera-target-set!~ ** ~tilemap-tile-at~ #+begin_src scheme (tilemap-tile-at tilemap col row) #+end_src Returns the tile ID at grid position ~(col, row)~ across all layers. Returns ~0~ if the position is out of bounds or all layers have a 0 tile at that cell. Used internally by the physics module; also useful for manual tile queries. * Entity (~downstroke-entity~) #+begin_src scheme (import downstroke-entity) #+end_src The entity module provides property list (plist) accessors for game objects. Entities are immutable plists, never modified in place. It also defines ~entity-skips-pipeline?~ and the ~define-pipeline~ macro for frame pipeline steps that respect ~#:skip-pipelines~ (see ~docs/physics.org~ for the built-in physics step names). ** ~entity-ref~ #+begin_src scheme (entity-ref entity key #!optional default) #+end_src Retrieves the value of a key from an entity plist. Returns ~default~ (or ~#f~) if the key is not found. If ~default~ is a procedure, it is called with no arguments to produce the default. Example: #+begin_src scheme (entity-ref player #:x 0) ; Get x, defaulting to 0 (entity-ref player #:tags '()) ; Get tags, defaulting to empty list #+end_src ** ~entity-type~ #+begin_src scheme (entity-type entity) #+end_src Shorthand for ~(entity-ref entity #:type #f)~. Returns the entity's ~#:type~ field or ~#f~. ** ~entity-set~ #+begin_src scheme (entity-set entity key value) #+end_src Returns a new plist with the key/value updated. **Does not modify the original entity.** This is a functional (immutable) operation. Example: #+begin_src scheme (define player (list #:x 100 #:y 200 #:type 'player)) (define moved (entity-set player #:x 150)) ; player is still (list #:x 100 #:y 200 #:type 'player) ; moved is (list #:x 150 #:y 200 #:type 'player) #+end_src ** ~entity-update~ #+begin_src scheme (entity-update entity key proc #!optional default) #+end_src Functional update: applies ~proc~ to the current value of ~key~ and updates the entity with the result. Returns a new plist. Example: #+begin_src scheme (entity-update player #:x (lambda (x) (+ x 10))) ; Equivalent to (entity-set player #:x (+ 10 (entity-ref player #:x 0))) #+end_src ** ~entity-skips-pipeline?~ #+begin_src scheme (entity-skips-pipeline? entity step-symbol) #+end_src Returns true if ~step-symbol~ appears in the entity’s ~#:skip-pipelines~ list (and that list is non-empty). The built-in physics step names are documented in ~docs/physics.org~; other engine modules may reserve additional symbols for their own frame phases (rendering, animation, etc.) using the same plist key. ** ~define-pipeline~ #+begin_src scheme (define-pipeline (procedure-name skip-symbol) (entity-formal extra-formal ...) body ...) #+end_src Syntax for authors of per-entity pipeline steps: expands to a ~define~ that returns the **first** formal (the entity) unchanged when ~skip-symbol~ is listed in ~#:skip-pipelines~; otherwise runs ~body ...~ inside ~(let () ...)~. Used throughout ~downstroke-physics~; other modules can use it for consistent skip behavior. The procedure name and skip symbol differ when needed (e.g. ~detect-ground~ vs ~ground-detection~). ** Shared Entity Keys All entities can have these keys. Not all are required: | Key | Type | Description | |-----|------|-------------| | ~#:type~ | symbol | Entity type (e.g., 'player, 'enemy) | | ~#:x~ | number | X position in pixels | | ~#:y~ | number | Y position in pixels | | ~#:width~ | number | Bounding box width in pixels | | ~#:height~ | number | Bounding box height in pixels | | ~#:vx~ | number | X velocity in pixels/frame | | ~#:vy~ | number | Y velocity in pixels/frame | | ~#:tile-id~ | integer | Sprite index in tileset (1-indexed) | | ~#:tags~ | list | List of symbols for lookup (e.g., '(player)) | | ~#:gravity?~ | boolean | Apply gravity to this entity? | | ~#:on-ground?~ | boolean | Is entity touching a solid tile below? | | ~#:facing~ | integer | 1 (right) or -1 (left) | | ~#:solid?~ | boolean | Participate in AABB entity collisions? | | ~#:skip-pipelines~ | list | Symbols naming pipeline steps to skip; physics defines the built-in set (~docs/physics.org~) | | ~#:anim-name~ | symbol | Current animation name | | ~#:anim-frame~ | integer | Current frame index | | ~#:anim-tick~ | integer | Ticks in current frame | * Physics (~downstroke-physics~) #+begin_src scheme (import downstroke-physics) #+end_src The physics module provides functions for movement, collision detection, and ground sensing. Call them manually in your ~update:~ hook in the order that suits your game type (see ~docs/physics.org~ for examples). ** Physics Pipeline Order The built-in physics functions are normally run in this order each frame (after reading input, before rendering): 1. ~apply-jump~ — if jump pressed and on ground, set ~#:ay~ 2. ~apply-acceleration~ — consume ~#:ay~ into ~#:vy~ 3. ~apply-gravity~ — add gravity to ~#:vy~ 4. ~apply-velocity-x~ — move by ~#:vx~ 5. ~resolve-tile-collisions-x~ — snap against horizontal tile collisions 6. ~apply-velocity-y~ — move by ~#:vy~ 7. ~resolve-tile-collisions-y~ — snap against vertical tile collisions 8. ~detect-ground~ — set ~#:on-ground?~ if standing on a tile 9. ~resolve-entity-collisions~ — push apart solid entities (whole list) Entities may list ~#:skip-pipelines~ to omit specific steps; see ~entity-skips-pipeline?~ under ~downstroke-entity~ and ~docs/physics.org~. (This separation ensures smooth sliding along walls.) ** ~apply-acceleration~ #+begin_src scheme (apply-acceleration entity) #+end_src Consumes ~#:ay~ (one-shot acceleration) into ~#:vy~ and clears ~#:ay~ to 0. Only applies if ~#:gravity?~ is true. ** ~apply-gravity~ #+begin_src scheme (apply-gravity entity) #+end_src Adds the gravity constant (1 pixel/frame²) to ~#:vy~. Only applies if ~#:gravity?~ is true. ** ~apply-velocity-x~ #+begin_src scheme (apply-velocity-x entity) #+end_src Updates ~#:x~ by adding ~#:vx~. Returns a new entity. ** ~apply-velocity-y~ #+begin_src scheme (apply-velocity-y entity) #+end_src Updates ~#:y~ by adding ~#:vy~. Returns a new entity. ** ~apply-velocity~ #+begin_src scheme (apply-velocity entity) #+end_src Legacy function: updates both ~#:x~ and ~#:y~ by their respective velocities. ** ~resolve-tile-collisions-x~ #+begin_src scheme (resolve-tile-collisions-x entity tilemap) #+end_src Detects and resolves collisions between the entity's bounding box and solid tiles along the X axis. Snaps the entity's ~#:x~ to the near/far tile edge and sets ~#:vx~ to 0. Returns a new entity. ** ~resolve-tile-collisions-y~ #+begin_src scheme (resolve-tile-collisions-y entity tilemap) #+end_src Detects and resolves collisions between the entity's bounding box and solid tiles along the Y axis. Snaps the entity's ~#:y~ to the near/far tile edge and sets ~#:vy~ to 0. Returns a new entity. ** ~detect-ground~ #+begin_src scheme (detect-ground entity tilemap) #+end_src Probes one pixel below the entity's feet to detect if it is standing on a solid tile. Sets ~#:on-ground?~ to true or false. Only applies if ~#:gravity?~ is true. Returns a new entity. ** ~apply-jump~ #+begin_src scheme (apply-jump entity jump-pressed?) #+end_src If the jump button is pressed and the entity is on ground, sets ~#:ay~ to ~(- #:jump-force)~ (default 15 pixels/frame). On the next frame, ~apply-acceleration~ consumes this into ~#:vy~. Returns a new entity. ** ~resolve-entity-collisions~ #+begin_src scheme (resolve-entity-collisions entities) #+end_src Detects and resolves AABB collisions between all pairs of entities with ~#:solid?~ true. Pushes overlapping entities apart along the axis of minimum penetration and sets their velocities in the push direction. Returns a new entity list. ** ~scene-resolve-collisions~ #+begin_src scheme (scene-resolve-collisions scene) #+end_src Applies ~resolve-entity-collisions~ to the scene's entity list. Returns the modified scene. ** Physics Constants - ~*gravity*~ = 1 (pixels per frame per frame) - ~*jump-force*~ = 15 (vertical acceleration on jump) * Input (~downstroke-input~) #+begin_src scheme (import downstroke-input) #+end_src The input module handles keyboard, joystick, and game controller events. It maintains the current and previous input state to support pressed/released detection. ** ~*default-input-config*~ The default input configuration record. Use as the ~input-config:~ parameter to ~make-game~, or create a custom one with ~make-input-config~. | Action | Keyboard | Joystick | Controller | |--------|----------|----------|------------| | ~up~ | W or Up | Y-axis negative | DPad Up, Left-Y negative | | ~down~ | S or Down | Y-axis positive | DPad Down, Left-Y positive | | ~left~ | A or Left | X-axis negative | DPad Left, Left-X negative | | ~right~ | D or Right | X-axis positive | DPad Right, Left-X positive | | ~a~ | J or Z | Button 0 | A button | | ~b~ | K or X | Button 1 | B button | | ~start~ | Return | Button 7 | Start button | | ~select~ | (unmapped) | Button 6 | Back button | | ~quit~ | Escape | (unmapped) | (unmapped) | ** ~input-held?~ #+begin_src scheme (input-held? state action) #+end_src Returns true if the action is currently held down. ~action~ is a symbol like ~'left~ or ~'a~. ** ~input-pressed?~ #+begin_src scheme (input-pressed? state action) #+end_src Returns true if the action was pressed in this frame (held now but not in the previous frame). ** ~input-released?~ #+begin_src scheme (input-released? state action) #+end_src Returns true if the action was released in this frame (held previously but not now). ** ~create-input-state~ #+begin_src scheme (create-input-state config) #+end_src Initializes an input state record from a configuration. All actions start as unpressed. ** ~input-any-pressed?~ #+begin_src scheme (input-any-pressed? state config) #+end_src Returns true if any action in the configuration was pressed this frame. Useful for "press any key to continue" prompts. ** ~input-state->string~ #+begin_src scheme (input-state->string state config) #+end_src Returns a human-readable string of all currently held actions, e.g., ~"[Input: left a]"~. Useful for debug displays. ** ~set-facing-from-vx~ #+begin_src scheme (set-facing-from-vx entity vx) #+end_src Sets ~#:facing~ to ~1~ if ~vx > 0~, ~-1~ if ~vx < 0~, and returns the entity unchanged if ~vx = 0~. Convenience helper for keeping the facing direction in sync with horizontal velocity. ** ~apply-input-to-entity~ #+begin_src scheme (apply-input-to-entity entity held?) #+end_src Applies an input map stored on the entity to compute a new ~#:vx~. The entity must have an ~#:input-map~ key: an alist of ~(action . (dvx . dvy))~ pairs. ~held?~ is a predicate ~(lambda (action) ...)~ (typically ~(lambda (a) (input-held? input a))~). Also updates ~#:facing~ from the resulting velocity. Example: #+begin_src scheme ;; Entity with embedded input map: (list #:type 'player #:input-map '((left . (-3 . 0)) (right . (3 . 0))) #:move-speed 1 ...) ;; In update: (apply-input-to-entity player (lambda (a) (input-held? input a))) #+end_src ** Example: Player Movement #+begin_src scheme (define (update-player game dt) (let ((input (game-input game)) (player (car (scene-entities (game-scene game))))) (if (input-pressed? input 'a) (apply-jump player #t) player))) #+end_src * Renderer (~downstroke-renderer~) #+begin_src scheme (import downstroke-renderer) #+end_src The renderer module provides SDL2 drawing abstractions. ** ~render-scene!~ #+begin_src scheme (render-scene! renderer scene) #+end_src Draws the entire scene: first the tilemap layers, then all entities. Called automatically by ~game-run!~ before the user's ~render:~ hook. Does nothing if the scene is ~#f~. ** ~entity-screen-coords~ #+begin_src scheme (entity-screen-coords entity camera) #+end_src Returns a list ~(x y width height)~ of the entity's bounding box in screen (viewport) coordinates. Pure function, testable without SDL2. Example: #+begin_src scheme (entity-screen-coords player (make-camera x: 100 y: 50)) ; If player is at world (200, 100) with size (16, 16): ; Returns (100 50 16 16) -- offset by camera position #+end_src ** ~entity-flip~ #+begin_src scheme (entity-flip entity) #+end_src Returns an SDL2 flip list based on the entity's ~#:facing~ field. Returns ~'(horizontal)~ if facing is -1, ~'()~ otherwise. ** ~draw-ui-text~ #+begin_src scheme (draw-ui-text renderer font text color x y) #+end_src Renders a single line of text to the screen at the given pixel coordinates. ~color~ is an SDL2 color struct. Positions are in screen (viewport) space, not world space. Does not cache; call once per frame for each text element. ** Debug Drawing Debug drawing displays collision boxes and tile grid overlays to visualize physics during development. Enable with the ~debug?:~ keyword on ~make-game~. *** ~render-debug-scene!~ #+begin_src scheme (render-debug-scene! renderer scene) #+end_src Renders debug overlays for the entire scene: tile grid boundaries and entity collision boxes. Call this in your render hook to see what the physics engine sees. *** ~draw-debug-tiles~ #+begin_src scheme (draw-debug-tiles renderer camera tilemap) #+end_src Draws a grid outline around all non-zero tiles in the tilemap. Useful for understanding tilemap layout and collision geometry. Color: purple (~+debug-tile-color+~). *** ~draw-debug-entities~ #+begin_src scheme (draw-debug-entities renderer camera scene) #+end_src Draws colored bounding boxes around all entities in the scene. Also draws attack hitboxes when the entity's ~#:attack-timer~ is active. Entity colors are: | Type | Color | Value | |------|-------|-------| | Player | Blue (~+debug-player-color+~) | rgb(64, 128, 255) | | Enemy | Red (~+debug-enemy-color+~) | rgb(220, 40, 40) | | Attack Hitbox | Green (~+debug-attack-color+~) | rgb(0, 200, 80) | | Tile | Purple (~+debug-tile-color+~) | rgb(140, 0, 220) | *** Example: Enable Debug Mode #+begin_src scheme (define my-game (make-game title: "My Game" width: 600 height: 400 debug?: #t)) ;; Enable debug overlay (game-run! my-game) #+end_src With debug mode enabled, the game renders the normal scene first, then overlays collision boxes and tile boundaries on top. This is useful for finding collision bugs and understanding the physics layout. ** Menus The ~draw-menu-items~ helper renders a list of menu options with a cursor indicator. *** ~draw-menu-items~ #+begin_src scheme (draw-menu-items renderer font items cursor x y-start y-step #!key (label-fn identity) (color #f) (prefix "> ") (no-prefix " ")) #+end_src Renders a vertical menu with a cursor indicator on the currently selected item. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | ~renderer~ | renderer | (required) | SDL2 renderer | | ~font~ | TTF font | (required) | Font for rendering text | | ~items~ | list | (required) | List of menu items (any values) | | ~cursor~ | integer | (required) | Index of highlighted item (0-based) | | ~x~ | integer | (required) | X position in screen space | | ~y-start~ | integer | (required) | Y position of first item | | ~y-step~ | integer | (required) | Pixel spacing between items | | ~label-fn~ | procedure | ~identity~ | Function to convert item to string (called for each item) | | ~color~ | SDL2 color | white (255, 255, 255) | Text color | | ~prefix~ | string | "~> ~" | Prefix for highlighted item | | ~no-prefix~ | string | "~ ~" | Prefix for non-highlighted items | *** Example: Menu State #+begin_src scheme (import downstroke-engine downstroke-world (prefix sdl2 "sdl2:") (prefix sdl2-ttf "ttf:")) (define menu-items '(("Start Game") ("Options") ("Quit"))) (define cursor (make-parameter 0)) (define my-game (make-game title: "Menu Game" width: 600 height: 400 preload: (lambda (game) (let ((font (ttf:open-font "assets/font.ttf" 24))) (game-asset-set! game 'menu-font font))) create: (lambda (game) (game-add-state! game 'menu (make-game-state render: (lambda (game) (let ((font (game-asset game 'menu-font))) (draw-menu-items (game-renderer game) font menu-items (cursor) 100 100 40)))))))) (game-run! my-game) #+end_src ** Sprite Fonts Sprite fonts render text using a bitmap tileset instead of system fonts. Characters are stored as tiles in your tileset image, making them pixel-perfect and zero-dependency on TTF files. *** ~make-sprite-font*~ #+begin_src scheme (make-sprite-font* #!key tile-size spacing ranges) #+end_src Creates a sprite font from character ranges. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | ~tile-size~ | integer | (required) | Pixel width/height of each character tile | | ~spacing~ | integer | 1 | Pixels between characters when rendered | | ~ranges~ | list | (required) | List of ~(start-char end-char first-tile-id)~ triples | The ~ranges~ parameter defines which tile IDs correspond to which characters. For example, ~(list (#\A #\M 917) (#\N #\Z 966) (#\0 #\9 868))~ maps characters A–M to tile IDs 917–929, N–Z to 966–978, and 0–9 to 868–877. Characters are automatically uppercased when rendered (all lookups use uppercase). *** ~sprite-font-char->tile-id~ #+begin_src scheme (sprite-font-char->tile-id font ch) #+end_src Returns the tile ID for a character, or ~#f~ if not found. The character is automatically uppercased before lookup. *** ~sprite-text-width~ #+begin_src scheme (sprite-text-width font text) #+end_src Computes the pixel width of a string when rendered with the given font. Useful for centering text. Formula: ~(* n tile-size) + (* (- n 1) spacing)~ where ~n~ is the string length. *** ~draw-sprite-text~ #+begin_src scheme (draw-sprite-text renderer tileset-texture tileset font text x y) #+end_src Renders sprite-font text to the screen. ~tileset-texture~ is the SDL2 texture of your tileset image, and ~tileset~ is the tilemap structure containing tile layout information. Characters not in the font are silently skipped. | Parameter | Type | Description | |-----------|------|-------------| | ~renderer~ | renderer | SDL2 renderer | | ~tileset-texture~ | SDL2 texture | Rendered tileset image | | ~tileset~ | tileset struct | Tileset from loaded tilemap | | ~font~ | sprite-font struct | Font created with ~make-sprite-font*~ | | ~text~ | string | Text to render | | ~x, y~ | integers | Screen position in pixels | *** Example: Sprite Font for Score Display #+begin_src scheme (import downstroke-engine downstroke-scene-loader downstroke-renderer) ;; Create a sprite font with A-Z at tiles 917–942, 0-9 at tiles 868–877 (define score-font (make-sprite-font* tile-size: 16 spacing: 1 ranges: (list (list #\A #\Z 917) (list #\0 #\9 868)))) (define my-game (make-game title: "Score Game" width: 600 height: 400 create: (lambda (game) (game-load-scene! game "assets/level.tmx")) render: (lambda (game) (let ((scene (game-scene game))) ;; Display "SCORE: 1500" using sprite font (draw-sprite-text (game-renderer game) (scene-tileset-texture scene) (tilemap-tileset (scene-tilemap scene)) score-font "SCORE: 1500" 10 10))))) (game-run! my-game) #+end_src * Assets (~downstroke-assets~) #+begin_src scheme (import downstroke-assets) #+end_src The assets module provides a simple registry for game resources. ** ~make-asset-registry~ #+begin_src scheme (make-asset-registry) #+end_src Creates an empty asset registry (hash-table). Returned by ~make-game~ automatically. ** ~asset-set!~ #+begin_src scheme (asset-set! registry key value) #+end_src Stores a value in the registry by key (any hashable value). Overwrites any existing value. ** ~asset-ref~ #+begin_src scheme (asset-ref registry key) #+end_src Retrieves a value from the registry by key. Returns ~#f~ if not found. ** Example: Storing Fonts #+begin_src scheme (define (preload game) (let ((font (ttf:open-font "assets/font.ttf" 16))) (game-asset-set! game 'main-font font))) (define (render-overlay game) (let ((font (game-asset game 'main-font))) (draw-ui-text (game-renderer game) font "Score: 100" (sdl2:make-color 255 255 255) 10 10))) #+end_src * Sound (~downstroke-sound~) #+begin_src scheme (import downstroke-sound) #+end_src The sound module provides music and sound effect playback via SDL_mixer. ** ~init-audio!~ #+begin_src scheme (init-audio!) #+end_src Initializes SDL_mixer with default settings (44100 Hz, stereo, 512 buffer). Call once in the ~preload:~ hook. ** ~load-sounds!~ #+begin_src scheme (load-sounds! sound-alist) #+end_src Loads sound effects from an alist of ~(name . path)~ pairs, where ~name~ is a symbol and ~path~ is a file path. Stores them globally for playback. Example: #+begin_src scheme (load-sounds! '((jump . "assets/jump.wav") (hit . "assets/hit.wav"))) #+end_src ** ~play-sound~ #+begin_src scheme (play-sound name) #+end_src Plays a loaded sound effect by name (symbol). Plays on the first available channel. No-op if the sound is not found. ** ~load-music!~ #+begin_src scheme (load-music! path) #+end_src Loads background music from a file. Replaces any previously loaded music. ** ~play-music!~ #+begin_src scheme (play-music! volume) #+end_src Plays the loaded music on an infinite loop. ~volume~ is a number from 0.0 to 1.0. ** ~stop-music!~ #+begin_src scheme (stop-music!) #+end_src Stops the currently playing music immediately. ** ~set-music-volume!~ #+begin_src scheme (set-music-volume! volume) #+end_src Changes the music volume while it is playing. ~volume~ is 0.0 to 1.0. ** ~cleanup-audio!~ #+begin_src scheme (cleanup-audio!) #+end_src Releases all audio resources. Call at shutdown or in a cleanup hook. * Tweens (~downstroke-tween~) #+begin_src scheme (import downstroke-tween) #+end_src Time-based interpolation of numeric entity properties. Library-only — call from ~update:~; see ~docs/tweens.org~ for patterns with ~#:skip-pipelines~. ** ~make-tween~ #+begin_src scheme (make-tween entity #!key props duration (delay 0) ease (on-complete #f)) #+end_src | Keyword | Description | |---------+-------------| | ~props~ | Alist ~((#:key . target-number) ...)~ | | ~duration~ | Milliseconds of interpolation after ~delay~ | | ~delay~ | Initial wait in ms (default 0) | | ~ease~ | Symbol (e.g. ~quad-in-out~) or ~(lambda (t) ...)~ with ~t~ in [0,1] | | ~on-complete~ | Optional ~(lambda (entity) ...)~ once at completion | ** ~tween-step~ #+begin_src scheme (tween-step tween entity dt) #+end_src Returns two values: updated tween struct and updated entity. ~dt~ is elapsed milliseconds for this frame. ** ~tween-finished?~ / ~tween-active?~ ** Easing exports ~ease-linear~, ~ease-quad-in~, ~ease-quad-out~, ~ease-quad-in-out~, ~ease-cubic-in~, ~ease-cubic-out~, ~ease-cubic-in-out~, ~ease-sine-in-out~, ~ease-expo-in~, ~ease-expo-out~, ~ease-expo-in-out~, ~ease-back-out~, ~ease-named~, ~ease-resolve~. * Animation (~downstroke-animation~) #+begin_src scheme (import downstroke-animation) #+end_src The animation module provides simple frame-based sprite animation. ** ~animation-frames~ #+begin_src scheme (animation-frames anim) #+end_src Returns the ~#:frames~ list from an animation plist. Each element is a 0-indexed tile ID. ** ~animation-duration~ #+begin_src scheme (animation-duration anim) #+end_src Returns the ~#:duration~ value from an animation plist (ticks per frame). ** ~set-animation~ #+begin_src scheme (set-animation entity name) #+end_src Switches the entity to a named animation, resetting frame and tick counters to 0. No-op if the animation is already active (prevents restarting mid-loop). Returns a new entity. ** ~animate-entity~ #+begin_src scheme (animate-entity entity animations) #+end_src Advances the entity's animation frame based on elapsed ticks. ~animations~ is an alist of ~(name . animation-plist)~ pairs. Each animation plist has ~#:frames~ (list of tile IDs, 0-indexed) and ~#:duration~ (ticks per frame). Returns a new entity with updated ~#:anim-tick~, ~#:anim-frame~, and ~#:tile-id~. Example: #+begin_src scheme (define player-animations '((idle #:frames (28) #:duration 10) (walk #:frames (27 28) #:duration 10) (jump #:frames (29) #:duration 1))) (define player (list #:anim-name 'walk #:anim-frame 0 #:anim-tick 0)) (animate-entity player player-animations) ; After 10 ticks, frame advances to 1 (wraps to 0 after reaching length) #+end_src ** ~frame->tile-id~ #+begin_src scheme (frame->tile-id frames frame-idx) #+end_src Converts a frame index to a tile ID (1-indexed). Used internally by ~animate-entity~. * Tilemap (~downstroke-tilemap~) #+begin_src scheme (import downstroke-tilemap) #+end_src The tilemap module parses Tiled TMX and TSX files using the expat XML library, and provides struct accessors for tile and map data. ** ~load-tilemap~ #+begin_src scheme (load-tilemap filename) #+end_src Loads and parses a TMX file. Automatically resolves and loads the referenced TSX tileset and its image. Returns a ~tilemap~ struct. ** ~load-tileset~ #+begin_src scheme (load-tileset filename) #+end_src Loads and parses a TSX tileset file. Loads the image referenced in the tileset. Returns a ~tileset~ struct. ** ~tileset-tile~ #+begin_src scheme (tileset-tile tileset tile-id) #+end_src Returns a ~tile~ struct for the given 1-indexed tile ID. The tile struct has ~tile-id~ and ~tile-rect~ (an SDL2 rect) fields with the source rectangle in the tileset image. ** ~tileset-rows~ #+begin_src scheme (tileset-rows tileset) #+end_src Returns the number of rows in the tileset image (computed from ~tilecount / columns~). ** Tileset Accessors (auto-generated by defstruct) - ~tileset-tilewidth~, ~tileset-tileheight~ — tile dimensions in pixels - ~tileset-spacing~ — pixel gap between tiles in the source image - ~tileset-tilecount~ — total number of tiles - ~tileset-columns~ — number of tile columns - ~tileset-image-source~ — path to the tileset image file - ~tileset-image~ — loaded SDL2 surface ** Tilemap Accessors (auto-generated by defstruct) - ~tilemap-width~, ~tilemap-height~ — map dimensions in tiles - ~tilemap-tilewidth~, ~tilemap-tileheight~ — tile dimensions in pixels - ~tilemap-tileset~ — the embedded ~tileset~ struct - ~tilemap-layers~ — list of ~layer~ structs - ~tilemap-objects~ — list of ~object~ structs (from the Tiled object layer) ** Layer Accessors (auto-generated by defstruct) - ~layer-name~ — layer name string - ~layer-width~, ~layer-height~ — layer dimensions in tiles - ~layer-map~ — 2D list of tile GIDs (rows × columns) ** Object Accessors (auto-generated by defstruct) - ~object-name~, ~object-type~ — object name and type (strings from XML) - ~object-x~, ~object-y~ — position in pixels - ~object-width~, ~object-height~ — size in pixels - ~object-properties~ — alist of custom properties from Tiled * Scene Loader (~downstroke-scene-loader~) #+begin_src scheme (import downstroke-scene-loader) #+end_src The scene-loader module provides utilities for loading Tiled maps and instantiating entities from prefabs. ** ~game-load-scene!~ #+begin_src scheme (game-load-scene! game filename) #+end_src Loads a TMX (Tiled) map from a file and creates a scene. Steps: 1. Loads the tilemap from the file 2. Creates an SDL2 texture from the tileset image 3. Creates an empty scene with the tilemap and camera 4. Stores the tilemap in game assets under key ~'tilemap~ 5. Sets the scene on the game 6. Returns the scene Example: #+begin_src scheme (define (create-game game) (game-load-scene! game "assets/level1.tmx") ; Now add entities to the scene... ) #+end_src ** ~game-load-tilemap!~ #+begin_src scheme (game-load-tilemap! game key filename) #+end_src Loads a TMX tilemap file, stores it in the game asset registry under ~key~, and returns the tilemap struct. ** ~game-load-tileset!~ #+begin_src scheme (game-load-tileset! game key filename) #+end_src Loads a TSX tileset file, stores it in the game asset registry under ~key~, and returns the tileset struct. ** ~game-load-font!~ #+begin_src scheme (game-load-font! game key filename size) #+end_src Opens a TTF font at ~size~ points, stores it in the game asset registry under ~key~, and returns the font. Wraps ~ttf:open-font~. ** ~create-tileset-texture~ #+begin_src scheme (create-tileset-texture renderer tilemap) #+end_src Creates an SDL2 texture from the tileset image embedded in a tilemap struct. Useful when you need the texture independently of ~game-load-scene!~. ** ~load-prefabs~ #+begin_src scheme (load-prefabs filename engine-mixin-table user-hooks) #+end_src Loads a prefab definition file and returns a ~prefab-registry~ struct. The file must contain a Scheme expression with ~mixins~ and ~prefabs~ sections (see ~docs/entities.org~). | Parameter | Type | Description | |-----------|------|-------------| | ~filename~ | string | Path to the prefab data file (e.g., ~"assets/prefabs.scm"~) | | ~engine-mixin-table~ | alist | Extra engine-level mixins to merge; pass ~'()~ if none | | ~user-hooks~ | alist | Extra instantiation hooks; pass ~'()~ if none | Example: #+begin_src scheme (define *prefabs* (load-prefabs "assets/prefabs.scm" '() '())) #+end_src ** ~reload-prefabs!~ #+begin_src scheme (reload-prefabs! registry) #+end_src Reloads the prefab file that the registry was originally loaded from. Returns a new registry. Useful for hot-reloading prefab data during development without restarting the game. ** ~instantiate-prefab~ #+begin_src scheme (instantiate-prefab registry type x y w h) #+end_src Looks up a prefab by type symbol in the registry and returns a fresh entity plist at the given position and size. Returns ~#f~ if the type is not registered. If the resulting entity has an ~#:on-instantiate~ hook, it is called with the entity before returning. ** ~tilemap-objects->entities~ #+begin_src scheme (tilemap-objects->entities tilemap registry) #+end_src Converts the TMX object list (from Tiled) into entity plists using the prefab registry. Each object's type string is converted to a symbol and passed to ~instantiate-prefab~. Objects whose type has no registered prefab are silently filtered out. Example: #+begin_src scheme (let* ((registry (load-prefabs "assets/prefabs.scm" '() '())) (entities (tilemap-objects->entities tilemap registry))) (scene-entities-set! scene entities)) #+end_src