キャラクターがドアを通るときに、頭や腕が壁を突き抜けることがあります。
Animation Riggingパッケージを使って、ドアを通過するときのアニメーションを動的に編集してみました。
Rigを設定
Animation Riggingでキャラを自然に振り向かせるとは別に空のゲームオブジェクトを作って、Rigコンポーネントを追加します。
キャラクターのルートに追加したRig Builderコンポーネントにアタッチします。
Chain IK Constraint
まずはRigの子として空のゲームオブジェクトを作り、頭と腕のためにChain IK Constraintコンポーネントを付けてみます。
Tipに頭、Rootに脊椎のボーンをアタッチします。このゲームオブジェクトのさらに子としてターゲット用の空のゲームオブジェクトを作り、Targetにアタッチしています。
ターゲットの位置を調節
ターゲットをプレイヤーの顔の前に置きました。
プレイモードでターゲットを動かすと頭から腰までのボーンが付いてきます。
左右にも動かせます。
腕も同様に、体の内側へ動かしました。
トリガー
ドアを通るときだけ、RigのWeightを上げて、ドアを抜けたらWeightを0まで下げます。ドアの前にBoxコライダーのトリガーを置き、敵のスクリプトのOnTriggerEnterとOnTriggerExitでWeightの変更を開始します。
[SerializeField] Rig doorRig;
[SerializeField] float doorRigMaxWeight = 0.538f;
IEnumerator IncreaseDoorRig()
{
while (doorRig.weight < doorRigMaxWeight)
{
doorRig.weight += Time.deltaTime;
yield return null;
}
doorRig.weight = doorRigMaxWeight;
}
IEnumerator DecreaseDoorRig()
{
while (doorRig.weight > 0f)
{
doorRig.weight -= Time.deltaTime * 0.8f;
yield return null;
}
doorRig.weight = 0f;
}
RigやコンストレイントのWeight、ターゲットの位置などで動きを調節できます。
Two Bone IK Constraint
今度は、Two Bone IK Constraintを使ってキャラクターの左手をドア枠に触れさせてみます。同じRigの子として空のゲームオブジェクトを作り「Two Bone IK Constraint」コンポーネントを追加しました。
Tipに左手のボーンをアタッチします。
コンテキストメニューを開いて、「Auto Setup from Tip Transform」をクリックします。
すると、左腕のボーンがアタッチされました。また、コンストレイントの子として、ターゲットとHintのゲームオブジェクトが作られて自動でアタッチされます。
ドア枠の位置に固定するために、ターゲットにはParent Constraintコンポーネントを付けました。ソースはスクリプトで設定します。
元々の左腕のコンストレイントはウェイトを0にしておきます。
ターゲットとヒントを調節
プレイモードでターゲットとヒントの位置や回転を調節しました。ヒントの位置で肘の向きを変えられます。
ターゲットの回転で手の向きを変えられます。
手を置く位置を設定
調節したターゲットの位置に空のゲームオブジェクトを起きます。これはドアのトリガーの子にしました。
トリガーにはスクリプトを付け、手を置くTransformを公開しておきます。
using UnityEngine;
public class EnemyDoorTrigger : MonoBehaviour
{
[SerializeField] Transform armIKTarget;
public Transform ArmIKTarget => armIKTarget;
}
キャラクターのスクリプトでは、トリガーにEnter/Exitした時やウェイトが0になったときに、コンストレイントの有効無効を切り替えています。
IEnumerator DecreaseDoorRig()
{
while (doorRig.weight > 0f)
{
doorRig.weight -= Time.deltaTime * 0.8f;
yield return null;
}
doorRig.weight = 0f;
DisableHandPlacementOnDoorframe();
}
protected override void OnTriggerExit(Collider other)
{
base.OnTriggerExit(other);
if (other.tag == "DoorTrigger")
{
//DisableHandPlacementOnDoorframe();
StopCoroutine("IncreaseFoorRig");
StopCoroutine("DecreaseFoorRig");
StartCoroutine("DecreaseFoorRig");
}
}
[SerializeField] ParentConstraint leftArmTarget;
void EnableHandPlacementOnDoorframe(Transform target)
{
// ソースを作成
var source = new ConstraintSource();
source.sourceTransform = target;
source.weight = 1f;
// 左腕のParentコンストレイントにソースを追加
leftArmTarget.AddSource(source);
// コンストレイントを有効にする。
leftArmTarget.constraintActive = true;
}
void DisableHandPlacementOnDoorframe()
{
// ソースを削除
leftArmTarget.RemoveSource(0);
// コンストレイントを無効にする
leftArmTarget.constraintActive = false;
}