今回はUnityのJoint機能を使ってゲーム内の敵にゲームオブジェクトが付くようにしたいと思います。
具体的には動いている敵に主人公がヤリを投げて敵に当たったら敵にヤリがささったままになるようにしたいと思います。
この機能を作成するにはヤリが敵に当たった時にヤリを敵キャラクターのボーンの子要素にするという方法もありますが、
今回はUnityのジョイント機能を使ってヤリを敵に付くようにしたいと思います。
Joint機能でくっつくようにすれば外部から力を加えた時に自然に取れるようにすることも出来ます。
ジョイント機能を使うにはヤリにJointコンポーネント、敵にRigidbodyが取り付けられている必要があります。
Jointコンポーネントは用途に応じていくつかの種類がありますが、今回は一番シンプルなFixedJointコンポーネントを使用します。
主人公が飛ばすヤリを作成する
まずは主人公が飛ばすヤリを作成していきましょう。
今回はUnityの3D ObjectのCubeで疑似的なヤリを作成します。
ヒエラルキー上で右クリック→3D Object→Cubeを選択し、名前をSpearとします。
SpearのScaleを調整しヤリっぽい形にします。
Box ColliderのIs Triggerのチェックが入っていますが、これは後でスクリプトから操作できるようにする為、
現時点では無視してください。
Add Componentを押してPhysics→Rigidbodyを取りつけます。
Use Gravityにチェックを入れヤリに重力が働くようにします。
Collision DetectionをContinuous Dynamicにし、速い移動で他のゲームオブジェクトをすり抜けないようにします。
ヤリが敵と接触した時の処理を行うCollisionEnemyを作成する
Collision Enemyという新しいスクリプトを作成し取りつけます。
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 62 63 64 65 66 67 68 69 70 71 72 73 74 | using UnityEngine; using System.Collections; public class CollisionEnemy : MonoBehaviour { enum Mode { collision, trigger }; // モード [SerializeField] private Mode mode; // FixedJoint private FixedJoint fixedJoint; // 外れる力 [SerializeField] private float breakForce = 200f; // 外れる角度 [SerializeField] private float breakTorque = 200f; // 刺さっているかどうか private bool isSticking; void Start() { if(mode == Mode.collision) { GetComponentInChildren<Collider>().isTrigger = false; } else if(mode == Mode.trigger) { GetComponentInChildren<Collider>().isTrigger = true; } } // 衝突ありの場合 void OnCollisionEnter(Collision col) { if (!isSticking) { JudgeEnemy (col.collider); } } // 接触なしの場合 void OnTriggerEnter(Collider col) { if (!isSticking) { JudgeEnemy (col); } } // 敵かどうか判断しJointの設定をする void JudgeEnemy(Collider col) { if(col.gameObject.tag == "Enemy") { if(fixedJoint == null) { gameObject.AddComponent<FixedJoint>(); fixedJoint = GetComponent<FixedJoint>(); fixedJoint.connectedBody = col.gameObject.GetComponent<Rigidbody>(); fixedJoint.breakForce = breakForce; fixedJoint.breakTorque = breakTorque; fixedJoint.enableCollision = true; fixedJoint.enablePreprocessing = true; isSticking = true; // Rigidbodyの速度を0にし、スリープ状態にして止める GetComponent <Rigidbody>().velocity = Vector3.zero; GetComponent <Rigidbody>().Sleep (); } } else { if(fixedJoint == null) { Destroy(gameObject); } } } // ジョイントが解除された時に呼ばれる void OnJointBreak() { isSticking = false; } } |
1 2 3 4 5 6 | enum Mode { collision, trigger }; |
↑のようにCollisionとTriggerのモード状態を作成し、ヤリを他のオブジェクトと衝突させるか、侵入だけを行うかを指定出来るようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // モード [SerializeField] private Mode mode; // FixedJoint private FixedJoint fixedJoint; // 外れる力 [SerializeField] private float breakForce = 200f; // 外れる角度 [SerializeField] private float breakTorque = 200f; // 刺さっているかどうか private bool isSticking; void Start() { if(mode == Mode.collision) { GetComponentInChildren<Collider>().isTrigger = false; } else if(mode == Mode.trigger) { GetComponentInChildren<Collider>().isTrigger = true; } } |
モードをインスペクタで設定できるようにし、それによってコライダのIs Triggerの設定を変更するようにします。
CollisionであればIs Triggerのチェックを外し、TriggerであればIs Triggerのチェックを入れます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // 衝突ありの場合 void OnCollisionEnter(Collision col) { if (!isSticking) { JudgeEnemy (col.collider); } } // 接触なしの場合 void OnTriggerEnter(Collider col) { if (!isSticking) { JudgeEnemy (col); } } |
衝突ありとなしはほぼ同じ処理なので衝突ありの処理だけ見ていきます。
衝突ありの場合ですでにヤリが敵に刺さっていない状態であればJudgeEnemyメソッドにコライダを引数として渡し呼び出します。
JudgeEnemyメソッドはコライダを引数として受け取るメソッドで、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // 敵かどうか判断しJointの設定をする void JudgeEnemy(Collider col) { if(col.gameObject.tag == "Enemy") { if(fixedJoint == null) { gameObject.AddComponent<FixedJoint>(); fixedJoint = GetComponent<FixedJoint>(); fixedJoint.connectedBody = col.gameObject.GetComponent<Rigidbody>(); fixedJoint.breakForce = breakForce; fixedJoint.breakTorque = breakTorque; fixedJoint.enableCollision = true; fixedJoint.enablePreprocessing = true; isSticking = true; // Rigidbodyの速度を0にし、スリープ状態にして止める GetComponent <Rigidbody>().velocity = Vector3.zero; GetComponent <Rigidbody>().Sleep (); } } else { if(fixedJoint == null) { Destroy(gameObject); } } } |
衝突した相手がEnemyタグを持つゲームオブジェクトだった時にヤリにfixedJointが設定されていなければ
FixedJointコンポーネントをヤリに取りつけます。
そのあとconnectedBodyに衝突した敵のRigidbodyを設定します。
Joint系のコンポーネントはconnectedBodyに相手方のRigidbodyを設定すると相手方に付くようになります。
このくっついている状態を解除する為に設定するのが
breakForce(力)とbreakTorque(回転)です。
今回のスクリプトではインスペクタでこれらの値を設定することが出来ます。
インスペクタでInfinityと設定すればどんだけ力や回転が加わってもヤリが敵から外れることはありません。
また、スクリプトでInfinityを設定したい時は
Mathf.Infinity
と設定します。
インスペクタでは
Infinity
と書きます。
またヤリが敵以外と衝突した時にfixedJointがヤリに取り付けられていない、つまりヤリを投げたけど敵じゃない他のゲームオブジェクトに接触した時は
ヤリを削除しています。
ヤリが敵に付いている時はfixedJointがnullではないはずなのでここで削除はされません。
敵にヤリが当たった時は与えていた力を無効化したいのでRigidbodyのvelocityを0にし、Sleepメソッドを呼び出してスリープ状態にします。
これでヤリのゲームオブジェクトの作成が終わったので、ゲームオブジェクトをAssetsフォルダにドラッグ&ドロップしてプレハブ化しておきます。
ヒエラルキー上のSpearは削除するか非表示にしておいてください。
主人公がヤリを投げる場所を作成する
次に主人公キャラがヤリを出現させる場所を作成しましょう。
↑のように主人公キャラクターの子要素にCreate Emptyで空オブジェクトを作成しSpearPointという名前にします。
Localにして、SpearPointの青矢印が前方を向くように調整します。
↑のように主人公より少し前の位置からヤリを出現させるようにします。
キャラクターにはThrowSpearという新しいスクリプトを作り取りつけます。
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 | using UnityEngine; using System.Collections; public class ThrowSpear : MonoBehaviour { [SerializeField] private GameObject spearPrefab; [SerializeField] private float power = 1000f; [SerializeField] private Transform spearPoint; // 削除するまでの時間 [SerializeField] private float deleteTime = 10f; void Update () { if(Input.GetKeyDown("t")) { // ヤリをインスタンス化し前方に飛ばす var spearInstance = Instantiate<GameObject>(spearPrefab, spearPoint.position, spearPoint.rotation); spearInstance.GetComponent<Rigidbody>().AddForce(spearInstance.transform.forward * power); // 削除時間を超えたら自動で削除 Destroy (spearInstance, deleteTime); } } } |
spearを宣言しインスペクタでヤリのプレハブを設定します。
キーボードのTキーが押されたらヤリオブジェクトをSpearPointの位置と角度の場所に生成し、
ヤリオブジェクトのRigidbodyを取得しヤリが向いている方向に力を加えます。
敵キャラクターの作成
次に敵キャラクターを作成します。
敵キャラクターのAnimatorにはIdle(ただ立っている状態)とWalk(歩いている状態)を作り、アニメーションパラメータのSpeedが0.1以上になったらWalk状態に遷移するようにしてあります。
↑が敵キャラクターのインスペクタです。
Rigidbodyを取りつけIs Kinematicにチェックを入れます。
敵キャラクターはCharacterControllerの機能を使って動かしますので、CharacterControllerを取りつけてコライダのサイズを調整してください。
敵キャラクターを移動させるスクリプトJointZombieControllerは以下のようになります。
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 | using UnityEngine; using System.Collections; public class JointZombieController : MonoBehaviour { private CharacterController cCon; // 移動するポイント [SerializeField] private Vector3[] points; // どのポイントに移動しているか private int moveTowards; // 移動速度 private Vector3 velocity; // アニメーターコントローラー private Animator animator; // 移動スピード [SerializeField] private float speed; void Start () { cCon = GetComponent<CharacterController>(); animator = GetComponent<Animator>(); moveTowards = 0; } void Update () { if(cCon.isGrounded) { velocity = Vector3.zero; velocity = (points[moveTowards] - transform.position).normalized * speed; animator.SetFloat("Speed", 1.0f); } velocity.y += Physics.gravity.y * Time.deltaTime; transform.LookAt(new Vector3(points[moveTowards].x, transform.position.y, points[moveTowards].z)); cCon.Move(velocity * Time.deltaTime); if(Vector3.Distance(transform.position, points[moveTowards]) < 0.3f) { moveTowards++; if(moveTowards >= points.Length) { moveTowards = 0; } } } } |
敵キャラクターの移動に関しては割愛しますが、移動するポイントをいくつか設定しておきその場所を巡回するように移動します。
これでJointZombieControllerの作成が完了したので、さきほどの画像を参考にインスペクタで巡回する位置と敵キャラクターのスピードを設定してください。
敵にヤリが付くかどうか確認する
機能が完成したので主人公キャラクターを動かしてTキーを押してヤリを前方に飛ばし敵に当ててみましょう。
今回のサンプルではヤリのCollisionEnemyスクリプトのmodeをCollisionにしておこなってみました。
↑のように敵にヤリが刺さりましたが、主人公と接触するとヤリが消えたり、ヤリは他のゲームオブジェクトと衝突判定がされる為か敵キャラクターが変な方向に移動してしまっています。
ヤリの登場位置を調整する
ヤリの登場位置SpearPointは主人公よりだいぶ前に設置していました。
これはヤリの中心位置がSpearPointの位置になっている為、主人公の体の近くにするとヤリの中心が主人公の辺りになり主人公を突き抜けて半分の位置で登場するからです。
そこでヤリを少し変更してSpearPointの位置を主人公の体の辺りにしても大丈夫なようにしましょう。
↑のようにSpearの子要素に空オブジェクトを作成します。
空オブジェクトをドラッグし親子関係を解除し↑のようにします。
空オブジェクトを移動させSpearの原点に移動させます。
最後にSpearを空オブジェクトの子要素にします。
空オブジェクトGameObjectをProjectタブのAssetsフォルダにドラッグ&ドロップしプレハブ化します。
SpearPointに設定しているThrowSphearのSpearにさきほど作成したGameObjectをドラッグ&ドロップします。
子要素のSpearにはBox Collider、Rigidbody、CollisionEnemyスクリプト等が設定されています。
親のGameObjectには新たにRigidbodyを取り付けます。
Rigidbodyの設定は子要素のSpearと同じ設定にします。
あとはキャラクターのSpearPointの位置をキャラクターの近くに寄せるだけでOKですが、スクリプトで接触した相手がEnemy以外のタグだった時に
ヤリを削除しているので、スクリプトにPlayer以外の時という条件を加えるか、SpearPointをキャラクターと接触しない程度の位置にしておく必要があります。
スクリプトを変更するならCollisionEnemyスクリプトのJudgeEnemyメソッドで、
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 | // 敵かどうか判断しJointの設定をする void JudgeEnemy(Collider col) { if(col.gameObject.tag == "Enemy") { if(fixedJoint == null) { gameObject.AddComponent<FixedJoint>(); fixedJoint = GetComponent<FixedJoint>(); fixedJoint.connectedBody = col.gameObject.GetComponent<Rigidbody>(); fixedJoint.breakForce = breakForce; fixedJoint.breakTorque = breakTorque; fixedJoint.enableCollision = true; fixedJoint.enablePreprocessing = true; isSticking = true; // Rigidbodyの速度を0にし、スリープ状態にして止める GetComponent <Rigidbody>().velocity = Vector3.zero; GetComponent <Rigidbody>().Sleep (); } } else if(col.gameObject.tag != "Player") { if(fixedJoint == null) { Destroy(gameObject); } } } |
↑のように修正します。
終わりに
CollisionEnemyのmodeをCollisionにするとBreak ForceとBreak TorqueをInfinityにしないとくっつかないですね・・・・。
ちなみにJointコンポーネントはbreakForceやbreakTorque以上の力が加えられて外れた時はコンポーネント自体が削除されますので、
自分でコンポーネントを削除する必要はありません。