A procedural animation feature consists of 2 entities:
FPSAnimatorLayerSettings: a Scriptable Object that contains all the data.
IAnimationLayerJob: a struct that executes custom animation logic.
Tip: the framework uses the Animation Job System, which ensures solid performance even with a large number of characters.
If you open up the source code of the framework, you will notice that every layer has its own folder with 2 .cs files:
FPSAnimatorLayerSettings class is responsible not just for containing the data, but for instantiating the IAnimationLayerJob as well:
AdditiveLayerSettings.cs
public class AdditiveLayerSettings : WeaponLayerSettings
{
...
public override IAnimationLayerJob CreateAnimationJob()
{
return new AdditiveLayerJob();
}
}
CreateAnimationJob() method must return a new instance of the desired IAnimationLayerJob-type. When you link a new Animator Profile, the system iterates over all animation features (Layer Settings) and then invokes the CreateAnimationJob() method to create the Layer State.
Example
First, create a new FPSAnimatorLayerSettings-derived class for data, and IAnimationLayerJob-implemented struct:
YourFeatureLayerSettings.cs
public class YourFeatureLayerSettings : FPSAnimatorLayerSettings
{
//Define your settings here, including the KRigElements.
public KRigElement myRigElement;
public override IAnimationLayerJob CreateAnimationJob()
{
return new YourAnimationLayerJob();
}
#if UNITY_EDITOR
public override void OnRigUpdated()
{
base.OnRigUpdated();
// (!) Always update KRigElements here.
// Called when a rig asset is updated/changed.
UpdateRigElement(ref myRigElement);
}
#endif
}
YourAnimationLayerJob.cs
public struct YourAnimationLayerJob
{
private YourFeatureLayerSettings _settings;
private LayerJobData _jobData;
private TransformStreamHandle _yourBoneHandle;
// Use this method for custom animation logic.
public void ProcessAnimation(AnimationStream stream)
{
}
// Use this for modifying root motion.
public void ProcessRootMotion(AnimationStream stream)
{
}
// This method is called when a new profile is linked.
public void Initialize(LayerJobData newJobData, FPSAnimatorLayerSettings settings)
{
_settings = (AdditiveLayerSettings) settings;
_jobData = newJobData;
Transform bone = newJobData.rigComponent.GetRigTransform(settings.myRigElement);
_yourBoneHandle = newJobData.animator.BindStreamTransform(bone);
}
public AnimationScriptPlayable CreatePlayable(PlayableGraph graph)
{
return AnimationScriptPlayable.Create(graph, this);
}
// Make sure to return a reference to the active asset.
// It will be used by the FPSBoneController to compute a layer weight.
public FPSAnimatorLayerSettings GetSettingAsset()
{
return _settings;
}
// Use this method to update job data when a layer is linked.
public void OnLayerLinked(FPSAnimatorLayerSettings newSettings)
{
}
// Use this method when a new weapon or item is equipped.
public void UpdateEntity(FPSAnimatorEntity newEntity)
{
}
// Use this method before the main update to gather input data.
public void OnPreGameThreadUpdate()
{
}
// Use this method to update playable data and gather game thread data.
public void UpdatePlayableJobData(AnimationScriptPlayable playable, float weight)
{
_jobData.weight = weight;
playable.SetJobData(this);
}
// Use this method when a finalized pose is required.
public void LateUpdate()
{
}
// Use this method to dispose data manually.
public void Destroy()
{
}
}
In the next chapter, we will learn how to work with the demo project.