aboutsummaryrefslogtreecommitdiff
path: root/README.org
blob: 45db4084035828693fce0a72cf6cf46f41809bab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
* Downstroke

A 2D tile-driven game engine for Chicken Scheme, built on SDL2. Targets old-school platformer and arcade games — NES-style and beyond. The API is inspired by Phaser 2: one call to start, lifecycle hooks to fill in. A minimal game is ~20 lines of Scheme.

#+begin_src scheme
(define my-game
  (make-game
    title: "My Game" width: 320 height: 240
    preload: (lambda (game) ...)      ; load assets
    create:  (lambda (game) ...)      ; set up scene
    update:  (lambda (game dt) ...))) ; per-frame logic (physics runs first)

(game-run! my-game)
#+end_src

** Features

- Tile-based physics: gravity, velocity, AABB collision against TMX tilemaps
- Built-in update pipeline: input → acceleration → gravity → x-collision → y-collision → ground detection → entity collisions
- Entities as plists — purely functional, no classes
- Data-driven prefab/mixin system for composing entity types
- FSM-based enemy AI via the =states= egg
- Configurable input: keyboard, joystick, and controller
- Asset registry with =preload:= lifecycle hook
- Scene and camera management
- Sprite animation with frame/tick tracking
- SDL2_mixer audio via a thin FFI binding

** Status

Early extraction phase. The engine logic is fully working — it powers [[https://genepasquet.itch.io/macroknight][macroknight]], built for Spring Lisp Game Jam 2025. Work is underway to extract it into a standalone, installable Chicken egg.

| Milestone | Description                        | Status   |
|-----------+------------------------------------+----------|
|         1 | Zero-risk module extraction        | DONE     |
|         2 | Configurable input system          | pending  |
|         3 | Data-driven entity rendering       | pending  |
|         4 | Renderer abstraction               | pending  |
|         5 | Asset preloading                   | pending  |
|         6 | Scene loading via =create:= hook   | pending  |
|         7 | =make-game= / =game-run!= API      | pending  |
|         8 | Camera follow                      | pending  |
|         9 | Named scene states                 | pending  |
|        10 | AI tag-based lookup                | pending  |
|        11 | Package as Chicken egg (v0.1.0)    | pending  |
|        12 | Macroknight ported to use the egg  | pending  |

Milestones 1–6 are pure refactoring. Milestone 7 is the design pivot where the public API stabilises. Milestones 11–12 produce the first installable egg and validate it against macroknight.

** Dependencies

System libraries: =SDL2=, =SDL2_image=, =SDL2_mixer=, =SDL2_ttf=

Chicken eggs:
#+begin_example
chicken-install sdl2 sdl2-image expat matchable defstruct states \
                srfi-197 simple-logger srfi-64
#+end_example

** Building

#+begin_src sh
make        # compile all modules to bin/
make test   # run SRFI-64 test suites
make demos  # build demo games (verifies they compile)
make clean  # remove bin/
#+end_src

Single test module:
#+begin_src sh
csi -s tests/physics-test.scm
#+end_src

** Architecture

Modules live at the project root. Each compiles as a Chicken unit (=csc -c -J -unit=). Compile order follows the dependency graph:

: entity, tilemap → world → animation, physics, ai → input → prefabs → mixer → sound → engine

| Module         | Responsibility                                    |
|----------------+---------------------------------------------------|
| =entity=       | Plist accessors: =entity-ref=, =entity-set=       |
| =tilemap=      | TMX/TSX parsing (expat), tileset loading          |
| =world=        | Scene struct, entity list ops, camera             |
| =physics=      | Gravity, velocity, AABB tile + entity collisions  |
| =input=        | SDL2 events → action mapping, configurable binds  |
| =animation=    | Frame/tick tracking, sprite ID mapping            |
| =ai=           | FSM enemy AI: idle → patrol → chase → attack      |
| =prefabs=      | Mixin composition, entity instantiation           |
| =renderer=     | =draw-sprite=, =draw-tilemap-layer=, =draw-text=  |
| =assets=       | Asset registry for the =preload:= hook            |
| =scene-loader= | =game-load-scene!=, =instantiate-prefab=          |
| =mixer=        | SDL_mixer FFI (no Scheme deps)                    |
| =sound=        | Sound registry, music playback                    |
| =engine=       | =make-game=, =game-run!=, lifecycle orchestration |

Entities are plists — no classes, purely functional:

#+begin_src scheme
(list #:type 'player
      #:x 100 #:y 200 #:width 16 #:height 16
      #:vx 0 #:vy 0
      #:gravity? #t #:on-ground? #f
      #:tile-id 29
      #:tags '(player)
      #:anim-name 'walk
      #:animations '((idle #:frames (28) #:duration 10)
                     (walk #:frames (27 28) #:duration 10)))
#+end_src

Entity types are composed from mixins declared in a data file:

#+begin_src scheme
(mixins
  (physics-body #:vx 0 #:vy 0 #:gravity? #t #:on-ground? #f)
  (has-facing   #:facing 1))

(prefabs
  (player physics-body has-facing #:type player #:tile-id 29))
#+end_src

** Credits

Engine extracted from [[https://genepasquet.itch.io/macroknight][macroknight]] (Spring Lisp Game Jam 2025).
Code: Gene Pasquet — Levels: Owen Pasquet — Art: [[https://kenney.nl][Kenney]] (1-bit pack)