协程
所谓协程,就是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);
}