I have an "Enemy" Animator Controller:
The enemy dead clip disables the Sprite Renderer after 4 frames:
The EnemyDamaged clip just moves position and rotation for a few frames:
For my green blob, I've set up an override controller that overrides the above clips with a series of sprites:
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.
If you're interested in tinkering with the code yourself, here's a link to a github repo of that produces this bug.
I have an "Enemy" Animator Controller:
The enemy dead clip disables the Sprite Renderer after 4 frames:
The EnemyDamaged clip just moves position and rotation for a few frames:
For my green blob, I've set up an override controller that overrides the above clips with a series of sprites:
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.
If you're interested in tinkering with the code yourself, here's a link to a github repo of that produces this bug.
Share Improve this question asked Nov 19, 2024 at 5:48 Erik OverflowErik Overflow 2,3161 gold badge6 silver badges19 bronze badges 7- My current proposed solution is to destroy the instance of enemy, and create a new one from the spawner. This will work to resolve my issue, but I'd like to better understand why setting the SpriteRenderer.sprite is ignored when there's an override controller. – Erik Overflow Commented Nov 19, 2024 at 6:00
- 1 As soon as any animation clip holds a key frame to a property it is bound to the animator and can not be modified by script - or better is overruled by the animator as it run last in a frame – derHugo Commented Nov 19, 2024 at 6:41
- This gave me the right direction to look. The active controller is showing as "Enemy" at the end of the frame, which doesn't bind to the .sprite property. Interestingly, moving the spriteRenderer.sprite line to after the animator assignment seems to resolve this. It looks like the animatorController is blocking changes to affected properties, rather than binding/overruling them. Where can I read more on the property blocking/binding functionality of the animator controller? @derHugo – Erik Overflow Commented Nov 19, 2024 at 7:14
- 1 idk to be honest ^^ that's just from the experiences I made with animations in Unity .. the system itself is unfortunately quite closed book as soon as the use case goes beyond the "normal" – derHugo Commented Nov 19, 2024 at 8:16
- 1 but yeah first changing the controller and only then the properties makes a lot sense from that perspective - as said on the inside no clue how the aniamtor does it exactly but seems that as long as the controller still claims ownership of a property it is already marked to be set for this frame (which is weird - probably almost a bug - but seems to be what it is) – derHugo Commented Nov 19, 2024 at 8:24
2 Answers
Reset to default 0Thanks to DerHugo's comment, I was able to identify the issue.
Here are some key things to know:
- Animator Controllers bind to all properties that are affected by its animation clips
- The Animator Override Controller was binding to the SpriteRenderer's sprite property through one of its animation clips
- The default animator controller was not binding to the SpriteRenderer's sprite property
- Properties binded by an animator controller cannot be changed. The animator controller does not "override" the property. It blocks changes to the property entirely.
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:
- 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");
}
- 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.
It makes sense since you're not updating the sprite when entering an idle state (you have no animation clip in this state!)
You almost had it.
The approach overall isn't the best, but following your structure, there's a simple solution and explanation.
The problem is that you're setting the animator controller after setting the sprite, making the sprite change redundant.
Simply call spriteRenderer.sprite = monster.sprite;
after animator.runtimeAnimatorController = monster.animatorController ? monster.animatorController : defaultAnimatorController;
Refined code:
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 = monster.animatorController ? monster.animatorController : defaultAnimatorController;
spriteRenderer.sprite = monster.sprite; // moved after setting animator
animator.SetTrigger("Spawned");
}
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745579847a4634186.html
评论列表(0条)