Actors — Movement & Dialog¶
A Character (minted from a rig, e.g. bob = brian("Bob")) is what you direct in a shot. Its
methods only record intent inside with shot:; the keyframes are built when the block closes.
This page covers every way an actor can move, act, and speak.
The action methods at a glance¶
| Method | What it does |
|---|---|
idle() |
hold a neutral pose (inside a shot: holds until the shot ends) |
say(line, action_name=…) |
speak a line — TTS audio + a chosen gesture. One speaker per shot. |
walk_to(point) |
walk to a point/anchor (looped locomotion + root motion) |
move_to(point, action=…) |
like walk_to, but choose the gait |
walk_to_join(target, blocking, start=…) |
walk into a 2-person formation beside another actor |
walk_to_shot(anchor) |
walk to a metadata shot anchor |
.actor.perform("<name>") |
play any of the ~100 animations in place (escape hatch) |
.actor.face_towards(p) · .actor.set_forward_axis(ax) |
orientation helpers |
Movement¶
walk_to — travel to a point or anchor¶
Looped walking gait + root motion, from the current position to a point or set anchor.
with canal_bridge as shot:
shot.add(sam, at=canal_bridge.Canal_Bridge1_North_End.D1)
sam.walk_to(canal_bridge.Canal_Bridge3_South_End.F1)
shot.camera("tracking_moving", mode="medium", R=4.0)
shot.clip(5.0)
| start (north end) | end (south end) |
|---|---|
![]() |
![]() |
move_to — choose the gait¶
move_to is walk_to with a selectable locomotion action:
Every locomotion gait, verified for travel (the LOCOMOTION_ANIMS whitelist in dsl/actors.py):
| Gait | Travel | Gait | Travel | Gait | Travel |
|---|---|---|---|---|---|
walking |
✅ | happy_walk |
✅ | running |
⚠️ broken |
walk_forward |
✅ | sad_walk |
✅ | run_forward |
✅ |
walking_backwards |
✅ | drunk_walk |
✅ | sprint |
✅ |
sneak_walk |
✅ (low, by design) | female_walk |
✅ | fast_run |
✅ |
crouch_walking |
✅ (low, by design) | strut_walking |
✅ | jogging |
✅ |
injured_walking |
✅ | zombie_walk |
✅ | injured_run |
✅ |
17 of 18 gaits travel correctly. Only running fails.
jogging ✅ |
running ⚠️ (collapses) |
|---|---|
![]() |
![]() |
action="running" is broken — use another run gait
move_to(action="running") progressively collapses the actor (lean → crouch → horizontal),
and a longer clip is worse. It's specific to running via move_to — running in place
is fine. For run-travel use jogging, sprint, fast_run, or run_forward. See
Known Issues #9.
walk_to_join — enter a formation¶
The walker walks into one slot of a 2-person blocking; the target is placed at the other.
Give it room to walk
start defaults to the anchor (right next to the formation → a tiny step). Pass an
explicit start= a few units away for a real entrance, as above.
walk_to_shot¶
Convenience that walks the actor to a metadata shot anchor (same as walk_to(anchor)):
Standing actions — any of the ~100 animations¶
idle¶
perform — the escape hatch for any animation¶
Character exposes idle/say/walk_to/move_to; to play any other action (gestures, combat,
sit, dance, …) reach the underlying actor and mark it so the auto-idle isn't layered on:
The full list + a per-family gallery are on the Animation page.
Dialog (say)¶
say() synthesizes per-character TTS audio, plays action_name while speaking, and muxes the
audio into the clip (cached on disk by a hash of the text). Only one actor may speak per shot.
A gesture while speaking — action_name¶
detective.say("We need to talk. Right now.", action_name="talking") # gestures
witness.say("...", action_name="idle") # just speaks, still
action_name="talking" |
action_name="idle" |
|---|---|
![]() |
![]() |
Per-character voices¶
Voice is split from the rig: each character has a curated default_voice + tone, but you
pick either at cast time. Each OpenAI voice is a gender-tagged singleton (full list in the
Voice Catalogue); tone is the character's instructions.
Define once at the top, then reuse in every shot (the recommended pattern):
from dsl.actors import james, megan
from dsl.voices import echo, nova # gender-matched voices
marcus = james("Marcus", voice=echo) # male rig + male voice
nina = megan("Nina", voice=nova)
# … then in any shot:
marcus.say("We need to talk.", action_name="talking")
Or override inline for a one-off — by Voice singleton or string name, plus a delivery note:
brian("Detective Hayes") # default voice + tone
brian("The Stranger", voice="onyx") # string lookup
brian("Hayes (paranoid)", instructions="Whisper. Paranoid, glancing back.") # same rig+voice, new delivery
brian("The Stranger", voice=onyx, instructions="Cold. Clipped. Military.") # both overridden
by_gender("male") returns the gender-matched voices (plus neutral) for filtered casting, and the
same rig can back different voices/tones for different characters in one scene.
The casting dials:
| Dial | Type | Default | What it does |
|---|---|---|---|
voice |
Voice · str · None |
character's default_voice |
The voice timbre — a singleton (echo) or its name ("echo"). |
instructions |
str · None |
character's default_instructions |
Tone / delivery direction handed to the TTS (e.g. "Whisper. Paranoid."). |
default_voice |
(in character_descriptions.json) |
per character | The curated fallback voice; gender-matched across all 36 rigs. |
by_gender(g) |
helper → list[Voice] |
— | Voices of gender g plus neutral, for filtered casting. |
Voice/gender mismatch — fixed
Voice + gender are now first-class, so a male character can't silently get a female voice — this
closed Issue #12. The
three original mispairings (james→echo, peasant_girl→coral, alex→verse) are corrected and
all 36 character↔voice pairings are gender-matched.
One speaker per shot
A second say() in the same shot raises. Put each speaker in their own shot and cut them
together (see Editing).
Orientation helpers¶
Lower-level MixamoActor controls for facing:
sam.actor.face_towards((3, 0, 0)) # rotate to face a point
sam.actor.set_forward_axis("-Y") # fix a rig whose "forward" isn't -Y
Parameter reference & tuning¶
Every movement/dialog method funnels through actor.perform(...) — these are its dials:
| Dial | Type | Default | What it does |
|---|---|---|---|
action_name / action |
str | "walking" (move_to) · "idle" (say/idle) |
Which of the ~100 animations plays. In move_to it's the gait; in say it's the gesture while speaking ("talking" to gesture, "idle" to stay still). |
to |
(x,y,z) / anchor |
None |
Travel target. Present → locomotion (the gait loops + root-moves there); absent → the action plays in place. |
duration |
int frames | None |
Explicit length. Honoured for locomotion; ignored for in-place gestures (reset to the action's native length — #10). Bound the shot with shot.clip() instead. |
motion_scale |
float (units / loop) | actor's motion_scale |
Stride length — distance one locomotion cycle covers (repeat = distance / motion_scale). Larger = fewer loops, longer strides (faster ground cover); smaller = more loops, busier footwork. |
dialog |
str | None |
A TTS line spoken during the action (talk-while-walking). One-speaker-per-shot still applies. |
hold |
bool | per-action | Whether the actor holds the last pose after the action ends (vs. the strip emptying). Set False for a hard stop. |
gap |
int frames | 0 |
A pause inserted after this action before the next starts — spacing for sequential actions on one actor. |
say(line, action_name=…, duration=…)=perform(action_name, dialog=line, duration=…). Clip length follows the gesture animation, not the speech (#8) — split long lines (which also re-triggers the gesture so it doesn't freeze, #19).move_to(location, action="jogging", motion_scale=…)— pick the gait and tune the stride;walk_toismove_tofixed towalking.
Combining these into directed moments: see Production-Quality Tuning.







