|
| 1 | +--- |
| 2 | +title: Play Animations |
| 3 | +description: Play and control animations in Needle Engine — with the Animation component, Animator, or Timeline, authored in Unity/Blender without code or driven from TypeScript. |
| 4 | +--- |
| 5 | + |
| 6 | +# Play Animations |
| 7 | + |
| 8 | +Needle Engine has several ways to play animations, and **most of them need no code** — you author them in Unity or Blender and they just work on the web. Pick the approach that matches your content, then trigger it from the editor, from UI, or from code. |
| 9 | + |
| 10 | +## Which approach should I use? |
| 11 | + |
| 12 | +| Approach | Best for | No-code in editor? | Trigger from UI? | |
| 13 | +| --- | --- | --- | --- | |
| 14 | +| **`Animation` component** | A single clip, or a few clips you start one at a time | Yes | Yes (Button → `play`) | |
| 15 | +| **`Animator` / `AnimatorController`** | Multiple states (idle → walk → run, or step 1 → 2 → 3) with conditions, blending, and chained playback | Yes | Yes (set parameters) | |
| 16 | +| **Timeline (`PlayableDirector`)** | A **sequence** of clips that play one after another, with **synced audio**, camera moves, activation, and other tracks | Yes | Yes (Button → `play`) | |
| 17 | +| **Everywhere Actions (`PlayAnimationOnClick`)** | No-code click-to-play that also works in AR / USDZ on iOS | Yes | Click on object | |
| 18 | +| **Code (`animation.play()`, builders)** | Fully custom or runtime-generated sequences | — | Yes (call from code) | |
| 19 | + |
| 20 | +For most "play this, then that" needs — especially with audio — reach for **Timeline** before writing your own sequencing code. |
| 21 | + |
| 22 | +--- |
| 23 | + |
| 24 | +## Author animations in the editor (no code) |
| 25 | + |
| 26 | +All three core animation components are exported from Unity and Blender. You set them up in the editor and they run on the web unchanged — no TypeScript required. |
| 27 | + |
| 28 | +### Animation component |
| 29 | + |
| 30 | +For basic playback of one or a few clips. |
| 31 | + |
| 32 | +1. Add an `Animation` component to your animated object. |
| 33 | +2. Assign your animation clip(s). Add more to the `clips` array if needed (by default only the first plays automatically). |
| 34 | +3. Enable `playAutomatically` to start on load, or leave it off and trigger later. |
| 35 | + |
| 36 | +### Animator + AnimatorController |
| 37 | + |
| 38 | +For multiple states with transitions, conditions, and blending — characters (idle/walk/run) or step-by-step sequences. |
| 39 | + |
| 40 | +1. Create an `AnimatorController` asset and add states for each animation. |
| 41 | +2. Add parameters (bool, float, int, trigger) and connect states with transitions and conditions. |
| 42 | +3. Add an `Animator` component to your object and assign the controller. |
| 43 | + |
| 44 | +In Unity you author this in the Animator window; in Blender, via the AnimatorController editor. See [Animation Workflows in Blender](/docs/blender/animation#animatorcontroller-state-machine-animations) for a Blender walkthrough. |
| 45 | + |
| 46 | +### Timeline (PlayableDirector) |
| 47 | + |
| 48 | +For coordinated, choreographed sequences — multiple clips one after another, audio tracks, camera moves, and object activation, all on one shared timeline. |
| 49 | + |
| 50 | +- **Unity:** Build a Timeline asset and add a `PlayableDirector` component referencing it. Needle Engine translates [Unity's Timeline](https://unity.com/features/timeline) into a web-ready format, including animation and audio tracks. |
| 51 | +- **Blender:** Author NLA tracks, then add a `PlayableDirector` component and list the objects whose NLA tracks should be exported. See [Animation Workflows → PlayableDirector](/docs/blender/animation#playabledirector-timeline-animation). |
| 52 | + |
| 53 | +Leave `playOnAwake` off if you want the timeline to start only when triggered. |
| 54 | + |
| 55 | +--- |
| 56 | + |
| 57 | +## Trigger animations from UI without code |
| 58 | + |
| 59 | +You don't need a script to start an animation from a button: |
| 60 | + |
| 61 | +- **`Button` component (`onClick`):** Wire the button's `onClick` event to a target component and method — for example call `play` on a `PlayableDirector`, or set a parameter on an `Animator`. This works for 3D and UI buttons. |
| 62 | +- **Everywhere Actions — `PlayAnimationOnClick`:** Select an animation state from an `Animator` to play when the object is clicked, with an optional follow-up state. It works in the browser, in WebXR, **and** in interactive USDZ/QuickLook on iOS. See [Everywhere Actions](/docs/how-to-guides/everywhere-actions/). |
| 63 | + |
| 64 | +--- |
| 65 | + |
| 66 | +## Play a sequence of animations with sound on a button press |
| 67 | + |
| 68 | +This is a common request: *play several animations automatically one after another, with sound, starting when a button is pressed, and stop only once the whole sequence has finished.* The cleanest solution is a **Timeline** driven by a `PlayableDirector` — you generally don't need to write your own sequencing controller. |
| 69 | + |
| 70 | +A Timeline lets you arrange animation clips and audio clips on tracks along one shared timeline. Because the order, timing, and audio are baked into the timeline, playing the whole sequence is a single call: |
| 71 | + |
| 72 | +```ts |
| 73 | +import { Behaviour, PlayableDirector, serializable } from "@needle-tools/engine"; |
| 74 | + |
| 75 | +export class PlaySequenceButton extends Behaviour { |
| 76 | + @serializable(PlayableDirector) |
| 77 | + timeline?: PlayableDirector; |
| 78 | + |
| 79 | + // Hook this up to a Button component's onClick in Unity/Blender, |
| 80 | + // or call it from your own HTML button. |
| 81 | + async playSequence() { |
| 82 | + if (!this.timeline || this.timeline.isPlaying) return; |
| 83 | + await this.timeline.play(); // resolves when the whole timeline finishes |
| 84 | + } |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +Why Timeline fits this use case: |
| 89 | + |
| 90 | +- **Animations play in order automatically** — place clips on the track in sequence; no manual "play next when finished" chaining. |
| 91 | +- **Audio is part of the timeline** — add an audio track and drop your sound clips where they should play. They stay in sync with the animations. Enable `waitForAudio` on the `PlayableDirector` if you want playback to wait for audio to load. |
| 92 | +- **Triggered from UI** — wire a `Button` component's `onClick` to the director's `play` (no code), or call `play()` from a custom component as shown above. Leave `playOnAwake` disabled so it only starts on the button press. |
| 93 | +- **Runs until complete** — `play()` returns a promise that resolves when the timeline reaches its end, so you can disable the button, show a "finished" state, or chain follow-up logic. Use `pause()` / `stop()` to interrupt. |
| 94 | + |
| 95 | +A **state machine** is another good fit when the sequence is really "states that follow each other": use an `AnimatorController` with one state per animation and transitions between them. Each state can automatically advance to the next, and you can drive it from UI by setting parameters. |
| 96 | + |
| 97 | +If you genuinely need custom logic between steps, you can still sequence the `Animation` component from code — `animation.play(clip)` returns a promise that resolves when that clip finishes, so you can `await` each one in turn. But for a fixed sequence with audio, Timeline is less code and easier to author for non-programmers. |
| 98 | + |
| 99 | +--- |
| 100 | + |
| 101 | +## Control animations from code |
| 102 | + |
| 103 | +When you do want code, every component exposes a runtime API. |
| 104 | + |
| 105 | +**Animation component** — `play()` returns a promise that resolves when the clip finishes (it won't resolve while looping): |
| 106 | + |
| 107 | +```ts |
| 108 | +// Play clips one after another (clipA, clipB are AnimationClips) |
| 109 | +await animation.play(clipA, { exclusive: true }); |
| 110 | +await animation.play(clipB, { exclusive: true }); |
| 111 | +``` |
| 112 | + |
| 113 | +**Animator** — set parameters or jump to a state: |
| 114 | + |
| 115 | +```ts |
| 116 | +animator.setBool("isWalking", true); |
| 117 | +animator.setFloat("speed", 2.5); |
| 118 | +animator.setTrigger("jump"); |
| 119 | +animator.play("Idle"); // play a state directly |
| 120 | +``` |
| 121 | + |
| 122 | +**PlayableDirector** — control timeline playback, or scrub it manually: |
| 123 | + |
| 124 | +```ts |
| 125 | +timeline.play(); |
| 126 | +timeline.pause(); |
| 127 | +timeline.stop(); |
| 128 | + |
| 129 | +// Manual scrubbing (e.g. scroll-driven storytelling) |
| 130 | +timeline.time = 5.0; // seconds |
| 131 | +timeline.evaluate(); // apply the new time while paused |
| 132 | +``` |
| 133 | + |
| 134 | +For scroll-driven playback, the [`ScrollFollow`](/docs/how-to-guides/components/scroll-follow) component binds scroll position to a `PlayableDirector` without custom code. |
| 135 | + |
| 136 | +--- |
| 137 | + |
| 138 | +## Create animations, timelines, and controllers from code |
| 139 | + |
| 140 | +Needle Engine ships fluent **builder** utilities so you can construct animation clips, timelines, and `AnimatorController` state machines at runtime — no editor asset required. These are available as a **preview** in **Needle Engine 5.1+**: `TimelineBuilder`, `AnimatorControllerBuilder` (via `AnimatorController.build()`), and `AnimationBuilder`. They're ideal for procedural content, runtime-generated sequences, and tooling. |
| 141 | + |
| 142 | +### Build a timeline (animations + audio) with `TimelineBuilder` |
| 143 | + |
| 144 | +The most direct way to recreate the "several animations one after another, with synced sound, triggered by a button" use case entirely in code. Clips added to a track are placed back-to-back automatically (each `.clip()` advances a cursor), and an audio track plays alongside: |
| 145 | + |
| 146 | +```ts |
| 147 | +import { Behaviour, serializable, TimelineBuilder, PlayableDirector } from "@needle-tools/engine"; |
| 148 | +import { AnimationClip } from "three"; |
| 149 | + |
| 150 | +export class SequenceFromCode extends Behaviour { |
| 151 | + @serializable(AnimationClip) |
| 152 | + clips: AnimationClip[] = []; |
| 153 | + |
| 154 | + private director?: PlayableDirector; |
| 155 | + |
| 156 | + start() { |
| 157 | + // A PlayableDirector hosts the timeline we build |
| 158 | + this.director = this.gameObject.getComponent(PlayableDirector) |
| 159 | + ?? this.gameObject.addComponent(PlayableDirector); |
| 160 | + |
| 161 | + TimelineBuilder.create("Sequence") |
| 162 | + .animationTrack("Animation", this.gameObject) |
| 163 | + .clip(this.clips[0], { easeIn: 0.2 }) |
| 164 | + .clip(this.clips[1]) // starts right after the previous clip |
| 165 | + .clip(this.clips[2]) |
| 166 | + .audioTrack("Audio", this.gameObject) |
| 167 | + .clip("audio/intro.mp3", { start: 0, duration: 3, volume: 0.8 }) |
| 168 | + .clip("audio/outro.mp3", { start: 3, duration: 2 }) |
| 169 | + .signalTrack("Events") |
| 170 | + .signal(5, () => console.log("sequence reached 5s")) |
| 171 | + .install(this.director); // assigns the timeline; use .build() if you have no signal callbacks |
| 172 | + } |
| 173 | + |
| 174 | + // Call from a UI Button's onClick: |
| 175 | + async playSequence() { |
| 176 | + if (this.director && !this.director.isPlaying) |
| 177 | + await this.director.play(); // resolves when the whole timeline finishes |
| 178 | + } |
| 179 | +} |
| 180 | +``` |
| 181 | + |
| 182 | +`TimelineBuilder` supports `animationTrack`, `audioTrack`, `activationTrack` (show/hide objects), `controlTrack` (nested timelines), and `signalTrack`/`markerTrack` (fire callbacks at a time). Use `.build()` to get the timeline asset and assign it to `director.playableAsset` yourself, or `.install(director)` to assign it **and** wire up any `.signal()` callbacks. |
| 183 | + |
| 184 | +### State machines with `AnimatorController` |
| 185 | + |
| 186 | +If the sequence is better modeled as states that follow each other, build an `AnimatorController` from code instead. |
| 187 | + |
| 188 | +**Chain clips into a sequence with `createFromClips`.** This turns each clip into a state and auto-transitions to the next — the quickest way to play several clips one after another: |
| 189 | + |
| 190 | +```ts |
| 191 | +import { AnimatorController, Animator } from "@needle-tools/engine"; |
| 192 | + |
| 193 | +// Each clip becomes a state; autoTransition advances to the next when finished |
| 194 | +const controller = AnimatorController.createFromClips(myClips, { |
| 195 | + autoTransition: true, |
| 196 | + looping: false, |
| 197 | +}); |
| 198 | + |
| 199 | +const animator = this.gameObject.getComponent(Animator) ?? this.gameObject.addComponent(Animator); |
| 200 | +animator.runtimeAnimatorController = controller; |
| 201 | +``` |
| 202 | + |
| 203 | +::: tip |
| 204 | +With `autoTransition: true`, the **last** clip transitions back to the first, so the whole sequence loops. For a sequence that stops at the end, use the fluent builder below and leave the final state without an outgoing transition (or use a Timeline). |
| 205 | +::: |
| 206 | + |
| 207 | +**Build a full state machine fluently with `AnimatorController.build()`.** Add parameters, states, transitions, and conditions — ideal when the sequence should be driven by UI: |
| 208 | + |
| 209 | +```ts |
| 210 | +import { AnimatorController, Animator } from "@needle-tools/engine"; |
| 211 | + |
| 212 | +const controller = AnimatorController.build("Sequence") |
| 213 | + .triggerParameter("Play") |
| 214 | + .state("Idle", { clip: idleClip, loop: true }) |
| 215 | + .state("Step1", { clip: step1Clip }) |
| 216 | + .state("Step2", { clip: step2Clip }) |
| 217 | + // Start the sequence when the "Play" trigger is set (e.g. from a button) |
| 218 | + .transition("Idle", "Step1", { duration: 0.1 }) |
| 219 | + .condition("Play") |
| 220 | + // Advance automatically once each step finishes (exitTime = normalized 0-1) |
| 221 | + .transition("Step1", "Step2", { exitTime: 1, duration: 0.1 }) |
| 222 | + .transition("Step2", "Idle", { exitTime: 1, duration: 0.1 }) |
| 223 | + .build(); |
| 224 | + |
| 225 | +const animator = this.gameObject.getComponent(Animator) ?? this.gameObject.addComponent(Animator); |
| 226 | +animator.runtimeAnimatorController = controller; |
| 227 | + |
| 228 | +// Trigger the sequence from a UI Button's onClick, or from code: |
| 229 | +animator.setTrigger("Play"); |
| 230 | +``` |
| 231 | + |
| 232 | +You can also set parameters with `animator.setBool(name, value)` / `setFloat(name, value)`, or jump straight to a state with `animator.play("Step1")`. |
| 233 | + |
| 234 | +### Build standalone clips with `AnimationBuilder` |
| 235 | + |
| 236 | +States and timeline tracks don't even need pre-existing clips — author keyframes inline with `.track()` on either builder, or build a standalone clip: |
| 237 | + |
| 238 | +```ts |
| 239 | +import { AnimationBuilder } from "@needle-tools/engine"; |
| 240 | + |
| 241 | +const slideClip = AnimationBuilder.create("Slide") |
| 242 | + .track(this.gameObject, "position", { from: [0, 0, 0], to: [2, 0, 0], duration: 1 }) |
| 243 | + .build(); |
| 244 | +``` |
| 245 | + |
| 246 | +`AnimationBuilder.track()` supports position/scale/quaternion/rotation/visibility on objects, plus material, light, and camera properties — with either a keyframe array or a `{ from, to, duration }` tween shorthand. |
| 247 | + |
| 248 | +--- |
| 249 | + |
| 250 | +## Related |
| 251 | + |
| 252 | +- [Animation Workflows in Blender](/docs/blender/animation) — editor walkthroughs for Animation, AnimatorController, and Timeline (NLA) |
| 253 | +- [Scripting Examples → Animation](/docs/reference/scripting-examples#animation) — more code snippets |
| 254 | +- [Everywhere Actions](/docs/how-to-guides/everywhere-actions/) — no-code, cross-platform interactions including iOS |
| 255 | +- [Scroll Follow](/docs/how-to-guides/components/scroll-follow) — scroll-driven timeline playback |
| 256 | +- [FAQ](/docs/reference/faq#animation) — common animation questions |
0 commit comments