Skip to content

1.3 Playing clips through code

Gabriel Dechichi edited this page Feb 18, 2023 · 3 revisions

Playing clips through code

In this section, you'll learn how to switch between animations through code.

1. Playing the sample

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.

image

2. Writing the Authoring component

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.

image

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:

  1. PlayClilpsThroughCodeBakeItem is a Burstable struct that implements ISmartBakeItem. It implements 2 functions: Bake and PostProcessBlobRequests
struct PlayClipsThroughCodeBakeItem : ISmartBakeItem<PlayClipsThroughCodeAuthoring>
  1. Bake is similar to Baker<T>.Bake. You have access to the IBaker 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;
    }
  1. PostProcessBlobRequests is where you can resolve the blob assets you requested, and then add them to your entity using the EntityManager.
    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
        });
    }

3. Switching between Walk and Run in a system

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!

4. Conclusion

In this page you learned how to play clips through code. In the following sections, we will explore DMotion's Animation State Machine.

Clone this wiki locally