8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-12-01

今回やること

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

プレイヤーの作成まで終わったので、②ブロック(障害物)に関連する実装を進めていきます。

ページのリンク

ブロックの作成

Flappy Bird はジャンプで障害物をタイミング良く回避するゲームです。今回は障害物として、ブロックが右から流れてくるようにします。

ブロック画像の追加

ダウンロードした素材フォルダの「5box.png」を Projectビューの Assetsフォルダにドラッグ&ドロップします。
スクリーンショット_2019_11_22_22_11.png
以下のように "5box" のスプライトが作られればOKです。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
作成した 5box スプライトを Hierarchyビューにドラッグ&ドロップします。
スクリーンショット_2019_11_22_22_15.png
作成した「5box」ゲームオブジェクトを Inspectorビューから「Block」にリネームします。
021.gif

動きをつける

まずは Block オブジェクトを右端に移動させます。
Hierarchyビューで Blockオブジェクトをダブルクリックすると、Sceneビューで中心に表示されます。そうしたら Sceneビューで Blockオブジェクトを右の方へドラッグすると右側に移動できます。
022.gif

ゲームオブジェクトに動きをつけるには Rigidbody が必要となります。Blockゲームオブジェクトを選択して、Inspectorビューの「Add Component」から Rigidbody 2Dを追加します。
UnityEditor_AdvancedDropdown_AddComponentWindow_と_Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
Blockオブジェクトは重力を無視したいので、 Gravity Scale0 にして重力を無効にします。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
念のため実行して Blockオブジェクトが動かないことを確認しておきます。

続けて、Scriptコンポーネントを追加します。
023.gif

スクリプトの名前は「Block」とし、以下のように記述します。

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

// ブロック
public class Block : MonoBehaviour {
  Rigidbody2D _rigidbody;
  float _speed = -100; // 移動速度

  void Start() {
    // 物理挙動コンポーネントを取得
    _rigidbody = GetComponent<Rigidbody2D>();
    // 力を加える
    _rigidbody.AddForce(new Vector2(_speed, 0));  
  }

  void Update() {
  }
}

メンバ変数に _rigidbody / _speed を追加し、Start() で Rigidbody2D コンポーネントを取得して、左向きに力を加えています。

では実行してブロックが動くことを確認します。

024.gif

プレイヤーとの衝突判定を行う

次にプレイヤーとの衝突判定を実装します。プレイヤーがブロックにぶつかったら衝突するようにします。

まずは Blockオブジェクトを選び、Add Component > Circle Collider 2D を追加します。
025.gif
Blockオブジェクトをダブルクリックすると、Sceneビューで周りに丸い線(当たり判定)があるのが確認できます。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
そして Inspectorビュー から、Circle Collider 2Dコンポーネントの Is Trigger にチェックを入れておきます。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
これにより衝突した際に物理挙動(跳ね返ったり、転がったりする)を行わずに、「当たったかどうか」だけを判定できるようになります。
例えば、プレイヤーがブロックにぶつかったときに、ブロックが跳ね返ってふっとんだりしなくなります。
例えば Is Trigger にチェックを入れないと以下のような挙動となります。
026.gif
これはこれで面白い挙動ですが、FlappyBirdを作るには不適切なので、Is Triggerで衝突を扱います。

Playerオブジェクトにも「Circle Collider 2D」を追加して、Is Triggerにチェックを入れておき、Radius の値を 0.4 に減らしておきます。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
Radius とは円の当たり判定の半径で、この値が小さくなると当たり判定が小さくなります。プレイヤーの当たり判定は小さめにすることでゲームを遊びやすくするためです。

では衝突したら Playerオブジェクトが消えるようにします。
Playerスクリプトを開いて、OnTriggerEnter2D 関数を追加します。

Player.cs
  // 衝突判定
  private void OnTriggerEnter2D(Collider2D collision) {
    // 衝突したので消滅
    Destroy(gameObject);
  }

これは Is Trigger が有効なコリジョンに衝突したときに発生する処理となります。DestroygameObject を渡すと削除処理が呼び出されます。

では実行してプレイヤーがブロックにぶつかると消滅することを確認します。
027.gif

Blockオブジェクトをプレハブ化する

ブロックが1つだけでは簡単すぎるので、たくさん発生するようにします。たくさん発生させるには、Blockオブジェクトをプレハブ化すると、複製しやすくなります。

プレハブを使う理由を簡単に説明すると、ここまでのプレイヤーやブロックは実体化という処理が行われており、ゲーム画面(シーン)で利用可能な状態になっています。
名称未設定.png
それに対して、プレハブ化を行うと、ゲーム外にオブジェクトが配置されます。
名称未設定.png
そして「実体化」を後から行うことで、ゲーム内にたくさん配置することが可能になります。これにより間違って複製したいオブジェクトをゲーム中に消してしまう心配はなくなります。また、Unityがプレハブを使って複製する仕組みを提供しているのでそれに従ったほうが楽、ということもあります。

では、Blockオブジェクトをプレハブ化します。
プレハブ化は簡単で、HierarchyビューにあるBlockオブジェクトを、Projectビューにドラッグ&ドロップするだけです。
028.gif
プレハブを作ったら、Hierarchyビューにある Blockオブジェクトは不要なので消しておきます。

ブロック生成管理オブジェクトを作成する

プレハブ化したブロックの生成を行うオブジェクトを作成します。
素材画像の「xbox.png」を Projectビューにドラッグ&ドロップします。
スクリーンショット_2019_11_25_10_10.png
追加された xbox スプライトを Hierarchyビューにドラッグ&ドロップします。
スクリーンショット_2019_11_25_10_12.png
そして xboxオブジェクトを「BlockMgr」にリネームします。
Windows環境であればF2、MacOSX環境であればEnterでリネームできます。Inspectorビューからリネームしてもよいです。
030.gif
ちなみに「Mgr」とは「Manager」の省略語です。人によっては「Mng」と省略するケースもあります。省略語が好きでない場合は「BlockManager」としてもよいです。

ブロック生成スクリプトの作成

BlockMgrオブジェクトを選択したまま、Inspectorビューから Add Component > New Script > Name に "BlockMgr" と入力 > Create and Add を選び BlockMgrスクリプトを追加します。
031.gif

BlockMgrスクリプトは以下のように記述します。

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

public class BlockMgr : MonoBehaviour {
  // 生成するBlockオブジェクト
  public GameObject block;

  // 0になったらBlockオブジェクトを生成
  float _timer = 0;

  void Start() {
  }

  void Update() {
    // ①経過時間を差し引く
    _timer -= Time.deltaTime;
    if(_timer < 0) {
      // ②0になったのでBlock生成
      // ③BlockMgrの場所から生成
      Vector3 position = transform.position;
      // ④プレハブをもとにBlock生成
      GameObject obj = Instantiate(block, position, Quaternion.identity);
      // ⑤1秒後にまた生成する
      _timer += 1;
    }
  }
}

スクリプトの説明です。
メンバ変数には block を用意し、Blockオブジェクトのプレハブをここに入れておきます。また _timer は生成を繰り返すためのタイマーです。この値が 0 以下になったとき Blockオブジェクトを生成します。

生成処理は Update 関数に記述しています。

BlockMgr.cs
    // ①経過時間を差し引く
    _timer -= Time.deltaTime;

Time.deltaTime は前回の Update からの経過時間です。この値で _timer から引き算をすることで一定時間ごとに何らかの処理を行う、という判定が可能となります。

BlockMgr.cs
    if(_timer < 0) {
      // ②0になったのでBlock生成

_timer は時間が経過するごとに減少する値となるため、これが 0 になったら何らかの処理を行う、という判定をしています。

BlockMgr.cs
      // ③BlockMgrの場所から生成
      Vector3 position = transform.position;

③ではまず BlockMgrオブジェクトの位置を取得しています。オブジェクトを生成するには座標が必要となるため、ひとまずBlockMgrの位置を基準に生成するようにします。

BlockMgr.cs
      // ④プレハブをもとにBlock生成
      GameObject obj = Instantiate(block, position, Quaternion.identity);

④が実際にBlockオブジェクトを生成している処理となります。 Instantiate という関数にプレハブ(ここでは block)を渡すことで、Blockオブジェクトを生成できます。Instantiate() の二番目の引数 Quaternion.identity は回転値です。今回は回転を加えないので identity (初期値=ゼロ)の値としています。

BlockMgr.cs
      // ⑤1秒後にまた生成する
      _timer += 1;

⑤ではタイマーを再設定しています。1 を足し込むことで、「1秒後」にもう一度生成を行います。

Blockプレハブの設定

このまま実行すると、エラーとなりブロックの生成は行われません。block に Blockプレハブが設定されていないためです。
そのためプレハブの設定を行います。
設定方法は簡単で、Unityエディタに戻って、

  1. Hierarchyビューから「BlockMgr」オブジェクトを選択
  2. Projectビューから「Blockプレハブ」を Inspectorビューの BlockMgr(Script) の Block にドラッグ&ドロップ

これで設定ができます。
032.gif

では実行して、Blockオブジェクトが次々と生成されることを確認します。
033.gif

Blockの破棄処理と移動速度の設定

一見うまくいってそうですが、実は大きな問題があります。実行中の Hierarchyビューを見てみます。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
すると、このように大量の Block(Clone) というオブジェクトが生成されています。今回のようなシンプルなゲームではあまり問題にはならないのですが、これが 何千何万と生成されると、実行時のメモリが不足してゲームがクラッシュする可能性があります。
そのため、不要になった Blockオブジェクトを削除するようにします。

Blockスクリプトを開いて、画面の左端に出たら消します。

Block.cs
  void Update() {
    Vector2 position = transform.position;
    if(position.x < GetLeft()) {
      Destroy(gameObject); // 画面外に出たので消す.
    }
  }

  float GetLeft() {
    // 画面の左下のワールド座標を取得する
    Vector2 min = Camera.main.ViewportToWorldPoint(Vector2.zero);
    return min.x;
  }

画面の左端を取得する GetLeft 関数を追加して、Update() で画面外に出たかどうかチェックし、画面外に出たら Destroy() で消滅させます。

では実行して、画面外に出た Blockオブジェクトが消えるのを確認します。

Blockオブジェクトの移動速度を変化させる

時間が経過するにつれて、Blockオブジェクトの移動が速くなるようにして、少しずつ難易度を上げるようにします。

まずは Blockスクリプトを開いて速度を設定する関数 SetSpeed() を追加します。

Block.cs
  // 速度を設定
  public void SetSpeed(float speed) {
    _speed = speed;
  }

Blockスクリプトであればどこに定義しても問題ありません。SetSpeed() は公開関数にするので、
public キーワードを忘れずにつけます。

続けて、BlockMgrスクリプトを以下のように修正します。

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

public class BlockMgr : MonoBehaviour {
  // 生成するBlockオブジェクト
  public GameObject block;

  // 0になったらBlockオブジェクトを生成
  float _timer = 0;
  // ①トータルの経過時間を保持
  float _totalTime = 0;

  void Start() {
  }

  void Update() {
    // 経過時間を差し引く
    _timer -= Time.deltaTime;
    // ②トータル時間を加算
    _totalTime += Time.deltaTime;

    if(_timer < 0) {
      // 0になったのでBlock生成
      // BlockMgrの場所から生成
      Vector3 position = transform.position;
      // プレハブをもとにBlock生成
      GameObject obj = Instantiate(block, position, Quaternion.identity);
      // ③Blockオブジェクトの「Block」スクリプトを取得する
      Block blockScript = obj.GetComponent<Block>();
      // ④速度を計算して設定
      // 基本速度100に、経過時間x10を加える
      float speed = 100 + (_totalTime * 10);
      blockScript.SetSpeed(-speed); // 左方向なのでマイナス
      // 1秒後にまた生成する
      _timer += 1;
    }
  }
}

メンバ変数に _totalTime を追加し、生成したBlockオブジェクトにトータル時間を加えた速度を設定するようにしています。

①は _totalTime の定義です。
②では _totalTime に経過時間の差分である Time.deltaTime を「加算」しています。これにより経過したトータルの時間が入ることになります。

③で生成したBlockオブジェクトから、Blockスクリプトコンポーネントを取得しています。
そして④で速度を計算し、SetSpeed() で速度を設定しています。
速度の計算式は経過時間を変数としたシンプルな一次式となっています。

$速度=100 + (10 * 経過時間[秒])$

経過時間 速度
0.0秒 100+(10*0) 100
1.0秒 100+(10*1) 110
2.0秒 100+(10*2) 120
... ... ...

では実行して動きを確認します。
034.gif

時間経過によりブロックの移動速度が少しずつですが上昇しています。

ブロックを生成する高さをランダムにする

ブロックの生成位置が常に中央では単調なゲームとなってしまうので、ばらつきを出すようにします。
その前に、ブロック生成位置を右にずらしたいので、BlockMgr オブジェクトを右にずらします。

Hierarchyビューで BlockMgr オブジェクトをダブルクリックして、Sceneビューの中央に表示し、それをドラッグ&ドロップすることで動かすことができます。
035.gif

BlockMgr スクリプトを開いて、Update() を修正します。

BlockMgr.cs
  void Update() {
    // 経過時間を差し引く
    _timer -= Time.deltaTime;
    // トータル時間を加算
    _totalTime += Time.deltaTime;

    if(_timer < 0) {
      // 0になったのでBlock生成
      // BlockMgrの場所から生成
      Vector3 position = transform.position;
      // ※上下(±3)のランダムな位置に出現させる
      position.y = Random.Range(-3, 3);
      // プレハブをもとにBlock生成
      GameObject obj = Instantiate(block, position, Quaternion.identity);
      // Blockオブジェクトの「Block」スクリプトを取得する
      Block blockScript = obj.GetComponent<Block>();
      // 速度を計算して設定
      // 基本速度100に、経過時間x10を加える
      float speed = 100 + (_totalTime * 10);
      blockScript.SetSpeed(-speed); // 左方向なのでマイナス
      // 1秒後にまた生成する
      _timer += 1;
    }
  }

修正箇所は※がついている一行だけです。

BlockMgr.cs
      // ※上下(±4)のランダムな位置に出現させる
      position.y = Random.Range(-4, 4);

Random.Range() 指定した数値の範囲でのランダムな値を返す便利な関数です。ここでは「-4」と「4」を指定しているので、-4〜4 の範囲のランダムな値を返します。

では実行して動作を確認します。
036.gif
ランダムな位置からブロックが出現するようになりました。

ブロックの生成に偏りを入れる

現状でもゲームとして成立していますが、発生タイミングが均一で単調さを感じてしまうので、少し偏りを入れます。
偏りのルールは「10のうち3つは連続で出現する」とします。

BlockMgrスクリプトを開いて以下のように修正します。

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

public class BlockMgr : MonoBehaviour {
  // 生成するBlockオブジェクト
  public GameObject block;

  // 0になったらBlockオブジェクトを生成
  float _timer = 0;
  // トータルの経過時間を保持
  float _totalTime = 0;
  // ①ブロック生成回数
  int _cnt = 0;

  void Start() {
  }

  void Update() {
    // 経過時間を差し引く
    _timer -= Time.deltaTime;
    // トータル時間を加算
    _totalTime += Time.deltaTime;

    if(_timer < 0) {
      // 0になったのでBlock生成
      // BlockMgrの場所から生成
      Vector3 position = transform.position;
      // ※上下(±3)のランダムな位置に出現させる
      position.y = Random.Range(-4, 4);
      // プレハブをもとにBlock生成
      GameObject obj = Instantiate(block, position, Quaternion.identity);
      // Blockオブジェクトの「Block」スクリプトを取得する
      Block blockScript = obj.GetComponent<Block>();
      // 速度を計算して設定
      // 基本速度100に、経過時間x10を加える
      float speed = 100 + (_totalTime * 10);
      blockScript.SetSpeed(-speed); // 左方向なのでマイナス

      // ②生成回数をカウントアップ
      _cnt++;
      if(_cnt%10 < 3) {
        // 0.1秒後にまた生成する
        _timer += 0.1f;
      } else {
        // 1秒後にまた生成する
        _timer += 1;
      }
    }
  }
}

変更箇所は2つです。
①でブロック生成回数を保持する変数 _cnt を定義しています。

BlockMgr.cs
  // ①ブロック生成回数
  int _cnt = 0;

Update() 内に記述した②で生成回数をカウントアップし、下一桁が「0〜2」の場合は「0.1秒後」に生成するようにしました。

BlockMgr.cs
      // ②生成回数をカウントアップ
      _cnt++;
      if(_cnt%10 < 3) {
        // 0.1秒後にまた生成する
        _timer += 0.1f;
      } else {
        // 1秒後にまた生成する
        _timer += 1;
      }

_cnt%10 は _cnt を 10で割った時の余りの値を求める記述です。これにより下一桁を求め、3より小さい(=0〜2)の場合は、「0.1秒後」にブロックを生成するようにしています。

実行すると、ブロックの生成に少し偏りがあることが確認できます。
037.gif

これでプレイヤーとブロックについての処理の実装は終わりです。
次では、スコアと、ゲームオーバー、リトライ処理を実装して完成とします。

次回

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

8
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
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?