LoginSignup
9
2

More than 3 years have passed since last update.

【Unity2D】Flappy Bird を作るチュートリアル (3/3)

Last updated at Posted at 2019-12-01

今回やること

  1. プロジェクトの作成
  2. プレイヤーの作成
  3. ブロック(障害物)の作成
  4. ブロック管理の作成
  5. スコアの実装 ←③
  6. ゲームオーバーの実装 ←③
  7. リトライの実装 ←③

最後にゲーム的な要素である「スコア」の実装と、ゲームオーバー表示、リトライを実装します。

ページのリンク

スコアの実装

ゲーム管理オブジェクトの作成

スコアなどゲーム全体を管理するオブジェクトを作成します。
最初にダウンロードした素材フォルダから「nasu.png」を Projectビューにドラッグ&ドロップします。
スクリーンショット_2019_11_27_21_42.png

続けて、作成した「nasu」スプライトを、Hierarchyビューにドラッグ&ドロップします。
スクリーンショット_2019_11_27_21_45.png

「nasu」ゲームオブジェクトが作られるので、名前を「GameMgr」に変更します。
038.gif
nasuオブジェクトを選択した状態で、Windows環境であれば「F2」、MacOSX環境であれば「Enter」で変更できます。

GameMgrオブジェクトは画面中央にあると邪魔なので、Sceneビューでドラッグ&ドロップして画面端に移動させておきましょう。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png

スクリプトでスコア表示をする

スクリプトでスコア表示の実装をします。
まずはスクリプトコンポーネントを追加します。GameMgrオブジェクトを選択し、Inspectorビューから、Add Component > New Script を選び、スクリプト名は「GameMgr」とします。
039.gif

スクリプトが追加できたら、GameMgr.cs を開いて以下のように記述します。

GameMgr.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameMgr : MonoBehaviour {
  // ①スコア
  int _score = 0;

  void Start() {
  }

  void Update() {
  }

  private void FixedUpdate() {
    // ②スコア上昇
    _score += 1;
  }

  private void OnGUI() {
    // ③スコアを描画
    _DrawScore();
  }

  // ④スコアの描画
  void _DrawScore() {
    // 文字を大きくする
    GUI.skin.label.fontSize = 32;
    // 左揃え
    GUI.skin.label.alignment = TextAnchor.MiddleLeft;
    Rect position = new Rect(8, 8, 400, 100);
    GUI.Label(position, string.Format("score:{0}", _score));
  }
}

スコア用の変数を追加して、時間経過でスコアを上昇させ、その値を表示します。

①ではスコア用の変数を追加しています。

GameMgr.cs
  // ①スコア
  int _score = 0;

②でスコア加算用に FixedUpdate() を追加してその中でスコアを加算しています。

GameMgr.cs
  private void FixedUpdate() {
    // ②スコア上昇
    _score += 1;
  }

FixedUpdate() を使用するのは、決まった間隔で呼び出しが行われるためとなります。

③で OnGUI() を定義しています。スコアなどのGUIの描画はこの関数で行います。

GameMgr.cs
  private void OnGUI() {
    // ③スコアを描画
    _DrawScore();
  }

本来であれば uGUI を使うべきですが、テスト・実験用に作る場合、OnGUI() を使った方が素早く作れるので、今回は OnGUI() を使います。

④が実際の描画処理です。

GameMgr.cs
  // ④スコアの描画
  void _DrawScore() {
    // 文字を大きくする
    GUI.skin.label.fontSize = 32;
    // 左揃え
    GUI.skin.label.alignment = TextAnchor.MiddleLeft;
    Rect position = new Rect(8, 8, 400, 100);
    GUI.Label(position, string.Format("score:{0}", _score));
  }

初期状態だとフォントが小さいので GUI.skin.label.fontSize = 32 で大きくします。
GUI.Label() で文字を描画するのですが、第一引数には描画領域として Rect で左上の位置と幅・高さを指定します。
数値を文字として描画する場合、string.Format() を使うと自由な書式で描画できます。

では実行して動作を確認します。
040.gif
時間経過によりスコアが上昇していくのがわかります。

ゲームオーバーの実装

プレイヤーがブロックに当たったらゲームオーバーとします。
そしてゲームオーバーになったらスコアを増加しないようにします。

処理の流れとしては以下の通りです。
名称未設定.png

①プレイヤーとブロックの衝突、②プレイヤー消滅、この2つはすでに実装しているので、PlayerからGameMgrにゲームオーバーになったことを通知するようにします(②から③の部分)。

GameMgrスクリプトを以下のように修正します。(修正部分は7箇所)

GameMgr.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameMgr : MonoBehaviour {
  // ①状態定数
  enum State {
    Main, // メインゲーム
    GameOver, // ゲームオーバー
  }

  // スコア
  int _score = 0;
  // ②状態
  State _state = State.Main;

  // ③ゲームオーバーの開始
  public void StartGameOver() {
    _state = State.GameOver;
  }

  void Start() {
  }

  void Update() {
  }

  private void FixedUpdate() {
    if(_state == State.Main) {
      // ④メインゲーム中のみスコア上昇
      _score += 1;
    }
  }

  private void OnGUI() {
    // スコアを描画
    _DrawScore();

    // ⑤画面の中心座標を計算する
    float CenterX = Screen.width / 2;
    float CenterY = Screen.height / 2;
    if(_state == State.GameOver) {
      // ⑥ゲームオーバーの描画
      _DrawGameOver(CenterX, CenterY);
    }
  }

  // ⑦ゲームオーバーの描画
  void _DrawGameOver(float CenterX, float CenterY) {
    // 中央揃え
    GUI.skin.label.alignment = TextAnchor.MiddleCenter;
    float w = 400;
    float h = 100;
    Rect position = new Rect(CenterX - w / 2, CenterY - h / 2, w, h);
    GUI.Label(position, "GAME OVER");
  }

  // スコアの描画
  void _DrawScore() {
    // 文字を大きくする
    GUI.skin.label.fontSize = 32;
    // 左揃え
    GUI.skin.label.alignment = TextAnchor.MiddleLeft;
    Rect position = new Rect(8, 8, 400, 100);
    GUI.Label(position, string.Format("score:{0}", _score));
  }
}

①で「メインゲーム中」「ゲームオーバー」を判定するための enum定数を定義しています。

GameMgr.cs
  // ①状態定数
  enum State {
    Main, // メインゲーム
    GameOver, // ゲームオーバー
  }

②では、状態を格納する変数を定義しています。

GameMgr.cs
  // ②状態
  State _state = State.Main;

③では外部からゲームオーバーに切り替えるための関数 StartGameOver を定義しています。

GameMgr.cs
  // ③ゲームオーバーの開始
  public void StartGameOver() {
    _state = State.GameOver;
  }

④では FixedUpdate 関数でのスコア上昇の判定を修正しました。状態変数 _state がメインゲーム中だったときだけスコア上昇するようにしています。

GameMgr.cs
  private void FixedUpdate() {
    if(_state == State.Main) {
      // ④メインゲーム中のみスコア上昇
      _score += 1;
    }
  }

⑤・⑥は GUI表示である OnGUI() を修正しています。

GameMgr.cs
  private void OnGUI() {
    // スコアを描画
    _DrawScore();

    // ⑤画面の中心座標を計算する
    float CenterX = Screen.width / 2;
    float CenterY = Screen.height / 2;
    if(_state == State.GameOver) {
      // ⑥ゲームオーバーの描画
      _DrawGameOver(CenterX, CenterY);
    }
  }

Screen.width / Screen.height で幅と高さを取得し、その半分の値を中心として文字の表示をする関数 _DrawGameOver を呼び出しています。

実際のゲームオーバーの描画を行なっているのが⑦の _DrawGameOver() です。

GameMgr.cs
  // ⑦ゲームオーバーの描画
  void _DrawGameOver(float CenterX, float CenterY) {
    // 中央揃え
    GUI.skin.label.alignment = TextAnchor.MiddleCenter;
    float w = 400;
    float h = 100;
    Rect position = new Rect(CenterX - w / 2, CenterY - h / 2, w, h);
    GUI.Label(position, "GAME OVER");
  }

少し計算がややこしいので図で説明します。
名称未設定.png
CenterX / CenterY は画面の中心です。文字を描画するには文字の左上座標と描画矩形(Rect)の幅(w)と高さ(h)が必要となります。左上の座標を求めるには、幅を2で割った値、高さを2で割った値を中央の座標から引き算します。

長くなりましたが、これで GameMgrスクリプトの準備はできました。
続けて、Playerスクリプトを修正します。

Player.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// ■プレイヤー
public class Player : MonoBehaviour {
  // スプライト番号定義
  const int SPR_FALL = 0; // 落下中
  const int SPR_JUMP = 1; // ジャンプ中

  [SerializeField]
  float JUMP_VELOCITY = 1000; // ジャンプ力の定義
  public Sprite[] SPR_LIST; // アニメーション用スプライトの保持
  public GameObject gameMgr; // ①ゲーム管理

  Rigidbody2D _rigidbody; // 物理挙動コンポーネント保持用
  SpriteRenderer _renderer; // スプライト描画
  GameMgr _gameMgr; // ②ゲーム管理スクリプト

  // 開始処理
  void Start() {
    // 物理挙動コンポーネントを取得
    _rigidbody = GetComponent<Rigidbody2D>();
    // スプライト描画コンポーネントを取得
    _renderer = GetComponent<SpriteRenderer>();
    // ③ゲーム管理スクリプトを取得
    _gameMgr = gameMgr.GetComponent<GameMgr>();
  }

  
  
  

  // 衝突判定
  private void OnTriggerEnter2D(Collider2D collision) {
    // 衝突したので消滅
    Destroy(gameObject);
    // ④ゲームオーバーを通知
    _gameMgr.StartGameOver();
  }
}

①でGameMgrオブジェクトを格納する変数を定義します。
②でGameMgrスクリプトを格納する変数を定義します。
③は Start() でGameMgrオブジェクトからスクリプトを取り出して、_gameMgr に格納しています。
④でブロックにぶつかって消滅する時に _gameMgr.StartGameOver() を呼び出すことでゲームオーバーを通知します。

では、Unityエディタに戻って、Player オブジェクトを選択し、Inspectorビューの Spriteの Game Mgr に GameMgrオブジェクトを指定します。
041.gif

では実行して、ブロックに衝突すると画面中央に「GAME OVER」表示がされ、スコアのカウントアップが停止しているのを確認します。
042.gif

リトライの実装

最後にリトライ処理を実装して終わりとします。
ゲームオーバー時に「Retry」ボタンを押すと、ゲームに再挑戦できるようにします。

シーンの登録

リトライ処理は、シーンを作り直す方法で実装します。
Unityエディタのメニューから File > Build Settings... を選びます。
File_と_Menubar.png

するとビルド設定画面が表示されるので、「Add Open Scenes」をクリックして、「SampleScene」をビルド対象に設定します。
Build_Settings_と_Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
追加したら、×ボタンを押して設定画面を閉じます。

なお、「SampleScene」とは今までに編集をしていたメインゲームとなるシーンです。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png

ファイルは Projectビューの Assets > Scenes に存在していて、通常はわかりやすい名称(例えば「MainScene」など)にリネームしておくべきですが、今回は "SampleScene" のままにしておきます。

リトライボタンの実装

リトライボタンは GameMgrスクリプトで実装します。
GameMgrスクリプトを開いて以下のよう修正します。

GameMgr.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// ①シーンの読み直しに必要
using UnityEngine.SceneManagement;

public class GameMgr : MonoBehaviour {
  // 状態定数
  enum State {
    Main, // メインゲーム
    GameOver, // ゲームオーバー
  }

  // スコア
  int _score = 0;
  // 状態
  State _state = State.Main;

  // ゲームオーバーの開始
  public void StartGameOver() {
    _state = State.GameOver;
  }

  void Start() {
  }

  void Update() {
  }

  private void FixedUpdate() {
    if(_state == State.Main) {
      // メインゲーム中のみスコア上昇
      _score += 1;
    }
  }

  private void OnGUI() {
    // スコアを描画
    _DrawScore();

    // 画面の中心座標を計算する
    float CenterX = Screen.width / 2;
    float CenterY = Screen.height / 2;
    if(_state == State.GameOver) {
      // ゲームオーバーの描画
      _DrawGameOver(CenterX, CenterY);
      // ②リトライボタンの描画
      if(_DrawRetryButton(CenterX, CenterY)) {
        // ③クリックしたらやり直しする
        SceneManager.LoadScene("SampleScene");
      }
    }
  }

  // ④リトライボタンの描画
  bool _DrawRetryButton(float CenterX, float CenterY) {
    float ofsY = 40;
    float w = 100;
    float h = 64;
    Rect rect = new Rect(CenterX - w / 2, CenterY + ofsY, w, h);
    if (GUI.Button(rect, "RETRY")) {
      // ボタンを押した
      return true;
    }
    return false;
  }

  // ゲームオーバーの描画
  void _DrawGameOver(float CenterX, float CenterY) {
    // 中央揃え
    GUI.skin.label.alignment = TextAnchor.MiddleCenter;
    float w = 400;
    float h = 100;
    Rect position = new Rect(CenterX - w / 2, CenterY - h / 2, w, h);
    GUI.Label(position, "GAME OVER");
  }

  // スコアの描画
  void _DrawScore() {
    // 文字を大きくする
    GUI.skin.label.fontSize = 32;
    // 左揃え
    GUI.skin.label.alignment = TextAnchor.MiddleLeft;
    Rect position = new Rect(8, 8, 400, 100);
    GUI.Label(position, string.Format("score:{0}", _score));
  }
}

修正箇所は、
1. using の追加
2. OnGUI() の修正
3. _DrawRetryButton() の追加

の3つです。

まずファイルの先頭で

GameMgr.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// ①シーンの読み直しに必要
using UnityEngine.SceneManagement;

というように using UnityEngine.SceneManagement; を追加し、シーン管理である SceneManager を使えるようにします。

次に、OnGUI() の修正です。

GameMgr.cs
  private void OnGUI() {
    // スコアを描画
    _DrawScore();

    // 画面の中心座標を計算する
    float CenterX = Screen.width / 2;
    float CenterY = Screen.height / 2;
    if(_state == State.GameOver) {
      // ゲームオーバーの描画
      _DrawGameOver(CenterX, CenterY);
      // ②リトライボタンの描画
      if(_DrawRetryButton(CenterX, CenterY)) {
        // ③クリックしたらやり直しする
        SceneManager.LoadScene("SampleScene");
      }
    }
  }

②で _DrawRetryButton() を呼び出し、戻り値が true なら③のやり直し処理を行います。 SceneManager.LoadScene() には「シーン名」を文字列で渡します。

④でリトライボタンの描画(とクリック)を行う _DrawRetryButton() を実装しています。

SceneMgr.cs
  // ④リトライボタンの描画
  bool _DrawRetryButton(float CenterX, float CenterY) {
    float ofsY = 40;
    float w = 100;
    float h = 64;
    Rect rect = new Rect(CenterX - w / 2, CenterY + ofsY, w, h);
    if (GUI.Button(rect, "RETRY")) {
      // ボタンを押した
      return true;
    }
    return false;
  }

ofsY とは 「Offset Y座標」の略で、Y座標を少しずらす値としてよく使われる略語です。
GUI.Button() はボタンの描画とクリックしたかどうかを判定する関数です。true を返した場合はボタンをクリックしたと判定されます。

では実行して動作を確認します。
043.gif

ゲームオーバー時に「RETRY」ボタンを押すことでゲームを遊び直すことができるようになりました。

BlockMgr / GameMgr のスプライト表示を消す

BlockMgr(xbox) / GameMgr(nasu) がスプライト表示されていますが、俺は不要なので消しておきます。

Inspectorビューから Sprite Renderer のチェックを外すことで表示されなくなります。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
再び表示したい場合は、ここにチェックを入れ直すと表示されるようになります。

最後に

まだゲームとして物足りない部分はありますが、これでひとまずゲームは完成です。

  • ブロックの出現パターンをランダムでなく決まった形にする
  • 接触すると得点になるアイテム
  • 一定距離進むとゲームクリア

など色々な要素を加えて、より面白いゲームに作り替えていくと、ゲームプログラムの勉強、ゲームデザインの練習になって良いかもしれません。

なお、今回作成したプロジェクトは以下の場所にアップロードしています。
http://syun777.sakura.ne.jp/tmp/Unity/FlappyBird/TestFlappyBird.zip

9
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
2