协程

所谓协程,就是return一个IEnumerator的一个函数
这个函数之后每次都会返回Update都会被调用知道返回,也就是在一个函数里面安排下未来的很多个动作,这种方式可以实现位移动画

协程等待一段时间的实现方案

参考资料:https://stackoverflow.com/questions/30056471/how-to-make-the-script-wait-sleep-in-a-simple-way-in-unity

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);
}