Unity入門13
Tanks!④
(タンク)
- 前回までで、カメラと体力ゲージが出来ました。
- 次はダメージ判定を作っていきましょう。
前回の確認
- まず、このゲームでのダメージ処理を確認しておくと、単純に【Shell】に当たった時だけでなく、【爆風の範囲】にいるすべてのタンク(自分含む)がダメージをくらうようになっています。
- また、その爆発の中心に近ければ近いほど、ダメージが大きくなっています。
- 例えば、弾自体にあたる→ダメージ50、弾は避けたが、近くで爆発した→ダメージ20のように計算されています(普通に遊んでいるとわかりにくいですが)
- つまり【①爆風範囲にいるタンクを調べる】→【②タンクごとの距離から、それぞれに与えるダメージを計算する】→【③それぞれのタンクにダメージを反映させる】
-
大雑把にわけると以上の処理が必要になります。③に関してはタンクごとでの処理が必要になるため、以下のように弾側とタンク側で処理を分けて行います。
- 弾側(Shell)での処理:
- 【①爆風範囲にいるタンクを調べる】
- 【②タンクごとの距離から、それぞれに与えるダメージを計算する(結果をタンクに渡す)】
-
タンク側(TankやTank(1)など)での処理:
- 【③ダメージを反映させる(それぞれのタンクが個別に行う)】
- 弾側(Shell)での処理:
- このように処理をわけて行います。
爆風判定を調べる
- まずは、弾側から作っていきましょう。これは【ShellExplosion】の中に追加で作ります。次へ。
- 追加する場所はもちろん【別のオブジェクト(コライダー)の当たった時】です。
- すでに「親子関係を解除」する処理などが記載されていると思いますが、その上に追加してください。
- この1行が【①爆風範囲にいるタンクを調べる】部分の処理になります。
- 【Physics.OverlapSphere()】というメソッドを実行し、その結果を【Collider[]】型の配列である【colliders】に代入しています。
-
この【Physics.OverlapSphere()】というのは、【第一引数を中心に、第二引数分の半径にあるコライダーをすべて調べてくれる】メソッドです。
- つまり【爆風範囲のコライダー】を調べてくれるメソッドというわけです。それを【colliders】配列に代入しています。
- 次に、第二引数部分の【explosionRadius】は、まだ作っていないですが、ただの変数です。
- この変数はfloat型の数値で、さらにInspectorビューからイジれるようにpublicで作ってください。代入する数字は、5.0fにしておいてください。

爆風判定を調べる
- ここまで出来たら、実際に弾を打って確かめてみましょう。
-
下のように、for文と、Debug.Log()を追加してください。
- ※すべての弾で処理が動きますので、Tank以外は弾が出ないようにしておいた方がわかりやすいです。次へ。

爆風判定を調べる
- 以下のように、爆風範囲のコライダーが取得できたと思います。
タンクの体力

- 上の動画では、弾は地面に着弾してますが、複数のコライダー(【Helipad(ヘリパッド)】【Terrain(地形)】【Tank(2)(画面左のタンク)】【Shell(弾)】)が取得されています。
- つまりは爆風範囲に、これらのオブジェクトがいる、ということです。
- 尚、上の例では【Helipad】が2つ表示されていますが、これは【Helipad】にBoxColliderとMeshColliderをアタッチしているからです。※説明のために追加しただけです。
- このように同じオブジェクトでも、コライダーごとに取得されます。次へ。
爆風判定を調べる
- 【Physics.OverlapSphere()】を使えば非常にカンタンに【爆風範囲】のオブジェクトが取得出来るのですが、今回必要なのは【爆風範囲のすべてのオブジェクト】ではなく、【爆風範囲のタンク】だけですね。
-
安心してください。【Physics.OverlapSphere()】は取得する対象を制限する機能もあります。
- 第三引数に【取得対象のLayerMask(レイヤーマスク)】というものを設定して制限します。
- このLayerMask(レイヤーマスク)とは、Layer(レイヤー:層)Mask(マスク:かぶせる)という意味で、特定のLayerに対してのみ指定する際に使います。
- Layerというのが言葉がわからない場合は、とりあえずココでは、グループみたいなものと考えといてOKです。
- 以下のように【LayerMask】型の変数を作ります。これはInspectorビューからイジれるようにしておきます。

- 次にTankのInspectorビューをみてください。次へ。
爆風判定を調べる
- 以下の場所のLayerがありますね。

- ここで、それぞれのオブジェクトに対するLayerを設定できます。
-
クリックして【Players】に変更してください。
- 以下の確認ウィンドウが開くと思いますが、これは「子オブジェクトのLayerも変更するか?」という確認です。特に必要ないので、一番左の【No,this object only】(子オブジェクトは変更しない)を選択してください。

爆風判定を調べる
-
これでTankオブジェクトにLayerの設定ができました。
- 他のタンクにも同じようにしておきましょう。
- 次に【Shell】プレハブのInspectorビューから、先ほど作った【tankMask】変数を設定します。
- 【Nothing】になっているところをクリックすると、Layerが選べるので【Players】を選択します。
- 後は【Physics.OverlapSphere()】の第三引数に、この【tankMask】を指定します。

- これで再度試してみましょう。次へ。
爆風判定を調べる
- タンク(例ではTank(2))だけが取得できましたね。

- これで【①爆風範囲にいるタンクを調べる】は完成です。
爆風判定を調べる
- そういえば、Layerと似た機能としてTagというものもありましたね。
LayerとTag

- 両方ともオブジェクトを【グループにわける】ような使い方をしますので、違いがわかりにくいと思いますが、Unityでは、以下のように使い分けされます。
-
Tag:
- 単純にプロジェクト内(ゲーム内)のグループ分けや、識別のための印として使う。※いくらでも増やせる。
-
Layer:
- Uniyt全体(複数のゲームを含めて)での、特定の役割ごとのグループ分けに使う。※最大32個(0-31)までしか作れない。
-
Tag:
- まぁ「こう使い分けてね」ってだけの話なので、どちらでも似たようなことは出来ます。
- 覚えておきたいポイントは、今回のように「何かしらの演算をするメソッド」が、【演算の対象グループを指定】する時や、【カメラ】【ライト】などの対象グループの指定には、Layerが使われます。
- 反対にTagは、if文で条件分岐をしたい場合に、そのオブジェクトの識別条件として使われることがほとんどです。
- では、次は【爆風でオブジェクトを動かす】部分の処理を作りましょう。
- 先ほど追加したfor文はそのままで【Debug.Log(colliders[i]);】の部分だけ削除しておきましょう。
- この処理もよく使われるので、【AddExplosionForce()】というメソッドで用意されています。
-
【AddExplosionForce()】は、
- 第一引数に、爆風の最大値。
- 第二引数に、爆風の中心点。
-
第三引数に、爆風の範囲(半径)
- を設定すると、爆風の中心点からの力を計算し、対象のオブジェクトを動かしてくれます。
- 使い方は、対象のオブジェクトの【Rigidbody】のメソッドとして使います。
-
対象のオブジェクトは、さきほどの【Collider[] colliders = Physics.OverlapSphere();】で、すでに取得できていますので、colliders配列の要素の1つ1つが、爆発範囲内のオブジェクトですね。
- ※【Collider型】ですが、オブジェクトと同じように【 .GetComponent<Rigidbody>()】をつければ、そのオブジェクトのRigidbodyが取得できます。
-
このオブジェクトのRigidbodyに対して【Rigidbodyコンポーネント.AddExplosionForce()】というように使います。
- これは「力を加えてオブジェクトを動かす」という処理の【Rigidbodyコンポーネント.AddForce()】などと同じ使い方です。
爆風の力を加える
- 下の画像の黒枠を埋めて、【爆風でオブジェクトを動かす】処理をつくってください。
- 条件:爆風の最大値は、とりあえず500−1000程度にしておきましょう。
課題①

- 次は、実際にダメージを与えられるようにしましょう。
- 今回、ダメージの処理は以下のように3ステップになります。
- 【ShellExplosion】スクリプトに【ダメージの値を計算をするメソッド】を用意する。
- 【TankHealth】スクリプトに【ダメージを減らす、および0以下になったらやられるメソッド】を用意する。
- 上記のメソッドを組み合わせ、爆風範囲のオブジェクトごとにダメージをあたえる。
ダメージを与える
- では、実際にやっていきましょう。
- まずは、【ShellExplosion】スクリプトにダメージ計算用のメソッドを用意します。名前は【CalcDamage】(カルクダメージ)にしておきます。

- 引数に【Vector3】型の引数【targetPosition】を用意しておきます。
- これはダメージをあたえる対象のオブジェクトの位置を渡すための引数です。
- 次に、以下のように入力してください。
ダメージを与える
- 上の行で、【Vector3】型の【explosionToTarget】という変数を作り、その中に【 targetPosition(引数)- transform.position(弾の座標)】を代入していますね。
- 引数【targetPosition】は、ダメージをあたえるオブジェクトの座標でした。それから弾自身の座標をひくことで、その差を【explosionToTarget】変数に代入しています。
-
その下の行で、【.magnitude】(マグニチュード)というメソッドが出てきています。これは、Vector(ベクトル)から【力】だけを計算して、float型で返してくれるメソッドです。
- 以前にも言いましたが、ベクトルは単純に【座標】を表すだけではなく、その座標に対する【向き】と【力】というプロパティも持ちます。
-
今回のように【targetPosition - transform.position】とした場合、その距離が離れているほど【力】は強くなります。
- つまり、変数 【explosionDistance】(エクスプロージョンディスタンス)の値が増えるということです。次へ。

- 次に、以下のコードを追加してください。
ダメージを与える
- 追加コードの1行目では【relativeDistance】(レラティブディスタンス)という変数に、先ほどベクトルから取り出した【力】を使った計算をしています。【explosionRadius】は【爆風範囲】の半径でしたね。
- この爆発範囲から先ほどの【力】を引くことで、【弾までの距離が離れていれば小さく、近ければ大きく】なります。
- 最後の【 / explosionRadius 】は半径分を割ることで半径の大きさによる差異をなくしています。
- 以上の計算で、爆発ダメージの係数が【relativeDistance】に代入されることになります。
- 下の行は、上で出した爆発ダメージの係数に100をかけて、実際のダメージを出しています。
- 係数は最大で1なので、理論上の最高ダメージが【100】ということになります。
- ただ、弾が直撃したとしても、オブジェクトの外枠で爆発するので、係数は1にはならず、最大でも80弱ぐらいのダメージが最高になると思います。
- ※弾が地面に落ちて爆発する瞬間に、その地点に移動すると100になる可能性はあります。

- 最後に以下を追加します。
ダメージを与える

- 【damage = Mathf.Max(0f, damage)】で、【damage変数と、0を比べて、大きい方をdamage変数に代入】しています。
- これは、爆風範囲の設定によっては、ダメージの値がマイナスになってしまう可能性があるので、もしマイナスになってしまう場合は0にする、という処理です。(マイナスのままだと回復してしまうので)
- まぁ今の設定ではマイナスになることはないのですが、念の為です。
- 最後に【return damage】で、計算した最終的なダメージを、メソッドの呼び出し元に返しています。
- これで【ダメージの値を計算をするメソッド】は完成です。
- 次は、【ダメージを減らす、および0以下になったらやられるメソッド】を作りましょう。
- これは【TankHealth】スクリプトに書いていきます。
- 以下のように新しいメソッドを作ってください。
ダメージを与える
- この【TakeDamage】メソッドは後で【ShellExplosion】スクリプトから呼び出します。そのためpublicをつけておいてください。
- 引数【damage】は、先ほど作った【CalcDamage】メソッドで計算されたダメージを受け取る用の引数です。

- 次に、以下のコードを追加してください。
ダメージを与える
- ここは非常に単純です。
- 現在のHP用の変数【currentHealth】から、計算結果のダメージを引いています。
- その後【SetHealthUI()】メソッドで、体力ゲージにも反映させているだけです。
- これで【HPをダメージ分減らす】という処理が出来ました。(0になったらやられる処理はまだです。)
- このメソッドを【ShellExplosion】スクリプトから呼び出して、確認してみましょう。次へ。

- 【ShellExplosion】から、先ほど作った【TankHealth】スクリプト(クラス)のメソッド【TakeDamage()】を呼び出したいわけですが、今まで別クラスのメソッドを使う場合は【 TankHealth targetHealth = new TankHealth();】として、そのクラスのインスタンス(実態)を作り、【targetHealth.TakeDamage()】で、そのクラスのメソッドを使っていましたが、今回の場合はこの方法ではうまくいきません。
- なぜなら【TankHealth】スクリプトはすべてのタンクにアタッチされていますので、この方法だと、すべてのタンクに同じダメージを与えることになってしまいます。
- 今回は【爆風範囲にいるタンクだけ】にダメージを与える必要があります。
- こういう場合は以下のように書きます。
- 【TankHealth targetHealth = targetRigidbody.GetComponent<TankHealth>();】
- 【targetRigidbody】は、for文の中で【爆発範囲にいたオブジェクトのRigidbody】を入れている変数でしたね。
- Rigidbodyではありますが、【.GetComponent<TankHealth>();】とすることで、そのオブジェクトの【TankHealth】コンポーネント(つまりはTankHealthクラス)を取得することができます。※Colliderと同じですね。
- 後は【targetHealth.TakeDamage()】で、そのオブジェクトの【TankHealth.TakeDamage()】だけを呼び出すことができます。次へ。
ダメージを与える
- 下記の画像を参考に、正しくダメージがあたえられるようにしてください。
-
ヒント:【targetHealth.TakeDamage】メソッドの引数には、【ダメージの計算結果】を渡します。またダメージを計算するメソッド【CalcDamage()】の引数には、【ダメージをあたえる対象のオブジェクトの位置】を渡します。
- ※このコードを「どこに書くのか?」も課題です。
課題②


左のようになればOKです。
- ダメージは出来たので、次はタンクの体力が0以下になったらやられる処理も作ります。
- これは、【TakeDamage】メソッドの中に作ってしまいましょう。次へ。
タンクがやられる処理
- タンクが消えた後は以下のエラーが何度も出るはずですが、これはタンクオブジェクトが減ってしまったことによって、【CameraRig】での処理が狂ってしまうため出るエラーです。後で直すので今は無視でOKです。
課題③
左のようになればOKです。


- まずは単純に【体力が0以下になったらタンクが消える】処理を作ってください。
- ヒント:単純にif文と【Destroy(gameObject); 】です。
課題④
左のようになればOKです。
- タンクがやられると同時に爆発するエフェクトをつけてください。
- ※パーティクルは【 Assets/Prefabs 】の中の【 TankExplosion 】を使ってください。
- ヒント:基本的に弾が爆発して消えるのと同じ処理でOKなので【 ShellExplosion 】スクリプトを参考に作ってください。
- 【TankExplosion】のパーティクルは、最初【Play On Awake】の項目にチェックついてますので、外しておきましょう。

- カメラのエラーはまだ出ますので、気にしないでください。
- これでタンクの処理は完成です。
- 次は、先ほどエラーが出ていたカメラの処理を直したいと思います。
- まず、このエラー原因は【CameraRig】オブジェクトの【CameraControl】コンポーネントで、【Tanks】という配列に、それぞれタンクオブジェクトを代入しているかと思いますが、そのタンクが【Destroy(gameObject)】で消えてしまうために起こるエラーです。
-
具体的なコードで言うと、以前の課題で【CameraControl】スクリプトの中に【tanks[i].position】というコードを2ヶ所に書いていると思いますが、ここでエラーになります。
- この【tanks[i].position】はfor文の中で、すべての配列の要素(タンク)の座標を取得する処理でしたね。
- つまり、【中身の空になった配列の要素】の座標を取得しようとして、【取得できない】とエラーになるわけです。
-
と言っても、このコード自体は問題ではありません。複数のタンクの処理をするためには必要なコードです。
- ではどうするかというと、この【tanks[i].position】が実行される前に【tanks[i]って中身あるの?】という確認をさせれば良い、ということです。次へ。
カメラ処理の修正
- 【tanks[i].position】が使われている場所は【FindRigPosition()】メソッドの中と【FindCameraSize()】メソッドの中のだと思いますが、それぞれ【tanks[i].position】の前(1行上)に以下のコードを追加しましょう。
カメラ処理の修正

- 単純なif文ですね。条件に【 !tanks[i] 】とすることで、【tanks[1]に中身が入っていればfalse、入っていなければ(やられていれば)true】になります。
- つまり、中身が入っていない場合だけ【continue】が実行されることになります。
- この【continue】(コンテニュー)とは【現在のループ処理をスキップし、次のループに進める】という処理です。
- つまり、中身が入っていない場合は、【tanks[i].position】を飛ばして、次のループ処理(iの中身が変わる)に進めるということです。
- これで、エラーは収まると思いますが、実はまだ問題が残っています。次へ。
- 下の動画を見てください。タンクの数が減ってから、カメラの動きがおかしいのがわかりますか?
- 本来は、タンクが3台なら3台の中心座標をカメラの中心にして、タンクが1台やられて2台になったら、その2台の中心座標をカメラの中心に、としないといけません。
- それが、2台になった途端、カメラの中心が狂っています。
カメラ処理の修正

課題⑤
左のようになればOKです。
- カメラの中心を以下のように正しくしてください。
- ヒント:カメラの中心を決めているのは【FindRigPosition()】メソッドです。この中の処理を見直してみましょう。
- 【生きているタンクの数(カメラが捉えるべきターゲットの数)】を数えなければいけませんね。それ用の変数も必要になります。
- ヒント:カメラの中心を決めているのは【FindRigPosition()】メソッドです。この中の処理を見直してみましょう。

お疲れ様でした
- 今回でゲームの基本動作は完成しましたが、まだ少し続きます。
- 次回は【プレイヤー数に応じて、タンクをプレハブから生成させる】【それぞれのタンクの色と操作方法を変える】など、を行っていきます。
- 「 Tank!⑤ 」に続く。
Tanks!④
By kinocode
Tanks!④
- 1,384