今回は何か物があった時に、キャラクターがそれを押す事が出来るようにしたいと思います。
物を押すアニメーションを作成し、押している間はそのアニメーションにしてもいいのですが、今回は普通に歩いているアニメーションを使い、両手を物に合わせて押しているようにします。
今回の機能を作成すると、
↑のような感じになります。
手からレイを飛ばしブロックに接触していた場合に手を付くようにするので、肩手だけブロックに手を付く事もあります。
物を押す力は主人公の前方に加えている為、肩手だと力を弱めたり、ブロックの接触面の方向と主人公の方向の角度から力を計算するのもいいかもしれませんね。
力の加減については今回の記事ではやっておりません。(-_-)
物を押すスクリプトPushBoxを作成する
物を押すスクリプトPushBoxを作成します。
PushBoxは主人公に設定してください。
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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | using UnityEngine; using System.Collections; public class PushBox : MonoBehaviour { // 右肩からRayを出す位置 [SerializeField] private Transform rightRayTransform; // 左肩からRayを出す位置 [SerializeField] private Transform leftRayTransform; // 右手が物を触っているか private bool isTouchRight = false; // 左手が物を触っているか private bool isTouchLeft = false; // 右手の位置 private Vector3 rightHandPosition; // 右手の角度 private Quaternion rightHandRotation; // 左手の位置 private Vector3 leftHandPosition; // 左手の角度 private Quaternion leftHandRotation; private Animator animator; // 右手のIKのウエイト private float rightHandWeight = 0f; // 左手のIKのウエイト private float leftHandWeight = 0f; // 手を付けるスピード [SerializeField] private float touchSpeed = 1.5f; // 押す力 [SerializeField] private float pushPower = 2000f; // レイの距離 [SerializeField] private float rayDistance = 0.7f; // 壁と手の間のオフセット値 [SerializeField] private float wallHandOffset = 0.1f; void Start () { animator = GetComponent<Animator>(); } void Update () { // 確認の為手を付く場所へレイを確認 Debug.DrawRay(rightRayTransform.position, rightRayTransform.forward * rayDistance, Color.yellow); Debug.DrawRay(leftRayTransform.position, leftRayTransform.forward * rayDistance, Color.yellow); RaycastHit hit; // 右肩からレイを飛ばし、Blockにヒットしているかどうか if (Physics.Raycast (rightRayTransform.position, rightRayTransform.forward, out hit, rayDistance, LayerMask.GetMask ("Block"))) { rightHandPosition = hit.point + hit.normal * wallHandOffset; rightHandRotation = Quaternion.FromToRotation (transform.up, hit.normal) * transform.rotation; isTouchRight = true; } else { isTouchRight = false; } // 左肩からレイを飛ばし、Blockにヒットしているかどうか if (Physics.Raycast (leftRayTransform.position, leftRayTransform.forward, out hit, rayDistance, LayerMask.GetMask ("Block"))) { leftHandPosition = hit.point + hit.normal * wallHandOffset; leftHandRotation = Quaternion.FromToRotation (transform.up, hit.normal) * transform.rotation; isTouchLeft = true; } else { isTouchLeft = false; } // 右手用のウエイトを変化させる if (isTouchRight) { if (rightHandWeight < 1f) { rightHandWeight = Mathf.MoveTowards (rightHandWeight, 1f, touchSpeed * Time.deltaTime); } } else { rightHandWeight = 0f; } // 左手用のウエイトを変化させる if (isTouchLeft) { if (leftHandWeight < 1f) { leftHandWeight = Mathf.MoveTowards (leftHandWeight, 1f, touchSpeed * Time.deltaTime); } } else { leftHandWeight = 0f; } } // CharacterControllerが他のコライダと接触している void OnControllerColliderHit(ControllerColliderHit col) { // ぶつかっている相手がBlockだったらキャラクターの方向に力を加える if(col.gameObject.layer == LayerMask.NameToLayer ("Block")) { col.gameObject.GetComponent<Rigidbody>().AddForce(transform.forward * pushPower); } } void OnAnimatorIK() { // 右手のIKのウエイト設定 animator.SetIKPositionWeight(AvatarIKGoal.RightHand, rightHandWeight); animator.SetIKRotationWeight(AvatarIKGoal.RightHand, rightHandWeight); // 左手のIKのウエイト設定 animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, leftHandWeight); animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, leftHandWeight); // 右手の位置設定 animator.SetIKPosition(AvatarIKGoal.RightHand, rightHandPosition); animator.SetIKRotation(AvatarIKGoal.RightHand, rightHandRotation); // 左手の位置設定 animator.SetIKPosition(AvatarIKGoal.LeftHand, leftHandPosition); animator.SetIKRotation(AvatarIKGoal.LeftHand, leftHandRotation); } } |
右肩と左肩付近にRightRayTransformとLeftRayTransformを作成し、外側の上辺りにrayDistanceの長さのレイを飛ばします。
もしそこでBlockレイヤーに指定したゲームオブジェクトに当たっていた場合はその位置を保存します。
1 2 3 | rightHandPosition = hit.point + hit.normal * wallHandOffset; |
の部分で接触面から接触面の表面の向いている方向にwallHandOffset値の位置を右手の位置にします。
1 2 3 | rightRotation = Quaternion.FromToRotation(transform.up, hit.normal) * transform.rotation |
としている部分でhit.normalはレイが当たった面の角度で、Vector3.upがY軸の上向きで、
Quaternion.FromToRotationでその間の角度を算出出来ます。
それに元々のQuaternionであるtransform.rotationをかける事で、元の角度も加味する事が出来ます。
あとで試す時に、キャラクターの手がBlockを触っている時にBlockの角度を変更してみるとわかりやすいかもしれません。
右肩からのレイがBlockレイヤーに当たっていたらisTouchRightをOnにします。
左肩からのレイの処理も右肩と同じです。
次にウエイトの値を変化させる処理をみていきます。
1 2 3 4 5 6 7 8 9 10 | // 右手用のウエイトを変化させる if (isTouchRight) { if (rightHandWeight < 1f) { rightHandWeight = Mathf.MoveTowards (rightHandWeight, 1f, touchSpeed * Time.deltaTime); } } else { rightHandWeight = 0f; } |
↑は右手用のウエイトを変化させている部分で、右手がブロックに付いている場合はMathf.MoveTowardsを使って現在のウエイトを1に変化させ、付いていない場合は0へと変化させます。
こうすることで手が付いていない状態から付いている状態へ以降する時に滑らかに手の位置を移動させる事が出来ます。
左手の処理も同じです。
次にキャラクターがブロックと接触している時にブロックのRigidbodyに力を加える処理を見ていきます。
1 2 3 4 5 6 7 8 9 | // CharacterControllerが他のコライダと接触している void OnControllerColliderHit(ControllerColliderHit col) { // ぶつかっている相手がBlockだったらキャラクターの方向に力を加える if(col.gameObject.layer == LayerMask.NameToLayer ("Block")) { col.gameObject.GetComponent<Rigidbody>().AddForce(transform.forward * pushPower); } } |
OnControllerColliderHitはCharacterControllerを持っていた場合に他のコライダと接触したら呼び出されます。
ぶつかった相手のコライダのレイヤーがBlockだったら主人公の前方に力を加えています。
次にIKの設定処理を見ていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | void OnAnimatorIK() { // 右手のIKのウエイト設定 animator.SetIKPositionWeight(AvatarIKGoal.RightHand, rightHandWeight); animator.SetIKRotationWeight(AvatarIKGoal.RightHand, rightHandWeight); // 左手のIKのウエイト設定 animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, leftHandWeight); animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, leftHandWeight); // 右手の位置設定 animator.SetIKPosition(AvatarIKGoal.RightHand, rightHandPosition); animator.SetIKRotation(AvatarIKGoal.RightHand, rightHandRotation); // 左手の位置設定 animator.SetIKPosition(AvatarIKGoal.LeftHand, leftHandPosition); animator.SetIKRotation(AvatarIKGoal.LeftHand, leftHandRotation); } |
右手のIK、左手のIKのウエイトを設定し、それぞれの位置や角度に設定しているだけです。
これでPushBoxスクリプトが完成しました。
次に右肩、左肩からレイを出す位置RightRayTransformとLeftRayTransformゲームオブジェクトを作成します。
上のようにキャラクターの子要素に設定します。
Debug.DrawRayで見える化したレイが上のようになります。
このレイがBlockレイヤーに設定した物に当たった場合、そこの位置と角度に合わせて、右手と左手をもっていきます。
押す物のサンプルとしてCubeを作り、インスペクタで上のようにRigidbodyを追加します。
Mass(質量)を50にします。
Collision DetectionはContinuousにします。
Collision Detectionは連続的な衝突検知の方法でBlockに主人公がめり込まないようにする為
Continuousにしました。Continuousは連続した検知をするのに適しています。
これで機能が完成しました。
キャラクターに設定したPushBoxスクリプトの設定は
↑のように設定しました。
Unityの実行ボタンを押して確認してみましょう。
上のようにBlockレイヤーに指定された物を押す事が出来るようになりました。
上のようにレイが外れている左手は物を触りません。
これでIKを使って物に手を合わせ、押す事が出来るようになりました。
主人公の移動スピードそのままなのと、歩いているアニメーションをそのまま使っているので、ダンボールのような軽い物を押している感じになっています。
押している状態になったら、アニメーションのスピードを落としたり、アニメーション自体を別に作成したり、といった対処が必要になるかと思います。