unity-game-engine

Animator Controller preventing change to Sprite Renderer from code, despite no animation clip changing the sprite


I have an "Enemy" Animator Controller:

Default Enemy SS

The enemy dead clip disables the Sprite Renderer after 4 frames: Enemy Dead SS

The EnemyDamaged clip just moves position and rotation for a few frames: EnemyDamaged SS

For my green blob, I've set up an override controller that overrides the above clips with a series of sprites:

Damaged Override Dead Override

So, a Blue or Red blob uses the standard "Enemy" animator controller that only turns on/off the SpriteRenderer, but does not change the sprite. The Green slime uses the Green Slime's "Override" animator controller with the override clips that do change the sprites.

The following code runs on my Enemy.cs script to spawn a new monster. When a Green slime dies (the only enemy with an override animator controller), the next spawned enemy always has the base green sprite, but if a red or blue slime dies the next enemy's sprite is updated correctly.

public void Initialize(Monster monster)
{
    animator.ResetTrigger("Dead");
    animator.ResetTrigger("Damaged");
    health = monster.health;
    maxHealth = monster.health;
    attack = monster.attack;
    defense = monster.defense;
    this.monster = monster;
    enemyNameText.text = monster.name;
    spriteRenderer.sprite = monster.sprite;
    currentHealthTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, maxHealthTransform.rect.width * (float)health / maxHealth);
    animator.runtimeAnimatorController = monster.animatorController ? monster.animatorController : defaultAnimatorController;
    animator.SetTrigger("Spawned");
}

If I change the code to the following to only ever use the defaultAnimatorController for all enemy types, the issue disappears, but I can no longer override my animations:

public void Initialize(Monster monster)
{
    animator.ResetTrigger("Dead");
    animator.ResetTrigger("Damaged");
    health = monster.health;
    maxHealth = monster.health;
    attack = monster.attack;
    defense = monster.defense;
    this.monster = monster;
    enemyNameText.text = monster.name;
    spriteRenderer.sprite = monster.sprite;
    currentHealthTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, maxHealthTransform.rect.width * (float)health / maxHealth);
    animator.runtimeAnimatorController = defaultAnimatorController;
    animator.SetTrigger("Spawned");
}

Here's a GIF of the bug happening. Notice how after the green slime dies, the sprite stays green despite it spawning a red slime and having the original "Enemy" animator controller that does not affect the SpriteRenderer.

Bug Gif

If you're interested in tinkering with the code yourself, here's a link to a github repo of that produces this bug.


Solution

  • Thanks to DerHugo's comment, I was able to identify the issue.

    Here are some key things to know:

    The cause of the problem:

    If the Green Slime controller is active while trying to set the SpriteRenderer's .sprite property, that line of code is ignored because changes to the .sprite property are controlled in one of its animation clip overrides.

    If the Default controller is active while trying to set the .sprite property, the line of code executes without problem.

    The base sprite updates correctly after Red and Blue slimes die because the Default controller is active (not binding the property). The base sprite does not update correct after a Green slime dies because the Green slime's override controller is active (binding and blocking the property).

    The solution is 2-fold:

    1. Update the animation controller before setting the sprite.
    public void Initialize(Monster monster)
    {
        animator.ResetTrigger("Dead");
        animator.ResetTrigger("Damaged");
        health = monster.health;
        maxHealth = monster.health;
        attack = monster.attack;
        defense = monster.defense;
        this.monster = monster;
        enemyNameText.text = monster.name;
        currentHealthTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, maxHealthTransform.rect.width * (float)health / maxHealth);
        animator.runtimeAnimatorController = defaultAnimatorController;
        spriteRenderer.sprite = monster.sprite; //Moving this line here fixes the problem for the default animator controller
        animator.SetTrigger("Spawned");
    }
    
    1. Create an idle animation clip for the Green slime's override animator controller, and set the SpriteRenderer's base sprite in the first frame. This is because the override animator controller is binding and blocking changes to that property directly, so the animator controller needs to make the change through an animation clip.