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.
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.
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);
}
There is a handy authoring component named AnimationAssetSetAuthoring
that can be used to simplify animation clip and avatar mask blob assets baking.