僕らは偶然性を大切にするためにクソゲーを作り続ける。

なんか気ままにクソゲームを作っています。

Unityでとりあえずポーズ画面を作りたい

こんにちはむさしです。 僕の担当は一応イラスト関連なんてすが、Unityも少しづつ勉強しています。 勉強していてこんな情報欲しいなとか思ったことなどを色々書いていきたいと思います。

1. ものすごい簡単なポーズ画面の作り方

UnityのHowTo本とか読んでも中々ポーズ画面の作り方が載っていません。少なくとも僕は見たことがありません!

このポーズ画面は色々と使えて、格ゲーなどのカットインエフェクトやRPGのお店の画面などの処理にも使える重要な機能の一つです。 ポーズ画面等の処理は本質的に言うと今あるシーンから一時的に(擬似的な)別のシーンに移動して再び元の状態に戻る処理の事です。だから、ただのシーンの切り替えと同じ処理をしては元の状態には戻れません。なぜなら、切り替える際にキャラクターの状態パラメータなどが保管している必要があるからです。 今回は自分が色々調べたり人から聞いたりしたポーズ画面についてのまとめを書いていきます。

一番多いやり方はTime.timescale = 0にすることです。これをすることでアニメーションやリジッドボディなどほとんどのものを止めてしまいます。及ぼす効果の範囲が大きく当然問題も発生します。後半にそれについて話します。 デフォルトではTime.timescaleの値は1でその値を小さくすることでスローモーションに、逆に値を大きくすることで早送り状態になります。 以下のように実装するのが一般的な方法です。

まず、上下に運動するハッピーフェイスを作ります。 ハッピーフェイスはこれです。ニコちゃんマークです。

f:id:playwao:20150227180341p:plain

僕はアメリカかぶれなんでハッピーフェイスと呼んでいます(笑)

まずスクリーンとアセットはこんな感じになります。

f:id:playwao:20150227181115p:plain

f:id:playwao:20150227180956p:plain

なんとなくわかるように同じ名前なやつを同じ名前のスクリプトコンポーネントしています。 例えばhappyface1にhappyface1.csをコンポーネントしています。 happyface1とhappyface2、pause1とpause2がそれぞれ2つあるのはTime.timescale=0にすることによって発生する現象を説明するためです。

まずはhappyface2.csを以下のような感じにします。

public class happyface2 : MonoBehaviour {
    float dy = 1f;

    void Start () {
        dy = Time.deltaTime * 1f;
    }
    
    void FixedUpdate () {
        Vector2 v = transform.position;
        v.y += dy;
        if (v.y > 3f) {
            v.y = 3f;
            dy *= -1;
        }
        if (v.y < -3f) {
            v.y = -3f;
            dy *= -1;
        }
        transform.position = v;
    }
}

Time.deltaTimeについては後々説明します。とりあえずそんなもんなんだなていどの認識でいいです。 FixedUpdateを使ってhappyfaceが上下に移動するようにしています。

次にpause2.csを作ります。 僕は面倒なんでスプライトをそのままボタンにします。 NGUIとか使うとかっこいいと思います。 スクリプトは以下のようになります。

public class Pause2 : MonoBehaviour {

    void Start () {
    }
    
    void Update () {
        if (Input.GetMouseButtonDown(0)) {
            Vector2 pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            Collider2D c = Physics2D.OverlapPoint(pos);
            if (c == this.collider2D) {
                if (Time.timeScale == 0) {
                    Time.timeScale = 1;
                } else {
                    Time.timeScale = 0;
                }
            }
        }
    }
}

マウスでクリックしてそれがオブジェクト(今回はボタンのスプライト)のcolliderに当たったらTime.timeScaleが1になら0に、0なら1になるようにしています。 つまり動いている時に押すと止まり、止まっている時に押すと動き出します。

…が、これではこのままではうまくいかない場合もあります。 たとえばhappyface1.csをこんな感じにすると…

public class happyface1 : MonoBehaviour {
    float dy = 1f;

    void Start () {
        dy = Time.deltaTime * 1f;
    }
    
    void Update () {
        Vector2 v = transform.position;
        v.y += dy;
        if (v.y > 3f) {
            v.y = 3f;
            dy *= -1;
        }
        if (v.y < -3f) {
            v.y = -3f;
            dy *= -1;
        }
        transform.position = v;
    }
}

先ほどのポーズでは止まりません。 初めの方にTime.timescale = 0にすることでほとんどのものの動きを止めてしまうと説明しましたが、実はUpdateは実行できます。 これも止めたい場合は止めたい対象にポーズフラグを入れればいいです。 今回の場合だとハッピーフェイスのUpdateにポーズフラグを入れる必要があります。 では、Updateも止めることができるボタンを実装しましょう。 pause1.csのスクリプトを以下のものにします。

public class Pause1 : MonoBehaviour {
    public bool is_pause = false;

    void Start () {
    }
    
    void Update () {
        if  (Input.GetMouseButtonDown(0)) {
            Vector2 pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            Collider2D c = Physics2D.OverlapPoint(pos);
            if (c == this.collider2D) {
                if (is_pause) {
                    Time.timeScale = 1;
                    is_pause = false;
                } else {
                    Time.timeScale = 0;
                    is_pause = true;
                }
            }
        }
    }
}

今現在ポーズかどうかをis_pauseで示しています。 それ以外にもう一度ボタンを押したらポーズが解除されるようにしました。 一方、ハッピーフェイスはポーズフラグを利用してreturnを返すようにします。 次にhappyface1.csを

public class happyface1 : MonoBehaviour {
    public Pause1 pause;
    float dy = 1f;

    void Start () {
        dy = Time.deltaTime * 1f;
        this.pause = GameObject.FindGameObjectWithTag("Pause1").GetComponent<Pause1>();
    }
    
    void Update () {
        if (this.pause.is_pause) {
            return;
        } else {
            Vector2 v = transform.position;
            v.y += dy;
            if (v.y > 3f) {
                v.y = 3f;
                dy *= -1;
            }
            if (v.y < -3f) {
                v.y = -3f;
                dy *= -1;
            }
            transform.position = v;
        }
    }
}

この時、ポーズボタンのタグとスクリプトの名前を「Pause1」にしておいてください。 また、ポーズボタンをプレハブにしておいてください。 毎回ゲームオブジェクトを探すのは負担がかかるのでStartであらかじめゲームオブジェクトを探しておいてGetComponentしておくことで負担を減らしています。っていうか、Start以外に書くと警告がでます(ノ゚ο゚)ノ this.pause.is_pauseがtrueなら止まり、falseなら実行されます。 これでUpdateの動きを止めることができます。

逆にこのUpdateのみ動くのを利用すればポーズ画面のボタンなどを実装できます。 あらかじめポーズ画面のプレハブを作っておき、ポーズボタンが押された時生成するようにすればいいと思います。 uGUIやNGUIなどを使うのもいいですが僕は使い方わからないのでこの処理で乗り切っています(笑)

完成したらこんな風になります。たぶん。

Unity Web Player | test_pause

上のPauseがis_pauseフラグありで、下はTime.timescaleのみいじっています。 左のhappy faceがUpdateで左のhappy faceはFixedupdateで移動しています。

2.Time.deltaTimeについてと問題の回避方法

Time.timescale = 0にするのは影響が大きく様々なものに影響を及ぼします。特に影響が大きいのはTime.deltaTimeが0になってしまうことです。まず、Time.deltatimeについて詳しく話していきます。

このTime.deltaTimeとはフレームの切替速度です。Unityではフレームを切り替えて表示して物を動かしています。このフレームの切替速度はゲームが動いているハードをに依存しています。つまり、切替速度が速いマシンでは一秒で切り替えられるフレームが多くなり早くなってしまいます。例えばある物体が毎フレームごとに移動する時、切替速度が速いと早く移動して、切替速度が遅いと遅く移動することになります。これでは問題が出てしまうのでそれを調整するために物体の毎フレームごとに移動する大きさにかけて調整しいてやります。

void Start(){
     dx = Time.deltaTime * 1f;
}

dxの大きさは適当に調整してください。今回は面倒なので1fにしています。これでマシンの切替速度に関係なく一定の速さで物体が移動させることができます。 これは、歩く人に例えるとわかりやすいです。一歩を踏み出すのが速い場合は歩幅を小さくして歩き、一歩を踏み出すのが遅い場合は歩幅を大きくして歩きます。ちょうど下の図のようなイメージです。

f:id:playwao:20150228031649p:plain

先ほどTime.timescale = 0にするとTime.deltaTimeも0になってしまいます。これでは先ほど例にあげた場合だと動きますが、例えばポーズ画面でUIを動かそうとする場合にTime.deltaTimeが0だと動きません。 これを解消するにはポーズに入る前にTime.deltaTimeの値をあらかじめ保管しておきそれを代入する必要があります。

3.その他に起こり得る問題など

*NGUIはTime.timescaleの大きさに関わらず動く

UIでよく使われるプラグインとしてNGUIというものがあります。これはNGUIが独自にTime.deltatimeの値を保管して持っているので、Time.timescaleの値に関係なく動きます。

*iTweenは動かない

アニメーションで動かす時によく使いプラグインですがこれも動きません。 どうやったら動くのか…もう少し調べて記事にしてみます。 まあ、どこかにTime.deltatimeの値を保管しとけはいいんですけどね。

*アニメーションは動かない

アニメーションも動かないので当然アニメーションイベントも動きません。あまり使う機会はないのではと思いますが問題がある場合はググってみてください。もう私の限界を超えています(^o^)/

4.おわりに

いかがでしたでしょうか?

正直説明できるほどUnityに関する知識はないですが必死になって書いたんで参考にしていただければと思います。