Howdy, Howdy, Howdy.
I think I’ve become error blind in my coding, but I need some help getting my layer syncing to work better. I’m currently working on a 2D platformer with sprites (think Megaman). I’ve managed to create two sync animator layers for the characters two states of Base and Attacking.
The trouble I run into is how to switch back and forth between the states immediately. Currently the way I am doing it with a co-routine; This works fine if I wait for the co-routine to finish between attacks, but it seems like if I mash the attack button (keep thinking Megaman) the attack layer weight will start to flicker on and off as if the co-routine is queuing itself and not caring if I re-enter the attack state.
So how do I make my Endshoot co-routine abort or start over if I keep re-entering the attack state?
void Shoot () {
GameObject obj = ObjectPooler.current.GetPooledObject ();
if (obj == null) return;
obj.transform.position = firePoint.transform.position;
obj.transform.rotation = firePoint.transform.rotation;
obj.SetActive(true);
//Setting Attack Sync layer to viewable and then turning it off (Layer#, Weight)
anim.SetLayerWeight (1,1);
StartCoroutine (EndShoot ());
}
IEnumerator EndShoot(){
yield return new WaitForSeconds (0.4f);
anim.SetLayerWeight (1,0);
}
//END Setting attack Sync Layer to viewable.
Rather than using coroutine, you should use a StateMachineBehaviour and override OnStateExit()
On your state shoot on your synchronized layer add a StateMachineBehaviour, override the function OnStateExit which is called when your animation is finish and simply set your layer weight to 0.
So if that looks something like this
public class AttackEnd : StateMachineBehaviour {
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
animator.SetLayerWeight(1,0);
}
then I get a scenario where if I am running , the animation loops instead of triggering a transition for OnStateExit to see, in which case my character will be stuck in attack mode until I perform an transition-able action.
Is there a way that at the end of the animation’s first loop it can switch the layerweight to 0?
There is many way to do this.
You could override StateMachineBehaviour.OnStateUpdate() and trigger a transition from attack to idle when normalize time is equal 1, each time a clip loop the normalize time interger part is increased by one.
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
if (stateInfo.normalizedTime > 1.0f)
animator.CrossFade("Idle", 0.1f);
}
// OnStateExit is called when a transition ends and the state machine finishes evaluating this state
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
animator.SetLayerWeight(1, 0);
}
Or simply create a transition from attack to idle with exit time equal 1 and use the same OnStateExit() function to lower the layer weight to 0.
I imported MegaMan for the sake of making this more viewable:
Resetting to Idle in the middle of a run animation creates a stutter step even if all transition durations have no exit time and are set to 0.

Perhaps I should look at this a different way, Is there a way to have the layer weights change in the middle of the animations instead of the end and without requiring a animator transition to happen?
You have complete control over layer weight from script so you can do what you want,It the only way to change a layer weight.
I’m not sure what you mean by stutter step, from the giff it look like like megaman is sliding back a few step, this is what you mean?
Nope that’s just the end of gif. If you watch you can see megaman’s Left hand appear behind his back every time he swings his left leg forward. If slowed down during that stutter it looks like this .
What you’re seeing is it switching from Run n Gun> Idle n gun>Running without gun>Run n gun.
So it not only switches to 1 frame of idle on not the base layer ,but also three frames of running(base) before it figures out to switch back to Run n Gun.
So this seem related to how you did build your state machine.
If you can share your project, I could take a look at your statemachine setup and find what is going wrong.
Sure ! Sounds like a plan, I’ve sent the project files link to a conversation with you. Thanks for taking peek.
So after looking at your project, effectively you cannot do a crossfade to ‘idle’ in this case because you are using synchronized layer which will force a transition to idle everytime the clip normalized time is greater than 1.
Remove the StateMachineBehaviour that I told you to add. It does nothing good in this case.
What I would do in this case is change the layer weight when the Fire button state change.
void Update () {
bool isShooting = Input.GetButton("Fire1");
//Crouch Attack
if ((fireRate == 0) && Input.GetAxisRaw ("Vertical") < 0 && ground) {
if (Input.GetButton("Fire1"))
{
Shoot ();
anim.SetTrigger ("cAttack");
}
}
else {
if (Input.GetButton("Fire1") && Time.time > timeToFire && Input.GetAxisRaw("Vertical") < 0 && ground)
{
timeToFire = Time.time + 1 / fireRate;
Shoot ();
anim.SetTrigger ("cAttack");
}
}
//END Crouch Attack
//Basic Attack
if (fireRate == 0) {
if (Input.GetButton("Fire1"))
{
Shoot ();
anim.SetTrigger ("Attack");
}
}
else {
if (Input.GetButton("Fire1") && Time.time > timeToFire)
{
timeToFire = Time.time + 1 / fireRate;
Shoot ();
anim.SetTrigger ("Attack");
}
}
//END Basic Attack
ground = (GameObject.FindGameObjectWithTag ("Player").GetComponent<Player2> ().ground);
anim.SetLayerWeight(1, isShooting ? 1 : 0);
}
void Shoot () {
GameObject obj = ObjectPooler.current.GetPooledObject ();
if (obj == null) return;
obj.transform.position = firePoint.transform.position;
obj.transform.rotation = firePoint.transform.rotation;
obj.SetActive(true);
}
I did change your Input.GetButtonDown to Input.GetButton to get auto fire like behaviour.
Wow , feels like we are getting so close! So here’s my next hiccup:
Holding the attack button is supposed to charge an attack, so having the auto fire function wont be an option for me. It does look fantastic if it were an option though haha.
Now it still looks good as far as If I do use “GetButtonDown” at lease he holds his gun arm out to start charging, but If i rapid click to fire (as auto isn’t an option) then he gets stuck in the awkward switching back and forth from gun and hand between shots.
So I guess I am wondering , got any tricks to delay the LayerWeight resetting since i can’t click faster then it updates frames?
That seems to be what i was trying at the beginning of this thread with a co-routine haha.
I think you were right with coroutine, the only thing is you need to stop the previous coroutine before you queue another one like you said.
Gosh this is killing me, I’ve tried the StopCoroutine all over my attack script. I can either get it to stop the coroutine 100% of the time which traps my character in the attack state as the coroutine never happens. Or I can get it to stop the coroutine 0% of the time, and which case I get the queuing of coroutines creating the annoying flickering between states that doesn’t work either.
Animator anim;
public IEnumerator endShoot;
void Update(){
endShoot = EndShoot();
if (Input.GetButtonDown ("Fire1")) {
Shoot ();
}
}
void Shoot () {
StopCoroutine (endShoot);
GameObject obj = ObjectPooler.current.GetPooledObject ();
if (obj == null) return;
obj.transform.position = firePoint.transform.position;
obj.transform.rotation = firePoint.transform.rotation;
obj.SetActive(true);
anim.SetLayerWeight (1,1);
StartCoroutine (endShoot);
}
IEnumerator EndShoot(){
yield return new WaitForSeconds (0.4f);
anim.SetLayerWeight (1,0);
}
I thought if I put it in this order I could have the Shoot () function turn off any previous instance of the coroutine and then turn a new instance on so that we’d get the off called just once when finished mashing the fire key… But that isn’t working. I tried attaching the StopCoroutine to the Input as well, but also unsatisfactory result.
If can’t get the result you want with coroutine you could try to emulate the same behaviour in Update(), simply store the time in a variable when you start to shot and when enough time is elapsed simply set your layer weight to 0
Hey! Alright ! That was the hint I needed man, Thanks!
Code for others who might be curious:
float sTime = 1;
float cooldown = 0.5f;
void Update () {
bool isCharging = Input.GetButton("Fire1");
if (isCharging) {
anim.SetLayerWeight(1,1);
}
if (Time.time >= sTime + cooldown && !isCharging) {
anim.SetLayerWeight (1,0);
}
}
void Shoot () {
GameObject obj = ObjectPooler.current.GetPooledObject ();
if (obj == null) return;
obj.transform.position = firePoint.transform.position;
obj.transform.rotation = firePoint.transform.rotation;
obj.SetActive(true);
//Setting Attack Sync layer to viewable and then turning it off (Layer#, Weight)
anim.SetLayerWeight (1,1);
sTime = Time.time;
}
Shoot puts me in attack layer, charging lets me hold the button and stay on attack layer, and sTime + Cooldown take me out of the attack layer if I haven’t attacked again within the cooldown time.
2 Likes