Unity入門13

Tanks!④

(タンク)

  • 前回までで、カメラと体力ゲージが出来ました。
  • 次はダメージ判定を作っていきましょう。

前回の確認

  • まず、このゲームでのダメージ処理を確認しておくと、単純にShell】に当たった時だけでなく、【爆風の範囲】にいるすべてのタンク(自分含む)がダメージをくらうようになっています。
    • ​また、その爆発の中心に近ければ近いほど、ダメージが大きくなっています。
    • ​例えば、弾自体にあたる→ダメージ50、弾は避けたが、近くで爆発した→ダメージ20のように計算されています(普通に遊んでいるとわかりにくいですが)
  • ​つまり【①爆風範囲にいるタンクを調べる】→【②タンクごとの距離から、それぞれに与えるダメージを計算する】→【③それぞれのタンクにダメージを反映させる
  • ​大雑把にわけると以上の処理が必要になります。③に関してはタンクごとでの処理が必要になるため、以下のように弾側とタンク側で処理を分けて行います。
    • 弾側(Shell)での処理:
      • 【①爆風範囲にいるタンクを調べる
      • 【②タンクごとの距離から、それぞれに与えるダメージを計算する(結果をタンクに渡す)
    • タンク側(TankTank(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】にBoxColliderMeshColliderをアタッチしているからです。※説明のために追加しただけです。
    • このように同じオブジェクトでも、コライダーごとに取得されます。次へ。

爆風判定を調べる

  • ​【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();】で、すでに取得できていますので、colliders配列の要素の1つ1つが、爆発範囲内のオブジェクトですね。
    • ​※【Collider型】ですが、オブジェクトと同じように【 .GetComponent<Rigidbody>()】をつければ、そのオブジェクトのRigidbodyが取得できます。
  • このオブジェクトのRigidbodyに対して【Rigidbodyコンポーネント.AddExplosionForce()】というように使います。
    • これは「力を加えてオブジェクトを動かす」という処理の【Rigidbodyコンポーネント.AddForce()】などと同じ使い方です。

爆風の力を加える

  • 下の画像の黒枠を埋めて、【爆風でオブジェクトを動かす】処理をつくってください。
    • ​条件:爆風の最大値は、とりあえず500−1000程度にしておきましょう。

課題①

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

ダメージを与える

  • では、実際にやっていきましょう。
  • まずは、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!⑤ 」に続く。

Tanks!④

By kinocode

Tanks!④

  • 1,384