#+TITLE: Entities Downstroke entities are the moving (and non-moving) things in your scene: the player, enemies, coins, bullets, platforms, invisible triggers. Internally each entity is an *alist* — a list of =(keyword . value)= pairs — so an entity is just plain data that every pipeline step reads, transforms, and returns a fresh copy of. There are no classes, no inheritance, no hidden state. You build entities either by hand as a plist and converting with =plist->alist=, or from a *prefab* data file that composes named mixins with inline fields. The module that owns this vocabulary is =(downstroke entity)= (all the =entity-*= procedures and the =define-pipeline= macro). The companion module =(downstroke prefabs)= loads prefab data files and instantiates them. Most games will touch both. * The minimum you need The simplest entity is a plist of keyword keys converted to an alist. From the getting-started demo (=demo/getting-started.scm=): #+begin_src scheme (import (only (list-utils alist) plist->alist) (downstroke entity)) (define (make-player) (plist->alist (list #:type 'player #:x 150 #:y 100 #:width 32 #:height 32 #:color '(100 160 255)))) #+end_src Read a value with =entity-ref=, update it (functionally) with =entity-set=: #+begin_src scheme (entity-ref player #:x) ; → 150 (entity-set player #:x 200) ; → new entity, player still has #:x 150 #+end_src *Getting-started demo* — run with =bin/demo-getting-started=, source in =demo/getting-started.scm=. * Core concepts ** Entities are alists of CHICKEN keywords An entity is an association list whose keys are CHICKEN keywords (=#:type=, =#:x=, =#:vx=, etc.). For example, the platformer's player after =plist->alist= looks like: #+begin_src scheme ((#:type . player) (#:x . 100) (#:y . 50) (#:width . 16) (#:height . 16) (#:vx . 0) (#:vy . 0) (#:gravity? . #t) (#:on-ground? . #f) (#:tile-id . 1) (#:tags . (player))) #+end_src The engine defines a handful of *shared keys* (=#:type=, =#:x=, =#:y=, =#:width=, =#:height=, =#:vx=, =#:vy=, =#:tile-id=, =#:tags=, and a few more per subsystem) that the built-in pipelines read and write. Your game is free to add any other keys it needs — they're just data and the engine ignores what it doesn't know. There is also a minimal constructor for the positional fields, which sets =#:type= to ='none=: #+begin_src scheme (make-entity x y w h) ;; → ((#:type . none) (#:x . x) (#:y . y) (#:width . w) (#:height . h)) #+end_src In practice most entities are built via =plist->alist= (for ad-hoc inline data) or via =instantiate-prefab= (for data-file-driven composition). Since an entity is a regular alist, you can inspect it the usual way at the REPL: =(entity-ref e #:vx)=, =(assq #:tags e)=, =(length e)=. ** The entity API All entity operations are *pure and immutable*: every call returns a fresh alist; your input is never mutated. The full surface is small. *** =entity-ref entity key [default]= Looks up =key=. If absent, returns =default= (or calls =default= when it's a procedure, so you can raise an error lazily): #+begin_src scheme (entity-ref player #:x) ; → 150 (entity-ref player #:missing #f) ; → #f (entity-ref player #:x (lambda () (error "no x"))) ; default is a thunk #+end_src *** =entity-type entity= Shorthand for =(entity-ref entity #:type #f)=. *** =entity-set entity key val= Returns a new entity with =key= bound to =val= (replacing any prior binding). Guaranteed to leave at most one entry for that key: #+begin_src scheme (define moved (entity-set player #:x 200)) (entity-ref player #:x) ; → 150 (unchanged) (entity-ref moved #:x) ; → 200 #+end_src *** =entity-set-many entity pairs= Applies a list of =(key . val)= pairs in order: #+begin_src scheme (entity-set-many player '((#:vx . 3) (#:facing . 1))) #+end_src Used internally by =instantiate-prefab= to layer all prefab fields onto a fresh =make-entity= base. *** =entity-update entity key proc [default]= Shortcut for "read, transform, write": #+begin_src scheme (entity-update player #:x (lambda (x) (+ x 1))) ; → x incremented (entity-update player #:score add1 0) ; with default 0 #+end_src Because everything is immutable, update chains are usually written as =let*= or =chain= (SRFI-197): #+begin_src scheme (let* ((p (entity-set player #:vx 3)) (p (entity-set p #:facing 1)) (p (animate-entity p anims))) p) #+end_src ** Prefabs and mixins Hand-writing a long plist for every enemy gets old fast. The =(downstroke prefabs)= module loads a data file that declares reusable *mixins* (named bundles of keys) and *prefabs* (named entities built by combining mixins and inline overrides). A prefab data file is a single sexp with =mixins=, =prefabs=, and an optional =group-prefabs= section. Here is the animation demo's file (=demo/assets/animation-prefabs.scm=): #+begin_src scheme ((mixins) (prefabs (timed-frames animated #:type timed-frames #:anim-name walk #:animations ((#:name walk #:frames ((28 10) (29 1000))))) (std-frames animated #:type std-frames #:anim-name attack #:animations ((#:name attack #:frames (28 29) #:duration 10))))) #+end_src Each prefab entry has the shape =(name mixin-name ... #:k v #:k v ...)=. Before the first keyword, identifiers name *mixins* to pull in; from the first keyword onward you write *inline fields*. The engine ships a small mixin table via =(engine-mixins)=: #+begin_src scheme (engine-mixins) ;; → ((physics-body #:vx 0 #:vy 0 #:ay 0 #:gravity? #t #:solid? #t #:on-ground? #f) ;; (has-facing #:facing 1) ;; (animated #:anim-name idle #:anim-frame 0 #:anim-tick 0 ;; #:tile-id 0 #:animations #t)) #+end_src User-defined mixins go in the =(mixins ...)= section of the data file and take precedence if they share a name with an engine mixin. *** Merge semantics — inline wins When a prefab is composed, the merge order is: 1. Inline fields on the prefab entry (highest priority). 2. Each mixin named in the entry, in the order written. =alist-merge= is *earlier-wins*, so inline fields always override mixin defaults. Using the entry: #+begin_src scheme (timed-frames animated #:type timed-frames #:anim-name walk ...) #+end_src =animated= contributes =#:anim-name idle= among other things, but the inline =#:anim-name walk= wins. Nested plist-valued keys (currently =#:animations= and =#:parts=) are deep-converted to alists at load time, so =#:animations= ends up as a list of alists like =((#:name walk #:frames (28 29) #:duration 10))=. *** =load-prefabs= and =instantiate-prefab= Load a prefab file once at =create:= time, then instantiate as many entities as you need: #+begin_src scheme (import (downstroke prefabs)) (define registry (load-prefabs "demo/assets/animation-prefabs.scm" (engine-mixins) ; engine's built-in mixins '())) ; no user hooks (define e1 (instantiate-prefab registry 'std-frames 80 80 16 16)) (define e2 (instantiate-prefab registry 'timed-frames 220 60 16 16)) #+end_src =instantiate-prefab= signature: =(registry type x y w h) → entity= (or =#f= if the prefab isn't registered; also =#f= if =registry= itself is =#f=). The =x y w h= arguments seed =make-entity= and are then overwritten by any corresponding fields from the prefab. If an entity carries an =#:on-instantiate= key — either a procedure or a symbol naming a *user hook* passed into =load-prefabs= — the hook is invoked on the fresh entity and its result replaces it. That's how prefabs run per-type setup logic (e.g. computing sprite frames from size) without the engine baking a policy in. Group prefabs (=group-prefabs= section, instantiated via =instantiate-group-prefab=) return a list =(origin member ...)= for rigid assemblies like moving platforms; see the existing entity-groups material in this file's future revisions and the sandbox demo (=demo/assets/sandbox-groups.scm=) for a worked example. ** Skipping pipeline steps Each frame the engine runs a sequence of per-entity *pipeline steps* (acceleration, gravity, velocity-x, tile-collisions-x, velocity-y, tile-collisions-y, on-solid, tweens, animation, entity-collisions, …). An individual entity can opt out of any of these by listing the step's symbol in its =#:skip-pipelines= key: #+begin_src scheme (entity-set player #:skip-pipelines '(gravity velocity-x)) ;; → player now ignores gravity and horizontal motion integration #+end_src The predicate is =entity-skips-pipeline?=: #+begin_src scheme (entity-skips-pipeline? player 'gravity) ; → #t / #f #+end_src Every built-in step is defined with the =define-pipeline= macro (=(downstroke entity)=), which wraps the body in the skip check. The macro has two shapes: #+begin_src scheme (define-pipeline (identifier name) (scene entity dt) body) (define-pipeline (identifier name) (scene entity dt) guard: guard-expr body) #+end_src - =identifier= is the procedure name (e.g. =apply-gravity=). - =name= is the symbol users put into =#:skip-pipelines= (e.g. =gravity=). - =guard-expr=, when given, must evaluate truthy for the body to run; otherwise the entity is returned unchanged. Example from =physics.scm=: #+begin_src scheme (define-pipeline (apply-gravity gravity) (scene entity dt) guard: (entity-ref entity #:gravity? #f) (entity-set entity #:vy (+ (entity-ref entity #:vy) *gravity*))) #+end_src Reading the shape: the procedure is =apply-gravity=; adding =gravity= to =#:skip-pipelines= disables it on one entity; =guard:= means the step is skipped entity-wide when =#:gravity?= is false. For the full list of built-in step symbols, see [[file:physics.org][physics.org]]. * Common patterns ** Build an ad-hoc entity inline with =plist->alist= Good for one-offs, tiny demos, prototypes, and scripts where pulling in a data file is overkill. The getting-started and scaling demos do this exclusively: #+begin_src scheme (plist->alist (list #:type 'box #:x (/ +width+ 2) #:y (/ +height+ 2) #:width +box-size+ #:height +box-size+ #:vx 0 #:vy 0 #:color '(255 200 0))) #+end_src *Scaling demo* — run with =bin/demo-scaling=, source in =demo/scaling.scm=. *Getting-started demo* — run with =bin/demo-getting-started=, source in =demo/getting-started.scm=. ** Create a prefab file and instantiate from it When several entities share fields, lift them into mixins and let prefabs stamp them out: #+begin_src scheme ;; assets/actors.scm ((mixins (enemy-defaults #:solid? #t #:tags (enemy) #:hp 3)) (prefabs (grunt physics-body has-facing enemy-defaults #:type grunt #:tile-id 50 #:width 16 #:height 16) (brute physics-body has-facing enemy-defaults #:type brute #:tile-id 51 #:width 32 #:height 32 #:hp 8))) #+end_src #+begin_src scheme (define reg (load-prefabs "assets/actors.scm" (engine-mixins) '())) (define g (instantiate-prefab reg 'grunt 100 100 16 16)) (define b (instantiate-prefab reg 'brute 200 100 32 32)) #+end_src =physics-body=, =has-facing=, =animated= are engine mixins — see =(engine-mixins)= above. Inline fields (e.g. =#:hp 8= on =brute=) override values from mixins. *Animation demo* (prefab + load-prefabs) — run with =bin/demo-animation=, source in =demo/animation.scm=. *Platformer demo* (hand-built player via =plist->alist=) — run with =bin/demo-platformer=, source in =demo/platformer.scm=. ** Add a user-defined mixin User mixins live in the same data file's =(mixins ...)= section; if the name collides with an engine mixin, the user version wins: #+begin_src scheme ((mixins ;; Overrides the engine's physics-body: no gravity for this game. (physics-body #:vx 0 #:vy 0 #:gravity? #f #:solid? #t) ;; A brand-new mixin: (stompable #:stompable? #t #:stomp-hp 1)) (prefabs (slime physics-body stompable #:type slime #:tile-id 70))) #+end_src No engine change is needed — mixin names are resolved at =load-prefabs= time. ** Write your own pipeline step When you have per-entity logic that should honor =#:skip-pipelines=, reach for =define-pipeline= instead of writing a plain function. A minimal example: #+begin_src scheme (import (downstroke entity)) ;; A decay step. Users can skip it with #:skip-pipelines '(decay). (define-pipeline (apply-decay decay) (scene entity dt) guard: (entity-ref entity #:decays? #f) (entity-update entity #:hp (lambda (hp) (max 0 (- hp 1))) 0)) #+end_src Call it like any other step: =(apply-decay scene entity dt)=. Wiring it into the frame is the engine's job; see [[file:physics.org][physics.org]] for how built-in steps are composed and how to provide a custom =engine-update= if you need a different order. * See also - [[file:guide.org][guide.org]] — getting started; the 20-line game that uses entities. - [[file:physics.org][physics.org]] — full list of pipeline step symbols, =guard:= clauses, and per-step behavior. - [[file:tweens.org][tweens.org]] — using =#:tween= and the =tweens= pipeline step on entities. - [[file:animation.org][animation.org]] — =#:animations=, =#:anim-name=, the =animated= mixin. - [[file:input.org][input.org]] — reading the input system to drive entity updates. - [[file:scenes.org][scenes.org]] — scene-level queries (=scene-find-tagged=, =scene-add-entity=, =update-scene=). - [[file:rendering.org][rendering.org]] — how =#:tile-id=, =#:color=, =#:facing=, and =#:skip-render= affect drawing. - [[file:audio.org][audio.org]] — triggering sounds from entity update code.