๐Ÿ‘‹ Introduction & Overview

This asset is a high-performance, strictly DOTS/ECS-driven animation system designed for the Universal Render Pipeline (URP). It bypasses traditional CPU animation bottlenecks by baking skeletal data into highly optimized BlobAssets and performing matrix/Dual Quaternion skinning directly inside the Vertex Shader.

Whether you are building a massive RTS, a factory automation game, or a swarm-survival (Vampire Survivors-like) project, this tool allows you to render 10,000+ animated units on screen with zero CPU overhead.


โœจ Core Features


๐Ÿ› ๏ธ Technical Requirements

To use this asset, your project must meet the following minimum requirements:

Requirement Supported Version Notes
Unity Engine 2022.2 or higher Tested up to Unity 6.
Render Pipeline URP 14.0+ HDRP and Built-in RP are not supported.
Entities (DOTS) 1.4.5 or higher Requires com.unity.entities.
Entities Graphics 1.4.18 or higher Requires com.unity.entities.graphics.
Burst Compiler 1.8.27 or higher Required for high-performance jobs.

๐Ÿ’ก Note: The asset relies strictly on the ECS paradigm. It does not use traditional GameObjects for runtime rendering. You must be familiar with spawning and managing entities via EntityCommandBuffer or Bakers.


โš ๏ธ Known Limitations

Because this system is built for maximum performance at massive scales, it relies on pre-computed (baked) data. Please be aware of the following architectural limitations:

๐Ÿ›‘ Important Limitations

  • No Runtime IK: Inverse Kinematics (like procedural foot placement or aiming) is not supported, as the bone matrices are baked into static arrays.
  • No Ragdolls: Physics-driven ragdolls cannot be applied to the GPU-skinned meshes.
  • Memory Footprint: Animation data is baked per-frame, per-bone. While highly optimized, baking 50+ long animations for a character with 100+ bones will consume noticeable VRAM/RAM.

โš™๏ธ 2. Installation & Setup

Getting started with GPU Animation Entities PRO requires a specific Unity environment, as the asset relies heavily on the Data-Oriented Technology Stack (DOTS) and the Universal Render Pipeline (URP).

Follow this step-by-step guide carefully to ensure your project is configured correctly. Missing the URP configuration steps is the #1 cause of invisible meshes!


๐Ÿ“ฆ Prerequisites

Before importing the asset, ensure your Unity project meets the following baseline requirements:


Step 1: Install Required Dependencies

Since this is a strict ECS asset, you must install the official Unity DOTS packages.

  1. Open your Unity project.
  2. Navigate to Window > Package Manager.
  3. Click the + icon in the top left corner and select "Add package by name...".
  4. Add the following packages one by one:
    • com.unity.entities (Version 1.4.5+)
    • com.unity.entities.graphics (Version 1.4.18+)
    • com.unity.burst (Version 1.8.27+)

๐Ÿ’ก Tip: If you are starting a fresh project, the easiest way to get all dependencies is to create a new project using the "3D (URP) Core" template, and then install the Entities packages.


Step 2: Import the Asset

Once the dependencies are resolved:

  1. Open the Window > Package Manager.
  2. Select My Assets from the dropdown.
  3. Search for GPU Animation Entities PRO.
  4. Click Download, then Import.
  5. Ensure all folders (especially Editor, Runtime, and Samples~) are checked, and click Import.

Step 3: URP Configuration (๐Ÿšจ CRITICAL)

For the GPU skinning to work, the Universal Render Pipeline must be allowed to pass DOTS data to the GPU. If you skip this step, your animated characters will be invisible.

  1. Locate your active URP Asset in the Project window (usually found in Assets/Settings/ or similar).
  2. Select the URP Asset to view it in the Inspector.
  3. Scroll down to the Advanced section.
  4. Ensure the following settings are ENABLED (Checked):
    • โœ… SRP Batcher
    • โœ… DOTS Instancing (This is mandatory for _SnivelerRenderFrames to reach the shader).

๐Ÿ›‘ Troubleshooting: If you cannot find the "DOTS Instancing" checkbox, ensure that the com.unity.entities.graphics package is successfully installed and compiled.


Step 4: Burst Compiler Settings

To achieve maximum performance, ensure the Burst compiler is active.

  1. Go to Jobs > Burst > Enable Compilation (Ensure it has a checkmark).
  2. Go to Jobs > Burst > Synchronous Compilation (Optional, but recommended during Editor playback to prevent initial stuttering).

Step 5: Explore the Demo Scenes

The best way to verify your installation and learn the system is to explore the provided demo scenes. Navigate to the Samples~ folder (you may need to copy it into your Assets folder if Unity imported it as a hidden package sample).

We have provided three distinct environments:


๐Ÿš€ 3. Quick Start Guide (Your First 10,000 Units)

In this guide, we will take a standard Unity character, convert it into a highly optimized DOTS Prefab using the Animator Baker, and write a simple ECS script to spawn thousands of instances on the screen.

By the end of this 5-minute tutorial, you will have a massive, fully animated crowd running at maximum FPS.


Step 1: Prepare Your Source Character

Before we can bake anything, we need a standard Unity GameObject set up correctly.

  1. Drag your character model (FBX) into a standard Unity Scene.
  2. Ensure the GameObject has a SkinnedMeshRenderer.
  3. Ensure it has an Animator component with a valid Animator Controller assigned.
  4. (Optional but Highly Recommended) Add an LODGroup component and set up your LODs. The Baker will automatically read this and generate optimized DOTS LODs!

๐Ÿ’ก Tip: Make sure your Animator Controller has at least one animation state (e.g., "Idle" or "Run"). The Baker will read all states directly from this controller.


Step 2: Bake the DOTS Prefab

Now we will convert this standard GameObject into a GPU-driven DOTS Prefab.

  1. Open the Baker window via Window > Sniveler Code > Animator Baker.
  2. Drag your character GameObject from the scene into the Prefab Model field.
  3. The window will populate with three tabs: Animator, Lods, and Bones.
  4. In the Lods tab, ensure the Shader is set to Sniveler Lit (or Sniveler Unlit).
  5. Click the big green Process button at the bottom.

What just happened?
The system played through all your animations, extracted the bone matrices, and saved them into highly optimized BlobAssets.
You will find your new ready-to-use DOTS Prefab in your Project window under:
Assets/SnivelerCode/GpuAnimation/Generated/[YourCharacterName]/Character


Step 3: The Scene Renderer (๐Ÿšจ CRITICAL STEP)

For the GPU to know how to animate your characters, the baked animation matrices must be uploaded to the video card's memory (GraphicsBuffer). We do this using a special scene component.

  1. Create a SubScene in your project (Right-click in Hierarchy > New Sub Scene > Empty Scene).
  2. Inside this SubScene, create an Empty GameObject and name it GPU_Animation_Renderer.
  3. Add the AnimatorRendererAuthoring component to it.
  4. In the Animators array of this component, click + and assign your newly baked DOTS Prefab.

๐Ÿ›‘ Warning: If you skip this step, your spawned entities will not animate, or they might disappear entirely! This component is responsible for generating the SceneAnimatorConfigData that links your entities to the GPU buffers.


Step 4: Spawning the Crowd (C# Script)

Now let's write a simple ECS script to spawn a massive grid of our characters.

Create a new C# script named CrowdSpawnerAuthoring.cs and paste the following code:

using SnivelerCode.GpuAnimation.Runtime.Components;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

namespace SnivelerCode.GpuAnimation.Tutorial
{
    // 1. The Component Data (No prefab field needed here!)
    public struct CrowdSpawnerData : IComponentData
    {
        public int GridSize;
        public float Spacing;
        public bool IsSpawned;
    }

    // 2. The Authoring Component
    public class CrowdSpawnerAuthoring : MonoBehaviour
    {
        public int GridSize = 100;         // 100x100 = 10,000 units!
        public float Spacing = 1.5f;

        class Baker : Baker<CrowdSpawnerAuthoring>
        {
            public override void Bake(CrowdSpawnerAuthoring authoring)
            {
                var entity = GetEntity(TransformUsageFlags.None);
                AddComponent(entity, new CrowdSpawnerData
                {
                    GridSize = authoring.GridSize,
                    Spacing = authoring.Spacing,
                    IsSpawned = false
                });
            }
        }
    }

    // 3. The Spawner System
    [UpdateInGroup(typeof(InitializationSystemGroup))]
    public partial struct CrowdSpawnerSystem : ISystem
    {
        public void OnUpdate(ref SystemState state)
        {
            // Ensure the GPU Animation registry exists and has prefabs
            if (!SystemAPI.HasSingleton<AnimatorPrefabBuffer>()) return;
            var prefabBuffer = SystemAPI.GetSingletonBuffer<AnimatorPrefabBuffer>();
            if (prefabBuffer.Length == 0) return;

            // Grab the first registered and fully baked prefab!
            Entity characterPrefab = prefabBuffer[0].Value;

            foreach (var (spawner, entity) in SystemAPI.Query<RefRW<CrowdSpawnerData>>().WithEntityAccess())
            {
                if (spawner.ValueRO.IsSpawned) continue;

                var ecb = new EntityCommandBuffer(Unity.Collections.Allocator.Temp);
                int size = spawner.ValueRO.GridSize;
                float spacing = spawner.ValueRO.Spacing;

                // Spawn a grid of units
                for (int x = 0; x < size; x++)
                {
                    for (int z = 0; z < size; z++)
                    {
                        Entity instance = ecb.Instantiate(characterPrefab);
                        
                        float3 position = new float3(x * spacing, 0, z * spacing);
                        ecb.SetComponent(instance, LocalTransform.FromPosition(position));
                    }
                }

                ecb.Playback(state.EntityManager);
                ecb.Dispose();

                // Mark as spawned
                spawner.ValueRW.IsSpawned = true;
            }
        }
    }
}

๐Ÿ’กNotice that our Spawner doesn't need a Prefab field! It automatically queries the AnimatorPrefabBuffer to grab the exact entity that was processed and uploaded to the GPU by your Scene Renderer.

Final Setup:

  1. Create another Empty GameObject in your SubScene.
  2. Attach the CrowdSpawnerAuthoring script to it.
  3. Drag your Baked DOTS Prefab into the Character Prefab slot.
  4. Set the Grid Size to 100 (which will spawn 10,000 units).

Step 5: Press Play! โ–ถ๏ธ

Hit the Play button in the Unity Editor.

You should instantly see a massive grid of 10,000 characters, all animating smoothly. Because the animation logic is handled entirely by the GPU and Burst-compiled jobs, your CPU frame time should remain incredibly low.

Congratulations! You have successfully integrated GPU Animation Entities PRO into your project.


๐ŸŽ›๏ธ 4. The Animator Baker Window

The Animator Baker is the heart of GPU Animation Entities PRO. Since the GPU needs pre-calculated data to animate thousands of units without CPU overhead, this window handles the heavy lifting: it plays through your animations, extracts the bone matrices, configures the Shader Graph materials, and generates a ready-to-use DOTS Prefab.

You can open the tool via the top menu: Window > Sniveler Code > Animator Baker.


๐Ÿ“ฆ The Source Prefab

To start, drag and drop a standard Unity GameObject from your scene or project into the Prefab Model field.

โš ๏ธ Requirement: The source GameObject must contain a SkinnedMeshRenderer. If it contains an Animator component with an assigned Controller, the Baker will automatically detect it. If it has an LODGroup, the Baker will import your LOD levels automatically.

Once a valid prefab is assigned, the window will unlock three configuration tabs: Animator, Lods, and Bones.


๐ŸŽฌ Tab 1: Animator Settings

This tab controls how your animations are baked and processed.


๐Ÿ“‰ Tab 2: LODs (Level of Detail)

Performance is everything. This tab allows you to configure how your mesh degrades at a distance to save GPU ALU instructions.


๐Ÿฆด Tab 3: Bones (Sockets Setup)

This tab is crucial if you plan to attach weapons, armor, or particle effects to your units later.

By default, GPU skinning happens entirely on the video card, meaning the CPU doesn't know where the character's hands or head are.
By adding a bone to the Bake List in this tab, the Baker will save that specific bone's exact position and rotation for every frame into the BlobAsset.

  1. Click Add the bone to the bake list.
  2. Select the desired bone from the dropdown (e.g., RightHand_Bone, Spine, Head).
  3. At runtime, our Zero-Latency Socket System will use this data to attach items perfectly. (We will cover this in detail in 6. Sockets & Attachments).

๐Ÿ’พ The Output: What Happens When You Click "Process"?

When you click the green Process button, the tool does the following:

  1. Creates a new folder in your project: Assets/SnivelerCode/GpuAnimation/Generated/[YourPrefabName].
  2. Bakes all animation matrices and root motion data into a AnimatorMatrices.asset.
  3. Generates optimized Shader Graph materials for each LOD.
  4. Creates a DOTS-ready Prefab in the Character subfolder.
  5. Generates a C# script (GeneratedParams.cs) containing static byte IDs for your animations and parameters, so you don't have to use strings in your code!

๐ŸŽจ 5. Shader Graph Integration (Custom Materials)

One of the most powerful features of GPU Animation Entities PRO is its native integration with Unity's Shader Graph.

Because our animation logic runs entirely in the Vertex Shader, you are completely free to customize the Fragment Shader. Want to add a dissolve effect to dying enemies? Need cel-shaded outlines? Want glowing emission maps for your sci-fi swarm? You can do it all without writing a single line of HLSL code.

There are two ways to create a custom animated material: The Easy Way (Templates) and The Manual Way (Existing Shaders).


๐ŸŸข Method 1: The Easy Way (Using Templates)

We provide pre-configured Shader Graph templates where the complex DOTS animation logic is already wired up for you. This is the recommended workflow.

  1. In your Project window, right-click in any folder.
  2. Navigate to Create > Shader Graph > Sniveler GPU Animation.
  3. Select either Lit Shader or Unlit Shader.
  4. Name your new shader (e.g., MySwarmDissolveShader).
  5. Double-click to open it in the Shader Graph editor.

What to do next:
Look at the graph. You will see the SnivelerAnimationNode already connected to the Vertex output block. Do not modify the Vertex connections.
Instead, focus on the Fragment block. You can drag in textures, add noise nodes, multiply colors, and connect them to Base Color, Emission, or Alpha just like you would in any normal Shader Graph!


๐ŸŸ  Method 2: The Manual Way (Upgrading Existing Shaders)

If you already have a massive, complex Shader Graph and want to add GPU animation to it, you can do so in just a few steps.

Step 1: Add the Animation Node

  1. Open your existing Shader Graph.
  2. Press Spacebar to open the search menu and type SnivelerAnimationNode. Add this SubGraph to your workspace.

Step 2: Create the DOTS Properties (๐Ÿšจ CRITICAL)

The GPU needs to know which animation frame to play. We pass this data from ECS to the shader using two specific properties.

  1. Open the Blackboard (left panel).
  2. Create a new Vector4 property and name it exactly: _SnivelerRenderFrames
  3. Create another Vector4 property and name it exactly: _SnivelerRenderFramesTarget
  4. CRITICAL STEP: Select each property, open the Graph Inspector (right panel), and check the box for Hybrid Rendered (DOTS Instancing).

๐Ÿ›‘ Warning: If you forget to check "Hybrid Rendered", the DOTS ECS system will not allocate memory for these properties. Your shader will receive zeros, and your meshes will be completely invisible!

Step 3: Wire the Inputs

Connect the following standard nodes into the left side of the SnivelerAnimationNode:

Step 4: Wire the Outputs

Connect the right side of the SnivelerAnimationNode directly into your Vertex Output Block:

Save your asset. Your custom shader is now fully compatible with GPU Animation Entities PRO!


๐Ÿง  Under the Hood: What about Keywords and LODs?

You might be wondering: "How do I enable Dual Quaternion Skinning (DQS) or configure LOD quality in my custom shader?"

You don't have to do anything!
We designed the system for a "Zero Setup" experience. The SnivelerAnimationNode internally contains all the necessary keywords (DQS, ANIM_STEP, BONE_LOD_1, etc.). When you use this node, your shader automatically inherits these variants.

When you bake your prefab, our C# Baker script will automatically enable or disable these keywords on your generated materials based on your LOD settings.


๐Ÿ”จ Applying Your Custom Shader

Once your Shader Graph is ready, you need to tell the Baker to use it:

  1. Open the Animator Baker window.
  2. Assign your source Prefab.
  3. Go to the Lods tab.
  4. Change the Shader dropdown from Sniveler Lit to Custom.
  5. A new object field will appear. Drag and drop your custom Shader Graph asset here.
  6. Click Process.

The Baker will now generate all LOD materials using your custom shader, perfectly configured for DOTS!


๐Ÿ”— 6. Sockets & Attachments (Weapons & VFX)

Since standard bones GameObjects do not exist in the scene at runtime when using GPU skinning, the classic transform.SetParent(bone) approach does not work. Our system solves this by calculating precise offset matrices, allowing you to bind items to bones both in a default state and dynamically at specific animation frames.

โš™๏ธ Animator Authoring Component

The starting point for working with sockets is the Animator Authoring component on your character.

Slots Configuration

Every slot in the Slots array represents a distinct logical attachment point (e.g., "Right Hand", "Back", "Head"). An array element holds a specific configuration asset โ€” the AttachmentProfileAsset.

๐Ÿ’ก Important: Offsets and sockets can only be edited in Prefab Mode. If you select a character directly in the scene, the inspector will display a warning and offer a blue OPEN PREFAB button.

๐ŸŽ›๏ธ Sockets Workflow Window

Clicking the green OPEN SOCKETS WORKFLOW button in the prefab inspector opens the main interface for configuring sockets. This is a professional dashboard divided into logical zones.

1. Animation Preview & Timeline (Top Section)

The top part of the window handles animation previews.

2. Sockets (Global List)

The left panel contains a list of all slots assigned to the character.

3. Socket Detail (Right Panel)

The right panel acts as the inspector for the selected socket. This is where the core attachment logic is set up.

๐Ÿ› ๏ธ Socket Configuration: Base vs. Overrides

The system's architecture relies on two key concepts: Base Configuration and Frame Overrides.

1. Base Configuration (Default State)

This is the default state of an item.
Example: A sword resting in its scabbard is bound to the Spine (back) bone by default.

2. Frame Overrides (Events)

Overrides are used when an item needs to change its parent bone during an animation.

Example: On frame 15 of an Attack animation, the character draws the sword from their back. We create an Override on frame 15 binding the sword to the Right_Hand bone.

How to add an Override:

  1. Choose the desired animation from the top dropdown.
  2. Scrub the timeline playhead to the exact frame you need.
  3. In the Add Override at Current Frame section, select the target bone (e.g., the hand).
  4. Click Map to Bone.
  5. A marker will appear on the timeline, and an override card will be added to the list below.

Managing Overrides:

๐ŸŽฎ Editing Offsets in the Scene View

To ensure an item fits perfectly in a hand or on a back, it needs to be positioned. The system allows you to do this visually directly in the Scene View using standard Unity transform tools.

  1. In the Sockets Workflow window, click Edit Base Offset (or Select on an override).
  2. Switch to the Scene View.
  3. A centered UI banner will appear at the top, indicating exactly what you are editing (Base Default or a specific Override index).
  4. A "Ghost Mesh" of your prefab will appear in the scene.
  5. Use Unity's standard Move Tool (W) and Rotate Tool (E) to position the item perfectly relative to the bone. Changes are saved automatically!

๐Ÿงฒ Transform Tools

At the very bottom of the right panel, you'll find utilities for managing offset matrices.

Sometimes you need an item to pass from one hand to another while maintaining its exact local orientation (so you don't have to manually rotate the sword hilt again).

๐Ÿš€ Quick Start: Drawing a Sword

  1. Open your character in Prefab Mode. Click OPEN SOCKETS WORKFLOW in the inspector.
  2. In the left panel, click + to create a new slot.
  3. In the right panel, assign your sword prefab to the Attachment Prefab field.
  4. In the Base Configuration block, select a back bone (e.g., Spine) and click Assign Bone.
  5. Click Edit Base Offset, switch to the Scene View, and position the sword so it rests nicely on the back.
  6. Select an attack animation (e.g., Attack_01) from the top dropdown.
  7. Scrub the timeline to the frame where the character's hand grabs the hilt (e.g., Frame 20).
  8. In the Frame Overrides block, select the Right_Hand bone and click Map to Bone.
  9. Click Select on the newly created override card, go to the Scene View, and align the sword perfectly inside the character's palm.
  10. Done! Now, when the Attack_01 animation plays, the sword will instantly snap from the back to the hand exactly on frame 20.

๐Ÿ’ป Spawning Attachments via Code (DOTS / ECS)

While the Auto Instantiate checkbox in the Animator Authoring component is great for static setups (like an NPC who always holds a spear), player characters usually require dynamic equipment. If a player equips a sword from their inventory, you need to spawn and attach that sword via code.

Our GPU Animation system provides a highly optimized, Burst-compatible ECS workflow for instantiating and binding attachments at runtime.

The Three Pillars of Code Attachment

To successfully bind an instantiated entity (e.g., a weapon) to a character so that it follows the GPU animation offsets, you must add three core components to the weapon entity:

  1. Parent: Points to the main character entity. This establishes the standard Unity Transform hierarchy.
  2. AnimatorAttachData: Contains the SlotID (e.g., 0). This tells the system which socket configuration to read from the animation data.
  3. BlobAttachData: Passes a reference to the character's baked animation blob data, giving the weapon access to the base matrices and frame overrides.

Retrieving the Attachment Prefab

In a DOTS environment, prefabs are converted into Entities. Our system automatically caches attachment prefabs defined in the Authoring component into a global SceneAttachmentBuffer.

To find the correct weapon prefab for a specific character, the system uses a Hash Match based on the character's animation data (MatricesHash) and the Target Slot ID.

Example Workflow (Burst System)

Below is a production-ready example of an ISystem that spawns a weapon and attaches it to a character dynamically.

In this example, the system looks for characters with a Demo3SpawnerTag, spawns the prefab assigned to Slot 0, binds it to the character, and removes the spawner tag.

using SnivelerCode.GpuAnimation.Runtime.Components;
using SnivelerCode.GpuAnimation.Runtime.Utils;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;

namespace SnivelerCode.GpuAnimation.DemoZone
{
    [UpdateInGroup(typeof(PresentationSystemGroup))]
    public partial struct DynamicEquipmentSystem : ISystem
    {
        private EntityQuery _query;

        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            _query = new EntityQueryBuilder(Allocator.Temp)
                .WithAll<Demo3SpawnerTag, LocalTransform, BlobAnimatorData>()
                .Build(ref state);

            state.RequireForUpdate(_query);
            // Require the global buffer containing our registered attachment prefabs
            state.RequireForUpdate<SceneAttachmentBuffer>(); 
            state.RequireForUpdate<BeginInitializationEntityCommandBufferSystem.Singleton>();
        }

        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            var ecbSingleton = SystemAPI.GetSingleton<BeginInitializationEntityCommandBufferSystem.Singleton>();
            var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
            var sceneAttachments = SystemAPI.GetSingletonBuffer<SceneAttachmentBuffer>();

            state.Dependency = new EquipWeaponJob
            {
                CommandBuffer = ecb.AsParallelWriter(),
                SceneAttachments = sceneAttachments
            }.ScheduleParallel(_query, state.Dependency);
        }

        [BurstCompile]
        public partial struct EquipWeaponJob : IJobEntity
        {
            public EntityCommandBuffer.ParallelWriter CommandBuffer;
            [ReadOnly] public DynamicBuffer<SceneAttachmentBuffer> SceneAttachments;

            private void Execute([EntityIndexInQuery] int sortKey, Entity characterEntity, in BlobAnimatorData blob)
            {
                // 1. Remove the tag so we only process this character once
                CommandBuffer.RemoveComponent<Demo3SpawnerTag>(sortKey, characterEntity);
                
                // 2. Get the unique Hash of the character's animation data
                ulong hash = blob.Value.Value.MatricesHash;
                
                // 3. Look up the prefab assigned to Slot 0 for this specific character hash
                if (SceneAttachments.TryGetSlot(hash, slotIndex: 0, out Entity weaponPrefab))
                {
                    if (weaponPrefab != Entity.Null)
                    {
                        // 4. Instantiate the weapon entity
                        var weaponEntity = CommandBuffer.Instantiate(sortKey, weaponPrefab);
                        
                        // 5. The Core Binding Logic: Link the weapon to the character
                        CommandBuffer.AddComponent(sortKey, weaponEntity, new Parent { Value = characterEntity });
                        CommandBuffer.AddComponent(sortKey, weaponEntity, new AnimatorAttachData { SlotID = 0 });
                        CommandBuffer.AddComponent(sortKey, weaponEntity, new BlobAttachData { Value = blob.Value });
                        
                        return;
                    }
                }

                // Fallback logging if the slot was empty or prefab was not found
                AnimatorLogger.BurstLog()
                    .Append("Failed to equip: Slot 0 not found in prefabs.")
                    .LogWarning();
            }
        }
    }
}

Code Breakdown:

  1. blob.Value.Value.MatricesHash: Every baked character animation asset generates a unique hash. We use this hash to query the SceneAttachmentBuffer.
  2. SceneAttachments.TryGetSlot(hash, 0, out Entity weaponPrefab): This safely retrieves the Entity Prefab that you configured in the Sockets Workflow window for Slot 0.
  3. EntityCommandBuffer (ECB): Because we are modifying the hierarchy (adding a Parent component) and instantiating entities inside a Burst-compiled job, all structural changes are recorded into the ECB and executed safely at the end of the frame.

๐Ÿ’ก Best Practice: When creating an inventory system, you can store various Entity Prefabs in your own components. You don't have to use SceneAttachmentBuffer. As long as you instantiate a prefab and add the Parent, AnimatorAttachData, and BlobAttachData components to it, the GPU animation system will correctly align it to the character's hand based on the data baked into that Slot ID!


๐Ÿ’ป 7. Runtime Control (C# ECS API)

While the GPU handles the heavy lifting of rendering the animations, your game logic (running on the CPU via ECS Systems) needs to tell the characters what to do. Should they run, attack, or die?

GPU Animation Entities PRO provides a clean, allocation-free C# API to control your units using standard DOTS IComponentData and DynamicBuffer components.


๐Ÿ—‚๏ธ The Generated Parameters Class

In traditional Unity, you trigger animations using strings (e.g., animator.SetFloat("Speed", 1.0f)). In DOTS, strings are slow and cause memory allocations.

To solve this, whenever you bake a prefab, our Baker automatically generates a static C# script containing byte IDs for all your animations and parameters.

You can find this file in your project at:
Assets/SnivelerCode/GpuAnimation/Generated/[YourPrefabName]/Scripts/GeneratedParams.cs

It looks something like this:

namespace SnivelerCode.GpuAnimation.Generated
{
    // Animation State IDs
    public static class AnimatorGuardCastle
    {
        public static readonly byte Idle = 0;
        public static readonly byte Run = 1;
        public static readonly byte Attack = 2;
    }

    // Parameter IDs
    public static partial class AnimatorParams
    {
        public static class GuardCastle
        {
            public static readonly byte Speed = 0;
            public static readonly byte IsDead = 1;
            public static readonly byte AttackTrigger = 2;
        }
    }
}

You will use these static IDs in your Burst-compiled jobs to control the animator with zero overhead.


๐ŸŽฌ Method 1: Direct Animation Control (Crossfading)

If you want to force a character to play a specific animation immediately (ignoring the Animator Controller's transition arrows), you modify the AnimatorData component.

We provide a convenient .Play() extension method that automatically handles smooth crossfading between the current animation and the new one.

using SnivelerCode.GpuAnimation.Generated;
using SnivelerCode.GpuAnimation.Runtime.Components;
using SnivelerCode.GpuAnimation.Runtime.Utils;
using Unity.Burst;
using Unity.Entities;

[BurstCompile]
public partial struct PlayAnimationJob : IJobEntity
{
    private void Execute(ref AnimatorData animator)
    {
        // Force the character to play the Attack animation
        // The second parameter is the crossfade duration in seconds (default is 0.15f)
        animator.Play(AnimatorGuardCastle.Attack, 0.2f);
    }
}

๐Ÿ’ก Tip: Direct control is perfect for combat abilities, hit reactions, or death states where you want immediate responsiveness without relying on complex Animator transition graphs.


๐ŸŽ›๏ธ Method 2: Parameter Control (Transitions & Logic)

If you prefer to use the logic you built in Unity's Animator window (e.g., transitioning from Idle to Run when the Speed parameter exceeds 0.1), you will interact with the AnimatorParameterData buffer.

We provide a fluent builder API to make setting parameters clean and readable inside your jobs.

using SnivelerCode.GpuAnimation.Generated;
using SnivelerCode.GpuAnimation.Runtime.Components;
using SnivelerCode.GpuAnimation.Runtime.Utils;
using Unity.Burst;
using Unity.Entities;[BurstCompile]
public partial struct UpdateMovementJob : IJobEntity
{
    public float DeltaTime;

    private void Execute(ref DynamicBuffer<AnimatorParameterData> paramBuffer)
    {
        float currentSpeed = 1.5f; // Calculated from your movement logic

        // Set the "Speed" parameter to 1.5f
        AnimatorParams.GuardCastle.Speed.Value(currentSpeed).Apply(paramBuffer);
    }
}

The AnimatorProcessSystem will read this buffer every frame. If the Speed value satisfies a transition condition you set up in the Unity Animator (e.g., Speed > 0.1), the system will automatically trigger the transition and handle the crossfade!


โšก Handling Triggers

Triggers in Unity's Animator are special boolean parameters that automatically reset to false once they are consumed by a transition.

Our DOTS system perfectly replicates this behavior. If a parameter was defined as a Trigger in your original Animator Controller, you simply set its value to 1.0f to fire it.

// Fire the Attack Trigger
AnimatorParams.GuardCastle.AttackTrigger.Value(1.0f).Apply(paramBuffer);

Once the AnimatorProcessSystem detects this and starts the transition to the Attack state, it will automatically reset the value back to 0.0f in the buffer, ensuring the animation doesn't loop endlessly.


โฑ๏ธ Manual Time Control

Sometimes you need absolute control over an animation's playback time (for example, syncing a door opening to a specific puzzle state, or freezing an enemy in ice).

You can take manual control by setting the ManualControl flag on the AnimatorData component.

private void Execute(ref AnimatorData animator)
{
    // 1. Enable manual control (stops automatic time progression)
    animator.ManualControl = true;
    
    // 2. Set the exact time in seconds
    animator.Time = 1.25f; 
}

Note: When ManualControl is true, automatic transitions and parameter logic are ignored for that specific entity.


๐ŸŽ๏ธ 8. LODs & Performance Optimization

Rendering 10,000 animated units at 60+ FPS requires more than just moving calculations to the GPU. If every single vertex of those 10,000 units calculates 4-bone influences and interpolates between frames, you will eventually hit the GPU's ALU (Arithmetic Logic Unit) and memory bandwidth limits.

GPU Animation Entities PRO includes a highly aggressive, automated optimization pipeline. It scales down both GPU rendering cost (via LODs) and CPU logic cost (via Tick-Rate scaling) based on camera distance.


๐Ÿ“‰ 1. Visual Degradation (The LOD Tab)

Our system perfectly integrates with Unity's standard LODGroup component. When you drag a prefab with an LODGroup into the Animator Baker, it automatically imports your LOD levels into the Lods tab.

For each LOD level, you can configure how the shader degrades to save performance:

๐Ÿฆด Skinning Quality (Bone Influences)

Standard Unity animation uses up to 4 bones per vertex. We allow you to reduce this per LOD:

๐ŸŽž๏ธ Interpolation (Stepped Animation)

By default, our shader reads two animation frames from the GraphicsBuffer and interpolates (Lerps) between them to create buttery-smooth motion.

๐ŸŒ‘ The Shadow Caster Trick (Zero Setup)

Shadow passes are notoriously expensive. You might be wondering how to optimize them.
You don't have to.
Under the hood, our SnivelerSGWrapper.hlsl automatically detects if Unity is rendering a ShadowCaster or DepthOnly pass. If it is, the shader forces 1-Bone Skinning and disables Interpolation, regardless of your LOD settings. You get ultra-cheap shadows completely for free.


โฑ๏ธ 2. Logic Degradation (Tick-Rate Optimization)

Even though the GPU handles the vertices, the CPU still has to calculate the current animation time, check transition conditions, and update the _SnivelerRenderFrames data for the shader.

To prevent the CPU from bottlenecking when you have 10,000+ units, we use Distance-Based Tick-Rate Optimization.

You configure this on the AnimatorRendererAuthoring component (the script you placed in your SubScene to upload the prefabs to the GPU).

Because the GPU continues to render the last known state, the units don't disappearโ€”they just update their animation frames slightly less frequently. This drastically reduces the execution time of the Burst-compiled jobs.


๐Ÿ‘๏ธ 3. Automatic Frustum Culling

You do not need to write custom culling logic for your animations.

The AnimatorProcessSystem automatically reads the AnimatorCameraData (updated by the Main Camera) and checks it against the WorldRenderBounds of your entities.

If an entity is completely off-screen (outside the camera frustum):

  1. The system flags it as invisible.
  2. It skips updating the GraphicsBuffer indices for that entity.
  3. It skips calculating complex transition blending.
  4. If the entity has Root Motion, it snaps the root motion delta to the final frame instead of calculating frame-by-frame interpolation.

This ensures that if you have 10,000 units in the level, but only 2,000 are on screen, your CPU is only doing the heavy lifting for those 2,000.


๐Ÿšถโ€โ™‚๏ธ 9. Root Motion

In traditional game development, Root Motion allows the animation itself to drive the character's movement in the world. For example, if a character performs a dodge roll or a heavy sword swing that steps forward, the animation moves the actual GameObject, preventing foot-sliding and ensuring realistic weight.

Implementing Root Motion in GPU-driven crowds is notoriously difficult because the GPU only moves vertices visuallyโ€”it doesn't move the actual Entity's LocalTransform.

GPU Animation Entities PRO solves this by extracting the root motion data during the baking process and evaluating it synchronously on the CPU via Burst-compiled jobs.

Here is how to set it up and use it.


๐Ÿ› ๏ธ Step 1: Baking Root Motion

To enable Root Motion, you must tell the Baker to extract the movement data from your Animator Controller.

  1. Open the Animator Baker window.
  2. Assign your source Prefab.
  3. In the Animator tab, check the box for Apply Root Motion.
  4. Click Process.

What happens under the hood?
The Baker evaluates your animation frame-by-frame, extracts the world-space movement of the root node, and saves it as an array of RigidTransform data inside the BlobAsset.


โš™๏ธ Step 2: How It Works at Runtime

When you spawn your baked DOTS Prefab, it automatically comes with two specific components:

Every frame, the AnimatorRootMotionSystem runs on the CPU. It looks at your current animation, your current frame, and your transition weight. It then calculates the exact difference (delta) in position and rotation since the last frame and writes it to the AnimatorRootMotionDelta component.

โœจ Pro-Tip: Transition Blending
If your character is crossfading between two animations (e.g., transitioning from a Walk to a Run), the system automatically calculates the root motion delta for both animations and blends them together based on the transition weight!


๐Ÿƒ Step 3: Applying the Movement (Default Behavior)

By default, you do not need to write any code to make Root Motion work.

The asset includes a built-in system called AnimatorRootMotionApplierSystem. This system runs right before the TransformSystemGroup. It reads the AnimatorRootMotionDelta and adds it directly to your Entity's LocalTransform.

If your game does not use a complex physics engine and you just want your units to move exactly as the animator intended, you are already done!


๐Ÿง  Step 4: Custom Physics & Character Controllers (Advanced)

If you are building a game with Unity Physics, Havok Physics, or a custom Kinematic Character Controller, directly modifying the LocalTransform is usually a bad idea. It can cause characters to clip through walls or ignore collisions.

Instead, you want to feed the animation's movement delta into your physics velocity.

How to do this:

  1. Disable the Default Applier:
    You need to stop our default system from moving the LocalTransform. You can do this by adding a system group attribute to your custom code, or simply by removing the AnimatorRootMotionApplierSystem from your world initialization.
  2. Read the Delta in your Custom System:
    Write your own system that reads the AnimatorRootMotionDelta and applies it to your physics components (like PhysicsVelocity).

Here is a quick example of how you can read the delta in your own Burst-compiled job:

using SnivelerCode.GpuAnimation.Runtime.Components;
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics; // Assuming you are using Unity Physics

[BurstCompile]
[UpdateBefore(typeof(Unity.Physics.Systems.PhysicsSystemGroup))]
public partial struct CustomPhysicsRootMotionJob : IJobEntity
{
    public float DeltaTime;

    private void Execute(ref PhysicsVelocity velocity, ref AnimatorRootMotionDelta rootDelta)
    {
        // 1. Convert the translation delta into a velocity (meters per second)
        float3 animationVelocity = rootDelta.Translation / DeltaTime;

        // 2. Apply it to the physics body (preserving gravity on the Y axis)
        velocity.Linear = new float3(animationVelocity.x, velocity.Linear.y, animationVelocity.z);

        // 3. Clear the delta so it isn't applied twice!
        rootDelta.Translation = float3.zero;
        rootDelta.Rotation = quaternion.identity;
    }
}

By taking control of the AnimatorRootMotionDelta, you get the best of both worlds: AAA-quality animation-driven movement, perfectly integrated with your custom DOTS physics logic.




๐Ÿš‘ 10. Troubleshooting & FAQ

Even with automated baking pipelines, working with DOTS and GPU skinning can sometimes lead to unexpected results. This page covers the most common issues developers face and how to fix them instantly.


๐Ÿ‘ป 1. My characters are completely invisible!

This is the most common issue when setting up custom materials. If your meshes are not rendering at all, it means the vertices are collapsing to (0,0,0).

Checklist to fix:

  1. URP Settings: Open your active URP Asset in the Inspector. Ensure SRP Batcher and DOTS Instancing are checked.
  2. Shader Graph Properties: If you are using a custom Shader Graph, open it and check the Blackboard. Ensure you have two Vector4 properties named exactly _SnivelerRenderFrames and _SnivelerRenderFramesTarget.
  3. Hybrid Rendered Checkbox: Select those two properties in the Shader Graph and look at the Graph Inspector. The Hybrid Rendered (DOTS Instancing) checkbox MUST be enabled.
  4. Scene Renderer: Ensure you have an AnimatorRendererAuthoring component in your SubScene, and your baked DOTS Prefab is assigned to its array.

๐Ÿฆ” 2. My characters look like spikes / are horribly deformed!

This happens when the C# script sends one type of mathematical data to the GPU, but the shader tries to read it as another type.

Checklist to fix:

  1. DQS Mismatch: If you baked your prefab with Use Dual Quaternion enabled, your material must have the _SNIVELER_DQS keyword enabled.
  2. How to fix: If you are using our templates Sniveler_Lit_Template, the Baker handles this automatically. If you are using a custom Shader Graph, ensure you added the _SNIVELER_DQS Boolean Keyword to your Blackboard (even if it's hidden/not exposed).
  3. Re-bake: Sometimes simply clicking Process again in the Animator Baker window resolves keyword mismatches on generated materials.

โš”๏ธ 3. My weapons/attachments are spawning at (0,0,0) on the ground!

The Socket system relies on the AttachmentData component to link the weapon to the character.

Checklist to fix:

  1. Missing Component: When you spawn the weapon via EntityCommandBuffer.Instantiate, you must manually add the AttachmentData component to it.
  2. Missing Link: Ensure you set the CharacterEntity field inside AttachmentData to the Entity ID of the character holding the weapon.
  3. Missing Slot ID: Ensure the SlotID matches the index of the weapon in the character's AnimatorSlotsBuffer.

Example of correct spawning:

ecb.AddComponent(weaponEntity, new AttachmentData 
{ 
    CharacterEntity = characterEntity, 
    SlotID = 0 
});

๐Ÿ›‘ 4. The Baker throws an error: "No shader selected" or "Shader does not use SnivelerAnimationNode"

The Baker validates your custom shaders before generating prefabs to prevent broken builds.

Checklist to fix:

  1. Missing Properties: Your custom Shader Graph is missing the _SnivelerRenderFrames and _SnivelerRenderFramesTarget properties. Add them.
  2. Missing Node: You forgot to add the SnivelerAnimationNode SubGraph to your custom shader, or you didn't connect it to the Vertex output block.
  3. Use Templates: If you are struggling, delete your custom shader, right-click in the Project window, and select Create > Shader Graph > Sniveler GPU Animation > Lit Shader. Use this template as your starting point.

๐Ÿƒ 5. My character is animating, but Root Motion isn't moving them!

Checklist to fix:

  1. Baker Setting: Open the Animator Baker, select your source prefab, and ensure Apply Root Motion is checked in the Animator tab. Re-bake the prefab.
  2. Animator Controller: Ensure the original animations in your Unity Animator Controller actually contain root motion data (the red trajectory line in the Animation preview window).
  3. Custom Physics: If you are using a custom physics system, ensure you haven't accidentally deleted or overridden the LocalTransform after the AnimatorRootMotionApplierSystem runs. (See the Root Motion page for how to integrate with custom physics).

๐Ÿ“‰ 6. Performance drops when I spawn 20,000+ units

While the GPU can handle massive numbers, the CPU still needs to process the logic.

Checklist to fix:

  1. Burst Compilation: Ensure Jobs > Burst > Enable Compilation is checked in the top menu. Running DOTS without Burst will destroy your framerate.
  2. Tick-Rate Optimization: Select your AnimatorRendererAuthoring component in the SubScene. Reduce the Half Tick Distance and Quarter Tick Distance values. This forces distant units to update their logic at 30fps or 15fps, drastically reducing CPU load.
  3. LOD Aggressiveness: Open the Animator Baker and check your LOD settings. Ensure your distant LODs (e.g., LOD 2) are set to 1 Bone Quality and have Interpolation disabled. Re-bake.

๐Ÿ’ฌ Still need help?

If you've checked all the above and are still experiencing issues, we are here to help!
Please reach out to us with a screenshot of your issue and any console errors:

๐Ÿ“ง Email: sniveler.code@gmail.com
๐ŸŒ GitHub: https://github.com/sniveler-code


โš™๏ธMigration Guide: v1.1.0 to v1.3.0

This guide will walk you through the necessary steps to upgrade your project to the latest version of the GPU Animation Pro package. This update includes significant architectural improvements that increase stability, performance, and long-term scalability.

While many changes are internal and automatic, a few key updates require manual steps to ensure a smooth transition.

Summary of Key Benefits


Required Migration Steps

Please follow these steps in order to ensure your project is fully compatible with the new version.

1. (CRITICAL) Backup Your Project

WARNING Before proceeding with any package update, please ensure you have a complete and reliable backup of your project.

2. Update the Package

Update the com.snivelercode.gpu-animation-pro package through the Unity Package Manager. After updating, you will likely see compilation errors. The following steps will resolve them.

3. Rename Core System Component

The main system component has been renamed for better clarity. This is a breaking change that will likely cause compilation errors until it is fixed.

Action Required:

4. (IMPORTANT) Update AnimatorRendererAuthoring Asset References

IMPORTANT This is a significant architectural change and the most critical manual step. The AnimatorRendererAuthoring component no longer references other AnimatorAuthoring components. It now directly references the animation data assets (AnimatorMatricesAsset).

Action Required:

  1. Find all GameObjects in your scenes and prefabs that have an AnimatorRendererAuthoring component.
  2. The list of animations on this component will now be empty or have missing references.
  3. Lock the Inspector window showing the AnimatorRendererAuthoring component.
  4. From the Project window, find your AnimatorMatricesAsset files (the ScriptableObjects containing the baked animation data).
  5. Drag these asset files directly into the animation list on the AnimatorRendererAuthoring component.

Why this change was made: This decouples the rendering system from the source GameObjects, making it more robust. It's a key part of the new Unique ID system that prevents references from breaking when you rename or move things.

5. Force Re-import of Animation Assets

The new version uses a robust Unique ID system for animation data assets. To ensure these IDs are generated and assigned correctly after the update, you must force a re-import.

Action Required:

This process will trigger the new AnimatorMatricesAssetProcessor, which automatically assigns the persistent unique IDs to your assets, ensuring the system works correctly.

6. Review Attachments Configuration

The Attachments system has been internally refactored. While we have aimed to maintain compatibility, it is wise to double-check your setups.

Action Required:


Changes That Are Automatic (No Action Required)

The following improvements are included in this update and will work automatically after the package is installed:


Post-Upgrade Checklist

After performing the migration steps, please verify the following:

  1. Ensure there are no compilation errors in the console.
  2. Enter Play Mode in your main scenes.
  3. Confirm that animations are playing correctly on your characters.
  4. Verify that any dynamic features, like attachments, are working as expected.

By following this guide, you will successfully transition to the latest, most powerful version of GPU Animation Pro.


Edit on GitHub v1.0.0