JuiceBox
01 Wait Effect and Yoyo
This guide assumes you have completed the free Quick Start Guide and are comfortable creating sequences, adding effects, connecting HookNodes, and using the DelegatePicker. Every section here builds on that foundation.
Wait
A Wait effect holds the current value for a period of time. Nothing moves: the sequence pauses at whatever value the previous effect left off at, then advances to the next effect in the chain. Use it to insert delays, hold poses, or wait for an external condition.
Example: Fade In, Hold, Fade Out with a Wait
This achieves the same result as the three-Tween chain from the free guide, but more cleanly: no need for a "target equals current value" trick to hold steady.
- Create a Float sequence called FadeInOut with OnUpdate bound to your fade method.
- Add a Tween. Bind GetTargetValue to your
fullyVisiblefield and set Duration to2. - Right-click the filmstrip to the right of the Tween and choose Add Effect > Wait.
- The Wait node appears. Set the end condition to Time and enter
1second. - Add a final Tween. Bind GetTargetValue to
fullyHiddenand set Duration to2.
The sequence fades in over 2 seconds, holds for 1 second, then fades out over 2 seconds. The Wait simply pauses the chain: the value stays at 1.0 the entire time.
Wait End Conditions
Wait supports the same end conditions as Follow:
- Time: waits a fixed number of seconds.
- Value in Range: waits until an external value (read via a delegate) comes within a threshold. Useful for waiting until the player is close enough, or until another animation reaches a certain state.
- Condition: waits until a custom delegate returns true. Wire a HookNode to the EvaluateCondition slot and bind it to a method on your script.
Yoyo
JuiceBox has two forms of yoyo behavior. A Yoyo node sits at the end of the filmstrip (like a Loop node) and bounces the entire sequence: all effects play forward, then all effects play backward, alternating indefinitely. A Yoyo signal is set on an individual effect and bounces only that effect: the effect plays forward then backward before the sequence advances to the next effect in the chain.
Example: A Breathing Scale (Yoyo Node)
- Create a Vector3 sequence called Breathe with OnUpdate bound to
Transform.localScale. - Add a Tween targeting a slightly enlarged scale (e.g.
1.1, 1.1, 1.1) with Duration1and easingSineInOut. - Right-click the filmstrip and choose Add Effect > Yoyo.
The object scales up over 1 second, then the Yoyo node plays the entire chain backward (scaling back down), then forward again. The result is a smooth breathing loop with no visible seam: unlike a regular Loop, there is no pop when the value resets.
Use a Yoyo node when the whole sequence should ping-pong. Use a Yoyo signal on one effect when only that effect should bounce, such as a quick shake in the middle of a longer chain.
02 Swing
A Swing effect oscillates like a pendulum or a price tag hanging from a shelf. The rest value is where the swing comes to rest: the equilibrium position at the bottom of the arc. The value swings back and forth past this point, with each pass losing energy until it settles. The result is natural oscillating motion driven by spring physics.
How Swing Differs from Follow and Shake
Follow chases a target at a constant speed with velocity drag: it moves toward the target and stops. Swing oscillates around a rest point: it passes through the rest value repeatedly, swinging wider or narrower depending on the spring parameters. Shake also oscillates, but around the starting value using a fixed periodic waveform (sine, triangle, etc.). Swing's oscillation is physically simulated using spring dynamics.
Example: A Swinging Sign
Suppose you want a hanging sign to swing when disturbed:
- Create a Quaternion sequence called SignSwing with OnUpdate bound to
Transform.localRotation. - Right-click the filmstrip and choose Add Effect > Swing.
- The Swing node appears with a RestValue slot: this is where the swing settles when it stops. Bind it to a field holding the sign's resting rotation.
- Set the end condition to InRange with a range of
0.01and velocity of0.1so the effect ends once the sign has settled.
Give the sign a starting rotation offset from its rest position (via SetStartingValue or a previous effect in the chain), and it swings back and forth around the rest rotation like a pendulum, each pass smaller than the last until it comes to rest.
Smoothing Parameters
A Swing effect always has a SmoothingNode connected (via the Smoothing port on the node header). The SmoothingNode controls the swing's behavior with two fields:
- Frequency: controls how fast the swing oscillates. Higher values make it swing faster. A frequency of 1 produces slow, lazy swings. A frequency of 10 produces rapid oscillation.
- Damping: controls how quickly the oscillation dies out. Below 1.0 the swing is underdamped and swings wide (the default is 0.2, which produces many visible oscillations before settling). At 1.0 the swing is critically damped: it reaches the rest point as fast as possible without swinging past it. Above 1.0 the swing is overdamped and creeps toward the rest point sluggishly.
Example: Adjusting the Swing
- Connect a SmoothingNode to the Swing's Smoothing header port.
- Set Frequency to
4and Damping to0.3for a pendulum-like swing that settles in about a second. - Try Damping at
1.0: the sign glides smoothly to rest with no overshoot. - Try Damping at
0.1: the sign swings back and forth many times before settling.
03 CurveNode: Custom Easing
The built-in easing functions (SineIn, Pow2Out, etc.) cover many cases, but sometimes you need a custom shape: a specific bounce pattern, a staircase effect, or a curve tweaked by hand. A CurveNode lets you wire a Unity AnimationCurve to any Tween's easing slot.
Example: A Custom Bounce Landing
- Set up a Vector3 Tween that moves an object downward (e.g. from above a platform to the platform surface) over 1 second.
- Right-click the canvas near the Tween node and choose Control Nodes > Curve.
- A CurveNode appears. Click it to open the AnimationCurve editor.
- Shape your curve: start at (0, 0), overshoot past (0.6, 1.1), dip below to (0.8, 0.95), and settle at (1, 1). This creates a custom squash-and-settle feel.
- Drag an edge from the CurveNode's output port to the Tween's Easing input port.
The Tween now follows your hand-drawn curve instead of a mathematical easing function. The curve maps normalized time (0 to 1 on the X axis) to normalized progress (0 to 1 on the Y axis). Values above 1.0 overshoot the target; values below 0.0 undershoot the start.
You can connect the same CurveNode to multiple Tweens if they should share the same easing shape. Disconnecting the CurveNode reverts the Tween to its built-in easing selection.
04 Arc Movement
By default, Tweens and Follows interpolate in a straight line between the start value and the target. Arc movement changes this to a curved path: a spherical interpolation (slerp) that sweeps through an arc rather than cutting across directly. This produces natural-looking motion for objects moving between points in 2D or 3D space.
Arc movement is available on Vector2, Vector3, and Vector4 sequences. It is always on for Quaternion sequences (rotation interpolation is inherently spherical). It is not available for Float sequences.
Example: A Projectile Arc
- Create a Vector3 sequence called Lob with OnUpdate bound to
Transform.localPosition. - Add a Tween targeting the landing position with a Duration of
1.5. - On the filmstrip's left cap, click the Arc toggle to enable arc movement for the sequence.
The object now travels along a curved arc from its starting position to the target instead of moving in a straight line. The arc is computed using spherical interpolation, so the path sweeps outward from the origin like a ball being lobbed.
Arc movement applies to every Tween and Follow in the sequence: it is a per-sequence setting, not per-effect. All effects in the chain use the same interpolation mode.
Arc movement pairs well with easing. A Pow2Out on an arc tween produces a natural throw-and-land feel: fast at launch, decelerating into the landing.
Controlling the arc: the zero point
The curve that's drawn will always have its center at the zero coordinate. If you set up your effect in the most straightforward way, that means every curve will depend on where zero is in your scene. If you happen to be far away from that zero point, the curves will look almost straight. That is often not what you want.
If you want a more extreme arc, or an arc that does not depend on where the object is in the environment, move the zero point. There are two ways to do this:
- Bind OnUpdate to a function of your own that offsets the values before they reach the object. Subtract a chosen origin going in (and add it back when you apply the result) so the arc is computed around that point instead of world zero.
- Create an empty "zero" GameObject, make it the parent of the object you are moving in an arc, and drive
Transform.localPositioninstead of world position. The parent's location becomes the center of the arc, so you can place the curve wherever you want by moving the parent.
05 ValueNodes
A ValueNode provides a constant value to an effect's input slot directly in the graph, without writing a script. This is most useful for delegate slots like GetTargetValue: instead of creating a MonoBehaviour with a public field just to hold a target position, you define the value right in the graph editor.
Example: Setting a Target Position Without Code
- Create a Vector3 sequence with a Tween effect. You want it to move to a specific position, but you don't want to write a script just to hold that position.
- Right-click a pocket below the filmstrip and choose Control Nodes > Value.
- A ValueNode appears. Set its type to Vector3 and enter your target coordinates (e.g.
2, 5, 0). - Drag an edge from the ValueNode's output port to the Tween node's GetTarget input port.
The Tween now reads its target from the ValueNode. No script, no public field, no delegate binding: the value lives entirely in the graph. You can adjust it visually at any time.
Twinned ValueNodes
If you want multiple effects to share the same value (for example, three Tweens on different sequences that should all move to the same position), you create twinned ValueNodes. Twinned nodes share the same source: editing one changes them all.
- Wire a ValueNode to the first Tween's GetTarget port.
- On the ValueNode, click the copy button (next to the close button). A new twinned ValueNode appears.
- Wire the twin to the second Tween's GetTarget port. Repeat for the third.
All three ValueNodes display the same value. Change any one and the others update to match: they are linked to the same underlying source.
ValueNodes store their value in the animation data: no MonoBehaviour or script field required. They are the quickest way to parameterize an effect when you don't need the value to come from code at runtime.
06 RandomNode
A RandomNode provides a random value to any numeric input slot on an effect. It replaces the delegate on that slot with a random function: either a one-shot value evaluated when the effect starts, or a continuous noise signal evaluated every frame.
Discrete Random Modes
| Mode | Type | What it produces |
|---|---|---|
| FloatRange | Float | A random number between min and max |
| V2Range / V3Range / V4Range | Vector | Per-component random between min and max |
| OnUnitCircle | Vector2 | A random point on the unit circle |
| InsideUnitCircle | Vector2 | A random point inside the unit circle |
| OnUnitSphere / InsideUnitSphere | Vector3 | A random point on or inside the unit sphere |
| Rotation / RotationUniform | Quaternion | A random rotation |
Noise Modes
| Mode | Type | What it produces |
|---|---|---|
| PerlinFloat / V2 / V3 | Float, Vector | Smooth Perlin noise, remapped to your min/max range |
| SimplexFloat / V2 / V3 | Float, Vector | Simplex noise with optional fBM octaves for richer variation |
Noise modes are always evaluated per-frame (the once/per-frame toggle is locked to per-frame). They read Time.time internally and produce smooth, continuous variation. The frequency parameter controls how fast the noise changes. Each node gets a unique seed so multiple RandomNodes produce uncorrelated patterns.
Example: Fireflies with Random Targets
- Create a Vector3 sequence called Firefly with OnUpdate bound to
Transform.localPosition. Add a Follow effect with Speed2and end condition Value in Range at0.3. Add a Loop node so it repeats. - Right-click a pocket near the Follow node and choose Control Nodes > Random.
- Drag an edge from the RandomNode's output to the Follow's GetTargetValue port.
- The RandomPicker opens. Select V3Range. Set the min/max for each axis to define the volume the firefly wanders in (e.g. X: -3 to 3, Y: 1 to 4, Z: -3 to 3).
- Leave the once/per-frame toggle on Once (the default). Each time the Follow effect starts a new loop iteration, it picks a fresh random target position.
The firefly now chases a new random position each loop, producing organic wandering movement.
Example: Flickering Light with Perlin Noise
- Create a Float sequence called Flicker with OnUpdate bound to a method that sets a light's intensity.
- Add a Follow effect with a high Speed (e.g.
20) and end condition Time at999seconds (effectively infinite). - Add a RandomNode and wire it to GetTargetValue.
- In the RandomPicker, select PerlinFloat. Set frequency to
3, min to0.6, max to1.0.
The Follow continuously chases a smooth, drifting Perlin noise value. The light intensity wanders between 0.6 and 1.0, producing a natural flicker.
Discrete modes with once-per-start evaluation are ideal for values like random Tween targets or random durations. Noise modes with per-frame evaluation are ideal for continuous organic variation like wind, flickering, or idle sway.
07 Parameter Bindings
Every numeric field on an effect (Speed, Duration, end condition thresholds, smoothing frequency, smoothing damping) can be bound to a delegate. The delegate can be evaluated once when the effect starts, or every frame while the effect runs. This distinction is the parameter binding's evaluation mode.
Once vs. Per-Frame
- Once (EvalOnce): The delegate is called a single time when the effect sets up. The returned value is used for the entire duration of the effect. This is the default for most parameters. It is efficient and predictable.
- Per-Frame: The delegate is called every frame. The parameter updates in real time. Use this when the value needs to change while the effect is running, such as a Speed that ramps up over time, or a target position that moves.
Example: Speed That Changes Mid-Effect
Suppose a Follow should accelerate as it gets closer to the target:
public class DynamicSpeed : MonoBehaviour
{
public Transform target;
public float GetChaseSpeed()
{
float dist = Vector3.Distance(transform.position, target.position);
return Mathf.Lerp(10f, 2f, Mathf.InverseLerp(20f, 1f, dist));
}
}
- On the Follow effect node, click the Speed slot and bind it to DynamicSpeed > GetChaseSpeed.
- In the DelegatePicker, toggle the evaluation mode from Once to Per-Frame.
Now the Follow reads a new speed value every frame. When the object is far from the target, it moves fast (speed 10). As it gets close, it slows to speed 2.
Which Parameters Default to What
Most parameters default to EvalOnce. GetTargetValue on a Tween evaluates once (the target is fixed for the Tween's duration). GetTargetValue on a Follow evaluates per-frame (so it can chase a moving target). You can override either default in the DelegatePicker.
Per-frame evaluation has a small runtime cost per parameter per frame. For parameters that genuinely don't change during an effect (like a Tween's Duration), leave them on Once.
08 Timescale
Every effect has a Timescale input that multiplies the passage of time for that effect. A timescale of 2.0 makes the effect run at double speed. A timescale of 0.5 runs at half speed. A timescale of 0.0 pauses the effect entirely: time stops but the effect remains active.
Negative timescale values play the effect backward. A Tween with timescale -1.0 interpolates from the target back toward the start. This lets you reverse individual effects without restructuring your sequence.
Example: Slow-Motion Hit Reaction
Write a script that controls the timescale:
public class HitReaction : MonoBehaviour
{
private float _timescale = 1f;
public float GetTimescale() => _timescale;
public void TriggerSlowMotion()
{
_timescale = 0.2f;
Invoke(nameof(RestoreSpeed), 0.5f);
}
private void RestoreSpeed() => _timescale = 1f;
}
- On the effect node, find the TimescaleInput port (a green input on the left side).
- Drag an edge from the port into empty space to create a HookNode. Bind it to HitReaction > GetTimescale.
- In the DelegatePicker, set evaluation to Per-Frame so the timescale updates in real time.
When TriggerSlowMotion() is called, the effect drops to 20% speed for half a second, then returns to normal. Only this specific effect is slowed: other effects in the sequence and other sequences on the object continue at full speed.
Example: A Reversing Tween
public class DirectionControl : MonoBehaviour
{
public float direction = 1f; // 1 = forward, -1 = backward
public float GetDirection() => direction;
}
Bind the Tween's TimescaleInput to GetDirection. Setting direction = -1f at runtime makes the Tween play backward from its current position.
Timescale stacks with Unity's Time.timeScale. If Unity's global timescale is 0.5 and the effect's timescale is 0.5, the effect runs at 25% real-time speed. Use global timescale for game-wide pause/slow-motion and per-effect timescale for animation-specific speed control.
09 Disable and Offscreen Behavior
Two controls at the top of the graph, to the right of Animation Timescale, decide what every sequence on the animation does when the object's lifecycle changes. Both are animation-wide: they apply to all sequences at once.
Behavior When Disabled
This dropdown chooses what happens to running sequences when the GameObject is deactivated in the hierarchy (its own SetActive(false), or a parent being turned off).
- Quit (default): stop the run. The sequence ends as if it had finished, and enabling the object again replays it from the top per its trigger.
- Pause: hold the sequence in place while the object is inactive and resume from the exact same point when it is re-enabled. No animation time passes while it is paused.
- Keep Running: keep advancing even while the object is inactive, so the animation is wherever it would have been once the object comes back.
A destroyed object always stops, regardless of this setting.
Behavior While Offscreen
This dropdown chooses what running sequences do while the object is not visible to any camera.
- Nothing (default): ignore visibility and keep running normally.
- Pause: hold every sequence while the object is offscreen and resume the moment it returns to view.
- Quit: stop the run when the object goes offscreen, the same way Quit on disable does. Pair it with the OnBecameVisible trigger to restart the sequence when the object comes back into view.
This relies on Unity's renderer visibility, so the object needs a Renderer for the setting to do anything; an object with no renderer is treated as always visible, so the dropdown is greyed out until you add one.
Need offscreen pausing (or stopping) but want to keep the animation on an object with no Renderer? Drive it yourself: detect visibility however suits your project (an OnBecameVisible/OnBecameInvisible pair on whatever object does carry the Renderer, a CullingGroup, or your own checks) and call Pause() and Resume() on the JuiceBoxAnimation from your script. The built-in toggle is only a convenience around those same calls.
Both pause behaviors hold a sequence the same way the Pause() API does, by parking its timescale at zero. If a sequence's timescale is driven by a bound delegate on the TIMESCALE port, that delegate keeps writing the timescale every frame and overrides the hold.
10 Runtime Outputs and Linked ValueNodes
Normally a sequence computes values internally and writes them out through OnUpdate. Runtime outputs let you read the sequence's current animated value from the graph and feed it into other systems, without OnUpdate being involved.
The filmstrip's left cap has output ports for the sequence's live values: Target, Result, Velocity, and Timescale. You can wire a ValueNode to these ports to enter linked mode: the ValueNode becomes a live reader of the sequence's runtime state.
Example: Driving a Particle System from a Sequence
Suppose you have a Float sequence animating an intensity value and you want a particle system's emission rate to follow the same curve:
public class ParticleBridge : MonoBehaviour
{
public ParticleSystem particles;
public void SetEmissionRate(float rate)
{
var emission = particles.emission;
emission.rateOverTime = rate * 50f;
}
}
- On the filmstrip's left cap, find the Result output port.
- Drag an edge from the Result port into empty space. A linked ValueNode appears.
- The ValueNode now shows the sequence's current result value in real time during playback.
- You can read this value from script, or use it to chain into other graph editor systems.
A linked ValueNode in this mode is read-only: it reflects the sequence's live state rather than providing a constant. It updates every frame while the sequence is running.
Runtime outputs are especially useful for debugging. Wire a linked ValueNode to a sequence's Result output to watch the animated value change in real time during Play mode, right in the graph editor.
11 OnComplete, Finally, and Sequence Signals
Individual effects have OnStart and OnDone signals (covered in the free guide). Sequences add two of their own: OnComplete and Finally. Both are on the right-hand panel of the filmstrip, Finally directly above OnComplete.
OnComplete fires once when the sequence finishes: after the last effect in the chain completes and no loop sends it back to the beginning. It is about success, so it does not fire when a run is cut short — if the object is destroyed or deactivated part-way through, OnComplete stays silent.
Finally fires when the run ends, however it ends: it completed, you called Stop(), the GameObject was deactivated or destroyed, or your own callback threw an exception. Use it for teardown that has to happen either way — returning a pooled object, releasing a lock, clearing a "busy" flag. It fires exactly once per run, one frame after the run stops.
Choosing between them: if the work only makes sense when the animation actually finished — starting the next sequence, awarding a score, opening a door — use OnComplete. If skipping the work would leave something broken, use Finally.
Example: Chaining Sequences
Suppose you want a "bounce in" animation followed by an "idle wobble" animation, and they are separate sequences because they have different loop behavior (bounce-in plays once, idle wobble loops forever).
- On the BounceIn filmstrip's right-hand panel, find the OnComplete port.
- Drag an edge from OnComplete into empty space. A HookNode appears.
- In the DelegatePicker, find StandardFunctions > StartSequence and select it. Enter
IdleWobbleas the sequence name parameter.
When BounceIn finishes its last effect, OnComplete fires and starts the IdleWobble sequence, no script code required. The two sequences are decoupled and you can modify either one independently.
OnComplete does not fire on looping sequences (the sequence never "completes"). It only fires when the chain reaches its natural end. Wiring Finally works exactly the same way — drag from its port, one row above OnComplete — and unlike OnComplete it will fire on a looping sequence, because stopping a loop still ends the run.
You can wire OnComplete and Finally from a sequence, and OnStart/OnDone from individual effects, to build complex multi-sequence choreography entirely in the graph editor, with no script code at all.
If a bound method goes missing
If you rename or delete a method that a slot is bound to, JuiceBox reports it in the console the next time the animation loads, naming the slot and the method it could not find. The slot's row in the graph also turns red so the broken binding stays visible — worth knowing, because a broken Finally or OnComplete has no other symptom: the animation still plays perfectly, it just stops calling you back.
12 Live Preview
Live Preview lets you play your animation directly in the Scene view without entering Play mode. It runs the full animation pipeline (all effects, easing, smoothing, looping, and signals) at real speed, showing you exactly what the animation will look like at runtime.
Example: Previewing a Tween
- Select a GameObject with a JuiceBoxAnimation that has at least one complete sequence (OnUpdate wired, effects configured).
- In the graph editor toolbar, click the Play button (▶).
- The animation begins playing in the Scene view. The graph editor highlights the currently active effect in the filmstrip.
- Click the Stop button (■) to end the preview. The GameObject returns to its original state.
Live Preview runs in editor time, not game time. Time.timeScale has no effect: the preview always runs at real-world speed. All sequences on the selected animation play simultaneously, just as they would at runtime.
Changes you make to effect parameters while the preview is running are picked up immediately: you can adjust a Duration or easing curve and see the result without restarting.
Pause has no effect during Live Preview. Pausing and resuming (whether through anim.Pause() or a sequence handle, see section 15) are runtime features that act on the running coroutine; edit-mode preview runs its own loop and does not carry a handle, so a pause call is ignored. To test pause behavior, enter Play mode.
Live Preview works best with scenes that are fully loaded in the editor. Scenes that load assets dynamically from an asset database at runtime (e.g. Addressables or custom asset pipelines) may not preview correctly, since those assets are not available outside of Play mode.
Live Preview records the GameObject's state before playing and restores it when you stop. Your scene won't be modified by previewing. If something looks wrong, just hit Stop and adjust.
13 Animation Definitions
By default, a JuiceBoxAnimation stores its sequence data directly on the component. An Animation Definition is a ScriptableObject asset that holds that data externally. When multiple GameObjects share the same animation setup (enemies with identical attack patterns, UI buttons with the same hover effect), a definition lets them all point to one shared asset instead of each carrying a duplicate copy.
Creating a Definition
- Select the GameObject whose animation you want to share.
- In the Inspector, click Create Definition From This Animation.
- Choose a save location and filename. A
.assetfile is created.
The animation data moves from the component to the new asset. The component now shows a Definition field pointing to the asset, and the inline data is cleared. Any other JuiceBoxAnimation components in the scene that had identical sequence names are automatically linked to the same definition.
How Linking Works
When a definition is linked, the graph data lives on the asset. Opening the graph editor for any linked component shows the same sequences: edits to one update all of them. The component itself is lightweight: it holds only the reference to the definition and any per-instance runtime state.
You can also link a definition manually by dragging the asset into the Definition field on a JuiceBoxAnimation component.
Unlinking a Definition
To go back to inline data, click Unlink Definition in the Inspector. This copies the definition's data back onto the component and removes the link. The component becomes independent again: edits to it no longer affect the definition or other linked components.
Asset GUID
When a definition is linked, the Inspector shows the asset's GUID below the Definition field. This is the unique identifier Unity uses to track the asset. It is read-only and exists for debugging: if a definition asset is moved or renamed, Unity tracks it by GUID rather than by file path.
Definitions are ideal for prefabs. Link a definition to the prefab's JuiceBoxAnimation, and every instance of the prefab shares the same animation data without duplicating it into each prefab instance.
14 Look At: Following a Target
The built-in OnUpdate setters cover position, scale, and color. Rotating one object to face another is different: it is a cross-object operation that needs both the rotating object and the thing it looks at, and that target reference lives in your game, not in the graph. You supply it with a short method you bind as the effect's target.
Example: Eyes That Follow the Player
Put a small script on the eye object with a reference to what it tracks:
using UnityEngine;
public class LookAtTarget : MonoBehaviour
{
public Transform target;
public Quaternion FaceTarget()
{
// Direction from the eye to the thing it is watching, in world space.
Vector3 toTarget = target.position - transform.position;
// When the target sits exactly on the eye the direction is the zero
// vector, which has no orientation. LookRotation cannot build a rotation
// from it: Unity logs an error and the result is garbage. Keep the
// current rotation until there is a real direction to face.
if (toTarget.sqrMagnitude < 0.0001f) return transform.rotation;
// Build the world rotation that aims the object's forward (+Z) down that direction.
return Quaternion.LookRotation(toTarget);
}
}
- Create a Quaternion sequence called Gaze with OnUpdate bound to
Transform.rotation(world rotation, since LookRotation returns a world-space orientation). - Add a Follow effect. Bind its target to the
FaceTargetmethod on your LookAtTarget component. - Set the Follow's Speed for how quickly the eyes catch up.
The Follow reads FaceTarget every frame, so as the target moves the eyes rotate to track it. The lag is the whole point: a low Speed gives a slow, creepy drift as the eyes lazily swing toward the player. If you wanted the eyes to snap exactly onto the target with no delay, you would not use an animation at all, you would just set the rotation in your own script. The reason to wire up a sequence is to shape the delay.
For a twitchier, more lifelike watch, swap the Follow for a quick Tween toward FaceTarget followed by a short Wait (around 0.2 seconds), looped. The eyes snap to the player, pause, then re-aim, instead of gliding continuously. A faster, smoother stalk and a sharp snap-and-hold read as creepy in different ways; pick the one that fits.
For a 2D sprite, rotate only around Z: compute the angle with Mathf.Atan2 and return Quaternion.Euler(0, 0, angle).
The method does not have to live on the eye object. If it lives elsewhere, write it as a static method whose first parameter is a GameObject; JuiceBox passes the bound object in automatically (the same convention StandardFunctions uses).
15 Runtime Parameters and Sequence Handles
Sometimes one animation needs different values each time it plays: a damage popup that flies to a different spot, a dash that varies in speed, a spawn puff scaled to the thing that spawned. Instead of building a separate sequence for every case, you expose a named parameter and pass its value when you start the run. (This is distinct from the effect Parameter Bindings in section 07, which wire delegates into an effect's inputs; a runtime parameter is a named value you hand in at start time.)
Exposing a Parameter in the Graph
A ParameterNode is a named input. Right-click the graph and choose Control Nodes > Parameter, then give it a name (the key your code uses) and a type. Its orange output connects into a ValueNode. Once connected, that ValueNode's own value field becomes the parameter's default, and the ValueNode feeds a slot exactly as any ValueNode does, through a HookNode or straight into a green slot.
- Add a Parameter node. Name it
speedand set its type to Float. - Drag from its orange output into a ValueNode (create one if you need it). The ValueNode adopts the Float type; its value field now holds the default speed.
- Wire that ValueNode into the slot you want driven, for example a Tween's Speed via a HookNode, just like any other ValueNode.
The default is simply the value authored on the driving ValueNode in the graph. Each run starts from a fresh copy of the sequence's value store, so passing a value overrides it for that run only, and omitting a parameter always uses the authored default. A value passed on an earlier run never carries over, even if the object is reused (for example from a pool). Unnamed parameters cannot be addressed from code.
Passing Values When You Start a Sequence
Pass the values as InstanceParameter arguments to RunSequence. Each one pairs a name with a value, and the constructor picks the type from the value you give it.
using JuiceBox;
var anim = GetComponent<JuiceBoxAnimation>();
anim.RunSequence("Dash",
new InstanceParameter("speed", 12f),
new InstanceParameter("direction", Vector3.left));
Any parameter you do not pass uses its authored default. Supported types match ValueNodes: float, int, string, Vector2, Vector3, Vector4, Quaternion, and Rect, and each keeps its native type (an int stays an int).
Sequence Handles: Controlling a Running Sequence
There are two families of start method that do the same thing but differ in what they return. StartSequence returns nothing, so it can be wired to a UnityEvent such as a Button's OnClick. RunSequence returns a SequenceHandle you hold to control that specific run.
SequenceHandle dash = anim.RunSequence("Dash");
// Hold the handle to control that specific run later:
dash.SetParameter("speed", 4f); // change a parameter live
dash.Pause(); // hold it in place
dash.Resume(); // continue from where it paused
dash.Stop(); // end it
bool active = dash.IsRunning; // true while the run is going, false once it ends
A handle is never null: you never check it for null and never have to clear it. It either points at a running instance or it does not. A freshly declared handle (default(SequenceHandle)) points at nothing, as does one whose run has finished or been stopped, and calling SetParameter, Pause, or Stop on a handle that points at nothing does nothing. A parameter fed into a per-frame slot updates live when you call SetParameter; a parameter read only once at the start of an effect applies only when the run begins.
Use StartSequence from the Inspector and UnityEvents (it is void, so Unity can bind it, but it takes no parameters). Use RunSequence from code when you want the handle back or need to pass values; RunSequence("Name") with no InstanceParameter arguments simply starts the sequence and returns its handle.
Pausing and Resuming
Pause holds a run in place at its current value; resume continues from there. You can pause every sequence on an animation, one by index or name, or a single run by its handle.
anim.Pause(); // every sequence on this animation
anim.Pause("Dash"); // one sequence by name
dash.Pause(); // one specific run
anim.Resume();
dash.Resume();
Pause gates the run without touching timescale, so it holds even when the effect's timescale is driven by a Hook or ValueNode. Pausing is a runtime feature only: it has no effect during edit-mode Live Preview (see section 12).
16 Running a Sequence Uniquely
RunSequenceUnique starts a sequence only in relation to runs that are already going, so you do not stack duplicates. You pick what happens when a match is already active:
- Abort – do not start if a match is running.
- Overwrite – stop the match, then start fresh.
- Wait – start once the match finishes.
- AbortAndUnpause – do not start, but resume the match if it was paused.
// Don't restart the open animation if it is already opening:
anim.RunSequenceUnique("Open", SequenceUniqueBehavior.Abort);
// Own-slot uniqueness, with per-run values:
anim.RunSequenceUnique("Open", SequenceUniqueBehavior.Overwrite,
new InstanceParameter("speed", 8f));
// Queue this to play after some specific runs finish (activeRuns is your
// List<SequenceHandle>); per-run values, if any, come after the list:
anim.RunSequenceUnique("Finish", SequenceUniqueBehavior.Wait, activeRuns);
With no handle list, a match means another instance of the same sequence (by index or name). Pass an IReadOnlyList<SequenceHandle> (your List<> or an array) to make it unique against those specific runs instead; per-run InstanceParameter values always come last. RunSequenceUnique returns a handle like RunSequence does; for Wait the returned handle is pending, and you can Stop it to cancel the queued run before it starts.
