やりたかったこと
ゲーム部分を徐々に停止させて、ポーズメニューを表示したかった。
諦めた方法
一般にゲーム部分の進行を止めてポーズメニューなどを表示したい場合、以下のどちらかが考えられる。
※以下の方法では要件を実現することができなかったため、今回は不採用。
- Time.timeScaleを使用する
- 更新処理(コルーチンが走る場合も含む)が走るコンポーネントをenable=falseする
Time.timeScaleを使用する
メリット:徐々に動作をゆっくりにし、停止する動作を実現できる
デメリット:全てのオブジェクトの動作が停止する(UIのアニメーションは停止させたくない)
更新処理(コルーチンが走る場合も含む)が走るコンポーネントをenable=falseする
メリット:一部のオブジェクト(ゲームの要素だけなど)を指定して停止させることができる
デメリット:動作させる、停止させるの二通りしかないので徐々に停止させることができない
やったこと
ゲーム部分に適用するTime.timeScaleもどきを作成することで実装した。
public class Timer
{
public float deltaTime { get; private set; }
public float time { get; private set; }
public float timeScale { get; set; }
public float prevTime;
public Timer ()
{
time = Time.unscaledTime;
Update();
timeScale = 1;
}
// LateUpdateなどで呼んでやる
public void Update ()
{
deltaTime = ( Time.unscaledTime - prevTime ) * timeScale;
time += deltaTime;
prevTime = Time.unscaledTime;
}
}
Q&A 1. コルーチンにtimeScaleが適用されない
本来コルーチンでWaitForSecond等を使用した場合もtimeScaleの影響を受けるが、独自で用意したものになるので影響させることができないことに注意する必要がある。
WaitForSecondに独自クラスのdeltaTimeを掛けてあげることで解決できる。(あまりスマートではない)
// どこかにインスタンスを保持していると仮定
// var myTimer = new Timer();
private IEnumerator インコにヘッドホンのもふもふかじられて辛い()
{
// 行き場のない気持ちに悶え3秒停止する
yield return new WaitForSecond(3.0f * myTimer.timeScale);
}
Q&A 2. Update内のRandom.RangeによってtimeScale=0でも動いてしまう
これについては通常のTime.timeScaleを変更した場合にも起きる問題。
徐々に停止させている時にランダムの値も小さくなってよいならtimeScaleをかけることで解決できる。
だめなら以下のような形で解決することができる。
(動作効率が著しく悪いです。教えてエロい人!)
// どこかにインスタンスを保持していると仮定
// var myTimer = new Timer();
private void Update ()
{
// timeScaleが減少していくにつれて抽選の当選確率が下がる
// timeScale=1のとき必ず当選する
// timeScale=0のとき必ず落選する
List<bool> lotTable = new List<bool>();
for ( int i = 0; i < 100; i++ )
{
lotTable.Add( i < 100 * myTimer.timeScale );
}
// くじ引きして当選していたらランダム値を獲得する
float result = 0f;
if ( lotTable.OrderBy( i => Guid.NewGuid() ).ElementAt( 0 ) )
{
result = Random.Range(0, 1);
}
}