カウントダウンの作り方

前回、ゲームオーバー処理を作ったのでかなりゲームらしい流れができてきました。しかし、まだ

  • ゲームオーバー&リトライ処理が不完全
  • ゲームの始まりが唐突なので、プレイヤーとしては「え?もう始まってるの?」という風になりかねない

といった問題があります。そこで、ここではゲームオーバー&リトライ処理を完成させ、さらにスタート時にカウントダウンして一旦間を置くようにしてみましょう。


※今回はスクリプトの変更箇所が多いので大変ですが頑張りましょう。

スポンサーリンク

カウントダウン処理の作り方

はじめにカウントダウン処理をどうやって作れば良いのかを簡単に説明します。

処理を一定時間待つ「コルーチン」を使う

まず、当たり前ですがカウントダウンは「3,2,1…」という感じに1秒ずつカウントしていくのが普通です。つまり、

  1. 文字を表示する
  2. 1秒待つ
  3. 文字の表示を切り替える
  4. 1秒待つ

という感じの処理内容になりますね。

それでこのように「〇秒待つ」という処理を実現するときに便利なのが「コルーチン」という関数です。コルーチンを使えば、処理→〇秒待つ→処理再開…といった流れを簡単に作ることができます。

コルーチンについては姉妹サイトのページ「処理を一定時間待つコルーチンの使い方」で詳しく説明していますので、そちらも併せてご覧いただければと思います。

実際のカウントダウンのC#スクリプト

コルーチンを使えばよいことを知って頂いたところで、今回のカウントダウン処理の実際のC#スクリプトを見てみましょう(※どこに書けばよいのかはこの後説明します)。

IEnumerator Countdown()
{
  countdownText.enabled = false;
    
  yield return new WaitForSeconds(1);	//1秒待つ

  //文字を表示する
  countdownText.enabled = true;

  //3,2,1…とカウントダウンするループ
  for(int i = 2; i >= 0; i--)
  {
    countdownText.text = (i + 1).ToString();	//文字の表示を切り替える
    yield return new WaitForSeconds(1);	//1秒待つ
  }

  //文字を非表示にする
  countdownText.enabled = false;

  //ゲーム開始
  playerObj = Instantiate(playerPrefab, playerSpawnPosition, Quaternion.identity);
  gameStarted = true;
  wallSpawner.isActive = true;
  coinSpawner.isActive = true;    //追加その4
}

コルーチンは「IEnumerator 関数名()」という風に定義します(引数を与えることもできます)。そして、コルーチン内で「yield return ~」と書くことで処理の流れを一時停止することができます。

それで今回のカウントダウンの核となる部分はfor文のところですね。「文字の表示を切り替えて1秒待つ」という処理を3回繰り返します。こうすることで「3,2,1…」という風に文字が切り替わって、カウントダウン後にゲームが始まるようにすることができます。

スポンサーリンク

カウントダウン用UIの追加

では作り方が分かったら実際の作業に入ります。まずはカウントダウン用のUI(3,2,1と表示するテキスト)をMainCanvasのプレハブに追加しましょう。

「UI」フォルダ内にある「Text_White」プレハブをドラッグ&ドロップし、その名前を「CountdownText」に変更します。そしてフォントサイズや位置を適当に調整したら「テキスト」コンポーネントのチェックをOFFにしておいてください。

スポンサーリンク

GameManagerのスクリプトの変更

そうしたら、またしてもGameManagerのスクリプトを変更します。GameManager.csを開き、下記のように書き換えましょう(※適宜省略してあります。変更箇所が多岐にわたっているので注意してください)。

//省略

//--ここから追加--
[Header("ゲーム設定")]
[SerializeField]
GameObject playerPrefab = null;	//追加その3
[SerializeField]
int maxLevel = 10;
[SerializeField, Min(1), Tooltip("LvUp毎に障害物の生成間隔を小さくするための除数")]
float divisor = 1.1f;
[SerializeField, Tooltip("スコアの桁数")]
int scoreDigits = 8;
[SerializeField]
Vector2 playerSpawnPosition = Vector2.zero;
[SerializeField, Tooltip("1メートルを何秒で走るか")]
float secondsPerMeter = 0.05f;
[SerializeField, Tooltip("1メートル走った時に加算される基本スコア")]
int scorePerMeter = 10;
[SerializeField, Tooltip("レベルアップに必要な走行距離")]
int meterPerLevel = 100;
[Header("UIの設定")]
[SerializeField]
string mileageTextName = "MileageText";
[SerializeField]
string scoreTextName = "ScoreText";
[SerializeField]
string highScoreTextName = "HighScoreText";
[SerializeField]
string levelTextName = "LvText";
[SerializeField]
string gameOverCanvasName = "GameOverCanvas(Clone)";	//追加その2
[SerializeField]
string retryButtonName = "RetryButton"; //追加その2
[SerializeField]
string countDownTextName = "CountdownText";	//追加その3
//--追加ここまで--

MoveSceneManager moveSceneManager;
SaveManager saveManager;
SoundManager soundManager;

//--ここから追加--
bool gameStarted = false; //追加その3
bool timerIsActive = false;
int level = 1;
int mileage = 0;    //走行距離
int maxScore = 0;
int score = 0;
int highScore = 0;
Text mileageText;
Text scoreText;
Text highScoreText;
Text levelText;
Text countdownText; //追加その3
WallSpawner wallSpawner;
Coroutine timer;
Canvas gameOverCanvas;  //追加その2
Button retryButton;	 //追加その2
GameObject playerObj;   //追加その2

//省略

void Update()
{
  if (!gameStarted || moveSceneManager.SceneName == "Title") //条件変更
  {
    if(timer != null)
    {
      StopCoroutine(timer);
    }

    return;
  }

  if (!timerIsActive)
  {
    timer = StartCoroutine("MileageTimer");
  }
}

//省略

//--ここから追加--
public void InitGame()
{
  gameStarted = false;  //追加その3
  level = 1;
  mileage = 0;
  maxScore = (int)Mathf.Pow(10, scoreDigits) - 1; //スコアの最大値を作成。例えば、8桁なら99999999
  score = 0;
  timerIsActive = false;  //追加その3
  wallSpawner.isActive = false;   //追加その3
  wallSpawner.InitSpawner();   //追加その3

  //追加その3。UI表示の初期化
  UpdateMileageUi();
  UpdateLevelUi();
  UpdateScoreUi();
}

public void GameStart()
{
  InitGame();

  StartCoroutine("Countdown");
}

public void GameOver()
{
  if(playerObj != null)
  {
    Destroy(playerObj);
  }

  gameStarted = false;  //追加その3
  timerIsActive = false;
  wallSpawner.isActive = false;
  gameOverCanvas.enabled = true;
  retryButton.enabled = true;  //追加その3
}

public void Retry()
{
  //シーンにある障害物を全部消去する
  GameObject[] walls = GameObject.FindGameObjectsWithTag("Wall");
  foreach(GameObject wall in walls)
  {
    Destroy(wall);
  }

  gameOverCanvas.enabled = false;
  retryButton.enabled = false;  //追加その3

  GameStart();
}

IEnumerator Countdown()
{
  countdownText.enabled = false;
  
  yield return new WaitForSeconds(1);

  countdownText.enabled = true;

  for(int i = 2; i >= 0; i--)
  {
    countdownText.text = (i + 1).ToString();
    yield return new WaitForSeconds(1);
  }

  countdownText.enabled = false;

  playerObj = Instantiate(playerPrefab, playerSpawnPosition, Quaternion.identity);
  gameStarted = true;
  wallSpawner.isActive = true;
}

//シーン読み込み時に各種コンポーネントを取得するメソッド
public void LoadComponents()
{
  if (moveSceneManager.SceneName == "Title")
  {
    return;
  }

  wallSpawner = GameObject.FindGameObjectWithTag("Spawner").GetComponent<WallSpawner>();
  mileageText = GameObject.Find(mileageTextName).GetComponent<Text>();
  scoreText = GameObject.Find(scoreTextName).GetComponent<Text>();
  highScoreText = GameObject.Find(highScoreTextName).GetComponent<Text>();
  levelText = GameObject.Find(levelTextName).GetComponent<Text>();

  //追加その2
  gameOverCanvas = GameObject.Find(gameOverCanvasName).GetComponent<Canvas>();
  retryButton = GameObject.Find(retryButtonName).GetComponent<Button>();

  //追加その3
  countdownText = GameObject.Find(countDownTextName).GetComponent<Text>();

  //ボタンにクリック時の処理を登録
  retryButton.onClick.AddListener(() => Retry());
}

//省略

カウントダウン関係の処理や、リトライ時の初期化処理などを追加しました。そのほか細かい部分も修正して、ゲームオーバー~再スタートの処理が上手くいくように調整してあります。

書けたら、GameManagerのプレハブをインスペクターで開き、「プレイヤープレハブ」にプレイヤーのプレハブを登録してください。

あと、ゲーム開始時にプレイヤーキャラのゲームオブジェクトを自動生成する処理を入れたので、もうシーンにプレイヤーを置いておく必要はありません。削除しておきましょう。

ここまできちんとできていれば次のGIFのようになるはずです。

カウントダウンを実装した場合の例

(クリックで再生)


次のページ→スクロールする背景の作り方