以前の記事で銃から物理的な弾を飛ばす機能を作成しました。
物理的な弾を他の物理的なゲームオブジェクトに衝突させた場合、
↑のように個別のゲームオブジェクトとそれに接触している物体に力が加わりますが、その周りのゲームオブジェクトには影響を与えません。
今回の記事ではさきほど紹介した記事のように銃から物理的な弾を飛ばし、弾がゲームオブジェクトに当たった時に周りのゲームオブジェクトにも力を加えるようにしたいと思います。
例えば手榴弾を投げた時に爆発でその周りの敵にも力を加えて飛ばすという時に使用します。
的になるブロックを配置する
まずはゲームオブジェクトを作成していきます。
ヒエラルキーで右クリック→Create Emptyを選択し、名前をCubeFolderとします。
その子要素に右クリック→3D Object→Cubeを作成します。
また新しくTagを作成し名前をBlockとします。
Tagの作成方法はゲームオブジェクトのインスペクタ名の下にあるTagの部分をクリックし、Add Tagで追加する事が出来ます。
追加したらTagをBlockに設定します。
Cubeを作成したらAdd Component→RigidbodyをしてRigidbodyを追加します。
物理的な力を加えたい場合はRigidbodyが取り付けられている必要があります。
↑がCubeのインスペクタになります。
ここまで出来たらCubeをCtrl+Dで複製し、少しづつ位置をずらして設置します。
↑のように横に4列とその上に4列並べました。
また通常のCharacterControllerで動くキャラクターも一人配置しておきました。
キャラクターのTagにはEnemyを設定してください、ない場合は追加して設定してください。
今回はサンプルなのでキャラクターにはCharacterControllerを取りつけ、コライダの調整を済ませておきます。
特別なスクリプトやAnimatorの設定等はしていません。
さきほどCubeにはRigidbodyコンポーネントを追加しましたが、キャラクターには追加しません。
キャラクターにはスクリプトからRigidbodyを追加するようにしてみます。
Rigidbodyについて
さて、ここで少しRigidbodyについて触れておきます。
Massは物体の質量です。
わたくしはあまりRigidbodyを使わないので常にデフォルトの1のまま使っていますが、質量を考えた処理をしたい場合、このMassの値を変更する必要があります。
Dragは空気抵抗です。
Angular Dragは力が加えられた時の回転の空気抵抗です。力によって回転が起きた時にこの値を大きくしておくと回転がすぐに止まります。
Use Gravityは重力を働かせるかどうかです。Rigidbodyが取りつけられたゲームオブジェクトはこのチェックを入れると重力が勝手に働いてくれる為スクリプトで重力値を計算する必要がありません。
Is KinematicはスクリプトでRigidbodyを操作したい時にチェックを入れるのだと思う・・・(^_^;)
ここにチェックを入れると物理的な処理がされません。
Interpolateは動きの補間についての設定です。
Collision Detectionは高速に移動するゲームオブジェクトの衝突でゲームオブジェクト同士が食い込んでしまう、すり抜けてしまうといった場合に設定します。
問題が出ない時は特に設定しなくていいかも?
ConstraintsはRigidbodyを設定したゲームオブジェクトの位置の移動や回転をさせるかどうかの設定です。
キャラクターをRigidbodyで動かす場合FreezePositionのYとFreezeRotationのX、Zはチェックを入れた方がいいかもしれません。
ただし、外部からの力でキャラクターを飛ばしたい場合位置の移動や回転をしない設定にしていると飛ばないので、
力を加える時にこれらの設定を変更するというやり方が必要になると思います。
Rigidbodyの項目については詳しく見るつもりはなかったんですが・・・(^_^;)
まぁ・・・自分の勉強になっていいかも(^。^)
それでは、さきほど作った最初のCubeだけMassを100に設定してください。
あとで力を加えた時に他とどう変わるかがわかります。
飛ばす弾が何かに当たった時の処理AddPowerToOtherObjectスクリプトを作成
次に物理的な弾がCubeやキャラクターに当たった時に力を加えるスクリプトAddPowerToOtherObjectを記述していきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | using UnityEngine; using System.Collections; public class AddPowerToOtherObject : MonoBehaviour { // 与える力 [SerializeField] private float power = 100; // 力を加える物体までの半径 [SerializeField] private float radius = 4; // 力の加え方 [SerializeField] private ForceMode forceMode = ForceMode.Force; // 物理的な衝突を検知したら void OnCollisionEnter(Collision col) { // 衝突したゲームオブジェクトのタグがEnemyまたはBlockでない時は以降の処理は行わない if (col.gameObject.tag != "Enemy" && col.gameObject.tag != "Block") { return; } // 弾が衝突した位置を中心に半径radius以内にあるコライダを取得 var colliders = Physics.OverlapSphere(transform.position, radius); // 拡張for文でコライダを順に取りだす foreach (var collider in colliders) { // タグがBlockだったらそのゲームオブジェクトのRigidbodyを取得しAddExplosionForceで力を加える if(collider.gameObject.tag == "Block") { collider.GetComponent<Rigidbody>().AddExplosionForce(power, transform.position, radius, 0f, forceMode); // タグがEnemyだったらRigidbodyがなければ追加し力を加える } else if(collider.gameObject.tag == "Enemy") { // CharacterControllerを無効化(CharacterControllerを使っていると仮定) collider.gameObject.GetComponent<CharacterController>().enabled = false; var rigidbody = collider.gameObject.GetComponent<Rigidbody>(); if(rigidbody == null) { rigidbody = collider.gameObject.AddComponent<Rigidbody>(); // キャラクターのConstraintsを設定 // rigidbody.constraints = RigidbodyConstraints.FreezePositionY | RigidbodyConstraints.FreezeRotationX| RigidbodyConstraints.FreezeRotationZ; } var capsuleCollider = collider.gameObject.GetComponent <CapsuleCollider> (); if (capsuleCollider == null) { // 物理的な影響を与える為にCapsuleColliderも取り付ける var capCol = collider.gameObject.AddComponent<CapsuleCollider>(); // コライダのサイズ調整 capCol.center = new Vector3(0f, 0.9f, 0f); capCol.radius = 0.3f; capCol.height = 1.6f; capCol.isTrigger = false; } rigidbody.AddExplosionForce(power, transform.position, radius, 0, forceMode); } } // 弾のゲームオブジェクトを消す Destroy(gameObject); } } |
少しづつ見ていきます。
1 2 3 4 5 6 7 8 9 10 11 | // 与える力 [SerializeField] private float power = 100; // 力を加える物体までの半径 [SerializeField] private float radius = 4; // 力の加え方 [SerializeField] private ForceMode forceMode = ForceMode.Force; |
↑のようにインスペクタで設定出来るフィールドを宣言します。
ForceModeは力の加え方で
Forceは質量を使用して力を加えます。
Accelerationは質量の事は考えず加速度を加えます。
Impulseは質量を使用して力を加えます。Impulseは爆発等をシミュレートする時に使用し飛び散る感じの力が加わります。
VelocityChangeは質量を無視して加速度を加えるImpulseという感じです。
細かい所はわからないので実際に試して確認するしかないですね・・・(^_^;)
1 2 3 4 | // 弾が衝突した位置を中心に半径radius以内にあるコライダを取得 var colliders = Physics.OverlapSphere(transform.position, radius); |
Physics.OverlapSphereは中心位置と半径を指定して、その球の範囲にあるコライダを取得します。
transform.positionは弾の位置でそこを中心に半径radiusで仮想の球を作りその範囲内にあるコライダを探します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | // 拡張for文でコライダを順に取りだす foreach (var collider in colliders) { // タグがBlockだったらそのゲームオブジェクトのRigidbodyを取得しAddExplosionForceで力を加える if(collider.gameObject.tag == "Block") { collider.GetComponent<Rigidbody>().AddExplosionForce(power, transform.position, radius, 0f, forceMode); // タグがEnemyだったらRigidbodyがなければ追加し力を加える } else if(collider.gameObject.tag == "Enemy") { // CharacterControllerを無効化(CharacterControllerを使っていると仮定) collider.gameObject.GetComponent<CharacterController>().enabled = false; var rigidbody = collider.gameObject.GetComponent<Rigidbody>(); if(rigidbody == null) { rigidbody = collider.gameObject.AddComponent<Rigidbody>(); // キャラクターのConstraintsを設定 // rigidbody.constraints = RigidbodyConstraints.FreezePositionY | RigidbodyConstraints.FreezeRotationX| RigidbodyConstraints.FreezeRotationZ; } var capsuleCollider = collider.gameObject.GetComponent <CapsuleCollider> (); if (capsuleCollider == null) { // 物理的な影響を与える為にCapsuleColliderも取り付ける var capCol = collider.gameObject.AddComponent<CapsuleCollider>(); // コライダのサイズ調整 capCol.center = new Vector3(0f, 0.9f, 0f); capCol.radius = 0.3f; capCol.height = 1.6f; capCol.isTrigger = false; } rigidbody.AddExplosionForce(power, transform.position, radius, 0, forceMode); } } |
foreach文では配列等の複数の値を持つ変数を一時変数に代入し使用します。配列の最後まで来たら終了になります。
今回の場合はcollidersがある限り、一時変数であるcolliderに値を代入して繰り返しています。
衝突したゲームオブジェクトのタグがBlockの時はそのコライダのゲームオブジェクトに取りつけてあるRigidbodyを取得しAddExplosionForceで力を与えます。
AddExplosionForceは
1 2 3 | AddExplosionForce(加える力, 中心位置, 半径, 物体を持ち上げた時の調整値?, 力の加え方); |
です。
第4引数がいまいちわかりません・・・・。
衝突したゲームオブジェクトのタグがEnemyだった時はCapsuleColliderとRigidbodyを取りつけます。
1 2 3 4 | // CharacterControllerを無効化(CharacterControllerを使っていると仮定) collider.gameObject.GetComponent<CharacterController>().enabled = false; |
↑のようにキャラクターに取りつけてあるCharacterControllerを無効化します。
飛んだ後にまたCharacterControllerの機能を使いたい場合は無効化にしておいた方がいいですね。
スクリプトでコンポーネントを取りつけるには
1 2 3 | ゲームオブジェクト.AddComponent<取りつけるコンポーネント>(); |
を使います。
今回はキャラクターはくるくる回るだけにしたいので位置はY座標を固定、回転はX座標とZ座標を固定させます。
1 2 3 | rigidbody.constraints = RigidbodyConstraints.FreezePositionY | RigidbodyConstraints.FreezeRotationX| RigidbodyConstraints.FreezeRotationZ; |
RigidbodyのConstraintsの設定はそれぞれの項目をtrueやfalseで設定するのではなくビット演算で行うようです。
今回の場合はRigidbodyのConstraintsのFreezePositionYとFreezeRotationX、FreezeRotationZにチェックを入れたいので
論理和を計算しています。
これでスクリプトが完成しました。
弾であるBulletにAddPowerToOtherObjectスクリプトを取りつけ、
↑のようにインスペクタでpower、radius、modeを設定してください。
ブロックやキャラクターに弾を当てて機能が完成しているか確認
Unityの実行ボタンを押して、Cubeやキャラクターに弾を当ててみましょう。
↑が実行結果です。
どれかのブロックに弾を当てると他のブロックやキャラクターにも力が加わっているのがわかります。
キャラクターにはRigidbodyが取り付けられ位置や回転が固定されている為クルクル回る感じになります。
他のブロックと同じように飛ばしたい場合はRigidbodyのConstraintsの設定を削除してください。
左下がRigidbodyのMassを100にしたCubeです。
どれだけ弾を当てても動きが鈍いですね。
BulletAttackのスクリプトでConstraintsの設定部分をコメント化し、インスペクタでmodeをImpulseにして確認してみましょう。
↑のように爆発する感じになりました。
キャラクターはあまり飛ばなかったですね・・・・。
今回のようにキャラクターにCapsuleColliderやRigidbodyをスクリプトから取りつけCharacterControllerを無効化したのはサンプルの為で、
すぐにゲームのキャラクターに使える!という感じにはなっていません。
キャラクターを飛ばした後に再度普通に動く敵キャラに戻すにはいろいろやる事がありそうです。
キャラクターをRigidbodyとCapsuleColliderで動かしている場合はそもそもスクリプトからコンポーネントを取りつける必要もありませんし、CharacterControllerの無効化も必要ありません。
CharacterControllerを使ってキャラクターを動かしている場合はRigidbodyを取り付けてないので物理的な力を加える時は必要になります。
敵キャラの面倒くさそうな処理をしたくない場合は爆発で一発でやられるようにしておくのがいいかもしれません。(^_^;)
今回の内容はFPSゲーム等の手榴弾の爆発とかそういった場面で活躍しそうですね。
今回の記事の作成にはUnityのライブトレーニング
を参考にさせて頂きました。