Skip to main content

Working without Animator

AnimatorControllerSystem, baked from Unity's Animator component, orchestrates a high level of animation processing computation. In a nutshell, it defines what animation needs to be played at a given point in time and calculates animation clip weight for all relevant animations. Processing flow can be controlled by using animator parameters, but this is roughly the only way to configure animator behavior. Every project has a distinct set of animator controller requirements, and Rukhanka can work with custom user-written controller systems.

Authoring Preparation

Rig Definition Authoring should be switched into User Defined rig config source mode. Normally Rukhanka reads all required rig information from Unity's Avatar component. Animated model Avatar needs to be specified explicitly in user mode.

Rig Definition User Mode

Now Animator component can be removed from authoring GameObject.

Scripted Animator

Without authoring Animator there is no AnimatorControllerLayerComponent buffer created for animated entity. Therefore AnimatorControllerSystem will no longer orchestrate animation flow for this entity. Now it is your, as a user of Rukhanka, task to command which animation should be played.

Components and Baking

Assume that we want to play a single animation in a loop. Let's define the required authoring and ECS components:

using Unity.Entities;
using UnityEngine;

// Authoring UnityEngine.Component
public class MyAnimatorAuthoring: MonoBehaviour
{
// Clip to play
public AnimationClip clip;
}

// Runtime ECS component
public struct MyAnimatorComponent: IComponentData
{
// Current animation time that will be advanced by custom controller system
public float normalizedAnimationTime;
// Animation clip blob that Rukhanka bakes from authoring UnityEngine.AnimationClip
public BlobAssetReference<Rukhanka.AnimationClipBlob> clipBlob;
}

The baker for MyAnimatorComponent should instruct Rukhanka to bake the given animation clip. Result BlobAssetReference needs to be stored in MyAnimatorComponent.clipBlob field.

//  Baker for the MyAnimatorAuthoring
class MyAnimatorBaker: Baker<MyAnimatorAuthoring>
{
public override void Bake(MyAnimatorAuthoring authoring)
{
// Avatar is needed for baking purposes
var rigDef = GetComponent<Rukhanka.Hybrid.RigDefinitionAuthoring>();
var avatar = rigDef.GetAvatar();

// Instantiate AnimationClipBaker class and bake required animations
var animationBaker = new Rukhanka.Hybrid.AnimationClipBaker();
var bakedAnimations = animationBaker.BakeAnimations(this, new [] {authoring.clip}, avatar, authoring.gameObject);

// Baked animation should be stored in the animation blob database for the ability to be queried in runtime using animation hash
// NewBlobAssetDatabaseRecord buffer serves this purpose
var entity = GetEntity(authoring, TransformUsageFlags.None);
var newBlobsBuffer = AddBuffer<Rukhanka.NewBlobAssetDatabaseRecord<Rukhanka.AnimationClipBlob>>(entity);
var animationBlobAssetReference = bakedAnimations[0];
if (animationBlobAssetReference.IsCreated)
{
var newAnimationBlob = new Rukhanka.NewBlobAssetDatabaseRecord<Rukhanka.AnimationClipBlob>()
{
hash = animationBlobAssetReference.Value.hash,
value = animationBlobAssetReference
};
newBlobsBuffer.Add(newAnimationBlob);
}

// Construct MyAnimatorComponent and assign baked animation clip blob asset reference to it
var myAnimator = new MyAnimatorComponent()
{
clipBlob = animationBlobAssetReference,
normalizedAnimationTime = 0
};
AddComponent(entity, myAnimator);
}
}

Animator Controller System

A simple scripted controller system needs to perform two tasks:

  • Increase MyAnimatorComponent.normalizedAnimationTime value according to time flow.
  • Update Rukhanka.AnimatioToProcessComponent buffer to instruct Rukhanka to play specific animation at a given time.
using Unity.Burst;
using Unity.Entities;

[UpdateBefore(typeof(Rukhanka.RukhankaAnimationSystemGroup))]
partial struct MyAnimatorSystem: ISystem
{
[BurstCompile]
partial struct MyAnimatorControllerJob: IJobEntity
{
public float deltaTime;
void Execute(ref MyAnimatorComponent myAnimator, ref DynamicBuffer<Rukhanka.AnimationToProcessComponent> animationToProcessBuffer)
{
// Increase animation play time by deltaTime. Take into account animation length in seconds
myAnimator.normalizedAnimationTime += deltaTime / myAnimator.clipBlob.Value.length;
// Clear all previous frame animations
Rukhanka.ScriptedAnimator.ResetAnimationState(ref animationToProcessBuffer);
// Play animation at a given time
Rukhanka.ScriptedAnimator.PlayAnimation(ref animationToProcessBuffer, myAnimator.clipBlob, myAnimator.normalizedAnimationTime);
}
}

[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
var myAnimatorControllerJob = new MyAnimatorControllerJob()
{
deltaTime = SystemAPI.Time.DeltaTime
};
myAnimatorControllerJob.ScheduleParallel();
}
}

This is all that is needed to play animation with Rukhanka. ScriptedAnimator class provides helper functions to play and blend animations.

Mixed Controller Mode

Often it is suitable to define necessary states using the Macanim animator design window, but control state transitions manually from own code. With Rukhanka you can use ScriptedAnimator.PlayAnimatorState function to play motion defined in the state (single animation, blend tree, or event blend tree of blend trees). In this mode, you must keep authoring Animator component with a given controller on the animated object.

warning

Do not forget to disable FillAnimationsFromControllerSystem and AnimatorControllerSystem systems because your controller system will be either overridden or override the results of default Rukhanka's controller systems.

Blob Database

Sometimes, elsewhere baked animation clip blob need to be runtime accessed. To serve this purpose BlobDatabaseSingleton was introduced. Store only animation hash during the baking stage, and query animation hash map during runtime:

//  Baker
AnimationClip authoringClip = ...
Avatar authoringAvatar = ...
var animationHash = Rukhanka.Hybrid.BakingUtils.ComputeAnimationHash(authoringClip, authoringAvatar);

...

// SystemBase or ISystem
if (SystemAPI.TryGetSingleton<Rukhanka.BlobDatabaseSingleton>(out var blobDBSingleton))
{
blobDBSingleton.animations.TryGetValue(animationHash, out var animationClipBlobAssetReference);
}
info

There is a handy authoring component named AnimationAssetSetAuthoring that can be used to simplify animation clip and avatar mask blob assets baking.

Animation Asset Set Authoring