JuiceBox / ECS Converter Pro guide → / Asset Store

ECS Converter

Turn a JuiceBox animation into a Burst-compiled ECS system and run the same motion on thousands of entities at once.
Add-on · Requires JuiceBox - PRO

01 What the Converter Does

The DOTS converter takes a JuiceBox animation you authored in the Sequence Editor and generates a single C# file that runs the same animation as a Burst-compiled ECS system. Nothing about your original animation changes: the component, the definition asset, and the graph all keep working exactly as before. The generated file is a separate, standalone copy of the animation translated into entity components and jobs.

Inside the generated file you get three things:

The point of all this is scale. A managed JuiceBoxAnimation is ideal for dozens or hundreds of animated objects; the generated ECS version of the same animation is designed to run on thousands of entities at once, scheduled in parallel across cores.

The converter refuses animations it cannot yet translate faithfully rather than converting them incorrectly, so expect some animations to convert cleanly, some to convert with stated caveats, and some to be refused with an explanation. Section 12 lists what does not convert yet.

02 Requirements

The converter itself lives in the editor and adds nothing to your builds. Only the generated files and the small JuiceBox.DotsSupport runtime (play tags and the event system) ship with your game.

03 Converting an Animation

Open any animation in the Sequence Editor and press Convert to DOTS in the toolbar. The Convert window opens with that animation as its target.

  1. Check the Animation field. It accepts a GameObject with a JuiceBoxAnimation on it, or an animation definition asset dragged straight from the Project window.
  2. Optionally edit the Name. This is the base name of the generated class and file (see section 5). It defaults to the animation's name.
  3. Pick an Output folder with Browse. The line underneath shows exactly which file the conversion will write, and whether it creates a new file, refreshes this animation's previous conversion, or would replace a file that came from somewhere else.
  4. Press Analyze. This classifies every binding in the animation and runs the full generation in memory. It writes nothing to disk, so you can analyze freely while you iterate on the animation.
  5. Read the report (section 4). If the animation is blocked, the findings tell you which binding to rewire and how.
  6. Press Convert. The file is written, Unity compiles it, and the report stays on screen through the recompile.

Analyze and Convert always re-examine the animation at the moment you press them, so you can leave the window open, edit the animation in the graph, and re-run Analyze without reselecting anything.

04 Reading the Report

The report is a list of findings, one per binding or per generation note, grouped by sequence. The filter toggles show or hide each kind. Click a finding to read its full text in the pane at the bottom; press Return on (or double-click) a finding that names a sequence to jump to that sequence in the Sequence Editor.

What the badges mean

BadgeMeaning
inlinedA library function (StandardFunctions, Easing, RandomFunctions) or a baked constant. Translated directly into the job; costs nothing extra.
burstA static method of yours. Its body is extracted from your source file and inlined into the job, so it runs Burst-compiled like everything else.
eventA callback slot (OnStart, OnDone, OnComplete, Finally). The job cannot call your method; it publishes an event instead. Read section 8; this is the most important difference from managed JuiceBox.
blockedA binding the generator cannot translate. Conversion is refused until it is rewired; the finding says what to rewire it to.
gapA feature whose generation template is not built yet (see section 12). Refuses conversion.
unresolvedA binding the converter could not convert. The file is still written, but everything in it is disabled, so your project keeps compiling and the animation simply does not run. A banner at the top of the file lists what to fix.
costConverted, but this does measurable work every frame, or gives up some parallelism: a cross-entity write to one shared object, or reading another object's transform while writing your own, both make the job run single-threaded. The finding explains the cheaper authoring where there is one.
fidelityConverted, with a behavioral difference from the managed runtime, stated precisely.

The headline above the list gives the verdict: converts cleanly, converts with caveats, or blocked. Copy all puts the entire report on the clipboard.

05 Names, Files, and the Animation Key

Two identities come out of a conversion, and they are deliberately not the same thing.

The class name

The Name field controls the generated class: a name of ChestOpen produces ChestOpenDotsAuthoring.cs containing the ChestOpenDotsAuthoring component. The file must be named after the class (Unity can only attach a MonoBehaviour from a file that matches it), so the converter fixes the file name and lets you choose only the base name and the folder.

If two of your animations share a name, the report warns you before anything is written: two generated classes with the same name in the same assembly will not compile, and in different assemblies they make the Add Component menu ambiguous. Rename one in the Name field; your choice is remembered for that animation.

The animation key

Every generated file carries a stable AnimationKey: an integer identifying this animation, derived from the underlying asset rather than from its display name. Renaming a GameObject does not change its key, and two different animations that happen to share a name get different keys. The key is what your gameplay code uses to receive events from the animation (section 8), and every instance of the same animation shares one key on purpose; the event's Source field tells you which entity fired it.

Convert from a saved scene. An unsaved scene has no asset identity yet, so the key falls back to the animation's name and the report warns you about it.

06 Using the Generated File

  1. Open (or create) a subscene and add the GameObject you want animated.
  2. Add the generated authoring component to it, for example Chest Open Dots Authoring.
  3. Close the subscene. The Baker runs and the entity now carries the animation's state component and a JuiceBoxPlay tag.
  4. Enter Play Mode. Sequences whose trigger is OnStart or OnEnable begin on their first frame, exactly like the managed version.

All of the animation's configuration (durations, targets, easings, loop modes) is baked into the file as literals at conversion time. If you edit the animation afterward, convert it again: the destination line will read Refreshes, and the file is regenerated in place.

Do not hand-edit a generated file. Regeneration overwrites it wholesale. The one exception is a file the report said had unresolved bindings: that file is written disabled, and its banner explains both ways to resolve it -- fix the cause in your own source and convert again, which is preferred, or edit the generated code and remove the wrapper by hand.

Where the animation writes

Transform outputs write the entity's LocalTransform. Material color and alpha outputs write the Entities Graphics color component. Values that have no ECS destination yet (see section 12) are still computed into the state component every frame; the report tells you when that is the case, and you can read them from your own systems.

07 Starting and Stopping

Every generated entity carries a JuiceBoxPlay tag: an enableable component that acts as the play switch. The Baker sets its starting state from the animation's triggers: OnStart and OnEnable sequences bake it enabled, Manual sequences bake it disabled and wait for you.

using JuiceBox.Dots;
using Unity.Entities;

// Start (or restart) the animation on an entity:
entityManager.SetComponentEnabled<JuiceBoxPlay>(entity, true);

// Stop it:
entityManager.SetComponentEnabled<JuiceBoxPlay>(entity, false);

When every sequence on the entity finishes, the system disables the tag itself, so a finished animation costs nothing until you enable it again. What disabling the tag mid-run does to the animation depends on its authored Disable behavior; see section 9.

08 Signals Become Events

This is the one place a converted animation asks you to change your gameplay code, so it is worth reading twice.

In managed JuiceBox, the four signal slots -- OnStart and OnDone on an effect, OnComplete and Finally on a sequence -- call the method you bound, directly. A Burst job cannot call managed code, so the generated system does something different: at the moment the hook would have fired, it publishes an event carrying the animation's key, the sequence and effect index, and the value at that moment. A small dispatch system drains these events on the main thread and hands them to whatever has registered for that key.

One family of signals skips events entirely: StandardFunctions.StartSequence and StopSequence aimed at this same animation (a plain Static binding with a baked Int index) run inside the job itself. The target sequence starts or stops exactly as managed does, no handler needed, and a stopped or interrupted run publishes its Finally and suppresses its OnComplete, matching the managed watcher.

The method you bound is not called. Binding it recorded your intent, and the report names it, but until your gameplay code registers a handler for the animation's key, completion events go nowhere, silently.

Registering a handler

using JuiceBox.Dots;
using UnityEngine;

public class ChestOpenFeedback : MonoBehaviour, IJuiceBoxDotsEventHandler
{
    void OnEnable()
    {
        JuiceBoxDotsEventHandlers.Register(
            ChestOpenDotsState.AnimationKey, this);
    }

    void OnDisable()
    {
        JuiceBoxDotsEventHandlers.Unregister(
            ChestOpenDotsState.AnimationKey, this);
    }

    public void OnJuiceBoxDotsEvent(in JuiceBoxDotsEvent e)
    {
        if (e.Kind == JuiceBoxDotsEventKind.OnComplete)
            PlayChime(e.Source);   // e.Source is the entity that finished
    }

    void PlayChime(Unity.Entities.Entity source) { /* ... */ }
}

The event's fields:

FieldMeaning
AnimationKeyWhich animation. The constant on the generated state type.
SequenceIndexWhich sequence within it fired.
EffectIndexWhich effect, for OnStart and OnDone; -1 for the sequence-level kinds.
KindOnStart, OnDone, OnComplete, or Finally.
SourceThe entity the animation ran on. With many instances of one animation, this is how you tell them apart.
ValueThe channel value at the moment of the event, packed into a float4.
ArgumentThe argument the authored binding passed to its method, packed like Value. Zero unless ArgumentKind names a non-Int type.
IntArgumentThe argument when ArgumentKind is Int, carried whole rather than rounded through a float. A parameter-fed argument reads the parameter's per-entity field at the moment the event fires, so a thousand instances each publish their own amount.
ArgumentKindWhich of the two fields carries the argument, or None when the binding took none.
TargetThe other entity a cross-object signal addressed -- a relative descriptor like ../Mana Bar, or a bound object. Entity.Null means the signal addressed the animated object itself.

The kinds match their managed meaning: OnStart fires when an effect sets up -- first entry, each handover from the previous effect, each loop pass -- and OnDone when it completes. OnComplete fires only when a sequence finishes on its own, while Finally fires when the run ends for any reason, including being aborted by the Disable behavior below or stopped by an in-job StopSequence. Handlers run on the main thread, so it is safe to touch GameObjects, audio, and UI from them.

09 Disable Behavior

The Disable dropdown you authored on the animation graph carries over, with the entity's JuiceBoxPlay tag standing in for the GameObject being deactivated:

Authored modeConverted behavior
Quit (the default)Disabling the tag mid-run aborts the animation: Finally fires, OnComplete does not, and the animation's state resets to its starting values, so re-enabling the tag replays it from the beginning. The generated file carries a small extra system that watches for this.
PauseDisabling the tag holds the animation exactly where it is. Re-enabling resumes mid-run. No hooks fire.
Keep RunningConverts. The generated job is not gated on the play tag, so the animation keeps advancing while the owner is disabled, matching the managed runtime. The report notes the cost: the job is scheduled over every entity carrying the animation and skips idle ones with a branch, where the other two modes let the query drop them first.

The Offscreen dropdown does not convert at all yet: a converted animation keeps running regardless of visibility, and the report says so whenever an animation is authored with offscreen handling.

10 Prefabs and Instances

Instances of one prefab are one animation. Convert from any instance and the converter automatically works from the prefab itself, so every instance shares a single generated file and a single animation key, which is what lets a thousand spawned copies run one system and report through one handler. The report notes when this redirect happens.

11 Parameters

Parameter bindings survive conversion. Each authored parameter becomes a field on the generated state component, seeded with the default you authored. To override one per entity, assign the field before the animation starts:

var s = entityManager.GetComponentData<BounceDotsState>(entity);
s.Seq0_Param_height = 4.5f;
entityManager.SetComponentData(entity, s);
entityManager.SetComponentEnabled<JuiceBoxPlay>(entity, true);

Overrides are per entity, which replaces the managed InstanceParameter pattern: a thousand instances of one animation can each carry their own values in their own components. An abort under Quit disable behavior resets the run but preserves your parameter overrides.

Bindings that read one of your fields

A slot bound to a field or property on one of your components -- rather than to a method -- converts the same way, and the report says so as a cost. The member becomes a field on the state component, seeded with the value the component held when you pressed Convert:

// authored: this sequence's target reads PanelOpener.PanelOpen
public float2 Seq0_Member_PanelOpener_PanelOpen;   // baked new float2(200f, 0f)

The job reads that field and never your MonoBehaviour. So a value nothing writes at runtime -- the usual case for something you set in the Inspector -- converts exactly, and there is nothing to do. If a script does write it while the animation runs, point that script at the entity field instead, exactly as you would set a parameter override. Like parameters, these survive an abort under Quit.

12 Current Limitations

Read this section before you buy into a conversion plan. The converter refuses what it cannot translate faithfully -- every item below produces a clear finding rather than a wrong conversion -- but the boundary is not where most people guess.

The rule of thumb. The motion converts; running your own code every frame is where it stops. An animation authored entirely in the graph editor almost always converts, including one sequence driving another through value slots. Reading one of your fields or properties converts too (section 11). What often does not convert yet is a slot that calls a method on a specific object -- to decide a duration, a target, or whether to continue. That is the most common reason a real animation is refused, and it has nothing to do with which effects you used.

Check before you plan, not after. Press Analyze on any animation. It classifies without writing a file, logging, or touching your project, so you can walk a whole scene and see exactly what would happen. Do that before committing a project to conversion.

Bindings that do not convert yet

Outputs that do not convert yet

Sequence features that do not convert yet

What does convert

The whole effect model: Tween, Follow, Swing, Wait and Shake; springs and smoothing; easing curves including AnimationCurve; seeded randomness; combiners; radial arcs; quaternion paths; material color; looping and Yoyo within the limit above; triggers; timescale; parameters; starting values, starting velocities, durations and a Follow's Speed from value slots -- durations and speeds can read another sequence's live output; reads of your own fields and properties; the position and scale getters from StandardFunctions, whether they point at the animated object or another one; the four signal hooks (OnStart, OnDone, OnComplete, Finally) as events, bound arguments and cross-object targets riding on the payload; and StartSequence/StopSequence aimed at the same animation, run inside the job itself. Per-animation caveats are always stated in the report rather than left for you to discover at runtime.

This is a phase-one release and the list above is the work queue, roughly in priority order within each group.

13 Troubleshooting

"The file is written DISABLED"

The conversion wrote a file whose contents are wrapped in #if false, because one or more bindings could not be converted. Your project still compiles and the animation does not run. The banner at the top of the file lists exactly what could not be converted and how to resolve it; fix the cause in your own source and press Convert to DOTS again. If you want to back out instead, delete the generated file.

This is deliberate. Writing a file that cannot compile would stop every later recompile in your project -- not just this file -- while Unity reports no compilation in progress, so your next edits would silently stop reaching your assemblies. Disabling the one file keeps the problem where it belongs.

My OnComplete method never runs

By design; see section 8. Register a handler for the animation's key; the bound method itself is not called in the converted animation.

"Already exists at…" name findings

Another animation (or an earlier conversion of this one under a different name) already produced a class with this name. Change the Name field, or point the Output at the folder holding this animation's previous conversion so the line reads Refreshes.

The animation does not start in Play Mode

Check the triggers: an all-Manual animation bakes with JuiceBoxPlay disabled and waits for your code to enable it. Also confirm the authoring component is inside a subscene; baking is what creates the entity.

Two animations answer one handler

They share a key, which the report will have warned about: usually two same-named animations converted from an unsaved scene. Save the scene and reconvert, and each gets its own identity.