• RuntimesUnity
  • Issue with 2 animation tracks using the same bones

Hi there! Hopefully it's an easy one and I'm just missing something very obvious. 🙂
TL;DR I want my Unity project to use 3 animation tracks (0,1, 2) but I run into stutter/glitch issues when tracks 1 and 2 use the same bones (here - eye bones) even if they don't use them at the same time. (more explained below).

I assigned the blink animation on track 2, track 1 is for intermittent idle animations (like look around and such). Track 0 is for breathing, causes no issues. Now, for the first few seconds in game, all works fine - the blink runs its course every few seconds, the intermittent idle works alright too, but as soon as an idle animation that uses eye bones runs at least once (and it runs fine the first time) it... Locks the eye bones? and causes different issues like:

  • Blink not playing at all, or
    -Blink ending abruptly (so closing half way and not completely), and/or
    -Idle animations not moving eyes at all (not looking around, not changing the eyelid positions to e.g. look down)

At first I thought maybe if these run together on accident they somehow cause issues? So I created a bool isPlayingAnimation on my idle animation coroutine, with blink not allowed to set its animation if the bool is true, but it still persisted... Okay. Then I changed Mix Duration to 0, maybe the animations are getting lost in transition time (somehow?) but no luck there either. Maybe clearing the track before the animation to have a blank slate? Nope. And now I'm out of ideas. Any help appreciated!!

(code below if needed)

MainSpriteIdle
`
[SpineAnimation] public string[] neutralIdle = { "ear-twitch", "head-side", "head-side-look-away", "look-around", "tail-swish" };
[SpineAnimation] public string[] happyIdle = { "ear-twitch", "head-side","shrug", "head-side-look-away", "look-around", "tail-swish" };
public int activeAnimationIndex = 0;
public string[] idleList;
private GameSession gameSession;
[SerializeField]private int moodIndex;
public bool isPlayingAnimation = false;

SkeletonAnimation skeletonAnimation;
private void Awake()
{
    skeletonAnimation = this.GetComponent<SkeletonAnimation>();
}
private void Start()
{
    gameSession = FindObjectOfType<GameSession>().GetComponent<GameSession>();
    StartCoroutine(PlayRandomAnimation());
}
// Update is called once per frame
void Update()
{
    //listen for mood changes
    if (gameSession.isInitialised &&  moodIndex != gameSession.mood)
        AssignListBasedOnMood();
}
private IEnumerator PlayRandomAnimation()
{
    AssignListBasedOnMood();
    
    while (true) 
    {
        
        float randomInterval = UnityEngine.Random.Range(10f, 12f);
        yield return new WaitForSeconds(randomInterval);
        //yield return new WaitForSeconds(2f);
        if (idleList != null)
        {
            // Generate a random index for the animation array
            activeAnimationIndex = UnityEngine.Random.Range(0, idleList.Length);
            skeletonAnimation.AnimationState.ClearTrack(1);
            // Play the selected animation
            //skeletonAnimation.AnimationState.SetAnimation(1, idleList[activeAnimationIndex], false); //(will uncomment after testing)
            skeletonAnimation.AnimationState.SetAnimation(1, happyIdle[2], false); //happyIdle[2] is an animation that uses eye bones
            isPlayingAnimation = true;

            yield return new WaitForSeconds(skeletonAnimation.AnimationState.GetCurrent(1)?.Animation?.Duration ?? 0f);
            isPlayingAnimation = false;
        }
        else continue;
    }
}
private void AssignListBasedOnMood()
{
    if (gameSession == null)
        gameSession = FindObjectOfType<GameSession>().GetComponent<GameSession>();

    moodIndex = gameSession.mood;
    if (moodIndex >= 60)
        idleList = happyIdle;
    if(moodIndex >= 30 && moodIndex < 60)
    {
        idleList = neutralIdle;
    }
    if(moodIndex < 30)
    {
        idleList = null;
    }
}

`

Blinking
`
private float minimumDelay = 4f;
private float maximumDelay = 7f;
private MainSpriteIdle spriteIdleScript;

void Start()
{
	spriteIdleScript = FindObjectOfType<MainSpriteIdle>().GetComponent<MainSpriteIdle>();
	StartCoroutine(Blink());
}

void Update()
{
}
private IEnumerator Blink()
{
	SkeletonAnimation skeletonAnimation = this.GetComponent<SkeletonAnimation>();

	
	while (true)
	{
		yield return new WaitForSeconds(Random.Range(minimumDelay, maximumDelay));

		if (spriteIdleScript.isPlayingAnimation)
			continue;
		skeletonAnimation.AnimationState.ClearTrack(2);
		skeletonAnimation.AnimationState.SetAnimation(2, "blink-neutral" , false);
		Debug.Log("Blinked");
	}
}`
Related Discussions
...

@VolfKing Please note that you're calling SetAnimation() with the loop argument set to false. I assume you don't want to play it once, so pass true instead.

In general animation tracks with a higher track index override animations on lower track indices when targeting the same bone (when they have an alpha or mix value of 1.0, otherwise it's obviously mixing). You usually don't want to clear tracks (and it's often a sign of using the API in the wrong way), as you're then losing any smooth mix transitions and get abrupt animation changes.

If you don't add the empty animation (animationState.AddEmptyAnimation()) after a non-looping animation, the animation's last pose will be held.

Regarding forum code tags: you need three backticks

```
<your code>
```

not one when using multi-line code statements, the single backtick is for inline (single-line) code.

Hi! Let me apologise upfront for the bad formatting, I just used the "Insert Code" button when typing the reply and didn't know any better, will keep the three backticks in mind, thank you!

The animations unfortunately had to be set to non-looping, as the animations are just short intermissions in the main breathing loop, and I want there to be empty time between these shorts, hence WaitForSeconds coroutine. And I think that's what was causing issues.

Commenting out the ClearTrack and putting AddEmptyAnimation now solved it all, and here I thought they are essentially the same thing... Clearly not, cause it's working now, very much appreciated! 🙂

  • Harald đã trả lời bài viết này.
  • NateHarald đã thích điều này.

    VolfKing Hi! Let me apologise upfront for the bad formatting, I just used the "Insert Code" button when typing the reply and didn't know any better, will keep the three backticks in mind, thank you!

    No need to apologize! Just wanted to save some confusion or frustration why it wasn't formatted properly with the backticks in the first place. 🙂

    VolfKing Commenting out the ClearTrack and putting AddEmptyAnimation now solved it all, and here I thought they are essentially the same thing... Clearly not, cause it's working now, very much appreciated! 🙂

    Glad to hear you've figure it out, thanks for letting us know! 🙂

    The empty animation really acts like a follow-up animation track entry with proper mixing out of the preceding animation, while ClearTrack() removes all animation track entries, leaving nothing playing at all, thus also not mixing out.