VR Body Tracking Tutorial Pt. 1 | Head and Torso

For a preview of what this tutorial teaches check out this article where I explain the basics of how my system works. Because body tracking is so complicated I decided to split it up into three parts, Head and Torso, Arms. and Feet. This is part one where we are going to set up everything we need. First off we are going to need a 3D model to use as our body, I used the free robot kyle asset for mine, (you can find it here). Once you have downloaded it drag it into your camera rig prefab. Now the last thing we need to do is add an empty game object as a child to the camera object to use as an offset for the head.

Now we can get into the code, let’s start with the variables

using UnityEngine;

public class BodyTracking : MonoBehaviour
{
    public float DefaultHeight;// = 1.62 is the normal height of a human;
    public float rotationWhenCrouched;

    public GameObject Head;//these keep track of our body objects.
    public GameObject CameraRig;
    public GameObject BodyRoot;
    public GameObject HeadRoot;
    public GameObject Torso;
    public GameObject Hips;

    private Quaternion HeadRotation;//these are all offset varibles to make sure evrything stays in place.
    private Quaternion TorsoRotation;
    private Vector3 TorsoOffset;
    private Vector3 HipOffset;
    private Quaternion HipOffsetRot;
    private Quaternion TorsoOffsetRotation;
    private Quaternion HipOffsetRotation;

Next, let’s set all of our offset variables:

void Awake()
    {
        HeadRotation = HeadRoot.transform.rotation;
        TorsoRotation = BodyRoot.transform.rotation;
        TorsoOffset = Torso.transform.position -HeadRoot.transform.position;
        HipOffset = Hips.transform.position - HeadRoot.transform.position;
        HipOffsetRot = Hips.transform.rotation;
        TorsoOffsetRotation = Torso.transform.rotation;
        HipOffsetRotation = Torso.transform.rotation;

    }

Now lets actually position our stuff:

void Update()
    {
        //position the entire body in the same place as the HMD or camera.
        BodyRoot.transform.position = new Vector3(Head.transform.position.x,CameraRig.transform.position.y, Head.transform.position.z);

        //If the head is looking more than 90% to the left or right rotate the body in that direction
        if (Quaternion.Angle(Quaternion.Euler(0, BodyRoot.transform.rotation.eulerAngles.y, 0), Quaternion.Euler(0, Head.transform.rotation.eulerAngles.y, 0)) > 90)
            {
                BodyRoot.transform.rotation = Quaternion.RotateTowards(BodyRoot.transform.rotation, Quaternion.Euler(0, Head.transform.rotation.eulerAngles.y, 0) * TorsoRotation, 3);
            }
        //And good luck understanding these, I barely understand my own math but it works. Explained Simply this makes you crouch when the headset gets close to the ground.
        Torso.transform.position = BodyRoot.transform.rotation * Quaternion.Euler((1 - (Head.transform.position.y-CameraRig.transform.position.y) / DefaultHeight) * rotationWhenCrouched, 0, 0) * (TorsoOffset) + Head.transform.position - (Quaternion.Euler(0, Head.transform.rotation.eulerAngles.y, 0) * TorsoRotation * Vector3.forward * (0.3f) * (FixEuler(Head.transform.rotation.eulerAngles.x) / 180));
        Hips.transform.position = BodyRoot.transform.rotation * Quaternion.Euler((1 - (Head.transform.position.y - CameraRig.transform.position.y) / DefaultHeight) * rotationWhenCrouched, 0, 0) * (HipOffset) + Head.transform.position - (Quaternion.Euler(0, Head.transform.rotation.eulerAngles.y, 0) * TorsoRotation * Vector3.forward * (0.3f) * (FixEuler(Head.transform.rotation.eulerAngles.x) / 180));
        Torso.transform.rotation = BodyRoot.transform.rotation * Quaternion.Euler((1 - (Head.transform.position.y - CameraRig.transform.position.y )/ DefaultHeight) * rotationWhenCrouched, 0, 0) * TorsoOffsetRotation;
        
        HeadRoot.transform.position = Head.transform.position;
        HeadRoot.transform.rotation = Head.transform.rotation* HeadRotation;
    }

Since we are using Euler angles we sometimes need to correct angle lock and I created a really simple program to do that:

float FixEuler(float angle)
    {
        if(angle < 180)
        {
            return angle;
        }
        else
        {
            return angle - 360;
        }
    }

So that’s all we need to code, for now, it should look stupid and really dumb since at this point as we are not controlling the arms or legs yet, drag and drop stuff in and you are ready to go:

Find the next tutorial here!

using UnityEngine;

public class BodyTracking : MonoBehaviour
{
    public float DefaultHeight;// = 1.62 is the normal height of a human;
    public float rotationWhenCrouched;

    public GameObject Head;//these keep track of our body objects.
    public GameObject CameraRig;
    public GameObject BodyRoot;
    public GameObject HeadRoot;
    public GameObject Torso;
    public GameObject Hips;

    private Quaternion HeadRotation;//these are all offset varibles to make sure evrything stays in place.
    private Quaternion TorsoRotation;
    private Vector3 TorsoOffset;
    private Vector3 HipOffset;
    private Quaternion HipOffsetRot;
    private Quaternion TorsoOffsetRotation;
    private Quaternion HipOffsetRotation;
void Awake()
    {
        HeadRotation = HeadRoot.transform.rotation;
        TorsoRotation = BodyRoot.transform.rotation;
        TorsoOffset = Torso.transform.position -HeadRoot.transform.position;
        HipOffset = Hips.transform.position - HeadRoot.transform.position;
        HipOffsetRot = Hips.transform.rotation;
        TorsoOffsetRotation = Torso.transform.rotation;
        HipOffsetRotation = Torso.transform.rotation;

    }
void Update()
    {
        //position the entire body in the same place as the HMD or camera.
        BodyRoot.transform.position = new Vector3(Head.transform.position.x,CameraRig.transform.position.y, Head.transform.position.z);

        //If the head is looking more than 90% to the left or right rotate the body in that direction
        if (Quaternion.Angle(Quaternion.Euler(0, BodyRoot.transform.rotation.eulerAngles.y, 0), Quaternion.Euler(0, Head.transform.rotation.eulerAngles.y, 0)) > 90)
            {
                BodyRoot.transform.rotation = Quaternion.RotateTowards(BodyRoot.transform.rotation, Quaternion.Euler(0, Head.transform.rotation.eulerAngles.y, 0) * TorsoRotation, 3);
            }
        //And good luck understanding these, I barely understand my own math but it works. Explained Simply this makes you crouch when the headset gets close to the ground.
        Torso.transform.position= BodyRoot.transform.rotation * Quaternion.Euler((1-Head.transform.localPosition.y/DefaultHeight)*rotationWhenCrouched,0,0) * (TorsoOffset)+Head.transform.position-(Quaternion.Euler(0, Head.transform.rotation.eulerAngles.y, 0) * TorsoRotation*Vector3.forward  *(0.3f)* (FixEuler(Head.transform.rotation.eulerAngles.x)/ 180));
        Hips.transform.position= BodyRoot.transform.rotation * Quaternion.Euler((1 - Head.transform.localPosition.y / DefaultHeight) * rotationWhenCrouched, 0, 0) * (HipOffset) + Head.transform.position - (Quaternion.Euler(0, Head.transform.rotation.eulerAngles.y, 0) * TorsoRotation * Vector3.forward * (0.3f) * (FixEuler(Head.transform.rotation.eulerAngles.x) / 180));
        Torso.transform.rotation = BodyRoot.transform.rotation * Quaternion.Euler((1 - Head.transform.localPosition.y / DefaultHeight) * rotationWhenCrouched, 0, 0) * TorsoOffsetRotation;
        
        HeadRoot.transform.position = Head.transform.position;
        HeadRoot.transform.rotation = Head.transform.rotation* HeadRotation;
    }
float FixEuler(float angle)
    {
        if(angle < 180)
        {
            return angle;
        }
        else
        {
            return angle - 360;
        }
    }
}
Liked it? Take a second to support WireWhiz on Patreon!
Become a patron at Patreon!