Unity入門14
Tanks!⑤
(タンク)
- 前回までで、基本動作部分は出来ました。
- 次回はゲーム全体を管理するクラスをつくり、システム部分をつくっていきます。
前回の確認
- 前回までで、基本動作部分は出来ました。
- 次回はゲーム全体を管理するクラスをつくり、システム部分をつくっていきます。
- 色々なスクリプトを行き来しながら進めていきますので、【どのスクリプトの話か?】は注意してください。
前回の確認
- ゲームのシステム部分を管理するためのスクリプトとして【GameManager】(ゲームマネージャー)スクリプトを作ってください。
- 次に新しい空オブジェクトを作り、同じく【GameManager】いう名前にしておきます。
- 出来たら【GameManager】オブジェクトに、【GameManager】スクリプトをアタッチしてください。
ゲームマネージャーの作成
- では、まず【タンクをプレハブから作り出す】処理をGameManagerにさせたいと思います。
- 現在のタンクはオブジェクトだと思いますので、これをプレハブにしてください。※複数のタンクがあると思いますが、プレハブにするのは1台だけで良いです。プレハブの名前は【Tank】になるようにしておきましょう。
- プレハブにしたらオブジェクトのタンクは不要ですので、すべて削除しましょう。
- 次はスクリプトの中身です。まずは以下のようにしてください。※まだエラーになります。
ゲームマネージャーの作成
- とりあえず3つの変数を作っています。
- 3つの目の【TankManager型の配列】に関しては、まだTankManagerクラスがない(スクリプトを作っていない)ため赤文字です。
- 次へ。

- 次に、もう一つ【TankManager】(タンクマネージャー)というスクリプトを作成します。
- まずは、以下のようにしてください。
タンクマネージャーの作成
- 1番のポイントは【MonoBehaviour】クラスを継承していない、ということです。
- 今までならクラス名(TankManager)の後に【: MonoBehaviour】が書かれていました。これで【MonoBehaviour】クラスというものを継承していましたね。
- 【MonoBehaviour】クラスとはUnityの基本クラスですので、これを継承しているからこそ【Start()】メソッドや、【Update()】メソッドなどの【Unityが用意してくれているメソッド】が使えたわけです。
- 今回はそれを継承させていません。つまり【Unityとは関係のないクラス】となっています。次へ。

- 【MonoBehaviour】クラスを継承しない、ということはUnityの独自メソッドを使えない、ということ以外に【インスペクタービュー上からもイジれない(参照できない)】というデメリットもあります。
- この【インスペクタービュー上からもイジれない】ことへの対策がその上の [System.Serializable]です。
- クラスの上に[System.Serializable]を書いておくと、インスペクタービュー上からも参照できるようになります。
- 「じゃあMonoBehaviourクラスを継承しないメリットは?」、きっとそう思うと思います。
- メリットというか基本的には【継承する必要がないから、しない】というだけです。継承していても同じことはできます。(作り方はかわりますが)
- 今回はサンプルゲームが継承しない方法で作っていたので、それに合わせて作ります。
- 逆にいえば、【MonoBehaviour】を継承するべきクラス、【MonoBehaviour】しないで良いクラス、これをきちんとわけて作っていけるようになればUnity開発も1人前と言えます。
- ちなみに、継承しないで良いクラスの目安としては【実態(オブジェクト)が必要ない】【Start()メソッドなどのUniyt独自メソッドも必要ない】クラスです。
タンクマネージャーの作成
- さて、【TankManager】(タンクマネージャー)のつづきです。新しいメソッドを追加します。
タンクマネージャーの作成
- 一言でいうと、これは【TankMoveとTankShootスクリプトに、タンクのナンバーを設定する】メソッドです。赤色になる部分がありますが、今は正常な動きなので気にしないでください。
- 【move = instance.GetComponent<TankMove>();】と【 shoot = instance.GetComponent<TankShoot>();】は見ての通り、【instance】に入っているオブジェクトの【TankMove】と【TankShoot】コンポーネントを取得していますね。
- この【instance】には、後でタンクが入ることになります。
- その下の【move.playerNumber = playerNumber;】と【 shoot.playerNumber = playerNumber;】は、タンクごとのナンバーを設定し、TankMoveとTankShootのスクリプトにそのナンバーを伝えている部分です。
- 現在は、TankMoveとTankShootのスクリプトにナンバーを受け取る変数がないので赤文字になっています。次はこれを追加します。

- 一旦、【TankManager】から離れて、TankMoveとTankShootスクリプトにそれぞれ以下の変数を追加してください。※両方ともに同じ変数を追加してください。
TaskMoveとTankShootのスクリプトの変更

- これで、先ほどのタンクのナンバーを受け取れるようになりました。
- 【TankManager】を開くと、赤文字がなおっているはずです。

- 次は【GameManager】(ゲームマネージャー)スクリプトに戻ってください。
- こちらも前は赤文字だった部分がなおっていると思います。
ゲームマネージャー

- この【GameManager】スクリプトの以下のようなメソッドを追加してください。

- この【SpawnTanks()】メソッドが【プレハブからタンクを生み出す】メソッドです。
- for文を使い、【tanks.Length】分、つまりは【tanks配列の要素の数】だけタンクを作り、tanksのinstance変数に入れています。次へ。
- 全体の構造がわかりにくいと思うので、図にしてみましょう。大雑把ですが以下の感じです。
- Tanks配列のそれぞれの要素の. instance変数に、タンクオブジェクトが入っています。こうすることで、すべてのタンクをゲームマネージャーから管理しています。
ゲームマネージャー
TankMove
GamaManager
TankManager(Tanks配列)
Tank
TankShoot
TankHealth
Tanks[0]
. instance
Tank
Tanks[1]
. instance
Tank
Tanks[2]
. instance
TankMove
TankShoot
TankHealth
TankMove
TankShoot
TankHealth
- さて、先ほどの【SpawnTanks()】メソッドに追加で記入し、以下のようにしてください。
ゲームマネージャー

- 追加した上の行で、それぞれのタンクのナンバー用変数(playerNumber)に、ナンバーを代入しています。
- +1しているのは、配列の番号は0からカウントしますが、タンクのナンバーは1からはじめたいからです。
- 下の行で、【TanksManager】の【Setup()】メソッドを実行しています。
- 【Setup()】メソッドは、【TankMoveとTankShootスクリプトに、タンクのナンバーを設定する】メソッドでした。
- つまり、【GameManager】がナンバーを作り、それを、それぞれのタンクが、【自分のTankMoveとTankShootスクリプト】に、ナンバーを教える、という動きです。つぎへ。
- つまり以下のイメージです。
ゲームマネージャー
TankMove
GameManage
Tank1
TankShoot
Tanks[0]
. instance
「キミ 1 な」
TankMove
Tank2
TankShoot
Tanks[1]
. instance
「キミ 2 な」
Setup()メソッド
「おれら 2 やて」
Setup()メソッド
「おれら 1 やて」
- では、このタンクのナンバーは、どのように使われるのでしょうか?
- 実は、このナンバーは【タンクの移動や、弾の操作方法をわける】ためだけに使われます。※【TankMoveとTankShootスクリプトだけ】にしかナンバーを伝えていないのはこのためです。
- 今まではすべてのタンクが同じ操作方法で動いていたので、このナンバーを使って、操作方法をわけるわけです。
ゲームマネージャー
- 現在の操作方法、つまりは入力されたキーの判定は、以下のようになっていると思います。

- それぞれの末尾の"1"という数字がポイントです。例えば前に移動するキーは【"Vertical1"】なら、【Wキー】、【"Vertical2"】なら【upキー】(上矢印キー)で前に動くように、InputManagerで設定されています。※本来は自分で設定しますが、今回の場合は最初から設定が入っています。
- つまり【"Vertical"+タンクナンバー】にしておけば、【タンクごとに、別の操作方法で動く】ということになります。次へ。
- さて、このタンクナンバーを試す前にタンクを生み出せるようにしておきましょう。
- 【GameManager】オブジェクトのインスペクタービューを見ると、下のようになっていると思います。
ゲームマネージャー

- 【CameraControl】には【CameraRig】を、【TankPre】には【Tank】プレハブを、さらに【Tanks】配列の【size】を2にしておきましょう。以下の感じです。

- 後は、【SpawnPoint】を指定すればタンクがその場所に生み出されることになりますので、【SpawnPoint】用のオブジェクトを用意しましょう。
- 空のオブジェクトを2つ作り、名前を【SpawnPoint1】【SpawnPoint2】としておいてください。
- このオブジェクトは場所と、方向を指定するだけの存在なのでTransformだけ設定します。それぞれ以下のようにしておきましょう。
ゲームマネージャー


- このオブジェクトは見た目がないので、Sceneビューのどこにあるかちょっとわかりにくいですね。
- こういう時は、アイコンをつけておくことができます。
ゲームマネージャー


ここをクリック
どれかを選択
(なんでもいい)

こんな感じで表示されるようになる
- 【SpawnPoint1】【SpawnPoint2】に別のアイコンをつけておきましょう。
- 今回のように、タンク本体もスタートしないと出現しない場合など、出現場所にアイコンをつけておくと、ゲームスタートしなくても場所の確認ができるようになります。
- では、この【SpawnPoint1】【SpawnPoint2】を、【GamaManager】に設定します。
ゲームマネージャー
- 【PlayerNumber】と【Instance】はスクリプトの中で代入されますので、インスペクタービューはこのままでOKです。
- これで準備が整いました。あとはナンバーをそれぞれのタンクに振り分けられるようになればOKです。

- タンクのナンバーごとに操作方法が変わるようにしてください。
- ヒント:【TankMove】と【TankShoot】の【Input.GetAxis()】の引数の部分ですね。※新しい変数を作って、一旦まとめて置くほうが良いコードになります。
- 【GameManager】スクリプトにも【SpawnTanks()】を実行するコードを追加しなくてはいけません。現時点では【SpawnTanks()】はゲームスタートの時に実行されればOKです。
- 現在のInputManagerの設定では、タンク2は以下の赤枠の操作方法で動くはずです。※タンク1は前と同じです。
課題①
タンク2の操作方法
左回転 ⇛ 十字キー左
右回転 ⇛ 十字キー右
前進 ⇛ 十字キー上
後進 ⇛ 十字キー下
弾を撃つ ⇛ エンターキー

- ※タンクをプレハブから作成する方法に変更したので、現在カメラはタンクについてきません。また【transform.position assign attempt for 'CameraRig' is not valid. Input position is { NaN, 0.000000, NaN }〜】というエラーも出ます。このエラーに関しては後で直しますので、気にしないで結構です。
Inputmanagerの設定
- これで【GameManager】からをタンクを管理し、操作方法を変えることができるようになりました。
- ただ、現在のInputmanagerの設定では、2台のタンクまでの設定しかありませんので、3台以上になると動きません。
- 3台以上を想定するなら、先にInputmanagerに設定を増やしておく必要があります。
- まぁキーボードだけだと、これ以上操作方法をわけるのは厳しいのですが、一応やり方だけ見ておきましょう。
- Inputmanagerを開くと、色々な項目が出てくると思いますが、まず【size】を確認してください。これは、登録された設定の数のことです。つまり【size】が16だと、16個の設定があるということです。
- もし3台のタンクを動かせるようにするなら、ここに設定を3つ増やしたいので、【size】を19にしてみましょう。設定項目が増えたと思います。※最後にあった設定項目のFire2が3つ複製されている状態だと思います。
- この増えた設定項目を開き、nameを【Horizontal3】などにしてすれば、3台目のタンクの設定となります。
- name以外の設定に関しては、細かくなりますので、今回は飛ばしますが【Horizontal1】などを参考にして、【Nagative Button】(マイナス側へのボタン)【Positive Button】(プラス側へのボタン)の設定をすれば、とりあえずはOKです。
- ※今回は設定不要です。
- name以外の設定に関しては、細かくなりますので、今回は飛ばしますが【Horizontal1】などを参考にして、【Nagative Button】(マイナス側へのボタン)【Positive Button】(プラス側へのボタン)の設定をすれば、とりあえずはOKです。
カメラの修正
- 次は、カメラがついてきていないエラーを直しましょう。
- 原因は単純です。カメラが映す対象(ターゲット)のタンクは、【CameraControlスクリプトのtanks】配列に、入れる必要がありましたが、現在、この配列は空っぽになっているせいです。
- なぜかというと、もともと作っていたタンクのオブジェクトは削除し、プレハブで作ることに変えたからでしたね。
- つまり、プレハブからタンクを作った後、それを【CameraControlスクリプトのtanks】配列に代入してあげる必要があるというわけです。
- ※【CameraControlにもtanks】配列があるので、ごっちゃにしないように注意してください。(ネーミングに失敗したパターン。あえてそのままいきます)
- これも【GameManager】から【CameraControl】に教えるような作り方をします。【GameManager】に、以下の【SetCameraTagets()】メソッドを追加してください。

カメラの修正
- 【Transform[] targets = new Transform[tanks.Length];】という部分で、【Transform型の配列】を、【tanks.Length分(tanks配列の要素数分)】作っています。※このtanks配列は【GameManagerのtanks】配列です。
-
カンタンにいうと、【tanks配列】と同じ要素数の【targets】配列を作っているということです。型が【Transform型】になっているのは、この【targets】配列は【CameraControlスクリプトのtanks】配列に代入する用だからです。
- ※【CameraControlスクリプトのtanks配列】はタンクの座標だけが必要なので、【Transform型】でしたね。
-
カンタンにいうと、【tanks配列】と同じ要素数の【targets】配列を作っているということです。型が【Transform型】になっているのは、この【targets】配列は【CameraControlスクリプトのtanks】配列に代入する用だからです。
- では、後は【targets】配列のそれぞれの要素に、【プレハブから生み出されたタンク】の【transform】だけを代入していけば良い、ということです。
-
ここで注意として、【プレハブから生み出されたタンク】は、【GameManagerのtanks】配列の要素そのものではなく、その要素がもつ【instance】変数に入っています。
- つまりは、【tanks[].instance】がタンク本体です。次へ。
-
ここで注意として、【プレハブから生み出されたタンク】は、【GameManagerのtanks】配列の要素そのものではなく、その要素がもつ【instance】変数に入っています。
-
【SetCameraTagets()】メソッドを完成させ、カメラのエラーを直してください。
- ヒント:【CameraControl】スクリプトの方は変更不要です。
- また、【SetCameraTagets()】メソッドもゲームスタート時にのみ実行されるようにしておきましょう。
課題②


左のようになって、エラーも消えればOKです。
タンクの色
- これで【GameManager】からをタンクを管理し、操作方法を変えることができるようになりました。
-
次は、タンクごとに色を変更しておきましょう。これはタンクごとに設定を変えたいの【TankManager】に追加します。
- まずは下のように変数を追加してください。※省略してますが、もともとのコードはそのままです。

- この時点で、【GameManager】オブジェクトのインスペクタージビューから、【playerColor】変数の中身の色が設定できるようになります。それぞれ、別の色になるように設定してください。
- ※この時点では、まだタンクの色はかわりません。
タンクの色
- ここでは、以下のようにしておきます。


- 注意点として、色のAの項目(不透明度)は255にしておいてください。
- ※これは後で、画面に表示する文字の色にも、この色を利用するので、透明だと文字が表示されないからです。

タンクの色
- 次に、実際にタンクの色を【playerColor】変数の色にするコードを追加しましょう。
- 色の設定は、タンクのナンバーを割り振るタイミングと同じで良いので、【Setup()】メソッドの中に追加で記入します。

- 追加した1行目は、【子オブジェクトのレンダラーをすべてrenderers配列に入れる】ということをしています。
- タンク本体は【instance】変数に入っていましたが、色を設定するレンダラーがあるのはその子オブジェクトです。そのため、まず子オブジェクトのレンダラーのみを【renderers】配列に代入しています。※上記のように書くことで、子オブジェクトはrenderers配列のそれぞれの要素に代入されます。
タンクの色
- その下のfor文で、【renderers】配列に代入した子オブジェクトのレンダラーすべてに、色を設定しています。※色の設定はレンダラーの中の【Material】の【Color】ですね。
- これでゲームをスタートすると、それぞれタンクの色が変わるはずです。試してみましょう。

ゲームシステム確認
- 次は、ゲームのシステム部分を再確認しておきたいと思います。
- ゲームがスタートすると、バトルスタート文字(ROUND1の文字)が表示される。
- ※タンクは動けない。
- 表示が消えるとタンクが操作できるようになり、タンクが1体になるまで戦う。
- 1体になった時点で、勝利文字(プレイヤー◯◯:WIN)が表示される。
- ※タンクは動けない。
-
1に戻る(ROUND数のみ増える)、上記を繰り返し、どちらかの勝利数が5になったら「プレイヤー◯◯の勝ち」と表示され、終了。またROUND1に戻る。
- このような流れでした。
- ゲームがスタートすると、バトルスタート文字(ROUND1の文字)が表示される。

- さきほどの全体の処理を、ざっくりとした図(フローチャート)にしてみるとこんな感じです。
- ポイントは、②のバトル以外の時は【タンクを動かないようにする(弾もでない)】ということです。
- つまり、基本はタンクの動きを止めておいて、バトルの時だけ動けるようにすればいいですね。
ゲームシステム確認

- では、まずタンクの動きを止める処理を足します。今回はstatic変数(クラス変数)を使って、カンタンに作ることにします。
- 【GameManager】スクリプトに変数を追加します。

- この【stop】変数は、タンクを止めるか?止めないか?の状態管理のために使います。
- 基本的にとめておきたいので、最初からtrueを代入しておきます。
- あとは、この変数がtrueの限りは、タンクが動けない(弾もでない)ようにします。
タンクの状態管理
- 先ほど作った【stop】変数がtrueの間は、タンクが動かないようにしてください。
- ヒント:タンクの動きは具体的に【前進(後進)】と【回転】と【弾をうつ】でしたね。これをすべて止めてください。
- static変数(クラス変数)は、インスタンスにではなく、クラス自体に紐づく変数でしたね。static変数を別クラスから使うには【クラス名.変数名】です。
- ※動かないように出来たら、今度は【public static bool stop = false;】に変更してみて、ちゃんと動くようになるかも確認してください。
課題③
- ちなみにstatic変数(クラス変数)は、publicがついていてもインスペクタビューには表示されません。これはUnityの仕様です。
- これでタンクの状態管理の準備が出来ました。
- このゲームでは、ゲーム全体の流れをコルーチンで作っていますので、まず【GameManager】スクリプトに以下のようなコルーチンを用意しましょう。
- このコルーチンの中で、それぞれのステップによって、別のコルーチンを3つ用意して、呼び出します。全部で4つのコルーチンということです。
- 以下のような形になる予定です。

ゲームシステム確認

- フローチャートでいうと➔の感じです。
- 全体を囲んでいる赤枠が【GameLoop】コルーチンです。
- 青枠、緑枠、オレンジ枠が、それぞれがコルーチン①〜③になります。
ゲームシステム確認

- では、【GameLoop】コルーチンの中に、残り3つのコルーチンを追加していきましょう。
ゲームシステム確認

- ここで、コルーチンの動きを思い出しておきましょう。
- 【yield return】とすることで、そのコルーチン(【GameLoop()】)は一旦処理を抜けます(停止する)。再度動くのは、その右側で指定された処理(今回なら【StartCoroutine(◯◯)】)が終わったあとでしたね。
- また、再度動くときは【yield return】した場所から再開するんでした。
- その【StartCoroutine()】とは、引数で指定されたコルーチンを呼び出す(実行する)メソッドでした。
-
つまり、最初に【RoundStarting()】コルーチンが実行され、それが終了したら次に【RoundPlaying()】が実行され、それが終わったら【RoundEnding()】、それが終われば【RoundStarting()】に戻る、という動きということです。
- ※尚、それぞれのコルーチンはまだ作っていないので、赤文字になっています。
- 【yield return】とすることで、そのコルーチン(【GameLoop()】)は一旦処理を抜けます(停止する)。再度動くのは、その右側で指定された処理(今回なら【StartCoroutine(◯◯)】)が終わったあとでしたね。
- また、【GameLoop】コルーチンの中の処理は、ゲームをオフにするまで延々と繰り返されるようにしたいので、【while(true)】で無限ループさせておきます。
ゲームシステム確認

- この【GameLoop】はスタートした時に動作するようにしておきましょう。

- では、実際に3つのコルーチンを作ります。まだ中身は空っぽです。
- もちろん【GameManager】スクリプトの、【GameLoop()】コルーチンの外につくります。
ゲームシステム確認
- これで、先ほどの赤文字は黒文字に変わっていると思います。

- 次は、コルーチンの中身を作っていきます。といっても、まだまだやることは多いので、まずは【タンクの動きの制御】の処理のみ作っていきます。
- 最初は【RoundStarting()】ですね。以下のようにします。
RoundStarting()

- 【yield return new WaitForSeconds()】は、引数に指定した数値の秒数分、コルーチンの処理を待つんでしたね。
- つまり、【RoundStarting()】は、単純に3秒待つだけの動きになります。
-
この時間は画面上に【ROUND1】などの表示が出ている間の待ち時間です。文字の表示は後でつくりましょう。
- タンクはスタート時に【stop】がtrue(動けない)状態なので、ここでは止まったままでいてもらいます。
- 次は、順番的には【RoundPlaying()】なのですが、先に【RoundEnding()】コルーチンの中身を追加しておきます。
- これは、ほぼ【RoundStarting()】と一緒ですが、バトルが終了した時に実行されるので、以下のようにします。
RoundEnding()

- 【stop = true】にすると、【タンクを動かなくする】ことができるようになっていましたね。
- つまり、バトルでタンクを動けるように変更するので、またバトルが終わったら再度動かなくするようにしています。
- その後はまた3秒間処理を待たせています。この間に【◯◯の勝利】というような文字を表示させておく予定です。
- 最後に【RoundPlaying()】です。ここはバトル中の処理なのでタンクが動けるようにします。
- これは単純に下のようしたらいいですね。

- さて、この【RoundPlaying()】が他のコルーチンと大きく違うのが、「3秒待つ」とか秒数ではなく【残りのタンク数が1台以下になるまで】処理を続ける必要があります。
-
そのために、繰り返し(while)を追加します。
- 以下のコードを追加してください。
RoundPlaying()

- 追加したコードは【残りタンクが1台以下になるまで、1フレームずつ進める】処理です。
- while文の条件にあるメソッド【GameContinue()】が【残りタンクが1台以下になるまで調べる】メソッドです。これは後で作りますので、今は赤線になっています。
- 【yield return null;】の部分が【1フレームずつ進める】動きになります。つまり1フレームごtに、再度【GameContinue()】が実行され、タンクが1台以下になっていないかを調べる動きになります。
RoundPlaying()
- では、残りタンクが1台以下になるまで調べる【GameContinue()】を作っていきましょう。
- 条件の部分でメソッドを呼び出しているということは、このメソッドはどんな情報を返して(retune)くるメソッドかはわかりますよね?
課題④
- 以下のコードの黒枠を埋めて、【GameContinue()】メソッドを完成させてください。


わかりにくいですが、右の動画のように、【スタートして3秒間は動けない】➔【タンクが1台以下になるまでは動ける】➔【1台以下になったら動けない】となればOKです。
お疲れ様でした
- 後は、文字の表示と、1ROUND終わった後の処理が必要ですね。
- これを次回つくりましょう。
- 「 Tank!⑥ 」に続く。
Tanks!⑤
By kinocode
Tanks!⑤
- 1,215