Co-routines, timing and framerate independence

Hi all,

I am trying to develop my first game on Unity and I am seeing a number of discreet discrepancies between how the application runs on different machines.

My partner, who does the art, often QAs the game and he usually has very harsh feedback about my coding. At first, I thought he was just being super anal but upon playing it on his PC, I found that a lot of things that worked fine on my machine were simply bad on his. This is made worse by the fact that our game is in the beat-em-up/fighting genre which requires strict timing and tight controls to be any good.

First of all, this game will be a 2D sprite-based game. Most of the animation tools provided in unity seem to be focused on 3D animation, since it is a 3d engine after all. Also, I am aware of Time.deltatime and I have been using it extensively in my movement-based scripts. Besides that, what are some general tips or things to avoid that can preempt these kinds of problems?

Secondly, I am a self-professed Coroutine addict. I fell in love with them because they were so useful and convenient. But are coroutines one of the culprits behind these mysterious phenomena? I am using coroutines in ALL of my animation scripts. Here is an example from my code:

This code plays a hitstun animation when the main character strikes a generic enemy.

public void HitStun()
 {
 StartCoroutine ( StomacheAcheMain () ); 
 }



IEnumerator StomacheAcheMain()
 {
 animating = true;
 renderer.material.mainTextureOffset = stomachache1;
 
 yield return new WaitForSeconds(0.1f);
 
 renderer.material.mainTextureOffset = stomachache3;
 
 yield return new WaitForSeconds(0.1f);
 
 renderer.material.mainTextureOffset = stomachache2;
 
 yield return new WaitForSeconds(0.1f);
 
 renderer.material.mainTextureOffset = stomachache3;
 animating = false;
 
 }

As you can see, I jump through 4 different images in a spritesheet using the coroutine timing to string it all together, making it look like the enemy shakes back and forth from the impact (it is named Stomacheache because the guy is clutching his gut like he had a bad burrito). Thanks to Mike for the tip about consolidating the animation into one. Anyway, this code works fine on my machine, while on my partner’s, the enemy shakes much too late.

I also use coroutines in conjunction with bools as convenient timers for things like tutorial text. Here is another exciting code example:

This code displays tutorial text for 5 seconds at a specific time.

if (!movementcleared && !enemyhere && !ms1msgdone && welcomedone)
 StartCoroutine ( ToggleMovementText (5) );

IEnumerator ToggleMovementText (float time)
 {
 movementText = true;
 
 yield return new WaitForSeconds(time);
 
 movementText = false;
 ms1msgdone = true;
 
 }

if (movementText)
 {
 GUI.TextArea(textRect, " ");
 myStyle.fontSize = 21;
 GUI.Label(new Rect(80, 100, 700, 160), "So...how do you feel, Rick?", myStyle);
 GUI.Label(new Rect(80, 130, 700, 160), "Try using WSAD or arrow keys to move.", myStyle);
 }

Is it bad that I’m using coroutines for timing-based things and animations? What are good alternatives besides having to generate another bool and another timer? And lastly, as i said earlier, I would really appreciate any general tips about how to avoid making a schizophrenic game that acts different on every machine it encounters.

Thanks for your time.

I’m pretty sure that you could create an animation with keys with linear handles (i.e. no curves) that would do this for you.

In my experience coroutines are fine to use, but be aware that if you fire off a coroutine like this, what happens if you were to fire another such coroutine off? Do you perhaps need to check at the beginning and if animating yield break?

To debug you might want to set Time.timeScale to 0.1f when you initiate the StomachAcheMain() and then add some print() calls throughout to see how you progress through the coroutine. You can of course set a bunch of breakpoints, but that’s not likely to tell you much as you need to see the flow through the coroutine.

if you print(“stomachache1”); and don’t see the change on the model, then something very odd is going on.

Oh, and if you use renderer.material this returns a copy - I don’t know if it will return a copy for every call - you should check the docs.

Looks like you’ll get a copy if it’s used elsewhere, so it may be okay, but you probably want to force the copy in Awake(). You need to check this though…

You also want to consider caching the renderer in your Awake() call as this.renderer is equivalent to this.GetComponent() I believe.

BUT, it would be much better if you were just calling CrossFadeQueued() in your HitStun() method.

Thanks
Bovine

Given that you’re in need of time critical response, coroutines MIGHT be inadequate, for the simple fact that performance fluctuations influence them heavier than what happens with other time-based game actions.

An alternative to coroutines is the Invoke command, which at the higher level does not rely on yield for proper timing. It’s limited, but can be used to hardcode animation behaviors like you’re doing.

Your best approach though could be the use of ANIMATION EVENTS

Take any gameobject, add an animation component to it, and use the ‘animation’ editor tab to select the root, at a certain point in time, right click just below the timeline, and choose “add event”

You should figure out pretty soon how convenient this feature is.

NOTE: you need a script hosted together with the animation component for the ‘event’ public routine to be visible by the animation editor.