【Unity】チュートリアルの「Roll a Ball」をやってみる話 #11

記事をご覧いただき、誠に有難うございます。

投稿主の無能です。

今回は、制限時間を設けてみたいと思います。


制限時間を設ける

現状だと、ゲームには失敗する要素が無いので、時間さえ掛けることができれば、誰にでも絶対にクリアできてしまう状態になっています。

そこで、制限時間を設けて、制限時間内にクリアできなければステージ失敗、という風にしてみようと思います。

制限時間を表示するテキストを作る

まずは制限時間を表示するテキストを作ります。

UI_GameStatusを選択してメニュー Game Object > UI > Text - TextMeshPro を選択して、「CountDownTimer」というテキストを作成します。

UI_GameStatusを選択してメニュー Game Object > UI > Text - TextMeshPro を選択

「CountDownTimer」という名前でテキストを作成

作成したテキストは、ゲーム画面の上部にします。
テキストは色は赤にして、テキストの内容はスクリプトで操作するので、今回は「00」にします。
設定は自由に調整してください。
参考に、このような形になりました。

テキストを設定した
中央揃え・Bold(太字)・フォントサイズ48

Gameビュー

次にスクリプトを記述します。

カウントダウンする仕組みを作る

制限時間なので、カウントダウンをしていく仕組みを作ります。

カウントダウンをする制限時間は任意で決められるものとします。
ここでは仮に10秒としておきます。

そして制限時間の10秒から、一定間隔、即ち時間を引いてあげれば良さそうです。
式として表すと

制限時間 - 一定間隔(時間) = カウントダウン

このような形になるかと思います。

では実際に記述しますので、ProjectビューのScriptsフォルダからGameManagerスクリプトを開きます。

このような変数を追加します。

    // 制限時間
    public int countStartTime;
    // 引き算される制限時間
    private int currentTime;
    // 制限時間を表示するテキスト
    public TextMeshProUGUI UICountDownTimer;

このようになります。


そしてStart関数を作成して、currentTimeにcountStartTimeを代入します。
このような記述になります。

private void Start()
{
    // 制限時間を引き算される制限時間に代入
    currentTime = countStartTime;
}

このようになります。


カウントダウンは、新たに関数を作成して処理させます。
関数名は「CountDownTimer」とします。

関数を作成するにはStart関数やUpdate関数と同じでこのような記述になります。

void CountDownTimer()
{

}

このようになります。


この中にカウントダウンの仕組みを記述します。

カウントダウンは、先程作成した変数currentTimeから時間を引いて、UICountDownTimerで表示させる、という流れになります。

このような記述になります。

void CountDownTimer()
{
    // 制限時間からdeltatimeを引く
    currentTime -= 1 * Time.deltaTime;

    // 制限時間を表示する
    UICountDownTimer.SetText("残り時間 : " + currentTime.ToString("00.00"));
}

このようになります。


あれ、エラーが出ています。

エラー

あ、そうか。整数型に小数型を入れようとしたからエラーになったのか。
currentTimeの型宣言をintからfloatに変えます。

このようになります。

currentTimeをintからfloatに変更

エラーが消えました。

エラーが消えた

このTime.deltaTimeですが、直前のフレームから今のフレームまでの時間を表しています。

このTime.deltaTimeは結構使うので、他のdeltaTimeもあわせて覚えておきましょう。
検索すると丁寧な説明が出てきます。

そして演算子の「-=」ですが、右辺を引いた値を代入する、という意味になります。

四則演算の通りに「+=」「-=」「*=」「/=」があります。

ではスクリプトを保存して、Unityに戻ります。

GameManagerにCount Start TimeとUI Count Down Timerが追加されています。

Count Start TimeとUI Count Down Timerが追加されている

Count Start Timeは10秒、UI Count Down TimerはCountDownTimerを設定します。

Count Start Timeは10秒、UI Count Down TimerはCountDownTimerを設定する

ではゲームを実行して確認します。

Gameビュー

あれ…?何も変化しない…?
エラーも出ていないし…

コンソールでエラーは出ていない

ちなみにコンソールですが、メニュー Window > General > Console で出せます。

コンソールはメニュー Window > General > Console で出せる

またはCtrl+Shift+Cキーでキーボードショートカットできます。

エラーが出ていないのに、処理が行われないということは、処理がそこまで到達していない、ということになります。

今回はUpdate関数内でCountDownTimer関数を呼んでいない、という事が原因です。

例えるなら、折角助っ人を呼んだのに割り当てる仕事が無くて、助っ人がずっと棒立ちしているような感じです。

このような些細な事はスクリプトを書いていると往々にして起こります。
ですので留意しておくと共に、こまめにUnityでゲームを実行して確認する癖をつけておきましょう。

ではUpdate関数内でCountDownTimer関数を呼びましょう。
このように記述します。

// CountDownTimerでカウントダウン
CountDownTimer();

このようになります。


ではスクリプトを保存して、Unityでゲームを実行して確認します。


カウントダウンが始まりました、が…


処理がそのまま継続されるので、ゼロ以下でも減少しています。

これをゼロ以下になったらゼロにする、制限時間になったらステージ失敗の処理をする、という2つの処理を追加します。

まずはゼロ以下になったらゼロにする、という処理を追加します。
GameManagerスクリプトに戻ります。

カウントダウンを制御する

カウントダウンをある一定の値になったら処理しない、という風な制御を追加します。

まずはカウントダウンをする・しないのbool値を追加します。
このような記述になります。

// カウントダウンを制御する
private bool isCountDown = true;

このようになります。


bool変数は、一般的に「is○○」のような形で書かれます。
覚えておきましょう。

次に、CountDownTimer関数でゼロ以下になった時の処理を追加します。

このような記述を追加します。

// currentTimeが0以下の場合
if (currentTime <= 0)
{
	// currentTimeを0にする
    currentTime = 0;
    // カウントダウン処理をしない
    isCountDown = false;
}

このようになります。


次に、Update関数内で呼んだCountDownTimer関数に追加していきます。

このような記述になります。

// カウントダウンする場合
if (isCountDown == true)
{
    // CountDownTimerでカウントダウン
    CountDownTimer();
}

このifの条件の部分ですが、カッコ内がtrueであれば波カッコ内を処理する、というものでした。
ですので、bool値を条件として記述する場合、一般的な書き方としては

// カウントダウンする場合
if (isCountDown
{
	// CountDownTimerでカウントダウン
    CountDownTimer();
}
と言う風に、比較演算子を省略して記述します。

比較演算子を用いて記述する場合、意味としては
true == true
と言う意味になるから省略する、という事ですね。

逆にfalseの場合は
// カウントダウンする場合
if (!isCountDown)
{
	// CountDownTimerでカウントダウン
    CountDownTimer();
}

という風に、bool値の変数の前に「!」(エクスクラメーションマーク)を付けて、意味を逆にします。

上記の比較演算子を用いる記述方法も勿論合っていて問題無いのですが、スクリプトの処理がより分かりやすい書き方をする、というのも、スクリプトを書く上で重要な点となりますので、気を付けておきたいところです。

このようになります。


ではスクリプトを保存して、Unityでゲームを実行して確認します。

CountDownTimer関数が呼ばれないことを確認するので、10秒以上待機してください。

Gameビュー

これでカウントダウンがゼロ以下になったらカウントダウンが止まるようにできました。

次にステージ失敗を作ります。

ステージ失敗のUIを作る

まずステージ失敗のUIを作成します。

UI_GameStatusを選択してメニュー Game Object > UI > Text - TextMeshPro でテキストを作成します。
名前を「StageFailed」にします。

UI_GameStatusを選択してメニュー Game Object > UI > Text - TextMeshPro でテキストを作成する

名前を「StageFailed」にする

StageClearと同様に設定します。
StageFailedはこのように設定しました。

Gameビュー

テキストを設定

StageFailedのテキストは黒にしました。

ではこのStageFailedを非アクティブにします。

StageFailedを非アクティブにする

Gameビュー

では、GameManagerスクリプトにステージ失敗の処理を追加します。

ステージ失敗の処理を追加する

ステージ失敗の処理をGameManagerスクリプトに追加します。

ステージクリアの処理に似たような処理ですので、GameManagerスクリプトをより分かりやすくする(見やすくする)ように改修もしてしまいましょう。

では、新たにStageClear関数とStageFailed関数を作ります。
このような記述になります。

    // ステージクリアの処理
    void StageClear()
    {

    }

    // ステージ失敗の処理
    void StageFailed()
    {

    }

このようになります。


そして、StageClear関数にUpdate関数内で記述したステージクリアの処理を切り取って持ってきます。

このようになります。

Update関数内のステージクリアの処理を切り取る

StageClear関数内に貼り付ける

ここで、ステージクリア・失敗に関わらず、「ステージが終了した」という事をUpdate関数に知らせて、余計な処理をさせないようにします。

具体的には、StageClear関数を毎回呼ばないようにする、という処理をさせます。

先程切り取ったUpdate関数内のif文の処理に、StageClear関数を呼ぶ処理を記述します。

このように記述します。

        // countがゼロになったら
        if (count == 0)
        {
            StageClear();
        }

このようになります。


そしてStageClear関数内に、次の一文を追加します。

Debug.Log("Stage Clear");

このようになります。


ではスクリプトを保存して、Unityに戻ります。

今回はステージクリアの処理がどうなっているのかを見るので、制限時間は余裕を持ってクリアできるようにしてください。

ステージクリアの処理が呼ばれると、コンソールは画像の様になっています。

コンソール画面

ステージをクリアすると、Update関数が処理を実行する度に、StageClear関数が呼ばれていることになります。
これは明らかに無駄な処理という事が分かります。

無駄な処理は何故ダメなのか?という事になりますが、このような無駄な処理が積み重なって重い処理へ発展してしまうことがあるからです。

このように小さな処理は、昨今のPCでは問題にならない程度のものでしょう。
しかし、このような処理が積み重なると、メモリを圧迫したり、CPUの処理能力を奪ってしまう原因になってしまいかねません。

最近のゲームなどは尚更、注意深く無駄な処理を軽減させようとするでしょう。

このような小さなプログラムだからこそ、無駄な処理を極力無くしていきましょう。

では変数を追加していきます。
追加するのは、ステージ失敗の時に表示するGameObject、ステージクリア・失敗に関わらず終了をUpdate関数に知らせるbool値になります。

このように記述します。

    // ステージ失敗のUI
    public GameObject UIStageFailed;
    // ゲーム終了のフラグ
    private bool isStageFinish = false;

ちょっとスクリプト内がごちゃついてきたので、整理してこのようになりました。

スクリプト内を整理した

ではStageClear関数のDebug.Logを削除して、ゲーム終了のフラグをtrueにします。
このような記述になります。

        // ステージ終了フラグをtrue
        isStageFinish = true;

このようになります。


次にStageFailed関数を記述します。
StageClear関数をコピーして、UIStageClearをUIStageFailedに変えてあげればいいだけです。

このようになります。


次に、Update関数内のステージクリアの処理をしているif文に処理を追加します。

ステージクリアの処理とステージ失敗の処理を分けます。

このように記述します。

        // countがゼロになったら
        if (count == 0)
        {
            // ステージが終了していない
            if (!isStageFinish)
            {
                StageClear();
            }
        }
        // カウントダウンしていない
        else if (!isCountDown)
        {
            // ステージが終了していない
            if (!isStageFinish)
            {
                StageFailed();
            }
        }

このようになります。


まずelse ifから説明します。

if文は、ifの条件の場合に当てはまらない場合に、else if(エルス イフ)で更に分岐することができます。

最初のifで収集アイテムを表すcountがゼロである場合、ここまではステージクリアで記述していました。

次のelse ifを付け加えることによって、else ifの前にあるifの条件に当てはまらない場合に、条件に合致するかを判断します。

この場合は収集アイテムの数がゼロではない場合にelse ifに処理が移ります。

そしてelse ifの条件に合致すれば、else ifの波カッコ内を処理する、という流れになります。

次に、if文の中にifが入っている状態ですが、この状態を「入れ子」(いれこ)と呼びます。
一般的には「nest」(ネスト)と呼ばれます。

ステージクリアの処理だと、まず最初のifで収集アイテムの数がゼロである、という処理をクリアします。

次にあるifでステージが終了していない場合にStageClear関数を呼ぶようになっています。
そして、ステージが終了している場合は、条件内の処理を実行しないようにしています。

これで先程、Debug.Logで試したStageClear関数が何度も呼ばれることが解消されます。

最初のうちは、何が何だか分からないとなりますが、スクリプトを書き慣れていけば自然と出来るようになっていきます。
条件分岐が入れ子になっている場合(if文がネストしている場合)は、大きい処理から落ち着いて読み解くようにしましょう。

最後に、Update関数内のカウントダウンの処理に追加します。

ステージが終了したらカウントダウンを止める

ステージが終了したら、カウントダウンを止める処理を追加します。
Update関数内のカウントダウンする処理にこのように記述します。

        // カウントダウンする場合
        if (isCountDown)
        {
            // ステージが終了していない
            if (!isStageFinish)
            {
                // CountDownTimerでカウントダウン
                CountDownTimer();
            }
        }

このようになります。


先程、ステージクリア・失敗に追加したif文と同じですね。

ではスクリプトを保存して、Unityに戻ります。

追加した項目を設定する

では追加した項目を設定しましょう。

まずはGameManagerに追加したUI Stage Failedです。


ここにStageFailedを設定しましょう。


そして、StageFailedに付ける効果音を探します。
効果音ラボさんから文字表示の衝撃音1という効果音をダウンロードしました。

前回オーディオ設定の仕方を説明したので、ここでは割愛します。
このような設定になりました。

StageFailedに効果音を設定した

ではゲームを実行して確認します。

制限時間は適切な値を設定しましょう。

ステージクリアの場合はカウントダウンが停止するかを、ステージ失敗の場合はStageFailedが表示され、効果音が鳴るか確認します。

ステージクリア

ステージ失敗

ステージのクリア・失敗が出来ました!

今回はここまで。


まとめ

本記事では
  • カウントダウンするタイマー機能を追加した
  • ステージ失敗の処理を追加した
  • GameManagerスクリプトを改修した
という事を行いました。

次は、タイトル画面を作りたいと思います。

本記事もご覧頂き、誠に有難うございます。
ではまた。


コメント