Unity入門15
Tanks!⑥
(タンク)
- 前回で、タンクの動きを制御する部分が出来ました。
- 次は、画面に表示する文字と、ROUNDが終わってからの処理を作りましょう。
- これが終われば完成です!
前回の確認
- では、まずは文字の表示です。当然【Canvas】が必要ですね。ヒエラルキービューの【Create】→【UI】→【Text】を選択しましょう。
- 【Canvas】と、その子オブジェクトの【Text】が出来たと思いますので、【Text】のインスペクタービューを以下のようにしてください。
Canvasの作成


- 現時点で、以下のように画面に表示されていればOKです。
- 明らかに違うときは、前のページを見直してください。
Canvasの作成

- 続いて、この文字に影をつけておきます。これは初めてやりますね。
- 【Text】のインスペクタービューの【Add Component】を押して、検索欄に【shadow】を入力してください。
Shadowの作成

- 表示された【Shadow】をクリックすると、コンポーネントととして追加されます。
- 追加された【Shadow】コンポーネントを以下のようにしてください。
Shadowの作成
- 【EffectColor】が影の色、【EffectDistance】が、影の伸びる座標と、長さです。
- 今の設定だと、茶色の影が、左(xがマイナス3)、下(yがマイナス3)に伸びます。
- もし、右上に伸ばしたい場合はxがプラス、yもプラスの数字を設定すれば良いということですね。

- 現時点で、このような感じです。
Shadowの作成

- 影をつけるだけで、けっこう変わりますよね?普段テレビやサイトなどで見ている文字、特にロゴマークなどには、影がついていることがほとんどです。
- 尚、この影や文字の色、形は自由に変更してもOKです。
- 次に、【GameManager】から、先ほどの【Text】を変更できるようにしたいので、【GameManager】スクリプトに変数を追加します。
- また、後で使う変数も用意しておきます。
文字の表示

- 追加できたら、インスペクタービューの【messageText】変数へ、【Text】を追加しましょう。
文字の表示

- これで、準備が出来ましたので、後はゲームの状況に合わせて表示を変えていきます。
- まずは【RoundStarting()】です。ここでは【ROUND1】【ROUND2】というように【"ROUND"+ラウンド数】が表示できればOKですね。
-
その次の、【RoundPlaying()】では、文字を消したいですね。文字を消すには【 messageText.text】に【""】を代入すればOKです。
- 【string.Empty】を代入しても同じ意味になります。
- とりあえず、この2つのコルーチンの表示を作りましょう。
課題①
-
【RoundStarting()】と【RoundPlaying()】での文字表示を完成させてください。
- 【RoundStarting()】に関しては、ラウンド毎に【roundNumber】変数が増えるようにもしておく必要があります。以下のコードを参考にしてください。
- 【RoundPlaying()】に関しては、ただ消すだけなので、1つ前のページを参考にしてください。
- また【text】などUIを使うので、【using UnityEngine.UI;】も必要です。

左のようになればOKです。
現在は、再スタートの処理がないため、バトル終了後は延々とROUNDだけが増えていきます。

- 最後に【RoundEnding()】での文字表示です。ここは少しややこしいです。
- まずは、サンプルゲームでの文字表示を見てみましょう。
文字の表示

- まず大きなポイントとしては上の【PLAYER 1 WINS THE ROUND!】の【PLAYER 1】の部分です。
- ここは勝者によって表示が変わらないといけないですね。つまり動的な処理が必要になります。また、この文字の色はタンクのカラーと同じにしていますので、その処理も必要になります。
- この【RoundEnding()】の文字の表示は、やることが多いので、専用の別メソッドを用意して、そちらで処理させたいと思います。
文字の表示
- この【EndMessage】メソッドでは、変数【message】を作り、その中に表示させる文字を代入してから、最後にそれを返す(return)予定です。
- 後は、返された文字をそのまま【messageText】に代入します。つまり、下のコードを足します。


- では、【EndMessage】メソッドでの処理ですが、わかりやすくするために、まずは静的な文字だけを作ります。静的とは動きがない、つまり、誰が勝っても同じ文字が表示されます。
文字の表示
- 追加した1行目で、一番上に表示される文字を【message】に代入しています。
- 2行目では、それに追加で【"\n\n\n\n"】というものを代入しています。これは「エスケープシーケンス」というものの一種で、【\n】が1つで改行1つを表します。
- つまり【"\n\n\n\n"】は4つの改行を追加していることになります。
- 3行目ではタンクの台数分【 "PLATER ? : " + "?" + " WINS\n"】のを代入しています。これは、それぞれのプレイヤーが何回勝利しているか?を表示する部分です。末尾の【\n】も改行ですね。

- この状態でバトルを終了させてみると、下のようになると思います。※ウィンドウサイズで、見え方は多少異なります。
文字の表示
- このように【message】変数の中に、文字を追加していって、画面に表示する文字を作ります。
- その処理をするのが【EndMessage】メソッドの役割ということです。

- 次は、この文字を動的に変化させます。
- そのために必要なのは【それぞれのプレイヤーの名前(PLAYER1など)】と、【それぞれのプレイヤーの勝利数】です。この変数を先に用意します。
- ”それぞれ”の、とあるように、これはタンクごとに管理させます。つまり【TankManager】の役割です。
- 【TankManager】スクリプトに以下の変数を用意します。
文字の表示
- 次に【playerText】変数に、プレイヤー名と色情報を代入します。以下のコードを【Setup()】メソッドの中の、一番最後に追加してください。※見にくいので、下のコピペ用を使ってください。


playerText = "<color=#" + ColorUtility.ToHtmlStringRGB(playerColor) + ">PLAYER " + playerNumber + "</color>";
- ホームページ作りなどを多少やった人ならピンときますが、この【<color=】などの書き方は、HTMLの書き方と同じです。つまりUnityではHTMLの書き方を利用して、色の情報も乗っけることができます。
- タンクごとの色情報は【playerColor】変数で設定していましたが、【ColorUtility.ToHtmlStringRGB(playerColor)】とすることで、タンクの色情報をHTMLでのカラーコード(#6桁の番号)の形式に変換してくれます。
- これをHTMLの【<color=】などの属性と組み合わせることで、【プレイヤーの名前と、タンクの色情報】をセットで保存できます。
文字の表示
- この段階で、それぞれのタンクが【playerText】に【プレイヤー名(色付き)】を保存できた状態になります。
- つまり、以下のように表示することができます。

- 以下のコードを参考に、画像と同じように、【プレイヤー名(タンクと同じ色)】を表示できるようにしてください。※変更するのは【EndMessage()】メソッドだけでOKです。
- ※この時点ではドロー(引き分け)はないものとして考えてください。
課題②


- では、残りの部分は【ラウンドの勝利者名】と【それぞれの勝利ラウンド数(wins)】です。
- まず【ラウンドの勝利者名】ですが、これを保存する様の変数は、すでに用意していましたね。【GameManager】スクリプトの【roundWinner】変数です。
- この【roundWinner】変数に、【そのラウンドを勝利したタンク】を代入して使います。
- その後に、【roundWinner.playerText】とすれば、【ラウンドを勝利したタンクのプレイヤー名】が取得できるというわけです。
文字の表示
- ただし、今回は少しひねったアルゴリズム(考え方)で作ってみます。(コードが少なくて済むので)
- それは、【そのラウンドを勝利したタンクを、roundWinnerに代入する】の部分を、【タンクが生きているか確認する処理の中で、とりあえず、生きていたらroundWinnerに代入する】という考え方です。
- 【roundWinner】は変数なので、代入するたびに中身が変わることになります。つまり、【最後の1台になった時点で、roundWinnerの中に代入されているタンクが勝者!】という考え方です。
- 【タンクが生きているか確認する処理】とは、すでに作っている【GameContinue()】メソッドの中の処理のことですね。
課題③
- 以下の画像になるようにコードを追加、訂正してください。※画像はプレイヤー1が勝利した画面ですが、プレイヤー2が勝利したら、文字表示が変わることも確認してください。
- ヒント:【最後の1台になった時点で、roundWinnerの中に代入されているタンクが勝者!】の処理を作るのが第1歩です。これは【GameContinue()】メソッドの中に、1行だけ追加すれば出来ます。
- 上記ができたら、【EndMessage()】メソッド内を修正して、画面の上部に【(勝利したプレイヤー名) WINS THE ROUND】と表示されるようにしてください。
- ※この時点ではドロー(引き分け)はないものとして考えてください。

- 続いては、ラウンド勝利数の表示です。これは【勝利したプレイヤーのwins変数】を増やしてから、それを表示する、ということになりますね。
- これは単純ではありますが、【値型・参照型】の理解をする良い機会なので、解説しながらやりましょう。
- まず、【勝利したプレイヤーのwins変数】とは、つまり【roundWinner.wins】のことですね。つまり【roundWinner.wins++】とでもしておくだけでOKです。
-
ここで、【値型・参照型】を思い出してください。たとえば勝利タンクが【tanks[0]】だとします。
- つまり、勝利タンクが決まった時点で【roundWinner = tanks[0]】がされている状態ということです。
-
この時、もし【roundWinner】が値型だった場合は【roundWinner】と【tanks[0]】は【中身は同じだけど、別の物】でした。
- つまり【roundWinner.wins】を1増やしても、【tanks[0].wins】は増えません。
-
でも、安心してください。【roundWinner】は【TankManager型】ですね?これはクラス型といって、参照型になりますので、【roundWinner】と【tanks[0]】は同じ物になります。
- つまり【roundWinner.wins】を1増やしたら、【tanks[0].wins】も増えます。
- この前提を理解すれば、後はカンタンです。
文字の表示
課題④

- 以下の画像になるようにコードを追加、訂正してください。※画像はプレイヤー1が勝利した画面ですが、プレイヤー2が勝利したら、勝利数の部分の文字表示が変わることも確認してください。
- ヒント:【roundWinner++】とするのは、【勝利プレイヤーが決定した後、文字を表示する前】であれば、どこでも構いません。
- 上記ができたら、画面の下部分に【プレイヤー名:(それぞれの勝利数) WINS 】と表示されるようにしてください。
- ※この時点ではドロー(引き分け)はないものとして考えてください。
- 次は、引き分けの場合の処理です。
- そもそも引き分けにするのが大変なので、まずはカンタンに引き分けにする状態を作ります。
- 弾の攻撃力と、爆発範囲を増やせば、OKですね。
文字の表示
課題⑤
- 以下のように、弾を1発うてば、両者とも爆発するようにしてください。
- ヒント:色々なやり方がありますが、Shellプレハブのインスペクタービューから弾の爆発範囲を、【ShellExplosion】スクリプトから弾の攻撃力をそれぞれ10倍ぐらいにしとけば即死します。
- この時点では、引き分けになっても【プレイヤー◯◯WINS】と、どちらかが勝ったことになります。
- また、コンソールにエラーが大量にでますが、これも気にしないでください。
- ※もちろん、後で元に戻すので、どこを変更したか覚えておいてください。

- さて、これでカンタンに引き分けれるようになりましたので、引き分けの処理を作っていきましょう。
- 普通に引き分けをつくろうとすると【ラウンド勝利者(roundWinner)がいないとき】と考えるのが自然ですが、今回は常にroundWinnerの中には、タンクが入っている状態でした。なので、この方法は、やめておきます。
- 今回は【生き残りタンクが0なら】という考え方で行います。生き残りタンクは【GameContinue()】メソッドで【numTanks】変数に入れていましたよね?この変数を利用しましょう。
- つまり【numTanks == 0】であれば【DROW】とだけ画面に表示する、ということが出来るわけです。
-
ただ、現在【numTanks】変数は【GameContinue()】で宣言されています。つまりローカル変数なので、別メソッドからは使えません。
- なので、この【numTanks】の宣言の場所を変えてから使う必要がありますね。
文字の表示
課題⑥
- 以下の画像のように、ちゃんと引き分けになるようにしてください。
- ヒント:まず初めに、【numTanks】変数を【EndMessage()】メソッドから使えるようにしてください。
- 次に、【EndMessage()】メソッドで、【numTanks == 0】であれば【message】変数の中身が【"DRAW"】だけになるようにしてください。
-
※上記が出来れば、【爆発の範囲と攻撃力】を元に戻して、【勝利者がいるパターンの表示】の場合でも、ちゃんと出来ていることを確認してください。
- 尚、まだコンソールにエラーは出ますので、気にしないでください。

- これで引き分けも完成しました。
- 最後は【ゲームクリアの表示】です。このゲームでは誰かが先にラウンド勝利数5回になった時、そのプレイヤーが、ゲームの勝利者となります。
- ゲームの勝利者が決まると、【プレイヤー名 + " WINS THE GAME!"】とだけ勝利されるようにしたいと思います。
- ※現在は以下のように、1度勝つとずっと勝ち進んでいく感じです(これは後で直します)。ROUND5の後だけ、表示が変わることを確認しておいてください。
文字の表示

課題⑦
- どちらかが5回勝った時に以下の表示になるようにしてください。
- ヒント:表示方法は【message = roundWinner.playerText + " WINS THE GAME!";】というコードでOKです。(【 += 】ではなく【 = 】になっているところに注目です。追加ではなく上書きということです)
- 後は、上のコードをif文と組み合わせてつかえばOKですね。

- これで、文字表示は全部終わりました!
- 後は、ROUNDが終わるごとに【タンクの位置】【タンクのHP】を元の状態にリセットする、という処理が必要になります。
- 現在の処理では、タンクのHPがなくなるとオブジェクトが削除されます。つまり、もう一度はじめにリセットするためには、削除されたタンクだけを、再度作り直す処理になります。
- しかし、ここで問題が出てきます。【現在の勝利数(wins)】はそれぞれのタンクが持っていますが、オブジェクトが削除されてしまうと、当然そのタンクのデータは消滅してしまいますね。
- そのため、【タンクのHPがなくなるとオブジェクトが削除する】という処理を変更します。オブジェクト自体は削除せず、【非アクティブ】にします。
- 【非アクティブ】とは、オブジェクトを無効の状態(Hierarchyビューにオブジェクトはあるが、ゲーム内には存在していないのと同じ状態)にすることです。逆に、通常の通りのオブジェクトの状態をアクティブといいます。
- 【アクティブ・非アクティブ】はInspectorビューから切り替えることが可能です。
- 【非アクティブ】とは、オブジェクトを無効の状態(Hierarchyビューにオブジェクトはあるが、ゲーム内には存在していないのと同じ状態)にすることです。逆に、通常の通りのオブジェクトの状態をアクティブといいます。
アクティブ・非アクティブ

チェックが入っていたらアクティブ、入っていなければ非アクティブの状態
- もちろんアクティブ状態は、スクリプトから変更も可能ですので、【タンクオブジェクトの削除( Destroy(gameObject))】の部分を、【非アクティブにする(gameObject.SetActive(false))】に変更します。
- それと同時に、現在【オブジェクトが存在しているか?(削除されていないか)】を判定しているところがいくつかあります。if文の条件式で【tanks[i]】または【tanks[i].instance】としているところです。
- 非アクティブの状態でもオブジェクトとしては存在しているので、このままではちゃんと判定がとれなくなります。(ずっとtrueになります)
- そのため、この部分も【オブジェクトはアクティブか?(オブジェクト.activeSelf)】というコードに変更してあげましょう。
- 【.activeSelf】は、アクティブならばtrue、非アクティブならfalseが入っています。
アクティブ・非アクティブ
課題⑧
-
①【タンクオブジェクトの削除(Destroy(gameObject))】の部分を、【非アクティブにする(gameObject.SetActive(false))】に変更してください。
- ヒント:【TankHealth】スクリプトにあります。
-
②【オブジェクトが存在しているか?(tanks[i]またはtanks[i].instance)】の判定を【オブジェクトはアクティブか?(オブジェクト.activeSelf)】に変更してください。
-
ヒント:【CameraControl】スクリプトに2ヶ所と、【GameManager】スクリプトに1ヶ所あります。
- ただし【CameraControl】スクリプトの【tanks[i]】は【Transform型】です。この場合、オブジェクトへの指定は【tanks[i].gameObject】と書く必要があります。
- また、【GameManager】スクリプトの【tanks[i]】は【TankManager型】です。この場合のオブジェクトへの指定は【tanks[i].instance】でしたね。
-
ヒント:【CameraControl】スクリプトに2ヶ所と、【GameManager】スクリプトに1ヶ所あります。
- 上記の変更がうまくいくと、まだゲームの動きは変わりませんが、コンソールのエラーはなくなるはずです。
- では、リセットの処理に仕上げましょう。これが最後のステップです。
- まず、ラウンドが始まる時にリセットをさせたいので、【GamaManeger】スクリプトの【RoundStarting()】コルーチンの中に以下を追加します。
- 場所は【yield return new WaitForSeconds(3);】より上であれば、どこでも良いです。
リセット処理

- リセットするものは具体的な内容は以下の通りです。
- ①ラウンドが始まる時は、【タンクの位置】【タンクの方向】【タンクのアクティブ設定】【タンクのHP】をリセットします。
- ②ゲームが終わった時(どちらかが5勝した時)は、さらに【ラウンド数】【タンクの勝利数】のリセットも必要です。
- まずは、ラウンドが始まる時ですが、それぞれのタンクで管理している情報をリセットしますので、【TankManager】スクリプトに【Reset()】メソッドを追加します。
リセット処理
- 上記のコードで、【タンクの位置】【タンクの方向】【タンクのアクティブ設定】をリセットできます。
- 残りは【タンクのHP】ですが、HPを管理しているのは【TankHealth】スクリプトでしたね。次は【TankHealth】スクリプトを変更します。

- 【TankHealth】スクリプトでのHPのリセット処理とは、【Start()】メソッドの中の以下の部分でしたね。
リセット処理

- 【Start()】メソッドだと最初しか実行されないので、これを別のメソッドとして定義しておきます。

- 名前は【ResetHealth()】メソッドにしておきます。【Start()】メソッドの分は削除し、【Start()】メソッドから【ResetHealth();】を実行する形にしておきましょう。

- この【ResetHealth()】メソッドは、【TankManager】スクリプトの【Reset()】メソッドから使いたいので、以下のように【Reset()】メソッドから呼び出します。
リセット処理

- でも、赤文字ですね。そういえば【TankManager】スクリプトから【TankHealth】スクリプトは、まだ使えませんでしたね。
課題⑨
-
【TankManager】スクリプトから【TankHealth】スクリプトを呼び出せるようにし、以下のコードが動くようにしてください。
- ヒント:【TankMove】などと同じです。

- ROUND1が終わった後、ちゃんと【タンクの位置】【タンクの方向】【タンクのアクティブ設定】【タンクのHP】がリセットされてROUND2が始まればOKです。
- ※この時点では、ROUND1で倒されたタンクが、ROUND2で不死身になります。これは後で直します。

- さて、リセットの処理はうまくいっているようですが、【一度復活したタンクが、不死身になってしまう】という変なバグが発生してしまいました。以下の感じです。
リセット処理

- このバグは、なぜ起きているのかと【復活したタンクは、TankExplosion(爆発のエフェクト)が削除されているから】です。
- そういえば、タンクの子オブジェクトにつけていたTankExplosion(爆発のエフェクト)はタンクが爆発した時に、子オブジェクトから外れて、その後削除するようにしていたのでした。
- ※ここから先【TankExplosion(tankExplosion)】という言葉が大量に出ますので、どの【TankExplosion】か注意してください。
- Hierarchyビューを見ると、【TankExplosion】オブジェクトが消えているのがわかります。
リセット処理
上のタンクには【TankExplosion】オブジェクトがあるが、下のタンクにはなくなっています。

- 問題となっているコードは【TankHealth】スクリプトの【TakeDamage】メソッドの中の以下のコードです。
リセット処理
- タンクと同じように、爆発のエフェクト(エミッター)も削除されると困るので、ちょっと作り方を変えます。
- 爆発のエフェクトは、子オブジェクトにするのではなく、【ゲーム開始されたら、それぞれのタンクの爆発エフェクトのオブジェクトを作り、そのオブジェクトを自分の位置で再生(.Play)させる】という考え方で作ります。
- 上のコードは不要になるので、消すか、コメントアウトしてください。

- 次に、Tankプレハブの子オブジェクトの【TankExplosion】も、子オブジェクトとしては使いませんので外しておきます。まずは、プレハブを確認してください。
- 元々の【TankExplosion】プレハブ(下の赤枠)を、もし削除している場合は、Tankプレハブから取り出して使います。
- 削除しておらず、【TankExplosion】プレハブが残っている場合は、子オブジェクトの【TankExplosion】は削除します。
リセット処理
このように【TankExplosion】が2つあれば、Tankの子オブジェクトの方は削除する。
1つしかなければ、Tankの子オブジェクトのやつを親オブジェクトから外して使う。
削除方法は次のページ。

- プレハブの子オブジェクトは、削除や、親オブジェクトから外すことができないので、一旦オブジェクトにしてから子オブジェクトを削除か、親オブジェクトから外し、再度プレハブ化してください。
- 親オブジェクトから外した場合は、外した【TankExplosion】オブジェクトもプレハブにし、元のオブジェクトは削除してください。
- ※【TankExplosion】をすべて削除しないよう気をつけてください。
- 最終的に以下の感じです。
リセット処理

リセット処理
- ここまで終われば【GameManager】オブジェクトの、Inspectorビューの【TankPre】変数の中身が空になっていますので、新しく作り直したTankプレハブを入れておいてください。
- また、新しいTankプレハブの【tankExplosion】変数には、プレハブの【TankExplosion】を入れておきましょう。


- 次に、あらためて【TankExplosion】プレハブをオブジェクトにし、それを使う設定をします。これは、すべてスクリプトから行います。
- 【TankHealth】スクリプトの【Start()】メソッドの中に、以下のコードを追加してください。
リセット処理
- このコードは、まずお馴染みの【Instantiate()】で【tankExplosion】変数に入っている【TankExplosion】プレハブをオブジェクトとして作り、そのまま【tankExplosion】変数を上書き(代入)しています。
- ちょっと特殊な書き方ですが、動きとしては【tankExplosion変数の中身を、プレハブからオブジェクトに変更する】といった処理です。(この処理は、結構いろんな時に使えます)

- これで、試してみましょう。
- ゲームをスタートすると、下のように【Tank】オブジェクトと、同じ数の【TankExplosion】オブジェクトが出来るはずです。
リセット処理

- ただ、ゲームをプレイしてみると、爆発していないようにみえると思います。これは、爆発の位置がタンクと違う場所になってしまっているからです。※動画ではわかりにくいですが、画面端で爆発してると思います。

- 爆発の位置を正しくしてください。
- ヒント:爆発するのは【tankExplosion.Play()】が実行された時ですね。その直前に【tankExplosion】の位置(position)を、タンクの位置と同じにすれば良いですね。
課題⑩

- 後は、【ゲームが終わった時】のリセット処理だけです。つまり、どちらかが5勝したら【ラウンド数】【タンクの勝利数】だけをリセットだけです。
- すでに【EndMessage()】コルーチンの中に、【勝利数が5になれば】という判定は作っているはずですので、ここで処理させれば簡単ですね。
リセット処理
-
どちらかが5勝したら、【ラウンド数】【タンクの勝利数】がリセットされるようにしてください。
- ヒント:【EndMessage()】コルーチンの中の、【勝利数が5になれば】というif文の中に作りましょう。【roundNumber】は、ただ0を代入するだけで良いですが、勝利数(wins変数)はそれぞれのタンクが管理していますので、それぞれのタンクの勝利数をすべて0にする必要があります。
- これはすべてのタンクをリセットする処理(tanks[i].Reset())をヒントに作ってください。
課題⑪
すべてのタンクの勝利数が0にリセットされることも確認して起きましょう。
- 最初に考えたアレンジ案を、何か1つ実装してみましょう。
- ※アレンジ案を忘れた場合は言ってください。
課題⑫
- これでTanks!ゲームは完成です。
お疲れさまです
Tanks!⑥
By kinocode
Tanks!⑥
- 1,140