I’m pretty new to Unity/C#. I’m porting over a card game I wrote in C several years ago. When the dealer deals the cards, I want to see them dealt (“thup thup thup”), not all appear at once. In C, I used a simple “Delay(500);” method to cause a 500ms delay before the next card.
I’ve tried probably a half-dozen different techniques I’ve found online, but none work. I tried setting up a Delay() coroutine method, thinking the execution flow would enter, wait, then resume. Nope. It happily throws out all the cards at once and then returns at the specified time to the Delay routine.
IEnumerator Delay(float Count)
{
Debug.Log("Time in: " + Time.time);
yield return new WaitForSecondsRealtime(Count); //Count is the amount of time in seconds that you want to wait.
Debug.Log("Time out: " + Time.time);
yield return null;
}
I’ve tried making the card display method itself a coroutine, but not only does it not delay, as soon as I change the method type from void to IEnumerator it doesn’t display the cards at all.
IEnumerator DrawCardBitmap2(int Player, int cardSpot, int cardIndex)
{
string spriteObjectName;
Debug.Log("Time in: " + Time.time);
yield return new WaitForSecondsRealtime(1.0f); //Count is the amount of time in seconds that you want to wait.
spriteObjectName = "Sprite_Player" + Player + "_" + cardSpot;
gameObjectSprite = GameObject.Find(spriteObjectName);
gameObjectSprite.GetComponent<SpriteRenderer>().sprite = cardSprites[cardIndex];
Debug.Log("Time out: " + Time.time);
}
I tried moving the yield statement to the end of the method; same result.
A simple delay shouldn’t be this hard, seems to me.
You need to use the StartCoroutine() method to start Coroutine. So in your last example StartCoroutine(DrawCardBitmap2(<Player>, <cardSpot>, <cardIndex>));
You have several examples that look like they should successfully wait, but you don’t have enough context in the examples to see how you’re testing them or what’s supposed to happen after the wait.
That one sounds an awful lot like you have made some fundamental error in how to define and/or invoke a coroutine.
It’s pretty straightforward. Call the delay routine and everything should pause for that fraction of a second, before (or after) drawing a card. Like this:
Delay(1.0f);
DrawCardBitmap2(...)
...more code
But it jumps into the Delay routine(s), the cards are instantly drawn, then a second (or two, or whatever) later, the rest of the Delay method runs. E.g., I get a Debug message that it has entered the routine, all the cards immediately appear, and a second (or two) later I get another Debug messages stating that the Delay method has finished.
Coroutines and async functions don’t cause the caller to pause–the whole point is to do stuff without blocking the caller! They only pause themselves, internally.
Inserting a delay into your Update() function is technically possible but is a very bad idea. Update() runs on the main Unity thread and blocks the game loop, so while it’s waiting Unity can’t update anything else, can’t redraw the screen, can’t accept user input, can’t advance the value of Time.time, etc. Basically your entire game freezes until Update() returns. Don’t do this.
If you want to do something gradually, either refactor your logic so that only a small part of it happens each time Update is called, or move the entire gradual process inside of a coroutine or async method. (And if you want to do anything involving the UnityEngine namespace, coroutine is probably better than async.)
Trying to find the subtle syntax error you probably made based on your hazy recollection of the code is probably not very productive, but IIRC the version of StartCoroutine that accepts a string doesn’t allow you to supply function arguments like that. And there’s not really any good reason to use the string version anyway; you can just invoke the function normally and pass its return value to StartCoroutine.
Well, it’s not a “gradual” process. I just want the program to pause for a split second so the user can see the cards come out one-by-one (in a loop). The other ten computer languages (or so) I know can do this extremely simply, usually with a single-line built-in function called “delay” or “timer” or some such. This isn’t like decoding the human genome. Hell, I’ve written timer routines in assembler that were more straightforward. I can’t believe my old acquaintance Mads Torgerson wrote this language.
A loop with waits in it is pretty much the definition of a “gradual process”.
You absolutely can write a single line of C# that will pause your function for a length of time. You just shouldn’t do that on the main thread, because it will freeze your entire game while you wait. And the player won’t actually “see” anything if you don’t allow the Update function to return, because you’ll be blocking the engine from drawing to the screen.
That’s got nothing to do with the language, it has to do with how the Unity engine is organized.
But it’s also absolutely typical of the way game engines work; i.e. having the main thread run a loop that alternates between incremental updates to the game state and then displaying the new state. (How else would you make a responsive game engine?)
The fact that coroutines and async methods don’t block the caller is a feature that is immensely valuable and the architects of those systems took pains to ensure that was the case. The reason it’s not as simple as you expected is that they’re tackling a different and vastly more complex problem (and reaping enormous benefits from doing so).
If you’ve really somehow never had to grapple with parallelism before (despite learning 10 computer languages?), I guess I can see how that might be hard to wrap your head around, but it’s a pretty important concept and it’s used all over the place in modern software engineering. It’s not some unusual quirk of this language or this engine. If you avoid learning about it, you are very likely going to continue tripping over it in the future.
And in this case, all you really have to do is deal your cards from a coroutine instead of from Update. I think the only reason the bottom example of your OP is not already working for you is that you made a simple syntax error in the portion of the code that you didn’t post (but of course I can’t confirm that, because you didn’t post it).
Thanks for all the assistance. I did finally get it sussed. These comments clarified the execution flow better than the Unity/C# documentation did for me.
I ended up putting the entire method as a coroutine, but when I tried attaching that to a game control, it threw an exception. (Reading, it sounds like that might be long-standing bug, at least since mid-2018.) So I had to attach the game control to a new void routine, then have that call my main method, now a coroutine.
I realize we’re talking threaded applications here, but it still seems like a boat-load of complexity for a simple task. But that doesn’t seem to be uncommon with C#.
Technically, coroutines aren’t threaded; they still execute on the main thread, just a little bit at a time. There are a lot of similarities to running on another thread, though.
That’s not enough detail on the last error to really figure out what’s going on, but note that when you call StartCoroutine that is associating the coroutine with the MonoBehaviour that made the call, and the coroutine’s continued execution is contingent upon that MonoBehaviour continuing to be active.
Also, certain of Unity’s magic methods (like Start) can be turned into coroutines and Unity will figure it out automatically, but others (like Update) cannot.
It’s not uncommon to have a wrapper function that does nothing except call StartCoroutine.