Unityにおけるポーズ処理の実装

#はじめに
Unityでゲームのポーズメニューを実装するためにはポーズ処理が必要である。ネットでいろいろ調べたが丁度いいのが見つからなかったので自分で書いた。
今回の方法はRigidbody2Dを使うことが前提なので2Dゲームにしか使えない。
#基本実装
基本的な仕様は[こちらの記事](http://ftvoid.com/blog/post/660)と同じ。behaviourを継承しているコンポーネントをdisableにする。
ただこの方法だとRigidbodyが停止しないので処理を追加する必要がある。Unity 5.5以降ではRigidbody2Dにsimulatedというbehaviourのenabledのようなメンバがあるので上の記事とほぼ同じ方法でポーズできる。

``` C#
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
 
public class Pauser : MonoBehaviour {
	static List<Pauser> targets = new List<Pauser>();	// ポーズ対象のスクリプト
    Rigidbody2D[] pauseRB = null; //ポーズ対象のRigidbody

    bool isPause = false;
 
	// 初期化
	void Awake() {
		// ポーズ対象に追加する
		targets.Add(this);
	}
 
	// 破棄されるとき
	void OnDestory() {
		// ポーズ対象から除外する
		targets.Remove(this);
	}

	// ポーズされたとき
	void OnPause() {
		if ( isPause ) {
			return;
		}

        pauseRB = Array.FindAll(GetComponentsInChildren<Rigidbody2D>(), (obj) => { return obj.simulated; });
        foreach ( var com in pauseRB ) {
			if(com != null)com.simulated = false;
		}
        
        isPause = true;
	}
 
	// ポーズ解除されたとき
	void OnResume() {
		if ( !isPause ) {
			return;
		}
 
		
        foreach ( var com in pauseRB ) {
			if(com != null)com.simulated = true;
		}

        pauseRB = null;
        isPause = false;
	}
 
	// ポーズ
	public static void Pause() {
		foreach ( var obj in targets ) {
			if(obj != null) obj.OnPause();
		}
	}
 
	// ポーズ解除
	public static void Resume() {
		foreach ( var obj in targets ) {
			if(obj != null) obj.OnResume();
		}
	}
}
```
#問題点
使ってみたところ問題が発生した。まず、コルーチンが止まらない。
下のように対策として停止するコンポーネントのOnDisableで止める。

```C#
    IEnumerator hoge;
    void OnDisable() {
        if( hoge != null )
        {
            StopCoroutine(hoge);
        }
    }
 
    void OnEnable() {
        if( hoge != null )
        {
            StartCoroutine(hoge);
        }
    }
```
これで何とかなるかと思ったがまだ上手くいかない。色々調査して分かったが、WaitforSecondsのカウントが止まってない。そこでポーズ中にカウントが止まるWaitメソッドをカスタムコルーチンで実装した。
PauserのStaticIsPauseというboolメンバがfalseのときのみカウントを進める仕様。


```C#
namespace PauserFunctions
{
    public class PausableWaitForFrames : CustomYieldInstruction
    {
        private float waitTime; //実際の待ち時間
        IEnumerator wait;

        public PausableWaitForFrames(int frame)
        {
            wait = Wait(frame);
        }

        public override bool keepWaiting
        {
            get { return wait.MoveNext(); }
        }

        IEnumerator Wait(int frame)
        {
            int i = 0;
            while (i < frame)
            {
                yield return null;
                if (!Pauser.StaticIsPause) i++;
            }
        }
    }


    public class PausableWaitForSeconds : CustomYieldInstruction
    {
        private float waitTime; //実際の待ち時間
        IEnumerator wait;

        public PausableWaitForSeconds(float time)
        {
            wait = Wait(time);
        }

        public override bool keepWaiting
        {
            get { return wait.MoveNext(); }
        }

        IEnumerator Wait(float time)
        {
            float t = 0;
            while (t < time)
            {
                yield return null;
                if (!Pauser.StaticIsPause) t += Time.deltaTime;
            }
        }
    }
}
```
問題を起こしているWaitforSecondsを上記のPausableWaitforSecondsに置き換えることでちゃんと止まるようになった。

#まとめ
全部まとめたコード。
コルーチンはBehaviourのOnDisableで止める。
WaitforSecondsを上記のPausableWaitforSecondsに置き換える。

```C#
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;

public class Pauser : MonoBehaviour
{
    static List<Pauser> targets = new List<Pauser>();   // ポーズ対象のスクリプト
    Behaviour[] pauseBehavs = null;	// ポーズ対象のコンポーネント
    Rigidbody2D[] pauseRB = null; //ポーズ対象のRigidbody

    bool isPause = false;
    public static bool StaticIsPause = false;

    // 初期化
    void Awake()
    {
        // ポーズ対象に追加する
        targets.Add(this);
    }

    // 破棄されるとき
    void OnDestory()
    {
        // ポーズ対象から除外する
        targets.Remove(this);
    }

    // ポーズされたとき
    void OnPause()
    {
        if (isPause)
        {
            return;
        }

        // 有効なBehaviourを取得
        pauseBehavs = Array.FindAll(GetComponentsInChildren<Behaviour>(), (obj) => { return obj.enabled; });
        pauseRB = Array.FindAll(GetComponentsInChildren<Rigidbody2D>(), (obj) => { return obj.simulated; });

        foreach (var com in pauseBehavs)
        {
            if (com != null) com.enabled = false;
        }
        foreach (var com in pauseRB)
        {
            if (com != null) com.simulated = false;
        }

        isPause = true;
    }

    // ポーズ解除されたとき
    void OnResume()
    {
        if (!isPause)
        {
            return;
        }

        // ポーズ前の状態にBehaviourの有効状態を復元
        foreach (var com in pauseBehavs)
        {
            if (com != null) com.enabled = true;
        }
        foreach (var com in pauseRB)
        {
            if (com != null) com.simulated = true;
        }

        pauseBehavs = null;
        pauseRB = null;


        isPause = false;
    }

    // ポーズ
    public static void Pause()
    {
        foreach (var obj in targets)
        {
            if (obj != null) obj.OnPause();
        }
        StaticIsPause = true;
    }

    // ポーズ解除
    public static void Resume()
    {
        foreach (var obj in targets)
        {
            if (obj != null) obj.OnResume();
            StaticIsPause = false;
        }
    }
}

namespace PauserFunctions
{
    public class PausableWaitForFrames : CustomYieldInstruction
    {
        private float waitTime; //実際の待ち時間
        IEnumerator wait;

        public PausableWaitForFrames(int frame)
        {
            wait = Wait(frame);
        }

        public override bool keepWaiting
        {
            get { return wait.MoveNext(); }
        }

        IEnumerator Wait(int frame)
        {
            int i = 0;
            while (i < frame)
            {
                yield return null;
                if (!Pauser.StaticIsPause) i++;
            }
        }
    }


    public class PausableWaitForSeconds : CustomYieldInstruction
    {
        private float waitTime; //実際の待ち時間
        IEnumerator wait;

        public PausableWaitForSeconds(float time)
        {
            wait = Wait(time);
        }

        public override bool keepWaiting
        {
            get { return wait.MoveNext(); }
        }

        IEnumerator Wait(float time)
        {
            float t = 0;
            while (t < time)
            {
                yield return null;
                if (!Pauser.StaticIsPause) t += Time.deltaTime;
            }
        }
    }
}
```