【Unity】動く障害物の作り方(ゲームの作り方チュートリアル「悪路王」その4)

悪路王 障害物 ゲームの作り方

前回、プレイヤーのC#スクリプトを書いてプレイヤーキャラクターが動くようになりました。次は障害物を動かすC#スクリプトを書いてみましょう。

前提:プレイヤーではなく「障害物を動かす」という発想

さてはじめにそもそもの話なんですけど、今回のゲームではプレイヤーキャラはジャンプできるだけで自分で横方向に動くことはできません。代わりに障害物が右から左に流れるようになっています。

なぜこのような仕様になっているのかというと、今回のゲームは「ジャンプ」だけに焦点を当てたシンプルなゲームなので、別にプレイヤーキャラを動かす必要はなく、逆に障害物が流れてくるように作ったほうが圧倒的に作りやすいからです。

これはゲームをプレイする人の立場からしてもジャンプボタン一つを操作すれば良いので分かりやすいですし、舞台裏を何も知らない人の視点では「キャラクターが走って前に進んでいるように見える」ので何の問題もありません。

ゲーム制作ではこのように作り方次第では手を抜いても全く問題にならない場合があるので、柔軟に考えて楽な方法がないか探してみると良いでしょう。

動く障害物の作り方

ではここからが本題で、動く障害物を作っていきます。

障害物のゲームオブジェクトの作成

まずは障害物のゲームオブジェクトを作っておきます。スプライト画像は最初にインポートしてもらった素材の中に「wall」という四角形の画像があるのでそれを使ってください。シーンにスプライトをドラッグ&ドロップしてゲームオブジェクトを作ったらBox Collider 2Dをアタッチします。

それができたらこのゲームオブジェクトを適当なフォルダにドラッグ&ドロップしてプレハブ化しておきましょう。

動く障害物のC#スクリプト

次に動く障害物のC#スクリプトを書きます。新しいC#スクリプトを作り、名前を「Wall」に変更したら次のソースコードを書いてください。

using UnityEngine;

public class Wall : MonoBehaviour
{

    [SerializeField]
    float speed = 5;

    public float Speed
    {
        get
        {
            return speed;
        }
        set
        {
            speed = value;
        }
    }

    void Update()
    {
        transform.position = new Vector2(transform.position.x - speed * Time.deltaTime, transform.position.y);
    }

    // サイズを調整
    public void SetWall(Vector2 size)
    {
        transform.localScale = new Vector3(size.x, size.y, 1);
    }

    // 画面外に出たら破棄(※テストプレイ時にシーンビューに映っていると破棄されないので注意)
    private void OnBecameInvisible()
    {
        Destroy(gameObject);
    }

}

難しい処理は特にやっていないのでソースコードを追っていただければ処理内容が分かると思います。書けたらこれを先ほど作ったゲームオブジェクトにアタッチしておきましょう。

実行結果

この時点でゲームを実行すると次のような感じになります。

悪路王 動く障害物

(画像クリックでGIF再生)

ちゃんと障害物が流れてきました。

障害物生成オブジェクトの作り方

さてこれで障害物が流れてくるようになりましたが、今のところは1個しか流れないのでこれをゲーム中にずっと流れ続けるようにしたいものです。そこで次は先ほどの障害物を自動生成する「障害物生成オブジェクト」を作っていきましょう。

障害物生成オブジェクトのC#スクリプト

ではさっそく障害物生成オブジェクトのC#スクリプトを書きます。新しいC#スクリプトを作り、名前を「WallSpawner」に変更したら次のソースコードを書いてください。

using System.Collections;
using UnityEngine;

public class WallSpawner : MonoBehaviour
{
    [SerializeField]
    GameObject wallPrefab = null;
    [SerializeField, Min(0.1f)]
    float defaultMinWaitTime = 1;
    [SerializeField, Min(0.1f)]
    float defaultMaxWaitTime = 1;
    [SerializeField]
    Vector2 defaultMinSize = Vector2.one;
    [SerializeField]
    Vector2 defaultMaxSize = Vector2.one;

    bool isSpawning = false;
    float minWaitTime;
    float maxWaitTime;
    Vector2 minSize;
    Vector2 maxSize;
    Coroutine timer;

    //外部から値を代入するためのプロパティ
    public float MinWaitTime
    {
        set
        {
            //あまりにも小さい値になるとものすごい数の障害物が生成されてしまうので、0.1未満にならないようにする
            minWaitTime = Mathf.Max(value, 0.1f);
        }
        get
        {
            return minWaitTime;
        }
    }

    public float MaxWaitTime
    {
        set
        {
            maxWaitTime = Mathf.Max(value, 0.1f);
        }
        get
        {
            return maxWaitTime;
        }
    }

    public bool IsActive { get; set; } = true;

    void Start()
    {
        InitSpawner();
    }

    void Update()
    {
        if (!IsActive)
        {
            //生成中なら中断する
            if (timer != null)
            {
                StopCoroutine(timer);
                isSpawning = false;
            }

            return;
        }

        //生成中じゃないなら生成開始
        if (!isSpawning)
        {
            timer = StartCoroutine(nameof(SpawnTimer));
        }
    }

    //初期化用メソッド
    public void InitSpawner()
    {
        minWaitTime = defaultMinWaitTime;
        maxWaitTime = defaultMaxWaitTime;
        minSize = defaultMinSize;
        maxSize = defaultMaxSize;
    }

    //生成処理を行うコルーチン
    IEnumerator SpawnTimer()
    {
        isSpawning = true;
        
        GameObject wallObj = Instantiate(wallPrefab, transform.position, Quaternion.identity);
        Wall wall = wallObj.GetComponent<Wall>();
        float sizeX = Random.Range(minSize.x, maxSize.x);
        float sizeY = Random.Range(minSize.y, maxSize.y);
        wall.SetWall(new Vector2(sizeX, sizeY));
        float waitTime = Random.Range(minWaitTime, maxWaitTime);

        yield return new WaitForSeconds(waitTime);

        isSpawning = false;
    }

}

少し長いスクリプトで、コルーチンといった処理も出てきたので難しく感じるかもしれませんがソースコードを丁寧に読めば何となく処理内容が分かってくると思います。もしコルーチンのところがよく分からないなぁという方は、姉妹サイトのほうに詳しい話が載っているので併せてご覧頂ければと思います。

【Unity】初心者でも簡単!コルーチンの使い方まとめ
今回はUnity初心者の方向けの話題で、処理を一定時間待つ「コルーチン」の使い方を丁寧に解説するという内容になっています。Unityでゲームを作っていると「〇秒間だけ待ってから処理を実行したい」という場面がよくあると思うのですが、そんな時に

書けたらこれを適当な空のゲームオブジェクトにアタッチし、「Wall Prefab」欄に先ほど作った障害物のプレハブを登録しておきましょう。

実行結果

この時点でゲームを実行すると次のように障害物が一定間隔で流れ続けるようになります。

悪路王 障害物の自動生成

障害物のサイズや流れる間隔をランダムにしたい場合は、WaitTimeやdefaultSizeを調整してみてください。

次のページ

【Unity】ゲーム進行を管理する「GameManager」の作り方(ゲームの作り方チュートリアル「悪路王」その5)
前回までの作業でゲームに障害物が加わり、だいぶゲームらしくなってきました。ですがこのままでは何となく障害物をジャンプして避けるだけなので何かプレイヤーのモチベーションを刺激するような要素を入れたいものです。 そこでここではそのような要素とし...