I have two functionality that need to achieve:
movement
rotation
both must be in Update(), i can combine it but i prefer both of them in separate script in each Movement.cs and Rotation.cs.
So i have two method:
Combine both into one script in and using one Update().
Separate into two script and each of them have Update().
My question : does it cost performance if separate them into each individual Update() instead of combine in one script with one Update().
Both script will be attach on one object, so if i have hundreds of object.
Method 1 - 100 objects and 100 script with Update().
Method 2 - 100 objects and 200 script with Update().
Another question : is it really bad to do Method 2 ?.
This is a case of micro-optimisation. You should use any of the methods that makes more sense in your particular case and after you're done you should to some profiling.
Based on the results of profiling you'll end up knowing where and what to optimise.
Getting back to the problem at hand having 2 Updates will cost you one more function call per frame and some time when your MonoBehaviour is first loaded, but that is negligible, you'll eat a lot more CPU cycles in other parts of your code.
If you can combine, do it. The Unity MonoBehaviours are usefull but eat quickly your resources, especially if you have many scripts running at the same time.
Check this blog ticket : https://blogs.unity3d.com/2015/12/23/1k-update-calls/
WHAT SHOULD YOU DO?
Of course it all depends on your project, but in the field it’s not rare to see a game using a large number of GameObjects in the scene each executing some logic every frame. Usually it’s a little bit of code which doesn’t seem to affect anything, but when the number grows very large the overhead of calling thousands of Update methods starts to be noticeable.
Perhaps a third option would be better for you, it elaborates on the first option but still splits your logic into two separate areas, thus achieving the loose coupling you were going for originally.
public static class Manipulators
{
public static void Rotate(MonoBehaviour behaviour, float amount)
{
Transform t = behaviour.GetComponent<Transform>();
// Do stuff with t
}
public static void Move(MonoBehaviour behaviour, float amount)
{
Transform t = behaviour.GetComponent<Transform>();
// Do stuff with t
}
}
Then in your monobehaviour...
public void Update()
{
Manipulators.Rotate(this, 15f);
Manipulators.Move(this, 15f);
}
Here are some tests I did on how Update() compares to some common Unity3d functions.
In the scene there are 10000 empty GameObjects with UpdateTest script on them.
The Average FPS value is smoothed heavily and I use the Mono backend.
So it seems the cost of Update() function is somewhat less than moving an empty GameObject with transform.localposition.
I've used Unity3d 2020.1.5
A legacy app is in an endless loop at startup; I don't know why/how yet (code obfuscation contest candidate), but regarding the method that's being called over and over (which is called from several other methods), I thought, "I wonder if one of the methods that calls this is also calling another method that also calls it?"
I thought: "Nah, the compiler would be able to figure that out, and not allow it, or at least emit a warning!"
So I created a simple app to prove that would be the case:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
method1();
}
private void button2_Click(object sender, EventArgs e)
{
method2();
}
private void method1()
{
MessageBox.Show("method1 called, which will now call method2");
method2();
}
private void method2()
{
MessageBox.Show("method2 called, which will now call method1");
// Note to self: Write an article entitled, "Copy-and-Paste Considered Harmful"
method1();
}
}
...but no! It compiles just fine. Why wouldn't the compiler flag this code as questionable at best? If either button is mashed, you are in never-never land!
Okay, sometimes you may want an endless loop (pacemaker code, etc.), but still I think a warning should be emitted.
As you said sometimes people want infinite loops. And the jit-compiler of .net supports tailcall optimization, so you might not even get a stack overflow for endless recursion like you did it.
For the general case, predicting whether or not a program is going to terminate at some point or stuck in an infinite loop is impossible in finite time. It's called the halting problem. All a compiler can possibly find are some special cases, where it is easy to decide.
That's not an endless loop, but an endless recursion. And this is much worse, since they can lead to a stack overflow. Endless recursions are not desired in most languages, unless you are programming malware. Endless loops, however, are often intentional. Services typically run in endless loops.
In order to detect this kind of situation, the compiler would have to analyze the code by following the method calls; however the C# compiler limits this process to the immediate code within the current method. Here, uninitialized or unused variables can be tracked and unreachable code can be detected, for instance. There is a tradeoff to make between the compiling speed and the depth of static analysis and optimizations.
Also it is hardly possible to know the real intention of the programmer.
Imagine that you wrote a method that is perfectly legal. Suddenly because you are calling this method from another place, your compiler complains and tells you that your method is no more legal. I can already see the flood of posts on SO like: "My method compiled yesterday. Today it does not compile any more. But I didn't change it".
To put it very simply: it's not the compiler's job to question your coding patterns.
You could very well write a Main method that does nothing but throw an Exception. It's a far easier pattern to detect and a much more stupid thing to do; yet the compiler will happily allow your program to compile, run, crash and burn.
With that being said, since technically an endless loop / recursion is perfectly legal as far as the compiler is concerned, there's no reason why it should complain about it.
Actually, it would be very hard to figure out at compile time that the loop can't ever be broken at runtime. An exception could be thrown, user interaction could happen, a state might change somewhere on a specific thread, on a port you are monitoring, etc... there's way too much possibilities for any code analysis tool out there to establish, without any doubt, that a specific recursing code segment will inevitably cause an overflow at runtime.
I think the right way to prevent these situations is through unit testing organization. The more code paths you are covering in your tests, the less likely you are to ever face such a scenario.
Because its nearly impossible to detect!
In the example you gave, it is obvious (to us) that the code will loop forever. But the compiler just sees a function call, it doesn't necessarily know at the time what calls that function, what conditional logic could change the looping behavior etc.
For example, with this slight change you aren't in an infinite loop anymore:
private bool method1called = false;
private void method1()
{
MessageBox.Show("method1 called, which will now call method2");
if (!method1called)
method2();
method1called = true;
}
private void method2()
{
MessageBox.Show("method2 called, which will now call method1");
method1();
}
Without actually running the program, how would you know that it isn't looping? I could potentially see a warning for while (true), but that has enough valid use cases that it also makes sense to not put a warning in for it.
A compiler is just parsing the code and translating to IL (for .NET anyways). You can get limited information like variables not being assigned while doing that (especially since it has to generate the symbol table anyways) but advanced detection like this is generally left to code analysis tools.
I found this on the Infinite Loop Wiki found here: http://en.wikipedia.org/wiki/Infinite_loop#Intentional_looping
There are a few situations when this is desired behavior. For example, the games on cartridge-based game consoles typically have no exit condition in their main loop, as there is no operating system for the program to exit to; the loop runs until the console is powered off.
Antique punchcard-reading unit record equipment would literally halt once a card processing task was completed, since there was no need for the hardware to continue operating, until a new stack of program cards were loaded.
By contrast, modern interactive computers require that the computer constantly be monitoring for user input or device activity, so at some fundamental level there is an infinite processing idle loop that must continue until the device is turned off or reset. In the Apollo Guidance Computer, for example, this outer loop was contained in the Exec program, and if the computer had absolutely no other work to do it would loop running a dummy job that would simply turn off the "computer activity" indicator light.
Modern computers also typically do not halt the processor or motherboard circuit-driving clocks when they crash. Instead they fall back to an error condition displaying messages to the operator, and enter an infinite loop waiting for the user to either respond to a prompt to continue, or to reset the device.
Hope this helps.
I understand the principle of coroutines. I know how to get the standard StartCoroutine / yield return pattern to work in C# in Unity, e.g. invoke a method returning IEnumerator via StartCoroutine and in that method do something, do yield return new WaitForSeconds(1); to wait a second, then do something else.
My question is: what's really going on behind the scenes? What does StartCoroutine really do? What IEnumerator is WaitForSeconds returning? How does StartCoroutine return control to the "something else" part of the called method? How does all this interact with Unity's concurrency model (where lots of things are going on at the same time without use of coroutines)?
The oft referenced Unity3D coroutines in detail link is dead. Since it is mentioned in the comments and the answers I am going to post the contents of the article here. This content comes from this mirror.
Unity3D coroutines in detail
Many processes in games take place over the course of multiple frames. You’ve got ‘dense’ processes, like pathfinding, which work hard each frame but get split across multiple frames so as not to impact the framerate too heavily. You’ve got ‘sparse’ processes, like gameplay triggers, that do nothing most frames, but occasionally are called upon to do critical work. And you’ve got assorted processes between the two.
Whenever you’re creating a process that will take place over multiple frames – without multithreading – you need to find some way of breaking the work up into chunks that can be run one-per-frame. For any algorithm with a central loop, it’s fairly obvious: an A* pathfinder, for example, can be structured such that it maintains its node lists semi-permanently, processing only a handful of nodes from the open list each frame, instead of trying to do all the work in one go. There’s some balancing to be done to manage latency – after all, if you’re locking your framerate at 60 or 30 frames per second, then your process will only take 60 or 30 steps per second, and that might cause the process to just take too long overall. A neat design might offer the smallest possible unit of work at one level – e.g. process a single A* node – and layer on top a way of grouping work together into larger chunks – e.g. keep processing A* nodes for X milliseconds. (Some people call this ‘timeslicing’, though I don’t).
Still, allowing the work to be broken up in this way means you have to transfer state from one frame to the next. If you’re breaking an iterative algorithm up, then you’ve got to preserve all the state shared across iterations, as well as a means of tracking which iteration is to be performed next. That’s not usually too bad – the design of an ‘A* pathfinder class’ is fairly obvious – but there are other cases, too, that are less pleasant. Sometimes you’ll be facing long computations that are doing different kinds of work from frame to frame; the object capturing their state can end up with a big mess of semi-useful ‘locals,’ kept for passing data from one frame to the next. And if you’re dealing with a sparse process, you often end up having to implement a small state machine just to track when work should be done at all.
Wouldn’t it be neat if, instead of having to explicitly track all this state across multiple frames, and instead of having to multithread and manage synchronization and locking and so on, you could just write your function as a single chunk of code, and mark particular places where the function should ‘pause’ and carry on at a later time?
Unity – along with a number of other environments and languages – provides this in the form of Coroutines.
How do they look?
In “Unityscript” (Javascript):
function LongComputation()
{
while(someCondition)
{
/* Do a chunk of work */
// Pause here and carry on next frame
yield;
}
}
In C#:
IEnumerator LongComputation()
{
while(someCondition)
{
/* Do a chunk of work */
// Pause here and carry on next frame
yield return null;
}
}
How do they work?
Let me just say, quickly, that I don’t work for Unity Technologies. I’ve not seen the Unity source code. I’ve never seen the guts of Unity’s coroutine engine. However, if they’ve implemented it in a way that is radically different from what I’m about to describe, then I’ll be quite surprised. If anyone from UT wants to chime in and talk about how it actually works, then that’d be great.
The big clues are in the C# version. Firstly, note that the return type for the function is IEnumerator. And secondly, note that one of the statements is yield
return. This means that yield must be a keyword, and as Unity’s C# support is vanilla C# 3.5, it must be a vanilla C# 3.5 keyword. Indeed, here it is in MSDN – talking about something called ‘iterator blocks.’ So what’s going on?
Firstly, there’s this IEnumerator type. The IEnumerator type acts like a cursor over a sequence, providing two significant members: Current, which is a property giving you the element the cursor is presently over, and MoveNext(), a function that moves to the next element in the sequence. Because IEnumerator is an interface, it doesn’t specify exactly how these members are implemented; MoveNext() could just add one toCurrent, or it could load the new value from a file, or it could download an image from the Internet and hash it and store the new hash in Current… or it could even do one thing for the first element in the sequence, and something entirely different for the second. You could even use it to generate an infinite sequence if you so desired. MoveNext() calculates the next value in the sequence (returning false if there are no more values), and Current retrieves the value it calculated.
Ordinarily, if you wanted to implement an interface, you’d have to write a class, implement the members, and so on. Iterator blocks are a convenient way of implementing IEnumerator without all that hassle – you just follow a few rules, and the IEnumerator implementation is generated automatically by the compiler.
An iterator block is a regular function that (a) returns IEnumerator, and (b) uses the yield keyword. So what does the yield keyword actually do? It declares what the next value in the sequence is – or that there are no more values. The point at which the code encounters a yield
return X or yield break is the point at which IEnumerator.MoveNext() should stop; a yield return X causes MoveNext() to return true andCurrent to be assigned the value X, while a yield
break causes MoveNext() to return false.
Now, here’s the trick. It doesn’t have to matter what the actual values returned by the sequence are. You can call MoveNext() repeatly, and ignore Current; the computations will still be performed. Each time MoveNext() is called, your iterator block runs to the next ‘yield’ statement, regardless of what expression it actually yields. So you can write something like:
IEnumerator TellMeASecret()
{
PlayAnimation("LeanInConspiratorially");
while(playingAnimation)
yield return null;
Say("I stole the cookie from the cookie jar!");
while(speaking)
yield return null;
PlayAnimation("LeanOutRelieved");
while(playingAnimation)
yield return null;
}
and what you’ve actually written is an iterator block that generates a long sequence of null values, but what’s significant is the side-effects of the work it does to calculate them. You could run this coroutine using a simple loop like this:
IEnumerator e = TellMeASecret();
while(e.MoveNext()) { }
Or, more usefully, you could mix it in with other work:
IEnumerator e = TellMeASecret();
while(e.MoveNext())
{
// If they press 'Escape', skip the cutscene
if(Input.GetKeyDown(KeyCode.Escape)) { break; }
}
It’s all in the timing
As you’ve seen, each yield return statement must provide an expression (like null) so that the iterator block has something to actually assign to IEnumerator.Current. A long sequence of nulls isn’t exactly useful, but we’re more interested in the side-effects. Aren’t we?
There’s something handy we can do with that expression, actually. What if, instead of just yielding null
and ignoring it, we yielded something that indicated when we expect to need to do more work? Often we’ll need to carry straight on the next frame, sure, but not always: there will be plenty of times where we want to carry on after an animation or sound has finished playing, or after a particular amount of time has passed. Those while(playingAnimation)
yield return null; constructs are bit tedious, don’t you think?
Unity declares the YieldInstruction base type, and provides a few concrete derived types that indicate particular kinds of wait. You’ve got WaitForSeconds, which resumes the coroutine after the designated amount of time has passed. You’ve got WaitForEndOfFrame, which resumes the coroutine at a particular point later in the same frame. You’ve got the Coroutine type itself, which, when coroutine A yields coroutine B, pauses coroutine A until after coroutine B has finished.
What does this look like from a runtime point of view? As I said, I don’t work for Unity, so I’ve never seen their code; but I’d imagine it might look a little bit like this:
List<IEnumerator> unblockedCoroutines;
List<IEnumerator> shouldRunNextFrame;
List<IEnumerator> shouldRunAtEndOfFrame;
SortedList<float, IEnumerator> shouldRunAfterTimes;
foreach(IEnumerator coroutine in unblockedCoroutines)
{
if(!coroutine.MoveNext())
// This coroutine has finished
continue;
if(!coroutine.Current is YieldInstruction)
{
// This coroutine yielded null, or some other value we don't understand; run it next frame.
shouldRunNextFrame.Add(coroutine);
continue;
}
if(coroutine.Current is WaitForSeconds)
{
WaitForSeconds wait = (WaitForSeconds)coroutine.Current;
shouldRunAfterTimes.Add(Time.time + wait.duration, coroutine);
}
else if(coroutine.Current is WaitForEndOfFrame)
{
shouldRunAtEndOfFrame.Add(coroutine);
}
else /* similar stuff for other YieldInstruction subtypes */
}
unblockedCoroutines = shouldRunNextFrame;
It’s not difficult to imagine how more YieldInstruction subtypes could be added to handle other cases – engine-level support for signals, for example, could be added, with a WaitForSignal("SignalName")YieldInstruction supporting it. By adding more YieldInstructions, the coroutines themselves can become more expressive – yield
return new WaitForSignal("GameOver") is nicer to read thanwhile(!Signals.HasFired("GameOver"))
yield return null, if you ask me, quite apart from the fact that doing it in the engine could be faster than doing it in script.
A couple of non-obvious ramifications
There’s a couple of useful things about all this that people sometimes miss that I thought I should point out.
Firstly, yield return is just yielding an expression – any expression – and YieldInstruction is a regular type. This means you can do things like:
YieldInstruction y;
if(something)
y = null;
else if(somethingElse)
y = new WaitForEndOfFrame();
else
y = new WaitForSeconds(1.0f);
yield return y;
The specific lines yield return new WaitForSeconds(), yield
return new WaitForEndOfFrame(), etc, are common, but they’re not actually special forms in their own right.
Secondly, because these coroutines are just iterator blocks, you can iterate over them yourself if you want – you don’t have to have the engine do it for you. I’ve used this for adding interrupt conditions to a coroutine before:
IEnumerator DoSomething()
{
/* ... */
}
IEnumerator DoSomethingUnlessInterrupted()
{
IEnumerator e = DoSomething();
bool interrupted = false;
while(!interrupted)
{
e.MoveNext();
yield return e.Current;
interrupted = HasBeenInterrupted();
}
}
Thirdly, the fact that you can yield on other coroutines can sort of allow you to implement your own YieldInstructions, albeit not as performantly as if they were implemented by the engine. For example:
IEnumerator UntilTrueCoroutine(Func fn)
{
while(!fn()) yield return null;
}
Coroutine UntilTrue(Func fn)
{
return StartCoroutine(UntilTrueCoroutine(fn));
}
IEnumerator SomeTask()
{
/* ... */
yield return UntilTrue(() => _lives < 3);
/* ... */
}
however, I wouldn’t really recommend this – the cost of starting a Coroutine is a little heavy for my liking.
Conclusion
I hope this clarifies a little some of what’s really happening when you use a Coroutine in Unity. C#’s iterator blocks are a groovy little construct, and even if you’re not using Unity, maybe you’ll find it useful to take advantage of them in the same way.
The first heading below is a straight answer to the question. The two headings after are more useful for the everyday programmer.
Possibly Boring Implementation Details of Coroutines
Coroutines are explained in Wikipedia and elsewhere. Here I'll just provide some details from a practical point of view. IEnumerator, yield, etc. are C# language features that are used for somewhat of a different purpose in Unity.
To put it very simply, an IEnumerator claims to have a collection of values that you can request one by one, kind of like a List. In C#, a function with a signature to return an IEnumerator does not have to actually create and return one, but can let C# provide an implicit IEnumerator. The function then can provide the contents of that returned IEnumerator in the future in a lazy fashion, through yield return statements. Every time the caller asks for another value from that implicit IEnumerator, the function executes till the next yield return statement, which provides the next value. As a byproduct of this, the function pauses until the next value is requested.
In Unity, we don't use these to provide future values, we exploit the fact that the function pauses. Because of this exploitation, a lot of things about coroutines in Unity do not make sense (What does IEnumerator have to do with anything? What is yield? Why new WaitForSeconds(3)? etc.). What happens "under the hood" is, the values you provide through the IEnumerator are used by StartCoroutine() to decide when to ask for the next value, which determines when your coroutine will unpause again.
Your Unity Game is Single Threaded (*)
Coroutines are not threads. There is one main loop of Unity and all those functions that you write are being called by the same main thread in order. You can verify this by placing a while(true); in any of your functions or coroutines. It will freeze the whole thing, even the Unity editor. This is evidence that everything runs in one main thread. This link that Kay mentioned in his above comment is also a great resource.
(*) Unity calls your functions from one thread. So, unless you create a thread yourself, the code that you wrote is single threaded. Of course Unity does employ other threads and you can create threads yourself if you like.
A Practical Description of Coroutines for Game Programmers
Basically, when you call StartCoroutine(MyCoroutine()), it's exactly like a regular function call to MyCoroutine(), until the first yield return X, where X is something like null, new WaitForSeconds(3), StartCoroutine(AnotherCoroutine()), break, etc. This is when it starts differing from a function. Unity "pauses" that function right at that yield return X line, goes on with other business and some frames pass, and when it's time again, Unity resumes that function right after that line. It remembers the values for all the local variables in the function. This way, you can have a for loop that loops every two seconds, for example.
When Unity will resume your coroutine depends on what X was in your yield return X. For example, if you used yield return new WaitForSeconds(3);, it resumes after 3 seconds have passed. If you used yield return StartCoroutine(AnotherCoroutine()), it resumes after AnotherCoroutine() is completely done, which enables you to nest behaviors in time. If you just used a yield return null;, it resumes right at the next frame.
It couldn't be simpler:
Unity (and all game engines) are frame based.
The whole entire point, the whole raison d'etre of Unity, is that it is frame based. The engine does things "each frame" for you. (Animates, renders objects, does physics, and so on.)
You might ask .. "Oh, that's great. What if I want the engine to do something for me each frame? How do I tell the engine to do such-and-such in a frame?"
The answer is ...
That's exactly what a "coroutine" is for.
It's just that simple.
A note on the "Update" function...
Quite simply, anything you put in "Update" is done every frame. It's literally exactly the same, no difference at all, from the coroutine-yield syntax.
void Update()
{
this happens every frame,
you want Unity to do something of "yours" in each of the frame,
put it in here
}
...in a coroutine...
while(true)
{
this happens every frame.
you want Unity to do something of "yours" in each of the frame,
put it in here
yield return null;
}
There is absolutely no difference.
Threads have utterly no connection to frames/coroutines, in any way. There is no connection whatsoever.
The frames in a game engine have utterly no connection to threads, in any way. They are completely, totally, utterly, unrelated issues.
(You often hear that "Unity is single-threaded!" Note that even that statement is very confused. Frames/coroutines just have absolutely no connection at all to threading. If Unity was multithreaded, hyperthreaded, or ran on a quantum computer!! ... it would just have no connection whatsoever to frames/coroutines. It is a completely, totally, absolutely, unrelated issue.)
If Unity was multithreaded, hyperthreaded, or ran on a quantum computer!! ... it would just have no connection whatsoever to frames/coroutines. It is a completely, totally, absolutely, unrelated issue.
So in summary...
So, Coroutines/yield are simply how you access the frames in Unity. That's it.
(And indeed, it's absolutely the same as the Update() function provided by Unity.)
That's all there is to it, it's that simple.
Why IEnumerator?
Couldn't be simpler: IEnumerator returns things "over and over".
(That list of things can either have a specific length, such as "10 things," or the list can go on forever.)
Thus, self-evidently, an IEnumerator is what you would use.
Anywhere in .Net you want to "return over and over," IEnumerator exists for this purpose.
All frame-based computing, with .Net, of course uses IEnumerator to return each frame. What else could it use?
(If you are new to C#, note that IEnumerator is also used for returning "ordinary" things one by one, such as simply the items in an array, etc.)
Have dig into this lately, wrote a post here - http://eppz.eu/blog/understanding-ienumerator-in-unity-3d/ - that shed a light on the internals (with dense code examples), the underlying IEnumerator interface, and how it is used for coroutines.
Using collection enumerators for this purpose still seems a bit weird for me. It is the inverse of what enumerators feels designed for. The point of enumerators is the returned value on every access, but the point of Coroutines is the code in-between the value returns. The actual returned value is pointless in this context.
On Unity 2017+, you can use the native C# async/await keywords for async code, but before that, C# had no native way to implement async code.
Unity had to use a workaround for async code. They achieved this by exploiting the C# iterators, which was a popular async technique at the time.
A look at C# Iterators
Let's say you have this code:
IEnumerable SomeNumbers() {
yield return 3;
yield return 5;
yield return 8;
}
If you run it through a loop, calling as if was an array, you will get 3 5 8:
// Output: 3 5 8
foreach (int number in SomeNumbers()) {
Console.Write(number);
}
If you are not familiar with iterators (most languages have them to implement lists and collections), they work as an array. The difference is that a callback generates the values.
How do they work?
When looping through an iterator on C#, we use MoveNext to go to the next value.
In the example, we are using foreach, which calls this method under the hood.
When we call MoveNext, the iterator executes everything until its next yield. The parent caller gets the value returned by yield. Then, the iterator code pauses, waiting for the next MoveNext call.
Because of their "lazy" capability, C# programmers used iterators to run async code.
Asynchronous Programming in C# using Iterators
Before 2012, using iterators was a popular hack to perform asynchronous operations in C#.
Example - Asynchronous download function:
IEnumerable DownloadAsync(string URL) {
WebRequest req = HttpWebRequest.Create(url);
WebResponse response = req.GetResponseAsync();
yield return response;
Stream resp = response.Result.GetResponseStream();
string html = resp.ReadToEndAsync().ExecuteAsync();
yield return html;
Console.WriteLine(html.Result);
}
PS: The code above is from this excellent, yet old, article about Async programming using iterators:
http://tomasp.net/blog/csharp-async.aspx/
Should I use async instead of StartCoroutine?
As for 2021, the official Unity docs use coroutines on their examples and not async.
Also, the community seems to be more in favor of coroutines instead of async:
Developers are familiar with coroutines;
Coroutines are integrated with Unity;
And others;
I recommend this Unity lecture from 2019, "Best practices: Async vs. coroutines - Unite Copenhagen 2019": https://youtu.be/7eKi6NKri6I
PS: This is an old question from 2012, but I'm answering it because it is still relevant in 2021.
The basis functions in Unity that you get automatically are the Start() function and the Update() function, so Coroutine's are essentially functions just like the Start() and Update() function. Any old function func() can be called the same way a Coroutine can be called. Unity has obviously set certain boundaries for Coroutines that make them different than regular functions.
One difference is instead of
void func()
You write
IEnumerator func()
for coroutines.
And the same way you can control the time in normal functions with code lines like
Time.deltaTime
A coroutine has a specific handle on the way time can be controlled.
yield return new WaitForSeconds();
Although this is not the only thing possible to do inside of an IEnumerator/Coroutine, it is one of the useful things that Coroutines are used for. You would have to research Unity's scripting API to learn other specific uses of Coroutines.
StartCoroutine is a method to call a IEnumerator function. It is similar to just calling a simple void function, just the difference is that you use it on IEnumerator functions. This type of function is unique as it can allow you to use a special yield function, note that you must return something. Thats as far as I know.
Here I wrote a simple flicker game Over text method in unity
public IEnumerator GameOver()
{
while (true)
{
_gameOver.text = "GAME OVER";
yield return new WaitForSeconds(Random.Range(1.0f, 3.5f));
_gameOver.text = "";
yield return new WaitForSeconds(Random.Range(0.1f, 0.8f));
}
}
I then called it out of the IEnumerator itself
public void UpdateLives(int currentlives)
{
if (currentlives < 1)
{
_gameOver.gameObject.SetActive(true);
StartCoroutine(GameOver());
}
}
As you can see how I used the StartCoroutine() method.
Hope I helped somehow. I am a begainner myself, so if you correct me, or apprecite me, any type of a feedback would be great.