协程
所谓协程,就是return一个IEnumerator的一个函数
这个函数之后每次都会返回Update都会被调用知道返回,也就是在一个函数里面安排下未来的很多个动作,这种方式可以实现位移动画
协程等待一段时间的实现方案
Wait系列:
- WaitForEndOfFrame
- WaitForFixedUpdate
- WaitForSeconds
- WaitForSecondsRealtime
- WaitUntil
- WaitWhile
一、使用WaitForSeconds类
yield return 一个WaitForSeconds类,这样调度的时候就会等待一段时间。
让物体旋转90度,然后休息4秒钟。接着再旋转,再休息。
void Start()
{
StartCoroutine(waiter());
}
IEnumerator waiter()
{
//Rotate 90 deg
transform.Rotate(new Vector3(90, 0, 0), Space.World);
//Wait for 4 seconds
yield return new WaitForSeconds(4);
//Rotate 40 deg
transform.Rotate(new Vector3(40, 0, 0), Space.World);
//Wait for 2 seconds
yield return new WaitForSeconds(2);
//Rotate 20 deg
transform.Rotate(new Vector3(20, 0, 0), Space.World);
}
二、使用WaitForSecondsRealtime
这个类与WaitForSeconds的唯一区别就是这个类使用了无所方的时间来等待,这就意味着当游戏挂起Time.timeScale的时候,WaitForSecondsRealtime函数并不会受到Time.timeScale的影响,而WaitForSecond会受到影响。
示例代码如下:
void Start()
{
StartCoroutine(waiter());
}
IEnumerator waiter()
{
//Rotate 90 deg
transform.Rotate(new Vector3(90, 0, 0), Space.World);
//Wait for 4 seconds
yield return new WaitForSecondsRealtime(4);
//Rotate 40 deg
transform.Rotate(new Vector3(40, 0, 0), Space.World);
//Wait for 2 seconds
yield return new WaitForSecondsRealtime(2);
//Rotate 20 deg
transform.Rotate(new Vector3(20, 0, 0), Space.World);
}
三、使用deltaTime
相当于自己写一个wait函数,使用float变量记录已经过去的时间,累加Time.deltaTime。
也可以使用Time.realTimeSinceStartup来表示,这样就不用累加了。
这种方式依赖Update的频率,因此准确性可能相比上面那种方式差一些。
bool quit = false;
void Start()
{
StartCoroutine(waiter());
}
IEnumerator waiter()
{
//Rotate 90 deg
transform.Rotate(new Vector3(90, 0, 0), Space.World);
//Wait for 4 seconds
float waitTime = 4;
yield return wait(waitTime);
//Rotate 40 deg
transform.Rotate(new Vector3(40, 0, 0), Space.World);
//Wait for 2 seconds
waitTime = 2;
yield return wait(waitTime);
//Rotate 20 deg
transform.Rotate(new Vector3(20, 0, 0), Space.World);
}
IEnumerator wait(float waitTime)
{
float counter = 0;
while (counter < waitTime)
{
//Increment Timer until counter >= waitTime
counter += Time.deltaTime;
Debug.Log("We have waited for: " + counter + " seconds");
if (quit)
{
//Quit function
yield break;
}
//Wait for a frame so that Unity doesn't freeze
yield return null;
}
}
四、使用WaitUtil等待某个条件的满足
float playerScore = 0;
int nextScene = 0;
void Start()
{
StartCoroutine(sceneLoader());
}
IEnumerator sceneLoader()
{
Debug.Log("Waiting for Player score to be >=100 ");
yield return new WaitUntil(() => playerScore >= 10);
Debug.Log("Player score is >=100. Loading next Level");
//Increment and Load next scene
nextScene++;
SceneManager.LoadScene(nextScene);
}
五、使用WaitWhile
WaitWhile语义上与WaitUtil恰好相反。
void Start()
{
StartCoroutine(inputWaiter());
}
IEnumerator inputWaiter()
{
Debug.Log("Waiting for the Exit button to be pressed");
yield return new WaitWhile(() => !Input.GetKeyDown(KeyCode.Escape));
Debug.Log("Exit button has been pressed. Leaving Application");
//Exit program
Quit();
}
void Quit()
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
六、使用Invoke函数
Invoke(函数名称,等待的秒数)
void Start()
{
Invoke("feedDog", 5);
Debug.Log("Will feed dog after 5 seconds");
}
void feedDog()
{
Debug.Log("Now feeding Dog");
}
在total帧内把一个游戏物体直线匀速移动到某个位置
这种写法存在的问题:移动的快慢依赖于帧数,如果帧率高,物体移动得就很快;如果帧率低,物体移动得就很慢。因此,最好是依赖于时间去移动物体。
//在total帧内移动到位置p
public static IEnumerator moveTo(GameObject gameObject, Vector3 p, int total)
{
var o = gameObject.transform.position;
for (var i = 1; i < total; i++)
{
var ratio = i * 1.0f / total;
gameObject.transform.position = p * ratio + o * (1 - ratio);
yield return null;
}
gameObject.transform.position = p;
}
在一个duration时间段内,插入frameCount帧把物体移动到某个位置
public static IEnumerator moveTo(GameObject gameObject, Vector3 p, float duration, int frameCount)
{
var o = gameObject.transform.position;
int total = frameCount;
var startTime = Time.realtimeSinceStartup;
for (var i = 1; i < total; i++)
{
while (true)
{
var ratio = (Time.realtimeSinceStartup - startTime) / duration;
if (ratio > 1.0f * i / total) break;
yield return null;
}
{
var ratio = (Time.realtimeSinceStartup - startTime) / duration;
if (ratio < 0) ratio = 0f;
if (ratio > 1) break;
gameObject.transform.position = p * ratio + o * (1 - ratio);
yield return null;
}
}
gameObject.transform.position = p;
}
MonoBehaviour的两个关键函数
- Invoke:在一段时间后定时执行
- StartCoroutine:开启一个协程执行任务
拼接IEnumerator
以下这种写法,使用while()遍历IEnumerator。
public void mergeGrow()
{
//发生合并的时候,先变大再变小
StartCoroutine("mergeGrowCoroutine");
}
private IEnumerator mergeGrowCoroutine()
{
//a是一个IEnumerator类型的对象
var a = AnimateUtil.growAndShrink(gameObject, defaultScale * 1.2f, defaultScale, 0.4f, 25);
while (a.MoveNext()) yield return null;
}
实际上,可以简写为yield return <一个IEnumerator对象的形式>
private IEnumerator mergeGrowCoroutine()
{
yield return AnimateUtil.growAndShrink(gameObject, defaultScale * 1.2f, defaultScale, 0.4f, 25);
}