You can set them as weighted targets by the character’s inverse kinematics system. Make sure the character rig is set up as humanoid rather than generic. From there going into it’s animation controller and make sure the Base Layer has IK Pass ticked (the little settings button next to Base Layer). From there you can modify the weights of the characters arms, legs, and head based from their targets (these are external Transform components in the world, like your 4 blue cubes). Here’s an example of me doing it:
using UnityEngine;
/// <summary>
/// Attach this class to a humanoid character with an Animator to make the arms, legs, and head to follow individual Transform targets.
/// Make sure the referenced AnimatorController in the Animator Component allows IK to edit weights. You can turn on and off via the <see cref="UpdateIK(bool)"/> method.
/// You can assign this character to a new VR target via the <see cref="SwitchWearables"/> class, by it calling
/// <see cref="SetIKTarget(Transform, Transform, Transform, Transform, Transform, Transform)"/> on this class.
/// </summary>
public class VRIK : BaseFramework
{
[SerializeField] private VRIK_Hand leftHand;
[SerializeField] private VRIK_Hand rightHand;
[SerializeField] private Animator animator;
[SerializeField] private Mesh mesh;
[SerializeField] private Transform leftHandTarget;
[SerializeField] private Transform rightHandTarget;
[SerializeField] private Transform rightFootTarget;
[SerializeField] private Transform leftFootTarget;
[SerializeField] private Transform headTarget;
[SerializeField] private Transform hipTarget;
[SerializeField] private Vector3 hipOffsetAngle = new Vector3(0f, 90f, 0f);
[SerializeField] private float lookWeight = 1f;
[SerializeField] private float bodyWeight = 0.25f;
[SerializeField] private float headWeight = 0.9f;
[SerializeField] private float eyesWeight = 1f;
[SerializeField] private float clampWeight = 1f;
/// <summary>
/// In metres.
/// </summary>
[SerializeField, Tooltip("In meters.")] private float characterHeight = 1.61544f;
[SerializeField] private float deltaHeadHeight = 1.61544f;
[SerializeField] private float lowerLeg;
[SerializeField] private float upperLeg;
[SerializeField] private bool updateIKTargets = true;
private bool setCharacterSize = false;
public bool IsIKActive
{
get
{
return updateIKTargets;
}
}
public bool LeftHandGripping
{
get
{
return leftHand.IsGripping;
}
}
public bool RightHandGripping
{
get
{
return rightHand.IsGripping;
}
}
public void UpdateIK(bool shouldUpdate)
{
this.updateIKTargets = shouldUpdate;
}
public void SetIKTarget(Transform leftHandTarget, Transform leftFootTarget, Transform rightHandTarget, Transform rightFootTarget, Transform headTarget, Transform hipTarget)
{
if(leftHandTarget != null && leftFootTarget != null && rightHandTarget != null && rightFootTarget != null && headTarget != null && hipTarget != null)
{
this.leftHandTarget = leftHandTarget;
this.leftFootTarget = leftFootTarget;
this.rightHandTarget = rightHandTarget;
this.rightFootTarget = rightFootTarget;
this.headTarget = headTarget;
this.hipTarget = hipTarget;
// Can manually set this class as ready
Ready = true;
}
}
protected override bool Initialise()
{
// Debug only
//PrintMuscleIndex();
leftHand.Setup(this, animator);
rightHand.Setup(this, animator);
return leftHandTarget != null && leftFootTarget != null && rightHandTarget != null && rightFootTarget != null ? base.Initialise() : false;
}
// Updates every frame, just before IK is sent to the Animator as a manipulator to it's current animation playing.
private void OnAnimatorIK()
{
if (Ready && updateIKTargets)
{
// Only need to set the characters size at the beginning of IK checks
if (!setCharacterSize)
{
SetCharacterSize(characterHeight);
}
// Set right hand to follow target this frame
SetLimbIK(AvatarIKGoal.LeftHand, 1, leftHandTarget);
// Set left hand to follow target this frame
SetLimbIK(AvatarIKGoal.RightHand, 1, rightHandTarget);
// Set right foot to follow target this frame
SetLimbIK(AvatarIKGoal.RightFoot, 1, rightFootTarget);
// Set left foot to follow target this frame
SetLimbIK(AvatarIKGoal.LeftFoot, 1, leftFootTarget);
// Get the head to follow the view of the headset
SetHeadIK();
}
}
private void Update()
{
if (Ready && updateIKTargets)
{
// Sets the overall position of the character as the player walks around their play area without teleporting.
SetCharacterPosition();
}
}
private void SetLimbIK(AvatarIKGoal limb, float weight, Transform target)
{
animator.SetIKPositionWeight(limb, weight);
animator.SetIKRotationWeight(limb, weight);
animator.SetIKPosition(limb, target.position);
animator.SetIKRotation(limb, target.rotation);
}
/// <summary>
/// Sets the head to face the direction of the player. Not currently working.
/// </summary>
private void SetHeadIK()
{
animator.SetLookAtWeight(lookWeight, bodyWeight, headWeight, eyesWeight, clampWeight);
animator.SetLookAtPosition(headTarget.TransformDirection(Vector3.forward));
}
private void SetCharacterPosition()
{
// Get raw transform data.
Vector3 standingDirection = transform.parent.InverseTransformDirection(hipTarget.eulerAngles);
Vector3 standingPoint = transform.parent.InverseTransformPoint(hipTarget.position);
Vector3 headPoint = transform.parent.InverseTransformPoint(headTarget.position);
deltaHeadHeight = headPoint.y;
// Remove transform elements not relevant.
standingPoint.y = deltaHeadHeight - characterHeight;
standingDirection.x = 0f;
standingDirection.z = 0f;
// Set character transform.
transform.localPosition = standingPoint;
transform.localRotation = Quaternion.Euler(standingDirection + hipOffsetAngle);
SetFeetPosition();
}
/// <summary>
/// Sets each foot position. Calculated as an offset from center along the X axis plane.
/// </summary>
private void SetFeetPosition()
{
// Set their base height and position
Vector3 leftRootPos = transform.TransformPoint(new Vector3(-0.15f, 0f, 0f));
Vector3 rightRootPos = transform.TransformPoint(new Vector3(0.15f, 0f, 0f));
// Set to left.
leftFootTarget.position = leftRootPos;
leftFootTarget.rotation = Quaternion.Euler(transform.eulerAngles);
// Set to right.
rightFootTarget.position = rightRootPos;
rightFootTarget.rotation = Quaternion.Euler(transform.eulerAngles);
}
/// <summary>
/// Changes this characters overall scale so model fits intended height.
/// </summary>
/// <param name="heightInMeters">The intended height in meters you want the character to be.</param>
public void SetCharacterSize(float heightInMeters)
{
if(characterHeight != heightInMeters)
{
characterHeight = heightInMeters;
}
transform.localScale = Vector3.one * (heightInMeters / mesh.bounds.size.y);
setCharacterSize = true;
}
/// <summary>
/// Muscle name and index lookup (See Debug.Log). Call this somewhere at the beginning to get a reference for all the difference muscles in this humanoid.
/// </summary>
public void PrintMuscleIndex()
{
string[] muscleName = HumanTrait.MuscleName;
int i = 0;
while (i < HumanTrait.MuscleCount)
{
Debug.LogFormat("{0}: {1} min: {2}, max: {3}", i, muscleName[i], HumanTrait.GetMuscleDefaultMin(i), HumanTrait.GetMuscleDefaultMax(i));
i++;
}
}
And here’s the Unity manual on it: Unity - Manual: Inverse Kinematics