From 618ed5fd6f5ae9c9f275c1e3cfb74762d7d51a01 Mon Sep 17 00:00:00 2001 From: Gene Pasquet Date: Tue, 7 Apr 2026 19:30:08 +0100 Subject: Added tweens --- docs/api.org | 75 ++++++++++++++++++++++++++++++++++++++++------ docs/entities.org | 5 ++++ docs/guide.org | 4 ++- docs/physics.org | 57 +++++++++++++++++++++++++++-------- docs/tweens.org | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 208 insertions(+), 23 deletions(-) create mode 100644 docs/tweens.org (limited to 'docs') diff --git a/docs/api.org b/docs/api.org index f18b80a..f6cfe50 100644 --- a/docs/api.org +++ b/docs/api.org @@ -287,7 +287,7 @@ Returns the tile ID at grid position ~(col, row)~ across all layers. Returns ~0~ (import downstroke-entity) #+end_src -The entity module provides property list (plist) accessors for game objects. Entities are immutable plists, never modified in place. +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~ @@ -344,6 +344,22 @@ Example: ; 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: @@ -363,6 +379,7 @@ All entities can have these keys. Not all are required: | ~#: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 | @@ -377,15 +394,19 @@ The physics module provides functions for movement, collision detection, and gro ** Physics Pipeline Order -The built-in physics runs in this order each frame: +The built-in physics functions are normally run in this order each frame (after reading input, before rendering): -1. ~apply-acceleration~ — consume ~#:ay~ into ~#:vy~ -2. ~apply-gravity~ — add gravity to ~#:vy~ -3. ~apply-velocity-x~ — move by ~#:vx~ -4. ~resolve-tile-collisions-x~ — snap against horizontal tile collisions -5. ~apply-velocity-y~ — move by ~#:vy~ -6. ~resolve-tile-collisions-y~ — snap against vertical tile collisions -7. ~detect-ground~ — set ~#:on-ground?~ if standing on a tile +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.) @@ -963,6 +984,42 @@ Changes the music volume while it is playing. ~volume~ is 0.0 to 1.0. 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 diff --git a/docs/entities.org b/docs/entities.org index db5663d..f6e870c 100644 --- a/docs/entities.org +++ b/docs/entities.org @@ -18,6 +18,10 @@ Entities in Downstroke are plain Scheme **plists** (property lists) — alternat This minimal approach keeps the engine lean: your game defines whatever keys it needs. The shared keys listed below are *conventions* for physics, rendering, and animation — use them to integrate with the engine's built-in systems. Custom keys are always allowed. +* Pipeline skips (~#:skip-pipelines~) + +The optional key ~#:skip-pipelines~ holds a list of **symbols** naming frame pipeline steps that should be skipped for that entity. The physics module defines the built-in step names (see ~docs/physics.org~). The predicate ~entity-skips-pipeline?~ and the syntax ~define-pipeline~ live in ~downstroke-entity~ so any subsystem (physics now; rendering or animation later if you extend the engine) can use the same mechanism without a separate “core pipeline” module. + * Creating Entities There is no ~make-entity~ constructor. Create an entity as a plain list: @@ -109,6 +113,7 @@ The engine recognizes these standard keys. Use them to integrate with the physic | ~#:gravity?~ | boolean | Whether gravity applies to this entity. Set to ~#t~ for platformers (gravity pulls down), ~#f~ for top-down or flying entities. Used by ~apply-gravity~. | | ~#:on-ground?~ | boolean | Whether the entity is touching a solid tile below (set by ~detect-ground~). Use this to gate jump input: only allow jumping if ~#:on-ground?~ is true. | | ~#:solid?~ | boolean | Whether this entity participates in entity-entity collision. If ~#t~, ~resolve-entity-collisions~ will check it against other solid entities. | +| ~#:skip-pipelines~ | list of symbols | Optional. Each symbol names a physics step to skip for this entity (e.g. ~gravity~, ~velocity-x~). See ~docs/physics.org~. | | ~#:tile-id~ | integer | Sprite index in the tileset (1-indexed). Required for rendering with ~draw-sprite~. Updated automatically by animation (~animate-entity~). | | ~#:facing~ | number | Horizontal flip direction: ~1~ = right (default), ~-1~ = left. Used by renderer to flip sprite horizontally. Update when changing direction. | | ~#:tags~ | list of symbols | List of tag symbols, e.g., ~'(player solid)~. Used by ~scene-find-tagged~ and ~scene-find-all-tagged~ for fast lookups. | diff --git a/docs/guide.org b/docs/guide.org index ca9cfbf..de07c82 100644 --- a/docs/guide.org +++ b/docs/guide.org @@ -240,7 +240,7 @@ This is invaluable for tuning collision geometry and understanding why entities * Demo Overview -Downstroke includes seven complete demo games that showcase different features: +Downstroke includes several demo games that showcase different features: | Demo | File | What it shows | |------|------|--------------| @@ -251,6 +251,7 @@ Downstroke includes seven complete demo games that showcase different features: | 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 | Each demo is self-contained and serves as a working reference for a particular game mechanic. @@ -283,5 +284,6 @@ 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! diff --git a/docs/physics.org b/docs/physics.org index a267d6d..2a82210 100644 --- a/docs/physics.org +++ b/docs/physics.org @@ -22,38 +22,69 @@ The =tilemap= argument is required by collision functions, since collision data * Physics Pipeline -The canonical 9-step physics pipeline is: +The canonical **per-entity** physics pipeline (what you typically call from =update:=) is: #+begin_src -input +apply-jump (set #:ay if jump pressed and on-ground) ↓ -apply-jump (set #:ay if jump pressed and on-ground) +apply-acceleration (consume #:ay into #:vy) ↓ -apply-acceleration (consume #:ay into #:vy) +apply-gravity (add gravity constant to #:vy) ↓ -apply-gravity (add gravity constant to #:vy) +apply-velocity-x (add #:vx to #:x) ↓ -apply-velocity-x (add #:vx to #:x) +resolve-tile-collisions-x (snap off horizontal tiles, zero #:vx) ↓ -resolve-tile-collisions-x (snap off horizontal tiles, zero #:vx) - ↓ -apply-velocity-y (add #:vy to #:y) +apply-velocity-y (add #:vy to #:y) ↓ resolve-tile-collisions-y (snap off vertical tiles, zero #:vy) ↓ -detect-ground (probe 1px below feet, set #:on-ground?) - ↓ -resolve-entity-collisions (push apart overlapping solid entities) +detect-ground (probe 1px below feet, set #:on-ground?) ↓ -render +resolve-entity-collisions (push apart overlapping solid entities; whole list) #+end_src +Input and rendering live **outside** this list — you read input first, then run the steps you need, then render. + **Not all steps are needed for all game types.** See the examples section for three different patterns: - **Platformer**: uses all 9 steps - **Top-down**: skips gravity, acceleration, jump, ground detection - **Physics Sandbox**: uses all steps, applies them to multiple entities +* Skipping steps (~#:skip-pipelines~) + +An entity may include ~#:skip-pipelines=, a list of **symbols** naming steps to **omit** for that entity only. Absent or empty means no steps are skipped. + +| Symbol | Skipped call | +|--------+----------------| +| ~jump~ | ~apply-jump~ | +| ~acceleration~ | ~apply-acceleration~ | +| ~gravity~ | ~apply-gravity~ | +| ~velocity-x~ | ~apply-velocity-x~ | +| ~velocity-y~ | ~apply-velocity-y~ | +| ~tile-collisions-x~ | ~resolve-tile-collisions-x~ | +| ~tile-collisions-y~ | ~resolve-tile-collisions-y~ | +| ~ground-detection~ | ~detect-ground~ | +| ~entity-collisions~ | participation in ~resolve-entity-collisions~ / ~resolve-pair~ | + +**Entity–entity collisions:** if *either* entity in a pair lists ~entity-collisions~ in ~#:skip-pipelines=, that pair is not resolved (no push-apart). Use this for “ghost” actors or scripted motion that should not participate in mutual solid resolution. + +**Legacy ~apply-velocity~:** skips each axis independently if ~velocity-x~ or ~velocity-y~ is listed. + +Helper: ~(entity-skips-pipeline? entity step-symbol)~ (from ~downstroke-entity~) returns ~#t~ if ~step-symbol~ is in the entity’s skip list. + +** ~define-pipeline~ (~downstroke-entity~) + +Physics steps are defined with ~(define-pipeline (procedure-name skip-symbol) (formals ...) body ...)~ from the entity module. The first formal must be the entity. The procedure name and skip symbol are separate so names like ~detect-ground~ can use the skip key ~ground-detection~. ~apply-velocity~ is still written by hand because it consults ~velocity-x~ and ~velocity-y~ independently. + +The renderer and other subsystems do **not** use ~#:skip-pipelines~ today; they run after your ~update:~ hook. If you add render-phase or animation-phase skips later, reuse the same plist key and helpers from ~downstroke-entity~ and document the new symbols alongside physics. + +Use cases: + +- **Tweens / knockback:** skip ~jump~, ~acceleration~, ~gravity~, ~velocity-x~, ~velocity-y~ while a tween drives ~#:x~ / ~#:y~, but keep tile resolution so the body does not rest inside walls. +- **Top-down:** omit gravity, jump, acceleration, ground detection from your *call order*; you usually do not need ~#:skip-pipelines= unless some entities differ from others. + * Pipeline Steps ** apply-jump diff --git a/docs/tweens.org b/docs/tweens.org new file mode 100644 index 0000000..9ac87cd --- /dev/null +++ b/docs/tweens.org @@ -0,0 +1,90 @@ +#+title: Tweens +#+author: Downstroke Contributors + +* Overview + +The =downstroke-tween= module interpolates **numeric** entity properties over wall-clock time. It is **decoupled** from the engine: you create tween values, call =tween-step= each frame from your =update:= hook, and store the returned entity back into the scene. + +Durations and delays are in **milliseconds**, matching the =dt= argument to =update:=. + +* Import + +#+begin_src scheme +(import downstroke-tween) +#+end_src + +* Core API + +** ~make-tween~ + +#+begin_src scheme +(make-tween entity #!key props duration (delay 0) ease (on-complete #f)) +#+end_src + +| Keyword | Meaning | +|---------+---------| +| ~props~ | Alist =((#:x . 200) (#:y . 40))= — keyword keys, numeric targets | +| ~duration~ | Positive integer, milliseconds of interpolation (after ~delay~) | +| ~delay~ | Non-negative integer ms before interpolation starts | +| ~ease~ | Easing symbol (see table below) or ~(lambda (t) ...)= with ~t~ in $[0,1]$ | +| ~on-complete~ | Optional ~(lambda (entity) ...)=, called **once** when the tween reaches its targets | + +Start values are captured from ~entity~ at construction time. While the tween runs, intermediate values may be **inexact** (flonums) even if starts and ends are integers. + +** ~tween-step~ + +#+begin_src scheme +(tween-step tween entity dt) +#+end_src + +Returns ~(values new-tween new-entity)~. Advance time by ~dt~ (ms). Before ~delay~ elapses, ~entity~ is unchanged. After completion, further steps return the same values (idempotent). When the tween completes, ~on-complete~ runs with the **final** entity (targets applied), then the callback slot is cleared. + +** ~tween-finished?~ / ~tween-active?~ + +Predicates on the tween struct. + +* Easing + +Each ease maps normalized time ~t ∈ [0,1]~ to an interpolation factor (usually in ~[0,1]~; ~back-out~ may exceed ~1~ briefly). + +| Symbol | Procedure | +|--------|-----------| +| ~linear~ | ~ease-linear~ | +| ~quad-in~, ~quad-out~, ~quad-in-out~ | quadratic | +| ~cubic-in~, ~cubic-out~, ~cubic-in-out~ | cubic | +| ~sine-in-out~ | smooth sine | +| ~expo-in~, ~expo-out~, ~expo-in-out~ | exponential | +| ~back-out~ | overshoot then settle (Robert Penner–style) | + +** ~ease-named~ / ~ease-resolve~ + +~ease-named~ turns a symbol into a procedure. ~ease-resolve~ accepts a symbol or procedure (identity for procedures) for use in custom tooling. + +All easing procedures are exported if you want to compose curves manually. + +* Order of operations with physics + +Tweens usually **fight** velocity and gravity if both update ~#:x~ / ~#:y~. Typical pattern: + +1. Set the entity’s ~#:skip-pipelines~ to skip integration steps you do not want (see [[physics.org][Physics]]). +2. Run ~tween-step~ for that entity. +3. Run your normal physics pipeline (collisions can still run). + +Clear ~#:skip-pipelines~ in ~on-complete~ when the tween ends. + +Example skip list for “kinematic shove” while keeping tile collisions: + +#+begin_src scheme +(entity-set player #:skip-pipelines + '(jump acceleration gravity velocity-x velocity-y)) +#+end_src + +* Demo + +=bin/demo-tweens= (source =demo/tweens.scm=) shows one row per easing and a crate that tweens horizontally while integration is skipped and tile resolution still runs. + +* Limitations (current version) + +- Single segment per tween (no built-in chains or yoyo). +- Numeric properties only. +- No engine integration — you wire ~tween-step~ yourself. -- cgit v1.2.3