aboutsummaryrefslogtreecommitdiff
path: root/docs/tweens.org
blob: 213b3ee6be676161f8ee534cde089b3b63ccfe4d (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
126
127
128
129
130
131
132
133
134
135
136
137
#+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) (repeat 0) (yoyo? #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 (first cycle only) |
| ~ease~ | Easing symbol (see table below) or ~(lambda (t) ...)= with ~t~ in $[0,1]$ |
| ~on-complete~ | Optional ~(lambda (entity) ...)=, called **once** when the tween fully ends (not called with ~repeat: -1~) |
| ~repeat~ | ~0~ = play once (default), ~N~ = replay N additional times, ~-1~ = loop forever |
| ~yoyo?~ | ~#f~ (default) = replay same direction, ~#t~ = reverse direction each cycle |

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.

*** Repeat and yoyo

~repeat:~ controls how many extra times the tween replays after the initial play. ~yoyo?: #t~ swaps start and end values on each cycle, creating a ping-pong effect.

| ~repeat~ | ~yoyo?~ | Behavior |
|------+-------+----------|
| ~0~ | either | Play once and finish (default) |
| ~1~ | ~#f~ | Play forward twice, then finish |
| ~1~ | ~#t~ | Play forward, then backward, then finish |
| ~-1~ | ~#f~ | Play forward forever |
| ~-1~ | ~#t~ | Ping-pong forever |

~delay:~ only applies before the first cycle. ~on-complete~ fires once when the last repeat finishes; it never fires with ~repeat: -1~. Overflow time from a completed cycle carries into the next cycle for smooth transitions.

** ~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. With ~repeat:~ / ~yoyo?:~, the tween automatically resets for the next cycle.

** ~tween-finished?~ / ~tween-active?~

Predicates on the tween struct.

** ~step-tweens~ (pipeline step)

#+begin_src scheme
(step-tweens entity dt)
#+end_src

Auto-advances ~#:tween~ on an entity. If the entity has no ~#:tween~ key, returns the entity unchanged. When the tween finishes (no more repeats), ~#:tween~ is removed from the entity. Respects ~#:skip-pipelines~: skipped when ~'tweens~ is in the list.

This is the recommended way to run tweens in most games. Attach a tween to an entity and include ~step-tweens~ in your per-entity pipeline:

#+begin_src scheme
(scene-update-entities scene
  (lambda (e) (step-tweens e dt)))
#+end_src

** Entity key: ~#:tween~

Attach a tween directly to an entity for automatic advancement by ~step-tweens~:

#+begin_src scheme
(list #:type 'platform
      #:x 100 #:y 300 #:width 48 #:height 16
      #:solid? #t #:immovable? #t #:gravity? #f
      #:tween (make-tween (list #:x 100)
                props: '((#:x . 300))
                duration: 3000 ease: 'sine-in-out
                repeat: -1 yoyo?: #t))
#+end_src

The platform ping-pongs between x=100 and x=300 forever. No manual tween management needed.

* 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 sequences).
- Numeric properties only.