Help us understand the problem. What is going on with this article?

ミニゲームを作ってUnityを学ぶ! [ タンクウォーズ編 - 8. シーンを作り込む(後) ]

More than 1 year has passed since last update.

ミニゲームを作ってUnityを学ぶ![タンクウォーズ編]

第8回目: シーンを作り込む(後)

前回はプロジェクト起動から実際にゲームをプレイするまでのシーンの流れを実装しました。
今回はプレイ中から終了に至るまでの流れと、リスタート機能を実装していきます。

ゲームの終了判定

今回のゲームは双方に分かれた戦車がお互いに弾を撃ちあい、相手戦車のHPを先にゼロにしたほうが勝ちというルールです。

第5回の弾の命中処理を実装する際に、HPがゼロになった戦車は死亡フラグ(mIsDead)がtrueになるようなコードを書いていますので、今回はこちらのフラグをTankManageから監視することでゲームの終了タイミングを判定します。

TankManager.cs
        public const int RESULT_WIN = 1, RESULT_LOSE = 2, RESULT_DRAW = 3;

        /// <summary>
        /// ゲームの終了判定
        /// </summary>
        /// <returns>WIN = 1, LOSE = 2, DRAW = 3, OTHERS = -1</returns>
        public int CheckResult()
        {
            // 個別の死亡判定
            bool playerDead = false;
            bool enemyDead = false;
            foreach (TankModel model in mTankList)
            {
                if (model.IsDead)
                {
                    if (model.IsPlayer)
                    {
                        playerDead = true;
                    }
                    else
                    {
                        enemyDead = true;
                    }
                }
            }

            // 結果判定
            if (playerDead)
            {
                if (enemyDead)
                {
                    return RESULT_DRAW;
                }
                else
                {
                    return RESULT_LOSE;
                }
            }
            else if (enemyDead)
            {
                return RESULT_WIN;
            }
            return -1;
        }

CheckResult()ではプレイヤーの操作する戦車と相手戦車の死亡フラグを取得し、その組み合わせに適した結果をintで返還しています。

さらに、このCheckResult()を実行するためにSceneMainのコードを修正します。

SceneMain.cs
        private enum STATE
        {
            WAIT_ENTER_KEY = 0,
            COUNT_DOWN,
            PLAY,
追加        RESULT
        };

        void Update()
        {
            switch (mState)
            {
                case STATE.WAIT_ENTER_KEY: // enterが押されたらカウントダウン開始
                    mUi.UpdateCenterMsg(); // 中央メッセージの点滅アニメーション
                    if (WaitEnter())
                    {
                        mUi.HideCenterMsg();
                        mState = STATE.COUNT_DOWN;
                    }
                    break;
                case STATE.COUNT_DOWN: // カウントダウン
                    if (mUi.UpdateCountDown())
                    {
                        mTank.RegisterTanks();
                        mTank.OnActiveAllTanks();
                        mState = STATE.PLAY;
                    }
                    break;
                case STATE.PLAY: // ゲームプレイ中
追加                CheckResult();
                    break;
            }
        }

追加    private void CheckResult()
        {
            switch (mTank.CheckResult())
            {
                case TankManager.RESULT_WIN:
                    mUi.ShowWin();
                    break;
                case TankManager.RESULT_LOSE:
                    mUi.ShowLose();
                    break;
                case TankManager.RESULT_DRAW:
                    mUi.ShowDraw();
                    break;
                default:
                    return;
            }
            mTank.StopAllTanks();
            mState = STATE.RESULT;
        }

SceneMainでは状態がゲームプレイ中の際に、TankManagerの終了判定を更新毎に呼び出しています。
そして終了条件を満たした場合にはUiManagerのShow~()とTankManagerのStopAllTanks()を呼び出したあと、最後に自分の状態をRESULTに変更します。

RESULTは状態としては定義されていますが具体的な処理は一切ありませんので、このタイミングでSceneMainのUpdate()は何も判定せず何も実行しないようになります。

*TankManagerのStopAllTanks()とUiManagerのShow~()は後述します。

ゲームの終了

ゲームの終了判定に続いて、今度はゲームを終了する際に必要な処理を実装していきます。

戦車の完全停止

ゲームが終了した際、それ以降に戦車が移動したり弾を撃ったりできないように制御しているのがTankManagerのStopAllTanks()です。

このメソッドを実装する前に、まずはFireControllerとTankModelに新しいメソッドを追加します。

FireController.cs
        public void SleepAllBullets()
        {
            foreach(BulletModel model in mBulletList)
            {
                if (!model.IsSleep) model.Sleep();
            }
        }

SleepAllBullets()ではListに格納されている全ての弾について、それが休眠状態でない場合にはSleep()を呼び出して休眠状態へ遷移させます。

TankModel.cs
        public void Stop()
        {
            // 操作不可状態に
            mIsActive = false;

            // 移動力をリセット
            mMovementScript.ResetVelocity();

            // 発射されている弾を全て休眠状態に
            mFireScript.SleepAllBullets();
        }

Stop()は自身をその場に停止させて操作を受け付けない状態にするのと同時に、先ほどのSleepAllBullets()によって自分の撃った弾を全て休眠状態にします。

そしてこのStop()を呼び出すのがTankManagerのStopAllTanks()です。

TankManager.cs
        public void StopAllTanks()
        {
            foreach (TankModel model in mTankList)
            {
                model.Stop();
            }
        }

StopAllTanks()ではリストに格納されている全てのTankModelに対してStop()を呼び出しています。
つまりこのメソッドを実行することで全ての戦車が機能を停止し、同時に画面上から全ての弾が消えることになります。

勝敗を表示する

次はゲームの結果を表示する機能を実装していきます。
SceneMainから呼び出していたUiManagerのShow~()の部分です。

UiManager.cs
        private readonly string WIN = "WIN!", LOSE = "LOSE", DRAW = "DRAW";

        public void ShowWin()
        {
            mTextCountDown.text = WIN;
            mTextCountDown.gameObject.SetActive(true);
        }

        public void ShowLose()
        {
            mTextCountDown.text = LOSE;
            mTextCountDown.gameObject.SetActive(true);
        }

        public void ShowDraw()
        {
            mTextCountDown.text = DRAW;
            mTextCountDown.gameObject.SetActive(true);
        }

それぞれのメソッドでは開始時にカウントダウンとして使用していたTextCountDownを流用して、勝敗に合わせたWIN・LOSE・DRAWの文字列を画面中央に表示しています。

tankwars_ss_8_1.jpg

ゲームのリスタート

現時点のプロジェクトでは勝敗が画面中央に表示され、戦車が操作できなくなってからはプレイヤーができることはありません。

自動的にゲーム起動時の状態に戻してももちろん良いのですが、今回はゲームを開始したときと同じようにこちらもプレイヤーのタイミングでゲームを最初の状態に戻すような機能を実装します。

SceneMain.cs
        void Update()
        {
追加        CheckSystemInput();
            switch (mState)
            {
                case STATE.WAIT_ENTER_KEY: // enterが押されたらカウントダウン開始
                    mUi.UpdateCenterMsg(); // 中央メッセージの点滅アニメーション
                    if (WaitEnter())
                    {
                        mUi.HideCenterMsg();
                        mState = STATE.COUNT_DOWN;
                    }
                    break;
                case STATE.COUNT_DOWN: // カウントダウン
                    if (mUi.UpdateCountDown())
                    {
                        mTank.RegisterTanks();
                        mTank.OnActiveAllTanks();
                        mState = STATE.PLAY;
                    }
                    break;
                case STATE.PLAY: // ゲームプレイ中
                    CheckResult();
                    break;
            }
        }

追加    private void CheckSystemInput()
        {
            // リスタート
            if (Input.GetKeyDown(KeyCode.Backspace)) mGame.OnRestartButton();
        }

SceneMainに新しくCheckSystemInput()を追加し、現在の状態に関係なく常にBackSpaceキーの入力を監視するようにしました。
そしてBackSpaceキーの入力を感知した場合にはGameControllerのOnRestartButton()が呼び出されます。

GameController.cs
        public void OnRestartButton()
        {
            // シーンの再読み込み
            SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        }

OnRestartButton()では現在のシーンを再読み込みすることで、ゲームを起動直後の状態に戻します。
これでゲームがどんな状態であってもBackSpaceキーを押すことで最初からやり直しができるようになりました。

*ゲームをリスタートした際に画面が暗くなってしまう場合は以下の設定を行ってください。

  1. メニュー>Window>Lighting>Settingsを選択
  2. Auto Generateのチェックを外す
  3. Generate Lightingをクリック

ゲームの操作方法を表示する

BackSpaceキーでやり直す仕組みが実装できましたので、開始時のEnterキーと同様にそれを促すテキストを表示します。
今回はそれと一緒に戦車の操作方法も表示してしまいましょう。

  • 既存のCanvas内にPanelBottomMsgという名前でPannelを作成
  • PanelにアタッチされたImageのColorを(R=0, G=0, B=0, A=200)に変更
  • Panelのポジションを以下のように設定

tankwars_ss_8_2.jpg

  • Panel内にTextを作成
  • TextのColorを(R=255, G=255, B=255, A=255)に変更
  • Textの赤枠部分とポジションを以下のように設定

tankwars_ss_8_3.jpg

[ W ]  Up       [ S ]  Down       [ Mouse ]  RotateTurret       [ Left Click ]  Fire      [ BackSpace ]  Restart 

これでプレイヤーはいつでも操作方法を見ることができるようになりました。

最後にプロジェクトを実行して、今回の内容が正しく実装されているか確認します。


次のページに進む
イントロダクションに戻る

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした