【Unity/2D】スイカゲーム風の落ち物ゲームを作ってみる話 #5 果物の生成から落下までの時間を調整する

記事をご覧いただき、誠にありがとうございます。

投稿主の無能です。

前回は、ポインターから果物を生成するという事をやりました。

今回は、果物の生成から落下するまでの時間を調整できる機能を実装したいと思います。

果物の生成と落下

生成と落下の動き方

まず、本家のスイカゲームでも、果物の生成は自動的に行われます。
最初に果物が生成され、それを落下させたら次の果物が生成される、という仕組みになっています。

また、生成された果物が落下する地点(このゲームではポインター)にくっついていて、落下させるボタンを押すことで果物が落下します。

まずはこの機能を実装したいと思います。

どのように実装するか

どのようにそれらの機能を実装するかですが、「果物が生成された」「果物を落下させた」というフラグを作って、それらの動作の間に待機時間を作って調整することで、機能を実装できそうです。

では、落下地点のくっつく(ぶら下がる)機能はどうやってやるのかと言うと、一時的に重力を切って、ポインターの子オブジェクトにしてやります。

そうすると、親オブジェクトのポインターに子オブジェクトの生成された果物がくっついて移動し、スペースキーを押して親と子オブジェクトの関係を断ち、再び重力を戻すと果物が落下する、という方法で機能を実装できそうです。

ここで一つ注意しなければいけないのは、子オブジェクトが親オブジェクトの移動を追従してくれるのは、「子オブジェクトにRigidbodyの機能が備わっていない場合」に追従してくれます。
つまり、子オブジェクトにRigidbodyがアタッチされていないか、またはRigidbodyの機能が非アクティブになっている必要があります。

じゃあ生成した果物のRigidbody2Dを非アクティブにすればいいのですが、Rigidbodyはアクティブ・非アクティブのスイッチがありません。

他のコンポーネントにはアクティブ・非アクティブを切り替えるスイッチがあるが、Rigidbodyには無い

じゃあ果物のRigidbodyは外さないといけないのか?と考えるかもしれませんが、それは違います。
Unityのドキュメントを見てみましょう。

Rigidbody 2D - Unityマニュアル

ドキュメント下部の「Simulated を無効にする方が、個々のコンポーネントを制御するよりも効果的な理由」という項目に「シミュレーションを無効にすることは、ここのコンポーネントを無効にするよりは、容易で効率的です。」という記載の通り、Rigidbody2Dコンポーネントを付け外しするより、物理シミュレーションを有効・無効にする、つまりRigidbody2DコンポーネントのSimulatedをオン・オフしてあげた方が、簡単で効率的だということです。

Rigidbody2DコンポーネントのSimulatedをオン・オフする方が簡単で効率的

Simulatedをオフにしてやることで、Rigidbody2Dは実質的な非アクティブ状態となり、果物をポインターの子オブジェクトにしても、親オブジェクトのポインターの移動に追従してくれるようになります。

機能の流れをまとめると、果物を生成したら、生成フラグを切り替え、Rigidbody2Dを実質的に非アクティブ状態にします。
そしてポインターの子オブジェクトにします。
スペースキーで落下する時に、生成フラグと落下フラグを切り替え、Rigidbody2Dを実質的にアクティブ状態にします。
そしてポインターの子オブジェクトから切り離して、自由落下させればいいですね。

では機能の見当も付いたので、早速スクリプトを編集していきましょう。

スクリプトの編集

今回編集するのはFruitsGeneratorになります。

FruitsGenerator.cs

using UnityEngine;

public class FruitsGenerator : MonoBehaviour
{
    #region Var-Fruits
    [Header("果物")]
    [SerializeField] Fruits[] fruits;
    #endregion

    #region Var-FruitsDrop
    [Header("果物を生成する待機時間")]
    [SerializeField] float generatedTime = 1f;
    [Header("果物を落下させる待機時間")]
    [SerializeField] float droppedTime = 1f;
    #endregion

    #region Var-Internal
    Fruits currentFruits;                                               // 現在の果物
    bool isGenerated = false,                                           // 果物を生成したかのフラグ
         isDropped = false;                                             // 果物を落下させたかのフラグ
    float timer = 0f;                                                   // 内部カウント用
    #endregion

    #region Start
    void Start()
    {
        // 果物を生成
        FruitsGenerate();
    }
    #endregion

    #region Update
    void Update()
    {
        // 内部カウントを加算
        timer += Time.deltaTime;

        // --- 果物の生成 ---
        // 内部カウントが生成待機時間を超えている場合
        if (timer > generatedTime)
        {
            // 生成フラグがfalseの場合
            if (!isGenerated)
            {
                // 果物を生成
                FruitsGenerate();
                // 内部カウントを0にする
                timer = Variables.zero;
            }
        }

        // --- 果物の落下 ---
        // 内部カウントが落下待機時間を超えている場合
        if (timer > droppedTime)
        {
            // 落下フラグがfalseの場合
            if (!isDropped)
            {
                // スペースキーが押された場合
                if (Input.GetKeyDown(KeyCode.Space))
                {
                    // 果物を落下
                    FruitsDrop();
                    // 内部カウントを0にする
                    timer = Variables.zero;
                }
            }
        }
    }
    #endregion

    #region FruitsGenerate
    // 果物の生成
    void FruitsGenerate()
    {
        // 落下フラグをfalse
        isDropped = false;
        // 果物のコピーを生成 生成物:登録された果物からランダムに生成
        currentFruits = Instantiate(fruits[Random.Range((int)Variables.zero, fruits.Length)],
                    // 位置:ポインターの位置 回転無し
                    transform.position, Quaternion.identity);
        // 現在の果物の物理シミュレーションをfalse
        currentFruits.GetComponent<Rigidbody2D%gt;().simulated = false;
        // 現在の果物の親オブジェクトをポインターにする
        currentFruits.transform.SetParent(gameObject.transform);
        // 生成フラグをtrue
        isGenerated = true;
    }
    #endregion

    #region FruitsDrop
    // 果物の落下
    void FruitsDrop()
    {
        // 生成フラグをfalse
        isGenerated = false;
        // 現在の果物の物理シミュレーションをtrue
        currentFruits.GetComponent<Rigidbody2D>().simulated = true;
        // 現在の果物の親オブジェクトをnull
        currentFruits.transform.SetParent(null);
        // 落下フラグをtrue
        isDropped = true;
    }
    #endregion
}

それでは追加したメンバ変数から見ていきます。

メンバ変数

追加したメンバ変数は、生成した後の待機時間となるfloatのgeneratedTime、そして落下させた後の待機時間となるdroppedTimeになります。

そして内部処理用として、現在の果物となるFruitsのcurrentFruitsになります。
それから生成・落下フラグのboolのisGeneratedとisDropped、そして内部カウント用のfloatのtimerになります。

次に処理を見ていきましょう。

Start関数

新たに追加したStart関数では、果物を生成するFruitsGenerate関数を呼んでいます。
ゲームが開始して最初の果物となります。

Update関数

Update関数は、まず内部カウントのtimerにdeltaTimeを足し合わせます。
Roll a Ballでやったゲーム内時間のカウント方法です。

そして内部カウントが生成カウントのgeneratedTimeを超えて、生成フラグのisGeneratedがfalseの場合、果物を生成するFruitsGenerate関数を呼んでいます。

そして内部カウントを0に戻します。

果物の落下処理も同様に、内部カウントが落下カウントのdroppedTimeを超えて、落下フラグのisDroppedがfalseの場合、スペースキーが押されたらFruitsDrop関数を呼んでいます。

そして内部カウントを0にします。

FruitsGenerate関数

FruitsGenerate関数では、まず落下フラグをfalseにします。
これは次の果物の生成→落下の処理を繰り返すのが理由です。

次に果物のコピーを生成したものを現在の果物のcurrentFruitsに代入します。
生成する果物の内容は前回までと変わりません。

次に現在の果物の物理シミュレーションをfalseにして、親オブジェクトをポインターにします。

そして生成フラグをtrueにします。

FruitsDrop関数

新たに追加したFruitsDrop関数では、まず生成フラグをfalseにします。

次に現在の果物の物理シミュレーションをtrueにして、親オブジェクトをnullにします。

そして落下フラグをtrueにします。

スクリプトが編集できたので、Unityでの作業になります。

Unityでの作業

ポインターのInspectorビューのFruitsGeneratorに項目が追加されているので、それぞれ違和感が出ないように待機時間を設定します。

生成・落下の待機時間を設定

では動作確認をします。

動作確認

今回の確認事項は
  • 果物が落下前の状態だとポインターにくっついているか
  • 果物の落下後にポインターが移動しても果物に影響していないか
  • 各待機時間はきちんと待機しているか
になります。

では確認しましょう。

ポインターの移動に果物が追従するようになった

ポインターの移動に果物が追従してくれるようになりましたね。

各待機時間もきちんと待機していて、前回までのようにスペースキーを連打しても、次々と果物が生成されて落下する、ということは解消されました。

これで想定通りの機能を実装できました。

まとめ

今回は
  • 果物がポインターに追従し、生成・落下の待機時間を実装した
という事をやりました。

次回はいよいよ、果物を進化させていきたいと思います。

では、また次回!

コメント