Unity入門13
Tanks!④
(タンク)
前回までで、カメラと体力ゲージが出来ました。
次はダメージ判定を作っていきましょう。
前回の確認
まず、このゲームでのダメージ処理を確認しておくと、単純に
【
Shell
】に当たった時だけでなく、【爆風の範囲】にいるすべてのタンク(自分含む)がダメージをくらうようになっています。
また、その爆発の中心に近ければ近いほど、ダメージが大きくなっています。
例えば、弾自体にあたる→ダメージ50、弾は避けたが、近くで爆発した→ダメージ20のように計算されています(普通に遊んでいるとわかりにくいですが)
つまり【①
爆風範囲にいるタンクを調べる
】→【②
タンクごとの距離から、それぞれに与えるダメージを計算する
】→【③
それぞれのタンクにダメージを反映させる
】
大雑把にわけると以上の処理が必要になります。③に関してはタンクごとでの処理が必要になるため、以下のように弾側とタンク側で処理を分けて行います。
弾側(
Shell
)での処理:
【①
爆風範囲にいるタンクを調べる
】
【②
タンクごとの距離から、それぞれに与えるダメージを計算する
(結果をタンクに渡す)
】
タンク側(
Tank
や
Tank(1)
など)での処理:
【③
ダメージを反映させる
(それぞれのタンクが個別に行う)
】
このように処理をわけて行います。
爆風判定を調べる
まずは、弾側から作っていきましょう。これは【
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)までしか作れない。
まぁ「こう使い分けてね」ってだけの話なので、どちらでも似たようなことは出来ます。
覚えておきたいポイントは、今回のように「何かしらの演算をするメソッド」が、【演算の対象グループを指定】する時や、【カメラ】【ライト】などの対象グループの指定には、
Layer
が使われます。
反対に
Tag
は、if文で条件分岐をしたい場合に、そのオブジェクトの識別条件として使われることがほとんどです。
では、次は【爆風で
オブジェクトを動かす】部分の処理を作りましょう。
先ほど追加したfor文は
そのままで【
Debug
.
Log
(
colliders
[
i
])
;
】の部分だけ削除しておきましょう。
この処理もよく使われるので、【
AddExplosionForce()
】というメソッドで用意されています。
【
AddExplosionForce()
】は、
第一引数に、爆風の最大値。
第二引数に、爆風の中心点。
第三引数に、爆風の範囲(半径)
を設定すると、爆風の中心点からの力を計算し、対象のオブジェクトを動かしてくれます。
使い方は、対象のオブジェクトの【
Rigidbody
】のメソッドとして
使います。
対象のオブジェクトは
、
さきほどの【
Collider
[]
colliders
=
Physics
.
OverlapSphere
(
)
;
】で、すでに取得できていますので、
collider
s
配列の要素の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
()
】メソッドです。この中の処理を見直してみましょう。
【生きているタンクの数(カメラが捉えるべきターゲットの数)】を数えなければいけませんね。それ用の変数も必要になります。
お疲れ様でした
今回でゲームの基本動作は完成しましたが、まだ少し続きます。
次回は【プレイヤー数に応じて、タンクをプレハブから生成させる】【それぞれのタンクの色と操作方法を変える】など、を行っていきます。
「
Tank!⑤
」に続く。