-
Notifications
You must be signed in to change notification settings - Fork 50
1.3 Playing clips through code
In this section, you'll learn how to switch between animations through code.
Open the scene at All Samples/1 - Basics/3 - Play Clips Through Code/DMotion - Basics - Play Clips Through Code.unity
and hit play. If you hit 1
you'll see the robot play a walking animation, and if you hit 2
it will blend to a running animation (and vice-versa).
The basic setup should be familiar to you at this point: There are 2 AnimationClipAssets
with each of the animations we want to play, and we connect then to the entity via a custom authoring component that we will write below.
A standard workflow to connect GameObjects and Entities in DOTS 0.51 and below is the Conversion Workflow. Make sure you have are familiar with writing Authoring Components before following along with this sample.
If you select the LowPolyRobot
object in the scene, you'll see a component called PlayClipsThroughCodeAuthoring
, which is the code adds the required data to our entity, during conversion, for it to play the walk and run animations.
All the code for the sample is in PlayClipsThroughCodeAuthoring.cs
. You will see a pattern familiar to Unity's Baker to converting entities, with one important difference: SmartBakers.
SmartBakers
are a feature from the Latios Framework that allows building BlobAssets with Burst and in parallel. This is considerably faster than baking in pure C# and in the main thread, if you need to compute complex data (like meshes and animation clips). You can read more about SmartBakers
in the Latios Framework documentation.
Here's a breakdown of the code:
public struct PlayClipsThroughCodeComponent : IComponentData
{
public SingleClipRef WalkClip;
public SingleClipRef RunClip;
public float TransitionDuration;
}
This is the component we will use to hold the Walk and Run clips, and a transition duration for blending between these clips. SingleClipRef
is a DOTS supported struct that holds a reference to one animation clip.
class PlayClipsThroughCodeAuthoring : MonoBehaviour
{
public GameObject Owner;
public Animator Animator;
public SingleClipRefConvertData WalkClip = SingleClipRefConvertData.Default;
public SingleClipRefConvertData RunClip = SingleClipRefConvertData.Default;
public float TransitionDuration = 0.15f;
}
This is the Authoring monobehaviour, that holds our data in the GameObject scene. SingleClipRefConvertData
is simple struct that receives an AnimationClipAsset
and a Speed
value.
class PlayClipsThroughCodeBaker : SmartBaker<PlayClipsThroughCodeAuthoring, PlayClipsThroughCodeBakeItem>
{
}
Here we define the SmartBaker
, similar to how you would define a regular Baker<T>
.
The actual code to handle the conversion is in PlayClipsThroughCodeBakeItem
. You will see a lot of explanatory comments there, but I'll summarize the steps here:
-
PlayClilpsThroughCodeBakeItem
is a Burstable struct that implements ISmartBakeItem. It implements 2 functions:Bake
andPostProcessBlobRequests
struct PlayClipsThroughCodeBakeItem : ISmartBakeItem<PlayClipsThroughCodeAuthoring>
-
Bake
is similar toBaker<T>.Bake
. You have access to theIBaker
there, and you can add components to your entity if you like. The extra thing that this function needs to do is request the construction of BlobAssets. In this case we request our clip and clip events blob assets.
public bool Bake(PlayClipsThroughCodeAuthoring authoring, IBaker baker)
{
Assert.IsNotNull(authoring.WalkClip.Clip, $"Missing walk clip");
Assert.IsNotNull(authoring.RunClip.Clip, $"Missing run clip");
//Add single clip components to your entity. Those are required to play individual clips
AnimationStateMachineConversionUtils.AddSingleClipStateComponents(baker, baker.GetEntity(authoring.Owner),
baker.GetEntity(),
false, true, RootMotionMode.Disabled);
//Store data we will need on PostProcessBlobRequest
TransitionDuration = authoring.TransitionDuration;
WalkClipSpeed = authoring.WalkClip.Speed;
RunClipSpeed = authoring.RunClip.Speed;
//Request clips conversion the clips will be ready when PostProcessBlobRequest executes
var clips = new[] { authoring.WalkClip.Clip, authoring.RunClip.Clip };
clipsBlobHandle = baker.RequestCreateBlobAsset(authoring.Animator, clips);
clipEventsBlobHandle =
baker.RequestCreateBlobAsset(clips);
return true;
}
-
PostProcessBlobRequests
is where you can resolve the blob assets you requested, and then add them to your entity using theEntityManager
.
public void PostProcessBlobRequests(EntityManager entityManager, Entity entity)
{
//Resolve the blob assets
var clipsBlob = clipsBlobHandle.Resolve(entityManager);
var clipEventsBlob = clipEventsBlobHandle.Resolve(entityManager);
//Add the component referencing animation clips
entityManager.AddComponentData(entity, new PlayClipsThroughCodeComponent
{
WalkClip = new SingleClipRef(clipsBlob, clipEventsBlob, 0, WalkClipSpeed),
RunClip = new SingleClipRef(clipsBlob, clipEventsBlob, 1, RunClipSpeed),
TransitionDuration = TransitionDuration
});
}
The last piece of this section is PlayClipsThroughCodeSystem
, which will play the walk clip if the player presses 1
or the run clip if the player presses 2
. The code is rather straightforward, so I'll paste it here:
[RequireMatchingQueriesForUpdate]
public partial struct PlayClipsThroughCodeSystem : ISystem
{
public void OnCreate(ref SystemState state)
{
}
public void OnDestroy(ref SystemState state)
{
}
public void OnUpdate(ref SystemState state)
{
var playWalk = Input.GetKeyDown(KeyCode.Alpha1);
var playRun = Input.GetKeyDown(KeyCode.Alpha2);
foreach (var (playSingleClipRequest, playClipsComponent) in
SystemAPI.Query<RefRW<PlaySingleClipRequest>, PlayClipsThroughCodeComponent>())
{
if (playWalk)
{
playSingleClipRequest.ValueRW = PlaySingleClipRequest.New(playClipsComponent.WalkClip,
loop: true,
playClipsComponent.TransitionDuration);
}
else if (playRun)
{
playSingleClipRequest.ValueRW = PlaySingleClipRequest.New(playClipsComponent.RunClip,
loop: true,
playClipsComponent.TransitionDuration);
}
}
}
}
The key of this code is the ref PlaySingleClipRequest
component. This is added by AnimationStateMachineConversionUtils.AddSingleClipStateComponents
when we pass true
for enableSingleClipRequests
. By writing to this component, you can tell DMotion that you want to blend to a specific clip.
As you can see, if we playWalk
is true, we use PlaySingleClipRequest.New
, passing the playClipsComponent.WalkClip
for the clip, true
for loop, and playClipsComponent.TransitionDuration
for the blend duration. The code for playing run is analogous. That's all you need to play clips through code!
In this page you learned how to play clips through code. In the following sections, we will explore DMotion's Animation State Machine.