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ビューから切り替えることが可能です。

アクティブ・非アクティブ

チェックが入っていたらアクティブ、入っていなければ非アクティブの状態

  • もちろんアクティブ状態は、スクリプトから変更も可能ですので、【タンクオブジェクトの削除( 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】でしたね。
  • ​​上記の変更がうまくいくと、まだゲームの動きは変わりませんが、コンソールのエラーはなくなるはずです。
  • では、リセットの処理に仕上げましょう。これが最後のステップです。
  • まず、ラウンドが始まる時にリセットをさせたいので、【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!ゲームは完成です。

お疲れさまです