【Unity】2Dシューティングを作ってみる話 #10

記事をご覧いただき、誠にありがとうございます。
投稿主の無能です。

前回は、敵オブジェクトを仮に作成して動かし、接触判定を持たせました。

今回は、敵が自動的に出てくる仕組みを作りたいと思います。

敵が自動で出てくる仕組みの考え方

仕組みを作るとは言っても、どうやったらその処理が実装できるかが想像つかないよ、と言う方もいらっしゃると思います。

ですので簡単な具体例を出してみたいと思います。

一例ですが、x軸の位置(x座標)は固定して、y軸の位置(y座標)がランダムであり、一定間隔で敵が出てくれば、敵が動く方向は一定なので、その処理が繰り返されている間は無限に敵が自動で出てきます。

敵が自動で出てくる仕組みの例

画にすると何となくイメージが伝わったでしょうか。

この処理を実装したいと思います。

敵の生成ポイントを作成する

そうと決まれば、早速敵の生成ポイントを作ります。

空のオブジェクトを作成し「EnemyGenerator」とします。

空のオブジェクトなので、PlayerFirePosと同様に視認できるようにしておきます。

そしてカメラの画角外に出しておきます。

EnemyGeneratorを作成して位置をカメラの画角外にする

あとはx座標はEnemyGeneratorで、y座標がランダムなものになれば、敵の自動生成ポイントの出来上がりになります。

ではスクリプトで処理を実装していきます。

敵の自動生成スクリプトの記述

ランダムな数は既にC#で作成できますが、ちょっと癖がありますので、その注意点を踏まえながら説明していけるようにします。

「EnemyGenerator」スクリプトを作成して、EnemyGeneratorオブジェクトへアタッチします。

EnemyGeneratorスクリプトを作成し、EnemyGeneratorオブジェクトへアタッチ

ではスクリプトを書いていきます。

このようになります。
EnemyGenerator.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyGenerator : MonoBehaviour
{
    [Header("敵の生成リスト")]      [SerializeField] GameObject[] enemies;
    [Header("敵生成の時間")]        [SerializeField] float genDelay = 2f;
    [Header("敵生成の間隔")]        [SerializeField] float genInterval = 2f;
    [Header("画面の余白(y軸方向)")][SerializeField] float marginY = 1f;

    Vector2 min, max;       // 画面サイズ取得用

    void Start()
    {
        // 画面サイズの最小値・最大値を取得
        min = Camera.main.ViewportToWorldPoint(Vector2.zero);
        max = Camera.main.ViewportToWorldPoint(Vector2.one);
        // 敵生成の関数を一定間隔でリピート
        InvokeRepeating("EnemyGenarate", genDelay, genInterval);
    }

    // 敵を生成する
    void EnemyGenarate()
    {
        // 敵を生成
        Instantiate(enemies[Random.Range(0, enemies.Length)],                                           // GameObjectの配列enemiesからランダムで生成する敵を選択
                    new Vector2(transform.position.x, Random.Range(min.y + marginY, max.y - marginY)),  // x座標:EnemyGeneratorオブジェクトのpositionのx,y座標:取得した画面サイズの最小値+余白から最大値-余白までの間
                    Quaternion.identity);                                                               // 回転しない
    }
}
では見ていきましょう。

メンバ変数では敵の生成リスト、生成の時間と間隔、そして画面のy軸方向の余白を設定しています。

そして画面サイズの取得用のmin・maxがあります。
min・maxは背景の時に作ったような…?

そしてStart関数内では、最初に画面サイズを取得しています。
やっぱり背景の時と同じだと思った方はその通りで、背景と同じ処理で最小値・最大値を取得しています。

これは何故かと言うと、敵を生成するのは前述の通りy座標をランダムにして生成するので、生成時のy座標の範囲を知りたいからです。

こうする事で、例え画面サイズが拡大・縮小してy座標の範囲が変化したとしても、その範囲をスクリプトが取得できるようになっています。

x座標の方をランダムにしたいのであれば、x座標に処理を書けば良いので、どちらにしても画面サイズは取得しておきたいものになります。

そして次のInvokeRepeatingは、繰り返し指定した関数を呼ぶための関数になります。

MonoBehaviour.InvokeRepeating - Unity Documentation

使い方はドキュメントを参照すれば分かると思いますが、「"」(ダブルクォーテーション)で囲まれた関数名を、初回は第二引数の値で呼び出し、2回目以降は第三引数の間隔を空けて呼び出します。

ここではEnemyGenerate関数を、初回はgenDelayで呼び出し、以降はgenIntervalの間隔を空けて呼び出す、という処理になります。

では次にEnemyGenerate関数を見ていきます。

EnemyGenerate関数は、シンプルにオブジェクトを生成するinstantiate関数が呼ばれているだけなのですが、その中身が少々ややこしい事になっています。

instantiate関数の第一引数でクローン生成するオブジェクトを指定するのですが、配列の要素の番号の部分をランダムで指定しています。

Random.Rangeで括弧内の第一引数を最小値、第二引数を最大値に取って、その範囲内でランダムな数を生成します。

このRandom.Rangeが結構な曲者で、小数(float)の場合最大値を含みますが、整数(int)の場合最大値を含みません

不等式で表すと
小数(float):最小値≦ランダムの範囲最大値
整数(int):最小値≦ランダムの範囲最大値
という仕組みになっています。

例えると0から5の整数の範囲で値をランダムで取得したい場合は、(0, 6)になります。

今回の例であれば、配列の範囲は整数なのでenemies.Lengthに+1しなければなりませんが、そのままで大丈夫です。

何故かと言う理由については、配列の仕組みを考えれば分かると思います。

例を挙げると、配列の要素数が3だったとします。

enemies.Lengthで返される値は3ですが、最大値を含まないので、配列内の要素を指定する時は0から始まり、Lengthで返される値から-1した数が最大値となります。

例で言えば0,1,2が配列の要素を呼び出す時に指定する値ですね。

そしてRandom.Rangeでは最大値を含まない、不等号では未満という事になるので、0,1,2の範囲で問題無い、という事になります。

何となく理解出来ましたでしょうか?

ただこの処理は、あくまでも今回はたまたま配列の要素数とLengthで返される値で違いがあることを利用できただけなので、Random.Rangeでの処理は前述の小数と整数での違いを把握しておいてください。

そして次の生成する場所でもRandom.Rangeを使用しています。

x座標はEnemyGeneratorオブジェクトの場所ですが、y座標がランダムになるため、Start関数で取得した画面サイズの最小値・最大値がここで使われます。

minのy座標に余白を最小値であれば足し合わせ、最大値であれば引いています。

これはプレイヤーの弾の当たらない位置生成されることを防ぐためです。

範囲内だからと言って、あんまり上や下の方にギリギリで生成されてしまうと、それは無理だろ、というような位置に生成されてしまうので、プレイヤーの移動制限と同じ手法で余白を取っています。

最後に生成するオブジェクトの回転ですが、取り敢えず回転しないようにしてあります。

これは後程敵にも画像と弾の発射ポイントを付けるので、敢えて回転させていません。

という事で、ゲームを実行して確認する準備をします。

敵の自動生成の処理を確認する

ではEnemyをプレハブ化します。

Enemyをプレハブ化する

そしてHierarchyビューにあるEnemyは削除します。

HierarchyビューにあるEnemyを削除

そしてPrefabsフォルダ内で、Enemyを複製します。

今回の動作確認までの間なので、2つ複製すれば事足ります。

Enemyを2つほど複製

そして分かりやすいように色を変えておきます。

Enemyが赤なので、緑と黄色にしておきます。

Enemy1:緑

Enemy2:黄色

これらのEnemyをEnemyGeneratorオブジェクトのInspectorビューに設定します。

Enemiesという項目のリストの部分に、+ボタンがあるので項目を追加し、Prefabsフォルダ内のEnemyと複製したものを設定します。

Enemiesという項目のリストの部分に+ボタンがあるので項目を追加する

項目を追加した

Prefabsフォルダ内のEnemyを設定する
以降は繰り返し

3つのEnemyが設定できた

ではこれでゲームを実行して確認します。

3つのEnemyが生成された

これで処理が実装出来ました。

ゲームで処理の確認ができたら、複製したEnemy1・2はPrefabsフォルダから削除します。

削除するとEnemyGeneratorオブジェクトのEnemiesに設定した項目がMissingになるので、-ボタンで項目を削除してください。

EnemyだけがEnemiesのリストに登録されている状態になればOKです。

InvokeRepeatingをUpdate関数内に書くと…

InvokeRepeatingは今回Start関数に記述しており、Update関数は使っていないので、その点だけ注意してください。

もしInvokeRepeatingをUpdate関数内に書いてしまうと、物凄い量のEnemyが一気に生成され、下手をすればUnityを強制終了させる羽目になります。

実際に無能はUpdate関数内に書いてUnityを強制終了させました。

まとめ

今回は
  • 敵を自動で生成する処理を実装した
という事をやりました。

次回は、敵に画像を設定して、弾が発射出来るようにしたいと思います。

では、また次回!

コメント