Tween

animate any property over time with easing.

▶ Run in browser

Tags: 2d animation tween

tween(obj, prop, target, duration, easing=...) drives an attribute from its current value to target over duration seconds. It works on any attribute that interpolates: a float, a Vec2, a Vec3 or a Quat. A colour is a plain tuple and holds until the end, so fade one with Tween.colour(node, target, duration). Here each row owns a progress Property that runs from 0 to 1 and back, and the row draws its marker that far along its track, so the layout follows the window while the tween stays in plain numbers. Every marker covers the same distance in the same two seconds; only the easing curve differs.

  • The easing module shapes the motion: easing.ease_linear has none, ease_in_* starts slow, ease_out_* ends slow and ease_in_out_* does both. Any function from 0..1 to 0..1 works as an easing.

  • repeat=2, repeat_mode="yoyo" plays out and then back, mirroring the curve on the return leg. "restart" snaps back instead and "incremental" keeps going.

  • A tween is a coroutine: hand it to self.start_coroutine() and the node runs it every frame until it finishes or the node leaves the tree. Inside your own coroutine, yield from tween(...) waits for it, so tweens sequence and loop with ordinary Python.

Source

 1# /// simvx
 2# tags = ["2d", "animation", "tween"]
 3# web = { width = 800, height = 600, responsive = true }
 4# ///
 5"""Tween: animate any property over time with easing.
 6
 7`tween(obj, prop, target, duration, easing=...)` drives an attribute from its
 8current value to `target` over `duration` seconds. It works on any attribute
 9that interpolates: a float, a `Vec2`, a `Vec3` or a `Quat`. A colour is a plain
10tuple and holds until the end, so fade one with
11`Tween.colour(node, target, duration)`. Here each row owns a `progress` Property
12that runs from 0 to 1 and back, and the row draws its marker that far along its
13track, so the layout follows the window while the tween stays in plain numbers.
14Every marker covers the same distance in the same two seconds; only the easing
15curve differs.
16
17- The `easing` module shapes the motion: `easing.ease_linear` has none,
18  `ease_in_*` starts slow, `ease_out_*` ends slow and `ease_in_out_*` does both.
19  Any function from 0..1 to 0..1 works as an easing.
20- `repeat=2, repeat_mode="yoyo"` plays out and then back, mirroring the curve on
21  the return leg. `"restart"` snaps back instead and `"incremental"` keeps going.
22- A tween is a coroutine: hand it to `self.start_coroutine()` and the node runs
23  it every frame until it finishes or the node leaves the tree. Inside your own
24  coroutine, `yield from tween(...)` waits for it, so tweens sequence and loop
25  with ordinary Python.
26"""
27
28from simvx.core import Node2D, Property, easing, tween
29from simvx.graphics import App
30
31CURVES = [
32    easing.ease_linear,
33    easing.ease_in_quad,
34    easing.ease_out_quad,
35    easing.ease_in_cubic,
36    easing.ease_out_cubic,
37    easing.ease_in_out_cubic,
38]
39LABEL_WIDTH = 200
40
41
42def ping_pong(lane, curve):
43    """Out and back over two seconds each way, forever."""
44    while True:
45        yield from tween(lane, "progress", 1.0, duration=2.0, easing=curve, repeat=2, repeat_mode="yoyo")
46
47
48class Lane(Node2D):
49    """One row: the easing's name, a track, and a marker `progress` of the way along it."""
50
51    progress = Property(0.0)
52
53    def on_draw(self, renderer):
54        end = self.viewport_size.x - 40
55        x = LABEL_WIDTH + (end - LABEL_WIDTH) * self.progress
56        renderer.text(self.name, (20, -8), fill=(0.7, 0.7, 0.7))
57        renderer.line((LABEL_WIDTH, 0), (end, 0), stroke=(0.3, 0.3, 0.35))
58        renderer.circle((x, 0), 14, fill=(0.4, 0.8, 1.0, 1.0))
59
60
61class TweenDemo(Node2D):
62    def on_ready(self):
63        for curve in CURVES:
64            lane = self.add_child(Lane(name=curve.__name__))
65            self.start_coroutine(ping_pong(lane, curve))
66
67    def on_update(self, dt):
68        height = self.viewport_size.y
69        spacing = (height - 60) / len(CURVES)
70        for i, lane in enumerate(self.children):
71            lane.position = (0, 60 + spacing * (i + 0.5))
72
73    def on_draw(self, renderer):
74        renderer.text("Tween: easing curves", (20, 20), fill=(1, 1, 1), scale=2)
75
76
77if __name__ == "__main__":
78    App(title="Tween", width=800, height=600).run(TweenDemo())