【Unity】2Dシューティングゲームの作り方⑧:敵の複製とステージの作り込み

Unity

こんにちは、ともくんのゲーム作り部屋にようこそ!

このページでは、Unityを使った2Dシューティングゲームの作り方8回目として、敵を複製してステージを作る処理について紹介しています。

前回、敵から攻撃を受けた際のプレイヤーの処理を作っていきました。

まだ敵が1体しかいなかったので、敵を複製する作業と新しい敵を配置してステージを作っていきましょう。

敵は同じ変数やメソッドを使うことが多いので、敵の基本クラスを作りそれを継承して新しい敵を作っていくことで、効率的に作り上げていくことができます。

そこで、敵の基本クラス作り継承、また敵の複製の処理までをまとめていきます。

この記事を書いた人

ゲーム作りを学び始めた一児のパパです。
このブログは、子供から「ゲームを作ってみたい!」と言われ、非プログラマーでゲーム作りをしたことない僕が、ゲーム作りの本を読んで独学でゲーム開発を学んでいるブログです。
同じように初めてゲーム作りをしている方と一緒に学んでいけるようなブログに出来たらいいなと思っています。
また、「このコードはおかしい」とか「もっと良い書き方があるよ!」などあれば、どんどん指摘して頂けると助かります。

敵の基本クラスを作り継承する

敵を複製していく前に、まずは敵の基本クラスを作成して、それを継承する仕組みを作っていきます。

継承とは、既存のクラス(スクリプトファイル)内に書かれている変数やメソッドなどを引き継いで、別のクラス(スクリプト)を作っていく仕組みのことをいいます。

今回、敵に設定している変数hpや、プレイヤーの弾と当たる処理、またhpが0になったら敵を消去させる処理というのは、どの敵に対しても変わらないため、この変わらない部分を基本クラスで作り継承させて、それ以外の変わる部分を派生クラスとして、新たにスクリプトファイルを作っていきます。

まずは、敵の基本クラスとして「EnemyBase」というスクリプトを作成します。

このスクリプトは、継承させる基本クラスになるので、アタッチしないで問題ありません。

using UnityEngine;

public class EnemyBase : MonoBehaviour
{
    public int hp; // HPを管理する変数を宣言

    void Update()
    {
        if (this.transform.position.y < -5)	// Y座標が-5以下になった場合
        {
            this.gameObject.SetActive(false);    // 敵オブジェクトを非表示にする
        }
    }

    void OnTriggerEnter2D(Collider2D collision) // 当たり判定のあるオブジェクトと当たった場合
    {
        if (collision.gameObject.name == "PlayerBullet")   // 当たったオブジェクトがプレイヤーの弾の場合
        {
            hp--;   // HPを1減らす処理

            if (hp <= 0)    // HPが0になった場合
            {
                this.gameObject.SetActive(false);    // 敵オブジェクトを非表示にする
            }
        }
    }
}

スクリプトの内容は、以前Enemy01のスクリプトで作成したものを基本に書いています。

新しい部分としては、5行目で変数のHPだけ初期値を設定せずに、他のクラスから参照できるようにpublicを付けて宣言しています。

また、Updateメソッド内で敵の座標が-5以下になったら、SetActive関数を使って画面から消去させる設定をしています。

それ以外は、同じ内容なので気になる方は以下のページを参照してください。

次に、Enemy01のスクリプトにこのEnemyBase継承させていくので、Enemy01を開きます。

using UnityEngine;

public class Enemy01 : EnemyBase    // EnemyBaseを継承する
{
    void Start()
    {
        hp = 1; // HPを1に設定する
    }
}

3行目で、「EnemyBase」と書いてあげることで、EnemyBaseのクラスを継承させることができます。

なお、継承できる基本クラスは1つだけと決まっているので、元々書いてあった「MonoBehaviour」を消して書き直す必要があります。

基本クラスに書かれている内容がそのまま使えることになるので、このEnemy01にはStartメソッドの中で変数hpに1を代入しておけば完了です。

実際にこれでゲームを実行しても、前回と変わらない仕組みでゲームが動いてくれます。

今後、別の敵を作成する場合でも、この継承して派生クラスを作っていくことで、そのまま書かれている仕組みを使うことができるため、非常に便利です。

敵をPrefab化して複製していく

敵の基本クラスを作って継承の仕組みが作れたところで、敵を複製していきましょう。

敵を複製する際は、弾のオブジェクトの際にも行いましたが、Prefab化を使っていきましょう。

Prefab化は、オブジェクトをアセットフォルダ内にドラッグ&ドロップすることでできます。

Prefab化ができたら、複製していきましょう。

複製する際は、Prefabファイルをシーンビューにドラッグ&ドロップして持ってきてもできますが、たくさん複製する場合は少し大変です。

そこで、すでに配置しているオブジェクトを選択して、Windowsならキーボードの「Ctrl+D」、macなら「Cmd+D」を押すことで、すぐに複製することができます。

また、複製した敵のオブジェクトは、すべてstageオブジェクトの子オブジェクトとして配置しておきましょう。

ここでは、以下のように配置してみました。

ゲームを実行してみると、

だいぶゲームに近づいてきましたね。

敵の発射開始タイミングを設定する

実際に、ゲームを実行した際に、敵の発射するタイミングがすべて一緒になってしまっていました。

EnemyShotのスクリプトで発射を等間隔で行うようにしていましたが、すべての敵がゲーム実行から時間を計測してしまっているので、このように同じタイミングで発射してしまっています。

そこで、このEnemyShotのスクリプトに、敵の発射を開始するタイミングを設定しておきましょう。

// 一部省略 //
    void Update()
    {
        if (player.activeSelf == true &&    // playerのステータスが「アクティブ」の場合
            this.transform.position.y < 4.5f)  // Y座標が4.5を下回った場合
        {
            this.delta += Time.deltaTime;   // フレーム間の差をdelta変数に加算している
            if (this.delta > this.span) // delta変数がspanで指定した間隔を上回った場合
            {
                this.delta = 0; // 0に戻す処理
                GameObject cloneObject = Instantiate(enemyBullet, this.transform.position, Quaternion.identity);  // 弾を表示させて座標を移動させる処理
                cloneObject.name = "EnemyBullet";   // Cloneの名前を削除する処理
            }
        }
    }
}

18行目で、if文の条件式にtransform.positionでY座標の条件を設定してあげたことで、敵が画面の近くに来てから発射されるようにしてみました。

もう一度ゲームを実行してみましょう。

敵の発射するタイミングがばらついていて、いい感じになりました。

新しい敵を作りステージに配置していく

ここまでは、敵が1種類だけでしたが、ここからは新しい敵を作ってステージに配置していきましょう。

敵の作り方は、これまでのenemy01と同じように作っていく形となります。

注意点として、

これらの設定を忘れずに作っていくようにしましょう。

今回は、以下のenemy02とenemy03を新しい敵として作成してみました。

using UnityEngine;

public class Enemy02 : EnemyBase    // EnemyBaseを継承する
{
    float enemySpeed = 0.10f;   // 横移動のスピードを決める変数
    int enemyDirection = 1; // 敵の向きを指定する変数

    void Start()
    {
        hp = 4; // HPを4に設定
    }

    void Update()
    {
        if (this.transform.position.y < 5)  // Y座標が5以下になった場合
        {
            transform.Translate(enemySpeed * enemyDirection, 0, 0); // 敵の向きに合わせて横移動する処理
        }

        if (this.transform.position.x > 2)  // X座標が2以上になった場合
        {
            enemyDirection = -1;    // 敵の向きを-1にして左向きに移動させる
        }

        if (this.transform.position.x < -2) // X座標が-2以下になった場合
        {
            enemyDirection = 1;     // 敵の向きを1にして左向きに移動させる
        }
    }
}

enemy02は、X軸を横移動する処理を加えていきたいので、スピードと向きを決める変数を5~6行目で書いています。

17行目で、transform.Translateを使って座標を横に移動させる処理を行っています。

そして、20行目以降でX座標を使って、敵をどちらの向きに進めるかを指定しています。

enemy02は、上記のように横移動をしながら下に進んでいく動きにしてみました。

using UnityEngine;

public class Enemy03 : EnemyBase    // EnemyBaseを継承する
{
    SpriteRenderer spriteRenderer;  // オブジェクトのSpriteRendererを参照する
    float enemySpeed = 0.05f;   // 敵のスピードを決める変数
    float stageSpeed;   // ステージのスピードを取得する変数

    public GameObject player;   // playerのオブジェクトを取得する
    Vector2 playerPosition; // プレイヤーの座標を取得する
    Vector2 enemyPosition;  // 敵の座標を取得する
    Vector2 differencePosition; // プレイヤーと敵の座標の位置の差
    float enemyTime;  //  座標の距離÷敵のスピードの値を代入

    void Start()
    {
        hp = 4; // HPを4に設定
        spriteRenderer = GetComponent<SpriteRenderer>();    // SpriteRendererコンポーネントを取得する
        stageSpeed = GameObject.Find("stage").GetComponent<StageController>().stageSpeed;   // stageSpeedを取得する
    }

    void Update()
    {
        if (hp == 4 && this.transform.position.y < 4.2f)    // 攻撃を受けていないかつY座標が4.2以下
        {
            transform.Translate(0 , stageSpeed, 0); // ステージと反対に動かして移動を止める処理
        }
        else if (hp < 4)    // 攻撃を受けた場合
        {
            spriteRenderer.material.color = Color.red;  // オブジェクトの色を赤色に変更する
            playerPosition = player.transform.position;  // プレイヤーの座標を取得する
            enemyPosition = this.transform.position;  // 敵の座標を取得する
            differencePosition = playerPosition - enemyPosition;  // それぞれの座標の差を代入する
            enemyTime = Vector2.Distance(playerPosition, enemyPosition) / enemySpeed;   // 座標の距離÷敵のスピードを計算
            transform.Translate(differencePosition.x / enemyTime, differencePosition.y / enemyTime, 0); // プレイヤーに向かって敵が移動する処理
        }
    }
}

enemy03は、画面内に入ったら一旦動きが止まりますが、攻撃を受けると敵ごとプレイヤーに向かっていくという処理を作っています。

5行目でSpriteRendererコンポーネント用の変数を作り、18行目で代入して取得しています。

GetComponentは、後ろの<>内にコンポーネント名を入れることで、そのコンポーネントを取得することができるメソッドです。

SpriteRendererのコンポーネントを取得して、30行目で攻撃を受けたら赤色に変えるという処理を加えています。

7行目で宣言しているstageSpeedは、別のスクリプトStageControllerで宣言したstageSpeedを取得しています。

別のスクリプトの変数を取得する場合は、19行目のようにFind関数でオブジェクトを探した後に、先ほどのGetComponentの<>にスクリプト名を書くことで、中の変数を取得することができます。

そして、攻撃を受けたかどうかは、HPが最大の4かそれより下かでif文を使って判断していて、攻撃を受けた場合に敵が直接プレイヤーに向かって進む処理は、敵の弾をプレイヤーに向けて飛ばす処理と同じです。

enemy03は、攻撃を当てるまでは止まっていて、攻撃を当てると赤色に変わって近づいてきます。

これで、敵が3体作れたので、それぞれをPrefab化してゲーム内に配置してステージを自由に作っていきましょう。

まとめ

このページでは、敵の基本クラス作りと継承、また敵を複製して配置までを行っていきました。

敵は、何度も同じ機能を使うことが多いため、基本クラスを作っておくと、継承するだけで簡単に新しい敵を作ることができます。

また複製する際は、Prefab化してから行うことで、あとで修正する際も手軽に行うことができます。

次は、UIでライフやスコアを表示させて、よりゲームに近づけていきましょう。

最後までお読みいただきまして、ありがとうございました!

コメント