余談:弾幕ディフェンスゲームを制作中!

どうも!個人ゲーム開発を開始してはや1年3か月ほどが経過しました。
これまで合計で7つのゲームアプリをGoogle Playにリリースしてきたのですが
いまだに総ダウンロード数が2桁前半な底辺個人ゲーム開発者の壁です!
そんな私は懲りずに次回作を製作中です!
どーせ売れないのは分かりつつも、もしかしたら売れるかも!?
とか思ってマーケティングとか全然しないでとりあえず思いついたゲームを
作っているので、もうバカにつける薬はないとはこのことですね!笑
今度はどんなクソゲーかというとタワーディフェンスゲームと
弾幕ゲームを掛け合わせたようなゲームを作ろうと考えています!
これがですね、(毎度そうなんですが)頭の中ではめっちゃ面白くなる予定なんですよ!
もう完成したら私はウハウハになること間違いなし!グヘヘ…
ということでお分かりの通り今日も私は能天気で元気です。
そんな前置きは置いといて弾幕ゲーム要素を入れたいと考えているので
弾を撃つ処理が必須になります。
で、色々な方向に弾を撃ちたいんですよね。
具体的にはプレイヤーユニットがいてマウスのクリックした方向に弾丸が飛んでいく
みたいな処理です。
見た方が早いので以下のような処理を実装したいです。

これ、意外とどうすればいいか分からなくないですか?
いや、ごめんなさい。私がアホなだけで一般的には
「これぐらい実装できないとかwww!!草!!」
って話かもしれませんが私は全然わかりませんでした。笑
ということで今回はマウスのクリックした方向に飛び出す弾丸の
生成方法を記載していこうと思います!
ちなみに結論だけ見たい方はSTEP5から見てください!
STEP1 まずはプレイヤーユニットを作ろう!
まずは弾丸を生成する中心点になるプレイヤーユニットを
作りましょう!
…とその前に興味がある方は
以下の記事もありますので参考にしていただければと思います。
(ただの宣伝なので見なくてもいいです 笑)
ということで始めていきましょう!
冒頭の通りまずはプレイヤーユニットを作りたいので
プレイヤーユニットとなる画像をプロジェクト上にインポートします。
今回私はいつもテスト時に使っている以下の画像をプレイヤーユニットとしようと思います。

対象の画像を選んでプロジェクトウィンドウにドラッグアンドドロップします。

これでUnity上にインポートされたのでScene上にプレイヤーユニットを
生成してあげます。
プロジェクト上のプレイヤーユニットをHierarchyウィンドウに
ドラッグアンドドロップします。

これでプレイヤーユニットが生成できました!簡単ですね!
STEP2 弾丸をprefab化する
続いて弾丸を生成しましょう。今回はこんな画像にしてみました。

まず最初に弾丸をprefab化する必要があります。
prefabって何?って思うかもしれませんが
これはコピーが出来るゲームオブジェクトの形式を
prefabと呼ぶって認識いただければいいかなと思います。
STEP1と同様に弾丸の画像をhierarchyウィンドウに
取り込んでおきます。


これを再度プロジェクトウィンドウ上にドラッグアンドドロップします。

すると文字色が青色に変わりました。
青色に変わったらprefab化は成功です!
STEP3 弾丸を生成するスクリプトを作成する
では続いて弾丸をプレイヤーユニットから生成しましょう。
まずはスクリプトを作成します。
以下の名前でスクリプトを作成しました。

PlayerUnit.cs
スクリプトを開いて以下のように編集します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerUnit : MonoBehaviour
{
[SerializeField]
private GameObject bullet;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Instantiate(bullet, transform.position, Quaternion.identity);
}
}
}
ちょっとだけ解説します。
[SerializedField]
っていうのは何かというとUnityのゲームウィンドウ上で表示されるのは
普通はpublic関数だけなんですが、これを付け加えるとprivate関数でも
表示されるようになるよっていう宣言文です。
またInstantiate関数はゲームオブジェクトを生成するというコマンドです。
今回はbulletというゲームオブジェクトを
transform.position、つまりこのスクリプトをアタッチした
ゲームオブジェクトの位置に生成します。
STEP4 スクリプトをプレイヤーユニットにアタッチする
スクリプトが出来たらUnity画面に戻ってプレイヤーユニットに
作成したスクリプトをアタッチします。

アタッチできたら次にbullet変数のもとになるゲームオブジェクトを
設定します。
これはSTEP2でprefab化した弾丸を設定しましょう。

いったんここまで出来たらゲーム開始してみましょうか。
どこでもいいので画面をクリックすると弾丸がプレイヤーユニット上に生成されるのが
分かるかと思います!

STEP5 弾丸がクリックした方向に移動する処理を付け加えよう!
さぁやっと本題です!
クリックした方向に弾が飛んでいく処理を追加してあげましょう。
まずポイントはクリックした「方向」ってどうやって取得できる?
っていうのですよね。
これってどうやって表現できるかというと「ベクトル」で表現できます!
高校の数学に出てきた単元ですね!忘れてても以下で説明するので大丈夫です。
まずベクトルは2点の座標を指定すれば計算できます。
では今回の場合2点の座標って何になるでしょうか?
こちらは「プレイヤーユニット」から「クリックした地点」まで
弾丸を飛ばしたいので2点の座標は
「プレイヤーユニット」
「クリックした地点」
になります。
ベクトルは方向をもつ量なので
「どこ(出発点)」から「どこ()」へ向かうのかを
指定しないといけません。
こちらも既に記載した通り
「プレイヤーユニット」から「クリックした地点」へ向かうようにしたいです。
ベクトルの公式では
到着点の座標 – 出発点の座標
で定義できます。
今回の場合は
到着点の座標 = クリックした地点のposition
出発点の座標 = プレイヤーユニットのposition
と変換できますので、これで計算できます。
ということでこのベクトル的要素を弾丸に追加したいので
弾丸用のスクリプトを作成しましょう。
今回私は以下のスクリプト名にしました。

Bullet.cs
このスクリプトの中に上記のベクトル要素とか移動する処理とかを追加します。
以下をご覧ください。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour
{
private Vector3 direction;
private Transform thisTransform;
// Start is called before the first frame update
void Start()
{
thisTransform = transform;
}
// Update is called once per frame
void Update()
{
Vector3 newPosition;
newPosition = thisTransform.position + new Vector3(direction.x * Time.deltaTime, direction.y * Time.deltaTime, 0);
transform.SetPosition(newPosition.x, newPosition.y, newPosition.z);
}
public void getVector(Vector3 from,Vector3 to)
{
direction = new Vector3(to.x - from.x, to.y - from.y, to.z - from.z);
}
}
ここでもちょっと解説します。
まず一番下のgetVector関数で
ベクトルdirectionを設定します。
そしてupdate関数内でdirectionの方向に
弾丸であるこのゲームオブジェクトを移動させています。
更に、なぜ移動時にはTime.DeltaTimeを掛けてるの?気持ち悪!
って思った方は以下の記事を参照いただければと思います!
では続いてgetVector関数はどこから呼び出されるのか?
という部分に関してですが、これはプレイヤーユニットにアタッチした
PlayerUnit.csで呼び出されます。
ということでPlayerUnitスクリプトに以下を追記しましょう。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerUnit : MonoBehaviour
{
[SerializeField]
private GameObject bullet;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
GameObject g = Instantiate(bullet, transform.position, Quaternion.identity);
g.GetComponent<Bullet>().getVector(transform.position,Camera.main.ScreenToWorldPoint(Input.mousePosition));
}
}
}
追記した箇所では
①生成した弾丸にアタッチされたBulletスクリプトを取得
②BulletスクリプトのgetVector関数を呼び出し
を行っています。
さぁ!これでクリックした地点に弾が移動するはずです。
ゲームを開始してみましょう!

期待通りにクリックした方向に弾が移動してくれました!
STEP6 正規化しよう!
では最後です。
実は今のままではクリックした箇所によって
以下の通り移動速度が変わってしまいます。

これはプレイヤーユニットから離れれば離れるほど
ベクトルの大きさがどんどん大きくなるのが理由で起きます。
クリックした箇所によらず一定の速度で弾を移動させるにはどうするかというと
ベクトルを正規化するんです。
名前が難しそうですがやっていることは簡単です。
どんな大きさのベクトルであれ必ず大きさ1のベクトルに変換するんです。
つまり向きはそのままで大きさだけ1に変えるのが正規化です。
これによりプレイヤーユニットからクリック地点がどんなに離れていようと
同じ速さで弾が発射されるようになります。
Bulletスクリプトの変更点は以下です。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour
{
private Vector3 direction;
private Transform thisTransform;
private float speed;
// Start is called before the first frame update
void Start()
{
thisTransform = transform;
speed = 0.1f;
}
// Update is called once per frame
void Update()
{
Vector3 newPosition;
Vector3 addVector;
addVector = new Vector3(direction.x * Time.deltaTime, direction.y * Time.deltaTime, 0);
addVector.Normalize();
newPosition = thisTransform.position + addVector * speed;
transform.SetPosition(newPosition.x, newPosition.y, newPosition.z);
}
public void getVector(Vector3 from,Vector3 to)
{
direction = new Vector3(to.x - from.x, to.y - from.y, to.z - from.z);
}
}
22~24行目がメインで追加した箇所になります。
ここでやっているのが正規化です。
とはいってもUnityに正規化用の関数が実装されているので
それを呼び出しているだけなんですけどね!
この処理を追加して実行してみたのが以下になります。

ちゃんと速度一定の弾が発射されるようになりました!
めでたしめでたし!