「Flappy Daruma」を作ってみよう!④キャラの操作

今回の内容を整理しよう!

前回の内容を復習しよう

前回は、障害物を作成しました。
地面に衝突の機能を与えるためにCollider2Dをつけたり、土管をプレハブから複製してランダムな高さに変更し無限に作り出しスクロールさせる処理を実装しました。

今回進める作業を確認しよう!

今回は、プレイヤーが操作するダルマを作っていきます。
ダルマのオブジェクトに、入力を受けたらジャンプする機能を独自コンポーネントとして追加し、実際に操作できるようにしましょう。
また、土管や地面とぶつかったら、ゲームオーバーに状態を変更するためのイベント関数についても紹介します。

前提知識を学ぼう!

剛体の物理挙動を扱うRigidbody2Dコンポーネント

前回のCollider2Dコンポーネントの説明で、落下するダルマが地面に衝突して着地するシーンを紹介しました。

今回は地面のオブジェクト側ではなく、ダルマのオブジェクト側から見てみましょう。
このシーンではダルマは落下して地面に衝突・着地しますが、それは下図のようにRigidbody2DとCollider2Dの2つのコンポーネントの機能により実現されています。

落下は物理挙動を扱うRigidbody2Dコンポーネントの機能で実現されています。
Body TypeにはDynamic、Kineamtic、Staticがあり、デフォルトのDynamicでは物理演算によってオブジェクトが動きます。

そして、前回地面に追加したCollider2Dコンポーネントの機能により、同じくCollider2Dを持った他のオブジェクトと衝突することができます。
ダルマには、その形状に合わせて円形のCircleCollider2Dを使いました。

この2つのコンポーネントにより、先ほどの動画のようにダルマが落下して地面に着地する振る舞いを実現しています。

また、今回紹介するダルマをジャンプさせる機能にも物理挙動を扱うRigidbody2Dコンポーネントが必要になってきます。

当たり判定のイベント関数

障害物やダルマには衝突を実現したり接触を検知するためのCollider2Dがついています。
そして、これらの衝突や接触は、イベント関数として呼び出され、スクリプトで衝突・接触のタイミングで処理を実行できます。
接触を検知するだけにするか、衝突させるかは前回お話したようにCollider2DコンポーネントのIs Triggerをチェックするかしないかで設定できます。

衝突するときはOnCollision~2D、接触を検知するだけのときはOnTrigger~2Dという3つのイベント関数が呼び出されます。

  • OnCollisionEnter2D・OnTriggerEnter2D
    衝突・接触した直後に呼び出されるイベント関数です。
  • OnCollisionStay2D・OnTriggerStay2D
    衝突・接触をしている間、毎フレーム呼び出される関数です。
  • OnCollisionExit2D・OnTriggerExit2D
    衝突・接触から離れた瞬間に呼び出されます。

衝突・接触、3つのタイミングに応じてイベント関数を使い分けて処理を実装していきましょう。

キャラを動かそう

ジャンプさせる

Flappy Darumaではプレイ中に画面をクリックすることで空中でジャンプします。

まずは、クリックの入力を受けて、一度だけジャンプする処理を作ってみましょう。
ここでは2つのイベント関数で、入力の検知と、入力に応じて行うジャンプの処理を実装しています。
Updateイベント関数は入力の検知、FixedUpdateイベント関数は物理演算の処理を行う際に適切なイベント関数なので、それぞれを分けて処理しています。。

それでは、入力を検知するUpdateイベント関数から説明していきましょう。

    /// Update で得た入力を FixedUpdate に伝えるフラグです。
    /// ジャンプの入力があった場合は true が設定され、反映されると false に戻されます。
    bool inputJump_ = false;

    /// フレームを更新する直前のイベント関数です。
    /// Input も毎回更新されるので、入力の受け取りはこちらで行います。
    void Update()
    {
        // マウス左ボタンが押された直後の場合
        if (Input.GetMouseButtonDown(0) == true)
        {
            // ジャンプの入力があったことをメンバ変数のフラグで保持し、 FixedUpdate イベントで対応してもらいます。
            inputJump_ = true;
        }
    }

これは、ダルマのオブジェクトに追加するDaruma.csコンポーネントのUpdate関数とそこで使われているメンバ変数の定義を抜粋したものです。
ここでは、クリックの入力があったらinputJump_メンバ変数の値を変え、クリックの入力があったことを覚えておきます。

クリックの入力があったら、その場でジャンプさせようとするのが自然かもしれませんが、先ほど説明したように物理演算によってジャンプさせる処理はFixedUpdateイベント関数で行います。
FixedUpdateイベント関数は物理演算を行うために1秒に何度も呼び出されます。
Rigidbody2Dコンポーネントの物理演算によってオブジェクトを動かす場合は、FixedUpdateイベント関数で実装するようにしましょう。

    /// 物理挙動の更新の直前に呼ばれるイベント関数です。 Update よりも多く呼ばれることもあります。
    /// Rigidbody などを使った物理挙動についての操作はこの関数で行います。
    private void FixedUpdate()
    {
        // ジャンプの入力があった場合
        if (inputJump_ == true)
        {
            // 上方向に力を加えてジャンプします。
            rb_.AddForce(Vector2.up * velocityJump_);

            // ジャンプの入力を伝えるフラグを元に戻します。
            inputJump_ = false;
        }
    }

rb_はダルマのオブジェクトが持つRigidbody2Dコンポーネントです。
GetComponent<Rigidbody2D>()で取得することができますが、FixedUpdateイベント関数が呼び出されるたびに取得するのは効率がよくないので、AwakeやStartイベント関数であらかじめ取得してメンバ変数として保持しています。

FixedUpdateイベント関数では、さきほどUpdateイベント関数でクリック入力があった場合に、ジャンプする処理を実装しています。
ジャンプはRigidbody2DコンポーネントのAddForce関数を使って実現しています。
AddForce関数は、オブジェクトに対して力を加える関数で、ここでは上向きの単位ベクトルに勢いを乗算して、ジャンプ力を加えています。

ジャンプするだけならばこれでもよいのですが、クリックを連打すると、上の動画のようにどんどんと1回ずつのジャンプ力が上昇していきます。
これは、加えられた力を使い切る前に、さらにジャンプする力を加えたために、上向きの加速度が増え続けた結果です。

そこで、AddForce関数でジャンプ力を加える前に、一度、オブジェクトの上向きの加速度を0にリセットする処理を追加します。
加速度はRigidbody2Dコンポーネントのvelocity変数で取得・設定できます。
今回は上方向へのジャンプ力のリセットなので、Y軸方向の加速度だけを0に設定します。

            // ジャンプの上昇量を毎回同じにするため、残っている Y 方向の加速度を 0 にリセットしてから行います。
            rb_.velocity = new Vector2(rb_.velocity.x, 0f);
            rb_.AddForce(Vector2.up * velocityJump_);

これにより、下の動画のように、クリックを連打しても、1回ずつのジャンプの勢いは一定にすることができました。

ジャンプの勢いによって向きを変える

Flappy Darumaでは、ジャンプの勢いまたは落下の勢いによってダルマの向きを変えています。
ジャンプ中は上向き、落下中は下向きにすることで、現在の勢いをわかりやすく表現するためです。

そのために、FixedUpdateイベント関数が呼び出されるたびに次の関数を呼び出して、加速度に応じた向きにオブジェクトのTransformコンポーネントを通じて角度を設定しています。


    /// 上昇・下降の加速度に応じてキャラの向きを上-右-下に回転させます。
    /// 上昇中は上向き、下降中は下向きになり、加速度 0 は右向きになります。その中間は補正された角度になります。
    /// キャラの絵が 0 度で右向きなのを考慮した角度になります。
    protected void ChangeDirectionByVelocity()
    {
        // 指定した最小値と最大値の範囲内に丸めた Y 方向の加速度を得ます。
        float _velocityY = Mathf.Clamp(rb_.velocity.y, minVelocityYChangeDirection_, maxVelocityYChangeDirection_);
        float _degreeZ = 0f;

        if (_velocityY > 0f)
        {
            // 上昇中の場合は、上向きから右向きまでの角度を加速度の度合いから計算します。
            _degreeZ = (_velocityY / maxVelocityYChangeDirection_) * maxDegreeDirection_;
        }
        else
        {
            // 下降中または加速度 0 の場合は、右向きから下向きまでの角度を加速度の度合いから計算します。
            _degreeZ = (_velocityY / minVelocityYChangeDirection_) * minDegreeDirection_;
        }
        // 計算した角度をキャラの角度に設定します。
        transform.localRotation = Quaternion.Euler(0, 0, _degreeZ);
    }

先ほど紹介したオブジェクトの加速度を表すRidigbody2Dコンポーネントのvelocity変数のY方向の勢いを取得し、Mathf.Clamp関数で一定範囲内に丸めて_velocityY変数に代入します。
_velocityY変数の大きさに応じて、勢いが0より大きい場合は上向きに、0以下の場合は下向きになるように角度を計算して、TransfromコンポーネントのlocalRotation変数に設定します。

2Dの場合、オブジェクトの角度はTransformコンポーネントのRotationのz軸で調整できます。

スクリプトで行う際はTransformコンポーネントのlocalRotationプロパティを設定します。
localRotationプロパティはQuaternionという型で、上の動画のようにVector3形式の値を直接代入することはできませんが、前述のスクリプトでも使われているQuarternion.Euler関数を使うことで簡単に設定できます。

これで、上の動画のように上昇・下降の勢いに応じてダルマが向きを変更できるようになりました。

当たり判定イベントを実装しよう

土管の間を通過したらスコアを増やす

Flappy Darumaでは、土管を潜り抜けるたびにスコアが増えていきます。
そのために、前回も紹介したように、表示されないScoreUpLineというオブジェクトを上下の土管の間に配置しています。
このScoreUpLineに触れるとイベント関数が呼び出されスコアを増やす仕組みになっています。

プレイ中のシーンビューでは下図のようにScoreUpLineのCollider2Dの緑の四角形でその配置を確認できます。

ScoreUpLineは、プレハブとしてAssets/Prefabsに保存されています。
インスペクターウィンドウでは、Collider2DのIs Triggerがチェックされていることが確認できます。
これにより、他のオブジェクトと接触した際にOnTriggerEnter2Dイベント関数などが呼び出されます。

OnTriggerEnter2Dイベント関数は接触したお互いのオブジェクトのコンポーネントで呼び出されます。
今回はダルマのオブジェクトに追加しているDaruma.csの中でScoreUpLineオブジェクトとのOnTriggerEnter2Dイベント処理を実装しました。

    /// IsTrigger が設定された Collider2D に接触した直後に呼び出されるイベントです。
    private void OnTriggerEnter2D(Collider2D collision)
    {
        // スコアアップのオブジェクトに接触した場合
        if (collision.gameObject.layer == layerIdScoreUp_)
        {
            // スコアアップの処理を呼び出します。
            battleScene_.addScore();
            // ジャンプの軌道によっては、一度離れてから、もう一度接触することもあるので、一度触れたスコアアップのオブジェクトは破棄します。
            GameObject.Destroy(collision.gameObject);
        }
    }

はじめに、接触したものがScoreUpLineオブジェクトであることを、オブジェクトのレイヤーによって判別します。
その後、battleScene_というバトルシーンの全体的な処理を行うコンポーネントのaddScoreメンバ関数を呼び出すことで、スコアを増やしています。

スコアを増やした後は、ダルマが一度離れてから再び接触して、もう1度スコアを増やすことがないように、すぐにSocreUpLineオブジェクトを削除します。

これで土管を潜り抜けた際に、スコアを増やす処理が実装されました。

土管や地面にぶつかったらゲームオーバー

Flappy Darumaでは、障害物である、土管または地面にぶつかるとゲームオーバーになります。

上の動画を良く見ると、ダルマは、地面とは衝突しますが、土管はすりぬけていることがわかります。
このことからもわかるように、Collider2DのIs Triggerが、地面は無効で、土管は有効になっており、それぞれ別のイベント関数で衝突・接触のイベントが処理されています。

    /// IsTrigger が設定された Collider2D に接触した直後に呼び出されるイベントです。
    private void OnTriggerEnter2D(Collider2D collision)
    {
        // スコアアップのオブジェクトに接触した場合の処理は先ほどと同じなので省略

        // 障害物に接触した場合
        if (collision.gameObject.layer == layerIdObstacle_)
        {
            // ダメージの効果音を再生します。
            Util.PlayAudioClip(acDamage_, Vector3.zero, 1f);
            // ゲームオーバーの段階に進めます。
            battleScene_.ChangePhaseGameOver();
        }
    }

    /// IsTrigger が設定されていない Collider2D に衝突した直後に呼び出されるイベントです。
    private void OnCollisionEnter2D(Collision2D collision)
    {
        // 地面に接触した場合
        if (collision.gameObject.layer == layerIdGround_)
        {
            // ダメージの効果音を再生します。
            Util.PlayAudioClip(acDamage_, Vector3.zero, 1f);
            // ゲームオーバーの段階に進めます。
            battleScene_.ChangePhaseGameOver();
        }
    }

Daruma.csのスクリプトを見てみると、地面はOnCollisionEnter2D、土管はOnTriggerEnter2Dイベント関数で処理されています。
処理の内容は同様で、battleScene_コンポーネントのChangePhaseGameOver関数を呼び出して、ゲームオーバーに状態を遷移させるだけです。

これで、障害物である土管と地面に接触・衝突した際に、ゲームオーバーに切り替わるイベント処理を実装できました。

まとめ

今回は、プレイヤーが操作するダルマについて実装しました。
一定の高さでジャンプをさせ、障害物との接触・衝突によりゲームオーバーに切り替わるイベント処理について紹介しました。

次回は、ゲームオーバー画面やタイトル画面の実装について紹介していきます。

オススメアプリ!渋谷で気軽に友達を作れるアプリが登場!