コンテンツにスキップ

第11週:制限時間とリトライ

前回はCanvasとTextMeshProでスコアを表示しました。

  • UIはCanvasの中に置く
  • public static instanceでどこからでも呼び出せるようにした
  • 合体時にAddScoreを呼んで加点した

今日はゲームの終わりを作ります。

  • 制限時間をカウントダウンして画面に表示する
  • 時間が0になったらゲームオーバーにする
  • ゲームオーバー画面と最終スコアを表示する
  • もう一度遊べるボタンを作る

どうやってゲームを終わらせるか

Section titled “どうやってゲームを終わらせるか”

このゲームの終わり方を決める

Section titled “このゲームの終わり方を決める”

今のままではゲームがいつまでも続きます。 終わりがないと、上手にできたのかどうかも分かりません。

終わらせ方には、いくつか考え方があります。

終わり方 よいところ むずかしいところ
制限時間 作るのが簡単。スコアを競える 箱があふれても続いてしまう
箱からあふれたら 本物のゲームらしい 判定の位置調整がむずかしい
玉を出せる回数を決める 分かりやすい 時間をかけると有利になってしまう

考え方はとても単純です。

  1. 残り時間の変数を用意して、最初に90秒を入れる
  2. 毎フレーム、経過した分だけ残り時間を減らす
  3. 残り時間が0以下になったらゲームオーバー
制限時間の流れ
制限時間の流れ

第4週でも少し出てきましたが、あらためて確認します。

// 前のフレームから今のフレームまでにかかった秒数
Time.deltaTime
void Update()
{
// 経過した分だけ残り時間を減らす
timeLeft -= Time.deltaTime;
}

スコアと同じ作り方です。第10週の復習にもなります。

  1. HierarchyでCanvas選択した状態で右クリック
  2. UIText - TextMeshPro
  3. 名前をTimeTextに変更
  4. RectTransform:
    • Anchor Presets右上を選択
    • Pos X:-150、Pos Y:-60
    • Width:400、Height:80
  5. TextMeshPro - Text:
    • Text Input欄にTIME 90と入力
    • Font Size:48
    • Alignment:右寄せ
    • Vertex Color:白
残り時間の表示設定
残り時間の表示設定
  1. HierarchyでCanvasを選択した状態で右クリック
  2. UIPanelを選択
  3. 名前をGameOverPanelに変更
  4. ImageのColorで黒の半透明にする(Aを180くらい)

GameOverPanel選択した状態で、その中に3つ作ります。

  1. 見出しの文字
    • 右クリック → UI → Text - TextMeshPro
    • 名前をTimeUpText、Text Input欄にTIME UP!
    • Font Size:96、Alignment:中央、Pos Y:150
  2. 最終スコアの文字
    • 右クリック → UI → Text - TextMeshPro
    • 名前をResultText、Text Input欄にSCORE 0
    • Font Size:56、Alignment:中央、Pos Y:20
  3. リトライボタン
    • 右クリック → UI → Button - TextMeshPro
    • 名前をRetryButton、Pos Y:-120
    • Width:300、Height:90
    • 中のText (TMP)のText Input欄にRETRY
    • Font Size:40
ゲームオーバー画面
ゲームオーバー画面

ゲーム全体の進行を管理する係を作ります。

  1. Hierarchyで右クリック → Create Empty
  2. 名前をGameManagerに変更
  3. Assets/ScriptsGameManagerスクリプトを作る
using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro;
public class GameManager : MonoBehaviour
{
// どこからでも呼び出せる入口
public static GameManager instance;
// 制限時間(秒)
public float timeLimit = 90f;
// 残り時間を表示するテキスト
public TextMeshProUGUI timeText;
// ゲームオーバー画面と、その中の最終スコア表示
public GameObject gameOverPanel;
public TextMeshProUGUI resultText;
// 玉を出す係(ゲームオーバー後に止めるため)
public BallSpawner spawner;
// 残り時間
private float timeLeft;
// もうゲームオーバーになったか
private bool isGameOver = false;
void Awake()
{
instance = this;
}
void Start()
{
timeLeft = timeLimit;
gameOverPanel.SetActive(false);
UpdateTimeText();
}
void Update()
{
// ゲームオーバー後は時間を減らさない
if (isGameOver) return;
// 経過した分だけ残り時間を減らす
timeLeft -= Time.deltaTime;
// 0を下回ったら0にそろえる
if (timeLeft <= 0f)
{
timeLeft = 0f;
UpdateTimeText();
GameOver();
return;
}
UpdateTimeText();
}
// 残り時間の表示を書き換える
void UpdateTimeText()
{
// 小数を切り上げて整数の秒数にする
int seconds = Mathf.CeilToInt(timeLeft);
timeText.text = "TIME " + seconds;
}
// ゲームオーバーにする
public void GameOver()
{
// すでにゲームオーバーなら何もしない
if (isGameOver) return;
isGameOver = true;
Debug.Log("ゲームオーバー!");
// ゲームオーバー画面を表示する
gameOverPanel.SetActive(true);
// 玉を出せなくする
if (spawner != null)
{
spawner.enabled = false;
}
}
// リトライボタンから呼ばれる
public void Retry()
{
// 今のシーンをもう一度読み込む
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
  1. GameManager.csGameManagerオブジェクトにドラッグ
  2. HierarchyでGameManagerを選択
  3. Inspectorで4つの欄を設定:
    • Time TextTimeText
    • Game Over PanelGameOverPanel
    • Result TextResultText
    • SpawnerBallSpawner

再生してみてください。

右上の数字が90からカウントダウンします! 0になるとTIME UP!が表示されます。

そのまま表示すると読みにくい

Section titled “そのまま表示すると読みにくい”

timeLeftは小数なので、そのまま表示すると TIME 87.34291のようになってしまいます。

命令 87.6のとき 意味
Mathf.CeilToInt 88 切り上げ
Mathf.FloorToInt 87 切り捨て
Mathf.RoundToInt 88 四捨五入

ゲームオーバー画面に、その回のスコアを出しましょう。

ScoreManagerからスコアを取り出す

Section titled “ScoreManagerからスコアを取り出す”

第10週のScoreManagerでは、 scoreprivateなので外から読めません。 読めるようにメソッドを追加します。

ScoreManager.csに追加してください。

// 今のスコアを教える
public int GetScore()
{
return score;
}

GameOverメソッドに追加します。

// ゲームオーバー画面を表示する
gameOverPanel.SetActive(true);
// 最終スコアを表示する
if (ScoreManager.instance != null)
{
resultText.text = "SCORE " + ScoreManager.instance.GetScore();
}
// 玉を出せなくする

再生して時間切れまで待つと、 その回のスコアが表示されます!

  1. HierarchyでRetryButtonを選択
  2. Inspectorの一番下、On Click ()を見る
  3. ボタンをクリックして枠を1つ追加
  4. None (Object)の欄に、 HierarchyのGameManagerドラッグ
  5. 右側のドロップダウン(No Function)をクリック
  6. GameManagerRetry ()を選択
OnClickの設定
OnClickの設定
  1. 再生して時間切れまで待つ(Time Limitを10にすると速い)
  2. RETRYボタンをクリック
  3. ゲームが最初から始まる(玉が消え、スコアと時間が戻る)
リトライ成功
リトライ成功

制限時間があるので、同じ条件で競えるようになりました。

症状 原因と対処法
NullReferenceException が出て動かない GameManagerの4つの欄のどれかが空
時間が減らない UpdateではなくStartに書いている
時間の表示が小数だらけ Mathf.CeilToIntを使っているか確認
時間がマイナスになる if (timeLeft <= 0f) の中で0fを代入しているか確認
ゲームオーバー画面が最初から出ている gameOverPanel.SetActive(false)Startにあるか確認
RETRYボタンが効かない On Clickの設定漏れ。GameManagerとRetryを指定
ドロップダウンにRetryが出ない メソッドがpublicになっていない
ボタンが押せない EventSystemを消してしまっている。 UI作成時に自動で作られるので、作り直す
時間切れ後も玉が出る Spawner欄が空。spawner.enabled が働いていない
最終スコアが0のまま GetScoreScoreManagerに追加したか確認

基礎課題全員必須

制限時間とリトライを作る:

  • TimeTextを作って残り時間を表示する
  • GameOverPanelに文字とボタンを置く
  • GameManagerを作り、Inspectorで4つの欄を設定
  • RetryButtonのOn ClickにRetryを設定する
  • 時間切れとリトライが動くことを確認
  • 最後にCtrl + Sで保存する

標準課題時間があれば

遊びやすく仕上げる:

  • ScoreManagerGetScoreを追加して最終スコアを表示
  • timeLimitを変えて、ちょうどよい長さを探す
  • 残り10秒以下でTimeTextを赤くする
    (ヒント:timeText.color = Color.red;
  • クラスでスコアを比べ、どうすれば伸びるか考える

発展課題チャレンジ

箱からあふれたら終わりにする(本格版):

  • 次の節を読んで、積み上がり判定を追加する
  • 時間制限と組み合わせて「どちらか早いほうで終わり」にする
  • ゲームオーバーの理由によって 表示する文字を変える(TIME UP!GAME OVER

発展:箱からあふれたら終わりにする

Section titled “発展:箱からあふれたら終わりにする”

ここからは余裕がある人向けです。 本物のゲームらしく「積み上がったら終わり」を追加します。

単純に考えるとうまくいかない

Section titled “単純に考えるとうまくいかない”

そこで「一定時間ずっといたら」という条件にします。 落ちてくる玉はすぐ通り過ぎるのでセーフ、 積み上がって止まっている玉はアウトになります。

Colliderには「ぶつかる」と「通り抜ける」の2つのモードがあります。

通常のCollider トリガー(Is Trigger オン)
物理的に ぶつかる(止まる) 通り抜ける(止まらない)
呼ばれるメソッド OnCollisionEnter2D OnTriggerEnter2D
使いどころ 壁、床、玉同士 ゴール判定、アイテム取得、範囲の検出
  1. Hierarchyで右クリック → 2D Object → Sprites → Square
  2. 名前をGameOverLineに変更
  3. Position:(0, 1.5, 0)、Scale:(7, 0.1, 1)
  4. Sprite RendererのColorを赤の半透明にする(Aを100くらい)
  5. Add Component → Box Collider 2D
  6. Is Triggerにチェックを入れる
ゲームオーバーラインの作成
ゲームオーバーラインの作成
もの Y座標 意味
玉が出てくる高さ 4 ラインより
壁の上端 2
GameOverLine 1.5 壁の上のほう
床の上面 -3.75 箱の底
using UnityEngine;
public class GameOverLine : MonoBehaviour
{
// この秒数を超えたらゲームオーバー
public float limitTime = 2f;
// 玉がラインにいる時間
private float timer = 0f;
// 玉がラインの中にいる間、毎フレーム呼ばれる
void OnTriggerStay2D(Collider2D other)
{
// 玉でなければ数えない
if (other.GetComponent<Ball>() == null) return;
timer += Time.deltaTime;
if (timer >= limitTime)
{
GameManager.instance.GameOver();
}
}
// 玉がラインから出たらリセット
void OnTriggerExit2D(Collider2D other)
{
timer = 0f;
}
}

ついに最終回です。

次回はをつけます。 合体の音、玉を落とす音、時間切れの音、そしてBGM。 音が入るとゲームの印象が驚くほど変わります。

最後に全体を調整して、自分のゲームとして完成させましょう。