Motion
Each character moves with its own Motion object, character.motion. You give it
paths, each a list of waypoints, and activate one; the character then moves a little along it every tick until
it reaches the end. This chapter covers building paths and controlling how characters travel along them.
A first path
path = character.motion.new_path(speed=0.5)
path.new_waypoint(character.input_coord)
character.motion.activate_path(path)
new_path()creates an emptyPathowned by this character.new_waypoint()adds aWaypoint, a coordinate to travel to. A path can have any number of waypoints; the character visits them in order.activate_path()starts the path from wherever the character is now. The engine adds a segment from the character's current position to the first waypoint, so a path doesn't need a starting waypoint.
To start somewhere other than the character's home, move it first with
character.motion.set_coordinate(coord). The character's position is character.motion.current_coord.
The path runs once the character is in your iterator's active_characters. When the character reaches the last
waypoint, the path completes and the character becomes inactive, unless it also has a scene playing.
The Travel example
The examples in this chapter use the Travel effect. It moves every character from the canvas's left edge to its home,
with options for speed, easing, and a curved path. Save it as travel.py in your community effects directory:
"""Travel: move every character home along a path.
An Effect Guide example for the Motion chapter.
"""
from __future__ import annotations
from dataclasses import dataclass
from terminaltexteffects import Coord, easing
from terminaltexteffects.engine.base_config import BaseConfig
from terminaltexteffects.engine.base_effect import BaseEffect, BaseEffectIterator
from terminaltexteffects.utils import argutils
def get_effect_resources() -> tuple[str, type[BaseEffect], type[BaseConfig]]:
"""Return the command name, effect class, and configuration class TTE registers."""
return "travel", Travel, TravelConfig
@dataclass
class TravelConfig(BaseConfig):
"""Configuration for the Travel effect."""
parser_spec = argutils.ParserSpec(
name="travel",
help="Moves every character home along a path.",
description="travel | Moves every character home along a path.",
epilog=f"{argutils.EASING_EPILOG}Example: tte travel --ease out_bounce",
)
speed: float = argutils.ArgSpec(
name="--speed",
type=argutils.PositiveFloat.type_parser,
default=0.5,
metavar=argutils.PositiveFloat.METAVAR,
help="Distance each character moves per frame.",
) # pyright: ignore[reportAssignmentType]
"float : Distance each character moves per frame."
ease: easing.EasingFunction = argutils.ArgSpec(
name="--ease",
type=argutils.Ease.type_parser,
default=easing.linear,
metavar=argutils.Ease.METAVAR,
help="Easing function applied to each path.",
) # pyright: ignore[reportAssignmentType]
"easing.EasingFunction : Easing function applied to each path."
curve: bool = argutils.ArgSpec(
name="--curve",
default=False,
action="store_true",
help="Start in the bottom left corner and curve home through the bottom right corner.",
) # pyright: ignore[reportAssignmentType]
"bool : Start in the bottom left corner and curve home through the bottom right corner."
class TravelIterator(BaseEffectIterator[TravelConfig]):
"""Produce the frames of the Travel effect."""
def __init__(self, effect: Travel) -> None:
"""Give every character a path home and start it moving."""
super().__init__(effect)
canvas = self.terminal.canvas
for character in self.terminal.get_characters():
path = character.motion.new_path(speed=self.config.speed, ease=self.config.ease)
if self.config.curve:
character.motion.set_coordinate(Coord(canvas.left, canvas.bottom))
path.new_waypoint(character.input_coord, bezier_control=Coord(canvas.right, canvas.bottom))
else:
character.motion.set_coordinate(Coord(canvas.left, character.input_coord.row))
path.new_waypoint(character.input_coord)
character.motion.activate_path(path)
self.terminal.set_character_visibility(character, is_visible=True)
self.active_characters.add(character)
def __next__(self) -> str:
"""Move the characters one step and return the frame."""
if self.active_characters:
self.update()
return self.frame
raise StopIteration
class Travel(BaseEffect[TravelConfig]):
"""Moves every character home along a path."""
@property
def _config_cls(self) -> type[TravelConfig]:
return TravelConfig
@property
def _iterator_cls(self) -> type[TravelIterator]:
return TravelIterator
With the default options, every character moves at a constant speed, so characters that start farther from home arrive later:

Speed and duration
speed is the distance a character covers per tick. The number of ticks a path takes is its length divided by its
speed, rounded: a path 20 cells long at speed 0.5 takes 40 frames, or two-thirds of a second at 60 frames per second.
Lengths are measured on screen, so one row counts as two columns (see Distances are measured on screen). Because the length includes the segment from the character's starting position, characters with different distances to travel take different times at the same speed. To make characters arrive together, give each a speed proportional to its distance.
Easing
An easing function changes how progress along the path is spread over its duration. With ease=None, the default,
the character moves the same distance every tick. With an easing function, it can start slowly, finish slowly, or
overshoot and settle. The path's duration doesn't change.
from terminaltexteffects import easing
path = character.motion.new_path(speed=0.5, ease=easing.out_expo)
The easing module has in_, out_, and in_out_ variants of the sine, quad,
cubic, quart, quint, expo, circ, back, elastic, and bounce curves, plus linear. easing.make_easing(x1, y1, x2, y2)
builds a custom cubic bezier curve, like CSS cubic-bezier().
Easing applies to the whole path, not each segment. On a path with several waypoints, an out_ easing slows the
character down near the last waypoint only.
Curves
Give a waypoint bezier_control to curve the segment that ends at it. One control point makes a quadratic curve;
a tuple of two makes a cubic curve. The character doesn't pass through the control point; the curve bends toward it.
With --curve, Travel starts every character in the bottom left corner and bends its path toward the bottom right
corner:

More path options
new_path() takes these keyword arguments:
| Argument | Effect |
|---|---|
speed |
Distance per tick. Must be greater than 0. |
ease |
Easing function for the whole path. |
layer |
Layer to put the character on when the path is activated. None leaves the layer unchanged. |
hold_time |
Number of ticks to stay at the last waypoint before the path completes. |
loop |
After reaching the last waypoint, travel back to the first and repeat forever. A path needs at least two waypoints to loop. |
path_id |
A name for the path, so you can find it with motion.query_path(path_id). One is generated if you don't give one. |
A character has at most one active path. Activating another path replaces the current one, starting from wherever the
character is. motion.deactivate_path() stops the active path without completing it, and
motion.movement_is_complete() reports whether no path is active.
Sequences of paths
To run paths one after another, chain them:
chain_paths() registers events so that each path activates the next one when it completes; pass loop=True to
return from the last to the first. Chaining is a shortcut for the event handling described in the Events chapter, which
can also start scenes, change layers, or run your own code when a path starts, completes, or reaches a waypoint.
Where to go next
- Tutorial step 3: Move gives MySpray's characters paths from a canvas edge to their homes.
- Canvas and coordinates explains where coordinates come from.


