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と同じですが、「撃て」という動詞としても使われます)
      • 次へ。

複数の弾を出す

  • ちなみにこの【"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】は、薄っぺらい画像でしたね。つまり方向に注意です。

試してみよう

  • 弾が複数出せるようになったら、次は、弾が画面外に消えたら、オブジェクト(クローン)も削除されるようにしてください。
    • 条件:別スクリプト【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】と同じ座標になります。

岩を作る

  • 今の状況を確認しておくとこんな感じですね。
  • 上の方に岩が出現していますが、まだ止まっているだけで、弾を当てても反応もありません。
    1. 岩を動かす
    2. 弾を当てると爆発する
    3. プレイヤーとぶつかると、プレイヤーを爆発させる
  • この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までの間の値になります。​
      • 次へ。

岩を回転させる

  • 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