22
25

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 を作るチュートリアル (1/3)

Last updated at Posted at 2019-12-01

概要

この記事は、Unity初心者向けに、Flappy Bird (みたいなゲーム)を作りながら操作方法を学べるチュートリアルです。
042.gif
こんなゲームを作ります。

ページ作成の都合でこの記事は3ページに分かれていて、ここは最初のページとなります。

ページのリンク

やることリスト

今回のチュートリアルでは以下のことを実装していきます。

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

第一回(このページ)では、①の部分まで進めていきます。

プロジェクト作成と環境設定

Unity Hub を起動して、プロジェクトを新規作成します。
001.png

  1. テンプレートは「2D」を選択
  2. プロジェクト名は「TestFlappyBird」
  3. 作成ボタンをクリックする

※保存先は任意の場所。デスクトップなどにしておくとフォルダへのアクセスがしやすいです。

レイアウトの設定

今回の説明で使用するレイアウトは「2 by 3」とします。
002.png
003.gif
Projectビューのアイコンは最小化しておくと見やすくて良いです。

素材のダウンロード

今回のゲームで使用する素材は以下の場所にアップロードしています。
http://syun777.sakura.ne.jp/tmp/Unity/FlappyBird/images.zip

■フォルダ構成
 images
  +-- 5box.png: 障害物
  +-- nasu.png: ゲーム管理の仮画像
  +-- player.png: プレイヤー
  +-- xbox.png: 障害物管理の仮画像

プレイヤーの作成

まずはプレイヤーを作成します。

スプライトの設定

素材画像の player.pngを Projectビューの Assets フォルダにドラッグ&ドロップします。
スクリーンショット_2019_11_19_10_19.png
すると、Assetsフォルダ内に "player" というスプライトリソースが作られます。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal__と_「【Unity2D】Flappy_Bird_を作るチュートリアル」を編集_-_Qiita.png
player を選択して、Inspectorビューから Sprite Mode を Multiple に変更して、 Sprite Editor ボタンをクリックします。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
この際にこのようなダイアログが表示されますが、これは「 Sprite Mode を変更したのに Apply(適用する)をしていませんがよろしいですか?」という確認メッセージです。特に問題ないので、Apply をクリックして適用します。

Sprite Editor が表示されるので、Slice のドロップダウンをクリックして、設定はそのままで Slice ボタンをクリックします。
UnityEditorInternal_SpriteEditorMenu_と_Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
Slice ボタンを押しても見た目に変化がないので、何も変わっていないように見えますが、下にある画像をクリックすると良い感じにスライスされているのが確認できます。
010.gif
Sprite Editor を閉じると、以下のようにメッセージが表示されますが、これも「 Apply ボタンを押していないけれど大丈夫?」という確認のメッセージなので、Apply を押して適用します。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
ちなみに Projectビューの Assets フォルダから player スプライトを選択してもスライスした情報を確認できます。
011.gif
player_0player_1 というスプライト名でスライスされたということがわかります。
なお、Sprite Mode の Multiple という設定は、このように1つの画像から複数のスプライトを作る、というものとなります。

プレイヤーオブジェクトを作成する

早速このプレイヤーをゲーム内に配置してみます。
スクリーンショット_2019_11_19_10_49.png
player_0 を Scene ビューにドラッグ&ドロップします。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
すると Hierarchy ビューに player_0 というゲームオブジェクトが作られます。この player_0 をクリックして選択し、Inspectorビューから
`Player' という名称に変更しておきます。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png

コンポーネント 「Rigidbody 2D」 を追加する

Player にコンポーネント「Rigidbody 2D」を追加します。「Rigidbody 2D」とは、オブジェクトの物理的な挙動(移動や落下)を制御するためのものです。これをゲームオブジェクトに登録することで、プレイヤーが操作したり、落下したりすることができます。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal__と_「【Unity2D】Flappy_Bird_を作るチュートリアル」を編集_-_Qiita.png
ちなみにゲームオブジェクトには初期状態で「Transform」というコンポーネントが登録されており、これは「座標」「回転値」「拡大縮小値」を保持しています。
画像が表示できているのは「Sprite Renderer」が登録されているためです。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
試しに「Sprite Renderer」の隣にあるチェックボックスをクリックするとスプライトの描画が無効になって何も表示されなくなります。
ゲームオブジェクトとコンポーネントの関係のイメージとしては以下の通りです。
名称未設定.png
ゲームオブジェクトが複数のコンポーネント(Transform / Sprite Renderer / Rigidbody 2D / Collider 2D)を持つことになります。

ゲームオブジェクトとコンポーネントの関係を説明したところで、物理挙動の機能をもつ「Rigidbody 2D」を登録します。
Player ゲームオブジェクトを選択した状態で、Inspectorビューから「Add Component」をクリックして、 Rigidbody 2D を選ぶと登録できます。
012.gif
登録できたら、Unityエディタの上中央にある「▶︎(再生ボタン)」をクリックして動作を確認します。
013.gif
実行すると Playerオブジェクトは重力で落下していきます。その動きを確認したら、もう一度「▶︎(再生ボタン)」をクリックして、再生を止めた状態にしておきます。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
(凹んだ状態を解除しておく)

スクリプトを追加して動きを作る

Playerに「スクリプト」というコンポーネントを追加して動きを作ります。
014.gif

  1. Playerオブジェクトを選択
  2. Inspectorビューから「Add Component」ボタンをクリック
  3. scriptで検索して「New Script」を選ぶ
  4. スクリプト名を「Player」にして「Create And Add」をクリック
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png これにより「Player」というC#スクリプトがプロジェクトに追加されます。

スクリプトの記述

Player スクリプトをダブルクリックしてスクリプトを開き、以下のコードを入力します。

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

// ■プレイヤー
public class Player : MonoBehaviour {
  [SerializeField]
  float JUMP_VELOCITY = 1000; // ①ジャンプ力の定義

  Rigidbody2D _rigidbody; // ②物理挙動コンポーネント保持用

  // 開始処理
  void Start() {
    // ③物理挙動コンポーネントを取得
    _rigidbody = GetComponent<Rigidbody2D>();
  }

  // 更新
  void Update() {
    if (Input.GetKeyDown(KeyCode.Space)) {
      // ④Spaceキーを押したのでジャンプ処理
      _rigidbody.velocity = Vector2.zero; // 落下速度を一度リセットする
      _rigidbody.AddForce(new Vector2(0, JUMP_VELOCITY)); // 上方向に力を加える
    }
  }
}

記述する場所には番号をつけています。
①はジャンプ力の定義です。ひとまず力の大きさは 1000 としました。[SerializeField] をつけることで、Unityエディタからも値の変更が可能となります。

②は物理挙動コンポーネントである Rigidbody 2D を保持する変数の定義です。コンポーネントを取得する関数は処理が重いので、あらかじめ保持するようにしておきます。

③は Rigidbody 2D を取得して _rigidbody 変数に格納しています。 Start() はゲームオブジェクトの最初の Update() が呼び出される前に呼び出される関数です。
詳細は UnityのAPIドキュメントに記載されています。
https://docs.unity3d.com/jp/460/ScriptReference/MonoBehaviour.Start.html

Start() はこのような「一番最初に一度だけ呼び出される」処理をするのに便利な関数となります。

Update() 内に記述した④でキー入力を受け取ります。 Input.GetKeyDown(KeyCode.Space) を使うと、キーボードのSpaceキーを押した瞬間かどうかを判定できます。そして Spaceキーを押したら、_rigidbody.AddForce() で上方向に力を加えます。力を加える前に _rigidbody.velocity に速度0 の値を代入することで、落下速度を無視したジャンプ力を加えています。
現実世界で考えると、落下速度の力を無視した上昇はできないのですが、操作感はこの方法が良くなります。ゲームでは時々、このような「ウソ物理挙動」がゲームをより面白くすることがあります。

スクリプトが入力できたら▶︎(再生ボタン)を押して実行してみましょう。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
015.gif
Space キーでプレイヤーが上昇しますが、力が大きすぎて画面外へ飛び出してしまいます。

ジャンプ力の調整

ここで先ほどスクリプトで指定した [SerializeField] が役に立ちます。

Player.cs
  [SerializeField]
  float JUMP_VELOCITY = 1000; // ジャンプ力の定義

の部分です。
スクリプトに指定した値を直接書き換えても良いのですが、 [SerializeField] を指定することで、Unityエディタから修正ができます。

Hierarchyビューから「Player」オブジェクトを選択して、
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
Inspectorビューの Player(Script) の JUMP_VELOCITY を 400 に変更します。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
そうしたら▶︎(再生ボタン)をクリックして実行してみます。
016.gif
良い感じのジャンプになりました!

重力の調整

個人的に少しふんわりした挙動が気になったので、もう少し強い重力をかけるようにします。
PlayerオブジェクトのInspectorから、Rigidbody 2D > Gravity Scale の値を 1 から 2 に変更します。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
Gravity Scale は重力加速度の強さです。これを小さくすると重力が小さくなり、大きくするほど大きな重力がかかるようになります。
017.gif
重力を2倍にすることで、鋭い落下になりました。

画面外に出ないようにする

プレイヤーを画面外に出ないようにします。方法としては、画面の端の位置を計算して、そこからはみ出ていたら押し戻す、という処理を行います。
名称未設定.png
画面の端の取得には Camera.main.ViewportToWorldPoint() という関数を使用します。この関数で画面に表示されている位置を取得できます。この関数の座標系は以下の通りです。
名称未設定.png
左下が(0, 0)となり、右上が(1, 1)です。これを使って画面の上の部分と下の部分を取得するコードの記述例は以下の通りです。

// 画面上を取得する
float GetTop() {
  // 画面の右上のワールド座標を取得する
  Vector2 max = Camera.main.ViewportToWorldPoint(Vector2.one);
  return max.y
}

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

Vector2.zero は (0, 0)を、Vector2.one は (1, 1) を表します。
では、Player.cs を開いて、以下のようにコードを修正します。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png

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

// ■プレイヤー
public class Player : MonoBehaviour {
  [SerializeField]
  float JUMP_VELOCITY = 1000; // ジャンプ力の定義

  Rigidbody2D _rigidbody; // 物理挙動コンポーネント保持用

  // 開始処理
  void Start() {
    // 物理挙動コンポーネントを取得
    _rigidbody = GetComponent<Rigidbody2D>();
  }

  // 更新
  void Update() {
    if (Input.GetKeyDown(KeyCode.Space)) {
      // Spaceキーを押したのでジャンプ処理
      _rigidbody.velocity = Vector2.zero; // 落下速度を一度リセットする
      _rigidbody.AddForce(new Vector2(0, JUMP_VELOCITY)); // 上方向に力を加える
    }
  }

  // 固定フレーム更新
  private void FixedUpdate() {
    // ①座標を取得
    Vector3 position = transform.position;
    // ②画面外に出ないようにする
    float y = transform.position.y;
    float vx = _rigidbody.velocity.x;
    if (y > GetTop()) {
      _rigidbody.velocity = Vector2.zero; // 速度を一度リセットする
      position.y = GetTop(); // ③押し戻しする
    }
    if (y < GetBottom()) {
      // ④下に落ちたらオートジャンプ
      _rigidbody.velocity = Vector2.zero; // 落下速度を一度リセットする
      _rigidbody.AddForce(new Vector2(0, JUMP_VELOCITY));
      position.y = GetBottom(); // 押し戻しする
    }

    // ⑤座標を反映する
    transform.position = position;
  }

  // 画面上を取得する
  float GetTop() {
    // 画面の右上のワールド座標を取得する
    Vector2 max = Camera.main.ViewportToWorldPoint(Vector2.one);
    return max.y;
  }

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

追加した部分は、FixedUpdate()GetTop() / GetBottom() 関数です。
FixedUpdate() 一定間隔で呼び出されることが保証される Update() です。Update() は呼び出される間隔が不定なので、物理挙動を行う場合は FixedUpdate() 内に記述すると都合が良いためとなります。

では FixedUpdate() をみていきます。まずは①です。

Player.cs
    // ①座標を取得
    Vector3 position = transform.position;
    float y = transform.position.y;

ここでは Playerオブジェクトの位置を取得するために、transform コンポーネントから Y軸の値を取得しています (transform.position.y) 。Playerオブジェクトは上下のみの移動なので、Y軸の値だけで判定できます。

続いて②・③の部分です。

Player.cs
    // ②画面外に出ないようにする
    if (y > GetTop()) {
      _rigidbody.velocity = Vector2.zero; // 速度を一度リセットする
      position.y = GetTop(); // ③押し戻しする
    }

画面上からはみ出してしまったら、上昇速度をリセットして(ゼロにする)、はみ出ない位置まで押し戻します。
push.png
また速度をゼロにすることで、移動によるめり込みを防ぐようにしています。

④は画面下の判定です。

Player.cs
    if (y < GetBottom()) {
      // ④下に落ちたらオートジャンプ
      _rigidbody.velocity = Vector2.zero; // 落下速度を一度リセットする
      _rigidbody.AddForce(new Vector2(0, JUMP_VELOCITY));
      position.y = GetBottom(); // 押し戻しする
    }

押し返し処理としては同じですが、自動でジャンプ処理を行うようにしました。

最後に⑤で押し返した位置を transform に反映させます。

Player.cs
    // ⑤座標を反映する
    transform.position = position;

では実行して動きを確認してみます。
018.gif
画面上からはみ出なくなり、画面下では自動でジャンプするようになりました。

ジャンプアニメーションの追加

ジャンプ上昇中はキャラクター画像を切り替える簡単なアニメーションを実装します。
Unityでアニメーションを作る場合、Animatorを使うのが一般的ですが、少し複雑なので、ここではスクリプトで制御する方法で実装してみます。
Player.cs を開いて、以下のように修正します。

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

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

  Rigidbody2D _rigidbody; // 物理挙動コンポーネント保持用

  // 開始処理
  
  
  

①のところで、スプライト番号に対応する画像定数 (SPR_FALLL / SPR_JUMP) を定義しています。
②で スプライトの配列 SPR_LIST を定義して、スプライト番号に対応するスプライトを格納します。

記述できたら、Unityエディタに戻って、Playerオブジェクトの Scriptコンポーネントに注目します。
Unity_2018_4_12f1_Personal_-_SampleScene_unity_-_TestFlappyBird_-_PC__Mac___Linux_Standalone__Personal___Metal_.png
JUMP_VELOCITY の下に、SPR_LIST という項目が増えているのが確認できます。ここの Size2 に変更して、スプライト player_0 / player_1 を割り当てます。
019.gif
これにより、SPR_LIST[0] には "player_0"、SPR_LIST[1] に "player_1" が割り当てられたことになります。
名称未設定.png
これを Playerオブジェクトの状態によって切り替えるようにします。
Player.cs を開いて、以下のように修正します。

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

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

  Rigidbody2D _rigidbody; // 物理挙動コンポーネント保持用
  SpriteRenderer _renderer; // ①スプライト描画

  // 開始処理
  void Start() {
    // 物理挙動コンポーネントを取得
    _rigidbody = GetComponent<Rigidbody2D>();
    // ②スプライト描画コンポーネントを取得
    _renderer = GetComponent<SpriteRenderer>();
  }

  // 更新
  void Update() {
    if (Input.GetKeyDown(KeyCode.Space)) {
      // Spaceキーを押したのでジャンプ処理
      _rigidbody.velocity = Vector2.zero; // 落下速度を一度リセットする
      _rigidbody.AddForce(new Vector2(0, JUMP_VELOCITY)); // 上方向に力を加える
    }

    // ③プレイヤーの状態でスプライトを切り替える
    if (_rigidbody.velocity.y < 0) {
      // 落下中
      _renderer.sprite = SPR_LIST[SPR_FALL];
    } else {
      // 上昇中
      _renderer.sprite = SPR_LIST[SPR_JUMP];
    }
  }
  
  
  

①でスプライト描画コンポーネントである SpriteRenderer を格納する変数を定義しています。

Player.cs
  SpriteRenderer _renderer; // ①スプライト描画

スプライト描画コンポーネントを取得するために、② (Start関数) で取得処理を行っています。

Player.cs
  // 開始処理
  void Start() {
    // 物理挙動コンポーネントを取得
    _rigidbody = GetComponent<Rigidbody2D>();
    // ②スプライト描画コンポーネントを取得
    _renderer = GetComponent<SpriteRenderer>();
  }

そして③(Update関数)で落下方向によってスプライトの切り替えを行っています。下方向の移動(yがマイナス)であれば落下とし、上方向の移動であればジャンプ中、という判定としています。

Player.cs
  // 更新
  void Update() {
    if (Input.GetKeyDown(KeyCode.Space)) {
      // Spaceキーを押したのでジャンプ処理
      _rigidbody.velocity = Vector2.zero; // 落下速度を一度リセットする
      _rigidbody.AddForce(new Vector2(0, JUMP_VELOCITY)); // 上方向に力を加える
    }

    // ③プレイヤーの状態でスプライトを切り替える
    if (_rigidbody.velocity.y < 0) {
      // 落下中
      _renderer.sprite = SPR_LIST[SPR_FALL];
    } else {
      // 上昇中
      _renderer.sprite = SPR_LIST[SPR_JUMP];
    }
  }

では実行して、ジャンプ中と落下中でスプライトが切り替わることを確認します。
020.gif

次回

次のページでは障害物となるブロック、それを生成するブロック管理を作成します。

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

22
25
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
22
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?