Unity入門6
Space Shooter③
(スペースシューター)
- 前回まででUnityでの【動かし方】を色々学び、【プレイヤーの動き】が完成しました。
- 今度は弾を打てるように作っていきたいと思いますが、一旦現在のコードを確認しておきます。
- 下のコードになっていると思います。※コメントアウトは除く。
状況確認
public class PlayerController : MonoBehaviour
{
public float speed;
void FixedUpdate ()
{
float moveH = Input.GetAxis ("Horizontal");
float moveV = Input.GetAxis ("Vertical");
//動かす処理
Vector3 nowP = new Vector3(moveH,0,moveV);
GetComponent<Rigidbody>().velocity = nowP * speed;
//傾きの処理
GetComponent<Rigidbody>().MoveRotation(Quaternion.Euler (0.0f, 0.0f, moveH * -50.0f));
//移動範囲の制限する処理
GetComponent<Rigidbody>().position = new Vector3(
Mathf.Clamp(GetComponent<Rigidbody>().position.x, -7, 7),
0.0f,
Mathf.Clamp(GetComponent<Rigidbody>().position.z, -4, 14)
);
}
}

- 実は現在のコードは、理解しやすさを重視のため、少し処理が重たい書き方をしています。
- それは【GetComponent<Rigidbody>()〜】の部分です。
- 【GetComponent<Rigidbody>()〜】の内部処理としては、このメソッドが呼び出される度に、「はいはい、Rigidbodyね、ちょっと探してきますね」みたいな感じで、イチイチRigidbodyを探す処理を行ってしまいます。
- でも、一度見つけた場所を保存しておけば、何度も探しに行く必要はないのですよね?
- そのために、GetComponent<Rigidbody>()の部分を、【繰返しの外】で、変数に代入しておくのが、よく使われる方法です。
- ※WEBを学んだ際に出てきたDOMの時も同じような事しましたね。変数の使い方の1つです。
- 単純に考えると下のコードになります。(変数名はrbにするのが一般的です)
処理を軽くする
public class PlayerController : MonoBehaviour
{
public float speed;
Rigidbody rb = GetComponent<Rigidbody>();
void FixedUpdate ()
{
//省略
}
}

- でも、先ほどのコードはエラーになります。
- これは【Rigidbody rb = GetComponent<Rigidbody>();】が実行されるタイミングでは、まだオブジェクトが出来ていないため、【GetComponent<Rigidbody>()】で「Rigidbodyとかないんですけど?」と、エラーになります。
- ゲームがスタートしたタイミングと言えば【Start()】メソッドでしたね?
- 下のように【Start()】メソッドを復活させて、その中で変数を作ってみましょう。
処理を軽くする
public class PlayerController : MonoBehaviour
{
public float speed;
void Start(){
Rigidbody rb = GetComponent<Rigidbody>();
}
void FixedUpdate ()
{
//省略
}
}

- これでゲームは動くようになったと思います。※次に続く。
- では【GetComponent<Rigidbody>();】の場所を、下のようにすべて【rb】に変えてください。
処理を軽くする
public class PlayerController : MonoBehaviour
{
public float speed;
void Start(){
Rigidbody rb = GetComponent<Rigidbody>();
}
void FixedUpdate ()
{
float moveH = Input.GetAxis ("Horizontal");
float moveV = Input.GetAxis ("Vertical");
Vector3 nowP = new Vector3(moveH,0,moveV);
rb.velocity = nowP * speed;
rb.MoveRotation(Quaternion.Euler (0.0f, 0.0f, moveH * -50.0f));
rb.position = new Vector3(
Mathf.Clamp(rb.position.x, -7, 7),
0.0f,
Mathf.Clamp(rb.position.z, -4, 14)
);
}
}
- 残念…またエラーになるはずです。※次のページへ

- この原因は【rbってなんすか?】というエラーです。
- つまり【FixedUpdate()】メソッドから変数【rb】が見れないのです。
- だって、メソッド内で宣言されたメソッドは【ローカル変数】(そのメソッドでしか使えない)ですもんね。
- さて、じゃあどうすれば良いのでしょうか?
- ちょっと難しいかも知れませんが、変数のスコープの良い勉強になりますので、次のページで挑戦してください。
処理を軽くする
- 下記のコードを修正して、【GetComponent<Rigidbody>();】を変数に代入して使えるようにしてください。※うまく動いたら一応確認のために見せてください。
- ヒント:ココらへんを見直してみよう。
試してみよう

弾を作る
- さて、いよいよ弾作りにいきましょう。
- 弾の画像(マテリアル)は用意してくれているので、その画像を貼り付けるための、新しいオブジェクトをつくります。
- Hierarchyビューの【Create】→【3D Object】→【Quad】を作ってください。
- 名前は【Bullet】にしておきます。
- Projectビューの【Assets】→【Materials】の中の【fx_bolt_orange】というマテリアルを、先ほど作った【Bullet】に貼り付けます。
- ※以上のことからわかるように、この弾は「薄っぺらい画像」です。方向によっては見えなくなります。

弾を作る
- 次にこの弾に【Rigidbody】コンポーネントと【CapsuleCollider】コンポーネントを追加してください。
- 最初から付いている【MeshCollider】不要になりますので、削除しておきましょう。
- ※【MeshRenderer】は必要なので消さないように注意。
- 【Rigidbody】の【Use Gravity】(重量)のチェックを外し、【CapsuleCollider】の【is Trigger】(当たり判定だけの存在)のチェックを付けてください。
- transformを以下のようにしてください。
- ここまで出来れば、この玉を動かすスクリプトを書きましょう。
- 名前は【Mover】にしておきます。
- つぎへ。

試してみよう
- 弾が、画面の上の方向に飛んでいく処理を作ってください。
- 条件①:速度のパラメータに代入して、動かすこと。
- 条件②:inspectorビューから速度を変更できるように、変数と組み合わせて作ること。
- 下記の画像がヒントです。
- 黒の部分を正しく埋めれば正解です。
- 出来たら【Bullet】にアタッチして、変数speedに数値を設定して動かしてみましょう。
- ※数字は20ぐらいがちょうどいいと思います。
- 弾が1つだけ飛んでいけば成功です。

複数の弾を出す
- 次は、この弾を複数出せるようにしましょう。複数出すといえば、Prefabですね。
- この【bullet】オブジェクトを【Assets】の【Prefabs】フォルダにほりこんで、Prefabにしてくださいう。
- 出来れば元の【bullet】オブジェクトは削除しましょう。
- 後は、【Prefabから弾を作り出す処理】を書けば良いですね。
- この弾はプレイヤーから出てくるので、すでにあるスクリプトの【PlayerController】に書くとラクです。
- 今回もマウスをクリックして弾を出すことにしますが、移動と同じように、ゲームパッドなどの操作も意識した作りにします。
- 次へ。
複数の弾を出す
- 以下のようにUpdateメソッドを追加してください。(FixedUpdateではないので注意)
- 【Input.GetButtonDown()】がポイントです。
- (今まで、マウスの入力を取る時は、【Input.GetMouseButtonDown()】でしたね)
- これは【Input.GetAxis()】と同じように()内に、指定した内容から入力を取ります。
- ちなみにココで使われる【Fire】(ファイア)というのは、「火」とか「炎」の意味ではなく、「撃(う)て」という意味です。
- (火や炎という名詞の意味でのFireと同じですが、「撃て」という動詞としても使われます)
- 次へ。
- (火や炎という名詞の意味でのFireと同じですが、「撃て」という動詞としても使われます)

複数の弾を出す
- ちなみにこの【"Fire1"】や【"Horizontal"】などが、「どのキーを表すか?」は、Unity画面上の[Edit]→[Project Settings]→[Input]の項目から設定できます。
- ※基本デフォルトで良いので、今回はいじりません。

- 同じ項目が2つずつありますが、これは入力デバイスによって分かれているからです。
複数の弾を出す
- 【Instantiate(shot, transform.position, Quaternion.Euler ());】という部分で、弾を作成しています。
- 【Instantiate()】はPrefabからオブジェクトを作成するメソッドでしたね。
- 第1引数は、オブジェクトにしたいPrefabを指定。
- 第2引数は、出る座標の指定。
-
第3引数は、方向の指定。※第2引数を指定した場合は必ず必要。
- でしたね。
- 次へ。
試してみよう
- 現在のコードはまだ未完成です。
- これを完成させて、下の画像のように弾を飛ばしてください。
- 条件①:Prefabは直接指定するのではなく、変数shotに入れて使ってください。
- ヒント①:【Instantiate(shot, transform.position, Quaternion.Euler ());】の第3引数が、今は空っぽです。
- ヒント②:Prefabの【Bullet】は、薄っぺらい画像でしたね。つまり方向に注意です。
- 条件①:Prefabは直接指定するのではなく、変数shotに入れて使ってください。

試してみよう
- 弾が複数出せるようになったら、次は、弾が画面外に消えたら、オブジェクト(クローン)も削除されるようにしてください。
- 条件:別スクリプト【Destroy】作りましょう。
- ヒント:今までやった内容(ボールを消す、コインを消す)などと同じでOKです。

ヒエラルキービューが、この画像のようになればOK。
ちょっと修正
- ここでちょっと修正します。
- 現在、【FixedUpdate】メソッドに、【Input.〜】から始まるメソッドが使われていると思いますが、本来これは【Update】メソッドの中に入れておくべきなので、移動させておきましょう。
- 具体的に言うと、【float moveH = Input.GetAxis ("Horizontal");】と【float moveV = Input.GetAxis ("Vertical");】の部分です。
- 移動した後の状態が下です。

- しかし、このままでは実はエラーになります。
- なぜなら、上記のコードで作られている(宣言されている)変数moveHとmoveVは、【FixedUpdate】メソッドで呼び出されますが、「メソッド内で宣言したメソッドは、別のメソッドからは使えない(ローカル変数)」のでしたね?※【rb = GetComponent<Rigidbody> ();】のとこでもやりましたね。
ちょっと修正
- これを解決するためには「メソッドの外で宣言しておく」ことが必要でしたね?つまり以下の感じです。

- これで適切に【Input】系のメソッドを使えるようになりました。(まぁほとんど違いはわからないのですが)
岩を作る
- これでプレイヤーの動きは、ほぼ完成しました。
- 次は、いよいよ敵を作っていきます。今まで作ったゲームは”敵”という概念がないゲームだったので、新しい内容ですね。
- 尚、今回の敵(用意されてる敵)は大きくわけて2種類です。
- ただ宇宙を漂っている岩
- 戦闘機
- まずは、【ただ宇宙を漂っている岩】を作っていきたいと思います。
- 次へ。
岩を作る
- 【岩】オブジェクトを作っておきます。
- Hierarchyビューの【Create】→【Create Empty】で、空のオブジェクトを作ってください。
- 名前は以下のように【Asteroid】(アステロイド:小惑星)にしておきます。


- Transformも以下のようにしてください。
岩を作る
- 次に【Capsule Collider】(カプセルコライダー)、【Rigidbody】(リジッドボディ)を追加してください。
- 【Rigidbody】は、以下のように一部変更してください。


- 【Angular Drag】(アンギュラドラッグ)とは回転に対する抵抗、つまり【回転しにくさ】を表します。
- このゲームの舞台は宇宙なので、実際の宇宙と同じく、回転に対する抵抗は0にしておきます。
- ちなみに、その上にあるただの【Drag】(ドラッグ)は【空気抵抗】を表します。これはほとんどの場合0のままだと思います。

岩を作る
- 次に【models】フォルダにある【prop_asteroid_01】を【Asteroid】に掘り込みましょう。
- 以下のようになればOKです。

- これで【Asteroid】が親、【prop_asteroid_01】が子という関係が出来ました。
-
【prop_asteroid_01】のTransformは以下のようにしておいてください。
- ※これで【prop_asteroid_01】は、【Asteroid】と同じ座標になります。

岩を作る
- 今の状況を確認しておくとこんな感じですね。
- 上の方に岩が出現していますが、まだ止まっているだけで、弾を当てても反応もありません。
- 岩を動かす
- 弾を当てると爆発する
- プレイヤーとぶつかると、プレイヤーを爆発させる
- この3点を作っていきます。

岩の動き
- まずは、岩の動きからです。
- 岩の動きは【ランダムに回転しながら、上から下に移動する】という単純な動きになります。
- この処理は色々な方法が考えられますが、今回は【ランダムに回転させる】という動きと【下に移動する】のは別のスクリプトにして作りたいと思います。
- まずはカンタンな【下に移動する】部分です。
- 実は、この処理はすでに作っているのですが、わかりますでしょうか?
- それは【Mover】です。
-
【Mover】は弾を【上に移動】するスクリプトとして作りましたが、実は【上】か【下】かというのは、プラスかマイナスかの違いだけですので、これがそのまま使えるというわけです。
- 次へ。
試してみよう
-
【Mover】スクリプトを使って、下の画像のように岩を下に移動させてください。
- ※アタッチするのは【prop_asteroid_01】ではなく、【Asteroid】にしておいてください。後で関係してきます。

岩を回転させる
- これで、岩が移動できるように出来ました。
- この【Mover】のように【再利用】出来るように考えてスクリプトを作るのも、非常に大事です。
- ムダなコードが減らせるので、単純にラクになりますし、メンテナンスし易いのでバグも減らせます。
- 次は【ランダムに回転させる】処理を作りましょう。
- これは新しくに作りますが、カンタンです。まずは新しいスクリプトを作りましょう。
- ※【Spricts】フォルダ内に作ることを忘れずに。
- 名前は【RandomRotator】(ランダムローテイター)にしておきます。
- 次へ。
岩を回転させる
- 中身は以下の感じです。

- 【GetComponent<Rigidbody>().angularVelocity】は、【回転速度】のプロパティです。
- ※以前プレイヤーの動きでやった、【GetComponent<Rigidbody>().velocity】という変数は【速度(加速度)】のプロパティでしたね。
- 【Random.insideUnitSphere()】(ランダム.インサイドユニットスフィア)というメソッドは【半径1の球体の内部のランダムな点を返す】というメソッドです。
- ※これだけだと意味不明だと思うので、【まんまるなスイカの内部の、タネの位置を返す】とイメージしてください。
- つまり【Vector3(x座標のランダムな値,y座標のランダムな値,z座標のランダムな値)】という形で数値が返されるということです。
- 尚、半径が1の球体なので、それぞれの「ランダムの値」は-1から1までの間の値になります。
- 次へ。
- 尚、半径が1の球体なので、それぞれの「ランダムの値」は-1から1までの間の値になります。
岩を回転させる
- 【Random.insideUnitSphere】に【tumble】(タンブル:転がる)という変数をかけています。
- この【tumble】は、ただの変数なので、いつもと同じく、後でinspectorビューから値を入れるだけです。
- この変数を【Random.insideUnitSphere】に掛けることで【半径1の球体】が【半径tumble分の球体】となることになります。
- ※例えばtumbleの値が5なら【半径5の球体】になるということです。
- では、早速、回転させてみましょう。
- このスクリプトも【Asteroid】にアタッチしてください。値は5ぐらいで良いと思います。
- 下の画像のように、回転しながら下にいけばOKです。

岩を消す
- 次は、弾を当てれば、岩を消すことができるようにしましょう。
- これも新しいスクリプトに作ります。
- 名前は【DestroyByContact】(デストロイバイコンタクト)としておきます。(当たり判定を取得し、破壊するからです)
- このコードは今までの内容で、できますね。
- 次へ。
試してみよう
- 下の画像を参考に、岩が消えるコードを書き、実際に消えるようにしてください。
- ヒント:タグの設定も忘れずに。


- 下の画像のようになればOKです。

破壊されるエフェクト
- 岩は消えましたが、ちょっとあっさりしすぎて物足りないですね。
- 実は、【破壊されるエフェクト】が用意されていますので、岩が消えると同時に、そのエフェクトを表示させて見ましょう。
- まずは、先ほどの【DestroyByContact】にpublic変数を用意します。
- ここでは【explosion】(爆発)という名前にします。

破壊されるエフェクト
- この変数に、用意されている爆発のPrefab(パーティクルエミッター)を代入します。
- 【Prefab】→【VFX】の中の【explosion_asteroid】を使います。
- 先ほどの変数に掘り込みましょう。

- まだ爆発はしません。もちろんコードが足らないからです。
- なので、さらに【DestroyByContact】にコードを追加します。
試してみよう
- 以下のコードの黒枠を正しく記入し、爆発のエフェクトが表示されるようにしてください。
- ヒント: 【Instantiate()】のメソッドのおさらい問題です。特に難しくはありません。


エミッターも削除する
- さて、岩が爆発するようなパーティクルが表示されたのは良いのですが、今度はこのパーティクルエミッターのオブジェクト(クローン)が、削除されずにゴミとして残ってしまっています。
- エミッターには、弾を消すときに使った【Destroy】メソッドを付ける、という方法が思い浮かびますが、実はエミッターに【Destroy】メソッドをつけても消えません。そもそも当たり判定がないのです。
-
まぁこの処理も、すでに前のゲームでやっていますね。
- 次へ。

エミッターも削除する
- エミッターも削除されるようにしてください。
- ヒント:コイン落としの時に使ったスクリプトそのままでOKです。
プレイヤーを爆発させる
- 次は、プレイヤー側も岩にぶつかったら爆発するようにしましょう。
- これも、今作っていた【DestroyByContact】に追加します。
- 爆発のエフェクトはプレイヤー用がありますので、まず、これを代入する用の変数を用意します。
- ここでは【playerExplosion】という名前にしています。
- 変数を用意したら、また【Prefab】→【VFX】の中から今度は【explosion_player】をほうりこみます。



プレイヤーを爆発させる
- 次は、スクリプトを追加します。
- 【if (co.gameObject.tag == "Bullet")】と同じように、【if (co.gameObject.tag == "Player")】を追加すれば良さそうです。
- プレイヤーにもタグの設定(Player)を忘れずにしてください。※【Player】タグはすでに用意されています。
- 次へ。

プレイヤーを爆発させる
- 試しに動かしてみましょう。
- ん!?岩の方が爆発してしまいました。
- これじゃあ無敵すぎますので、コードを直します。
- 次へ。

プレイヤーを爆発させる
- 以下のように修正してください。
- 【co.◯◯】とすることで、【引数coの】という意味になります。
- これで試すと良い感じになるはずです。



プレイヤーのエミッターも削除する
- プレイヤーのエミッターも削除されるようにしてください。
- ヒント:岩と同じです。
ランダム位置から複数の岩を出す
- 次は、さらに岩の動きを【ランダムな場所(横の座標のみ)から、複数出す】ようにしましょう。
- 複数出すので、当然Prefabにします。Prefabにしたら、いつも通り元のオブジェクトは消しておきましょう。
- 次はスクリプトで、このPrefabを、〇〇秒ごとに作ればOKですね。
- 新しいスクリプトを作り【GameController】としておきます。(このスクリプトは、後々いろいろな処理をする予定なので、【GameController】にしています。)
- このスクリプトをアタッチするためのオブジェクトも用意します。
- 空のオブジェクトを作り、名前を同じく【GameController】にしておきます。
- そしてこのオブジェクトに、先ほど作ったスクリプトをアタッチしておきます。


ランダム位置から複数の岩を出す
- 続いては【GameController】スクリプトの中身です。
- 【数秒ごとに、自動でPrefabを作る】というのは今まで何度もやっていますね。
- ランダムの部分は後にするとして、まずは【数秒ごとに、自動でPrefabを作る】を作っておきましょう。
- 次へ。
試してみよう
- 下のコードの黒枠を記入し、岩が何度も出てくるようにしてください。
- ※画像では、プレイヤーは消しています。また、出る間隔は3秒ごとにしています。
- 条件:変数名は下の画像通りにしておいてください。
- ヒント:ボール飛ばしゲームの時に同じような処理をしています。見直しましょう。


ランダム位置から出す
- 岩を複数出すのは出来ましたね。次はこれをランダム位置から出るようにしていきます。
- これは先ほどのUpdateメソッドに書いてしまっても良いのですが、少しごちゃごちゃしてしまいますので、別のメソッドを作って、それを呼び出して使いたいと思います。
- スクリプトファイルは【GameController】のままで良いので、以下のメソッドを追加してください。
- この処理も特に新しいことはしていません。
- 出て来る座標を変数【spawnPosition】に、出てきた時の角度(方向)を変数【spawnRotation】にそれぞれ代入し、【Instantiate (asteroid, spawnPosition, spawnRotation);】でオブジェクトを作っているだけですね。
-
ただし、まだ未完成です。
- 次へ。

試してみよう
- 先ほど作ったメソッド【SpawnWaves()】はまだ未完成です。一部正しく書き直して完成させてください。
- メソッドが完成すれば、それを正しく呼び出して、下の画像のように岩がランダムに出るようにしてください。※画像ではプレイヤーは消しています。
- ヒント①:まずは【SpawnWaves()】を完成させましょう。現在のメソッドでは「xが0、yも0、zも0の場所に岩を作る」になっています。ランダムにするためのメソッド【Random.Range()】を思い出しましょう。
- ヒント②:【SpawnWaves()】メソッドはUpdateメソッドから呼び出して使います。【SpawnWaves()】メソッドに【Instantiate()】が含まれていますので、余分なものは消しておきましょう。

岩を削除する
- 岩のランダムな場所に作ることが完成できたので、今度は画面から消えるとオブジェクトを削除をしたいと思います。
- つまり【岩が画面から消えたら、削除される】という処理です。今までも何度もやっています。
- 今までの経験上、単純に【Destroy】をアタッチすれば、弾と同じように画面外で消えてくれる気がします。やってみましょう。
- ※アタッチするのはPrefabの中の【Asteroid】です。ドラッグ&ドロップではアタッチしにくいので、【Add Component】→【Scripts】→【Destroy】でアタッチしましょう。
- これを試してみるとわかりますが、いつまでたっても【Asteroid】のオブジェクトは消えません。
- でもエラーが出るわけではありません。正常に処理が終わっていることになっています。
- 実は、【Destroy】スクリプトの中の、【OnBecameInvisible()】に秘密があります。
- この【OnBecameInvisible()】は【カメラに映らなくなったら】自動で実行されるメソッドでした。
-
この【カメラに映らなくなったら】の判定には【Mesh Renderer】(メッシュレンダラー)というコンポーネントが使われています。
- ※【Mesh Renderer】は、メッシュ(ポリゴンの集合体)の描画をしています。まぁ3次元の形を書いてくれる感じです。
-
その【Mesh Renderer】が【Asteroid】にはアタッチされていないのです。
- 次へ。
岩を削除する
- なぜ、【Asteroid】には【Mesh Renderer】がないかと言うと、最初に【CreateEmpty】(空のオブジェクト)で作っているからです。
- 逆に形があるオブジェクトは、最初から【Mesh Renderer】がアタッチされていますので【Destroy】スクリプトをつけるだけでOKでした。
-
ということで【Asteroid】にも【Mesh Renderer】をアタッチしてあげましょう。
- 【Add Component】→【Mesh】→【Mesh Renderer】を選択でアタッチできます。
- これで【岩が画面から消えたら、削除される】が完成しました。
- ※本来、形のないオブジェクトに【Mesh Renderer】をつけるのは、あまりお行儀の良くないコードな気がしますが、今回は話の単純化のために、このままいきます。

お疲れ様です
スペースシューター3
By kinocode
スペースシューター3
- 1,711