0
\$\begingroup\$

I'm working on creating an animation system used for mouth movement that works like this:

  1. BEFORE the animation clip starts playing, cache the SpriteResolver's state (category and label)
  2. Once the animation clip finishes playing, set the SpriteResolver's state back to what it was before the animation started playing

The problem I'm currently dealing with is that sometimes the category and label during runtime seem to be null. As a result, I can't set the SpriteResolver's sprite back. In the video below, I've created a timeline to play the animation tracks I made. During OnBehaviourPlay and OnBehaviourPause, I have it debug the category and label which are both null.

Link to video

This doesn't make any sense as the SpriteResolver clearly has a category and label set before the scene is even ran. enter image description here

Here's my code for the custom timeline elements seen in the video:

TestMouthAnimationBehaviour.cs

using UnityEngine;
using UnityEngine.Playables;
 
public class TestMouthAnimationBehaviour : PlayableBehaviour
{
    public MouthSyncAnimationController MouthSyncAnimationController;
 
    /// <inheritdoc />
    public override void OnBehaviourPlay(Playable playable, FrameData info)
    {
        MouthSyncAnimationController.DebugCategory();
        MouthSyncAnimationController.PlayLipSyncAnimation();
        MouthSyncAnimationController.DebugCategory();
    }
 
    public override void OnBehaviourPause(Playable playable, FrameData info)
    {
        var duration = playable.GetDuration();
        var count = playable.GetTime() + info.deltaTime;
 
        if ((info.effectivePlayState == PlayState.Paused && count > duration) ||
            playable.GetGraph().GetRootPlayable(0).IsDone())
        {
            // Execute your finishing logic here:
            Debug.Log("Clip done!");
            MouthSyncAnimationController.StopLipSyncAnimation();
        }
    }
}

TestMouthAnimationPlayable .cs

using UnityEngine;
using UnityEngine.Playables;
 
public class TestMouthAnimationPlayable : PlayableAsset
{
    double _duration = 1;
    public override double duration => _duration;
 
    public ExposedReference<MouthSyncAnimationController> LipSyncControllerReference;
 
    /// <inheritdoc />
    public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
    {
        ScriptPlayable<TestMouthAnimationBehaviour> playable =
            ScriptPlayable<TestMouthAnimationBehaviour>.Create(graph);
 
        TestMouthAnimationBehaviour behaviour = playable.GetBehaviour();
        MouthSyncAnimationController mouthSyncController = LipSyncControllerReference.Resolve(graph.GetResolver());
 
        behaviour.MouthSyncAnimationController = mouthSyncController;
 
        return playable;
    }
}

Here's the controller script I made that the timeline elements depend on:

    using UnityEngine;
using UnityEngine.U2D.Animation;
 
public class MouthSyncAnimationController : MonoBehaviour
{
    [SerializeField] private SpriteResolver _mouthSpriteResolver;
 
    private string _mouthStateBeforeStart;
 
    [SerializeField] private Animator _lipSyncAnimator;
    [SerializeField] private string _lipSyncAnimationState;
 
    // Start is called before the first frame update
    void Start()
    {
        enabled = true;
    }
 
    private void CacheCurrentMouthState()
    {
        _mouthStateBeforeStart = _mouthSpriteResolver.GetLabel();
    }
 
    private void SetCachedMouthState()
    {
        _mouthSpriteResolver.SetCategoryAndLabel(_mouthSpriteResolver.GetCategory(), _mouthStateBeforeStart);
    }
 
    public void DebugCategory()
    {
        Debug.Log(
            $"Name: {gameObject.name} | Current category name: {_mouthSpriteResolver.GetCategory()} | Current label name: {_mouthSpriteResolver.GetLabel()}");
    }
 
    public void PlayLipSyncAnimation()
    {
        CacheCurrentMouthState();
        _lipSyncAnimator.enabled = true;
        _lipSyncAnimator.Play(_lipSyncAnimationState);
    }
 
    public void StopLipSyncAnimation()
    {
        SetCachedMouthState();
        _lipSyncAnimator.enabled = false;
    }
 
    public void SetMouthState(string category, string label)
    {
        _mouthSpriteResolver.SetCategoryAndLabel(category, label);
    }
}

Edit: Link to another Youtube video showing how the category and labels are null until the animation clips play. https://youtu.be/_NzUbxK-xp8?si=UYjAncteQloZNpam

Note that I want the category and label of the SpriteResolver BEFORE the animation starts. The problem depicted in the video is that the category and label values are always null before the animations start so it's impossible to cache its initial state before the animation starts.

\$\endgroup\$
2
  • \$\begingroup\$ Have you tried to call CacheCurrentMouthState in either Awake or Start as well to make sure there is an initial state in _mouthStateBeforeStart? You might want as well add that string to your Debug Log in DebugCategory to make sure it is not a problem of storing a null somehow \$\endgroup\$
    – Zibelas
    Commented Sep 19, 2023 at 19:08
  • \$\begingroup\$ @Zibelas There is an initial state when I call CacheCurrentMouthState in Awake but as the Timeline progresses the strings become null until the animations start and then the categories and labels show. See the video linked where DebugCategory is ran on update as well as on Awake. youtu.be/_NzUbxK-xp8?si=UYjAncteQloZNpam \$\endgroup\$
    – KD867746
    Commented Sep 19, 2023 at 20:13

0

You must log in to answer this question.

Browse other questions tagged .