- Home /
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.
Well I guess your code looks like it is very dependent on the ordering of coroutines - that animation code looks very strange indeed to me. Why do you have it as 3 coroutines when presumably one would make much more sense e.g:
renderer.material.mainTextureOffset = stomachache1;
yield return new WaitForSeconds(0.1f);
renderer.material.mainTextureOffset = stomachache2;
yield return new WaitForSeconds(0.1f);
renderer.material.mainTextureOffset = stomachache3;
Starting all of those extra coroutines is a performance impact, makes it hard to read your code and might conceivably not be in a guaranteed order.
I see. I was not aware that you could have multiple yield calls in the same co-routine. Thanks very much for the tip. This just makes me like coroutines even more
Answer by The-Arc-Games · Jul 11, 2012 at 06:32 AM
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.
Hi, thanks for the input. I tried out this method and it seems to work pretty well. I will convert a few of my animations to this method as a trial to see if the performance improves. If it does, I won't forget to come back and accept your answer. Thanks again.
While the answer, to use animation events is a sound one. For anyone else reading this I affirm once more that the first paragraph is factually inaccurate - coroutines are not affected any more or less by 'performance fluctuations', whatever is meant by 'performance fluctuations'.
Coroutines are reliable and as dependable as any other time-based approach.
Care needs to be taken as to what you have set in motion however.
I will say no more on the subject.
First paragraph is accurate, ins$$anonymous$$d. You can reproduce it with a test. We've seen this with cpu-throttled systems (overclocked and power savers), but it doesn't seem to be limited to that, so these might not be the only causes.
Final word: you can certainly use coroutines to your hearts' content, but if you stumble upon these ti$$anonymous$$g problems, animation events are a valid alternative.
I believe I know how coroutines are implemented in Unity - which is just a call in the frame loop on active objects after the Update call and before LateUpdate - I'd be fascinated to know how the performance of the processor affects these. If you do come across that problem then it is actually straightforward to implement your own coroutine calling system that is tied to one of the well known Unity events (usually that would be LateUpdate) - the disadvantage is not being able to guarantee the calling order versus the routine you call it from e.g. LateUpdate (but script execution priority would help with that).
The normal sequence is:
Update
Coroutines
Animation
LateUpdate
Your own method can easily be put in Update or LateUpdate but I've never found the need and as will be obvious from my former comments, cannot understand why that would happen given how Unity implements coroutines - doesn't mean it isn't - maybe they've put some wacky ti$$anonymous$$g in there :)
Answer by Bovine · Jul 10, 2012 at 08:42 AM
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.
http://docs.unity3d.com/Documentation/ScriptReference/Renderer-material.html
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
Ah, thanks. This looks like a good way to animate a single image or parallax scrolling. It would work perfectly for the hitstun example, but most of my other animations involve iterating through a spritesheet in a non-linear way.
Your keyframes can jump about, the key handles must just be linear so that the animation system doesn't create a curved path between the keyframes
Your answer
Follow this Question
Related Questions
Sprite Sheet animation without fancy add-ons 1 Answer
How do I flip a 3D object to look towards left or right on a 2D plane 0 Answers
2d Beat Em Up Jump Functionality 0 Answers
Multiple Cars not working 1 Answer
2D Attack For Fighting Game 1 Answer