Table of contents
First, preamble:
Coroutine is a very important concept in Unity. We know that when using Unity for game development, multithreading is generally (note that it is generally) not considered. So how to deal with some requirements outside the main task? Unity provides us with the coroutine method.
Why is multithreading generally not considered in Unity?
Because in Unity, you can only get the components, methods, and objects of objects in the main thread. If you leave these, many functions of Unity cannot be realized, so the existence of multithreading is meaningless.
What is the difference between a thread and a coroutine?
For a coroutine, only one coroutine can be executed at the same time, while a thread is concurrent and multiple threads can be running at the same time.
The two use the same memory, sharing the heap but not the stack.
In fact, the most critical and simple difference between the two is that threads are parallel (for multi-core CPUs) at the micro level, while coroutines are serial. If you don't understand, it doesn't matter. You will understand it through the following explanation.
2. About coroutines
1. What is a coroutine?
Coroutine, literally means assisting program. While the main task is in progress, we need some branch tasks to cooperate to achieve the final effect. Let me explain it in a slightly more vivid way. Imagine that in the process of performing the main task, we need an operation that consumes a lot of resources. If such an operation is implemented in one frame, the game will become very stuck. At this time, we can use the coroutine to complete the processing of this work within a certain frame without affecting the progress of the main task.
2. The principle of coroutine
First of all, you need to understand that coroutines are not threads. Coroutines are still carried out in the main thread. Then you need to know that coroutines implement their functions through iterators. Use the keyword IEnumerator to define an iteration method. Note that IEnumerator is used, not IEnumerable.
The difference between the two:
IEnumerator: An interface that implements iterator functions
IEnumerable: An encapsulation interface based on IEnumerator. There is a GetEnumerator() method that returns IEnumerator
in the iterator. The most important thing is the use of yield, which is the main way to implement our coroutine functions. Through this key method, the coroutine can be paused, the time and location of the next startup can be recorded, and so on.
For a detailed explanation of iterators, please refer to: C# official documentation for a detailed description of iterators .
3. Use of coroutines
Misunderstanding:
If a coroutine runs almost every frame and does not pause during long-running operations, it is usually more reasonable to replace the coroutine with Update or LateUpdate callbacks. For example, long-running or infinitely looping coroutines.
Minimize nesting as much as possible: Although nested coroutines are very helpful for ensuring code organization and maintenance, the coroutine tracking objects themselves will cause higher memory overhead.
1. Functional method
Use the transfer function to start the coroutine:
StartCoroutine(Cor_1());
Stop coroutine: (❎ Wrong usage 1)
StopCoroutine(Cor_1());
I did this when I first started learning, but for some reason it just didn't work. Later I realized that although the same function name was passed, the address passed when stopping was not the address passed when starting.
Stop coroutine: (❎ Wrong usage 2)
StopCoroutine(”Cor_1“);
Incorrect usage by novices: Start the coroutine by passing a function, and stop the coroutine by passing a string.
So how can we manually stop a coroutine if we start it using StartCoroutine(Cor_1());? Please read on...
2. Function name method
Start the coroutine by passing the function name:
StartCoroutine("Cor_1");
Stop the coroutine:
StopCoroutine(”Cor_1“);
There is no problem using it this way (I guess the internal implementation is saved in the form of <Key, Value>).
Disadvantage: Only supports passing one parameter.
From the first and second methods, we can conclude that it is only feasible to open and close it by function name, but this does not solve the problem left in our method one. Please continue reading...
3. Receive return value
No matter which of the following methods is used to start the coroutine, its return value can be used to stop the corresponding coroutine;
private Coroutine stopCor_1;
private Coroutine stopCor_2;
stopCor_1 = StartCoroutine("Cor_1");
stopCor_2 = StartCoroutine(Cor_2());
Stop the coroutine:
StopCoroutine(stopCor_1);
StopCoroutine(stopCor_2);
Using this method of receiving return values, we can stop the coroutine according to our needs;
this solves the problems left in methods 1 and 2.
4、StopAllCoroutines
Start a coroutine in any way
StartCoroutine("Cor_1");
StartCoroutine(Cor_2());
You can use StopAllCoroutines to stop
StopAllCoroutines();
StopAllCoroutines() stops all coroutines in the current script.
Note:
It is recommended to use it with caution, because a new coroutine may be created when the logic is modified later, and it may be stopped when it does not need to be stopped (don't ask me how I know).
It is necessary to make sure that all coroutines that call the script need to be terminated (for example: all states need to be reset when disconnecting and reconnecting)
5. Disable/Destroy Game Objects
Disabling/destroying a game object stops the coroutine. When the object is activated again, the coroutine will not continue to execute.
gameObject.SetActive(false);
//通过销毁游戏对象方式和禁用同效果
//Destroy(gameobject)
That's not it:
script.enabled = false;
That is, hide the game object mounted by the script (the same is true when its parent object is hidden).
6. Introduction to yield return:
yield return null; // 下一帧再执行后续代码
yield return 0; //下一帧再执行后续代码
yield return 6;//(任意数字) 下一帧再执行后续代码
yield break; //直接结束该协程的后续操作
yield return asyncOperation;//等异步操作结束后再执行后续代码
yield return StartCoroution(/*某个协程*/);//等待某个协程执行完毕后再执行后续代码
yield return WWW();//等待WWW操作完成后再执行后续代码
yield return new WaitForEndOfFrame();//等待帧结束,等待直到所有的摄像机和GUI被渲染完成后,在该帧显示在屏幕之前执行
yield return new WaitForSeconds(0.3f);//等待0.3秒,一段指定的时间延迟之后继续执行,在所有的Update函数完成调用的那一帧之后(这里的时间会受到Time.timeScale的影响);
yield return new WaitForSecondsRealtime(0.3f);//等待0.3秒,一段指定的时间延迟之后继续执行,在所有的Update函数完成调用的那一帧之后(这里的时间不受到Time.timeScale的影响);
yield return WaitForFixedUpdate();//等待下一次FixedUpdate开始时再执行后续代码
yield return new WaitUntil()//将协同执行直到 当输入的参数(或者委托)为true的时候....如:yield return new WaitUntil(() => frame >= 10);
yield return new WaitWhile()//将协同执行直到 当输入的参数(或者委托)为false的时候.... 如:yield return new WaitWhile(() => frame < 10);
4. Summary:
- If it is started using the StartCoroutine(function()); form, it can only be stopped by receiving the return value; [No limit on the number of parameters]
- If you start it with StartCoroutine("function name");, you can stop it with StopCoroutine("function name"); or you can stop it with the return value. [Disadvantage: only one parameter can be passed]
- Both open forms are controlled by StopAllCoroutines(). StopAllCoroutines() can stop all coroutines in the current script.
- gameObject.SetActive(false); can stop all coroutines on this GameObject, and the coroutine will not continue when activated again.
- StopCoroutine(function()); script.enabled = false; The coroutine cannot be stopped.
5. Notes
In actual projects, coroutines should be used with caution, as their use will affect performance.