Setup the Player Avatar

For optimal performance and efficiency in Unity VR development, it is advisable to maintain a consistent game object for the player's avatar. Rather than reconstructing the avatar's logic with each new user load, we recommend implementing a mesh-swap technique to change the avatar's appearance. This approach minimizes frame rate disruptions and reduces the computational overhead associated with repeatedly setting up the avatar logic.

The Unity SDK includes a Template Avatar located in Packages -> Ready Player Me Core -> Resources, which can be easily integrated into your scene via drag and drop.

To optimize the player avatar's performance and prevent clipping issues, it is recommended to assign all head-related meshes to a distinct layer. This layer should then be excluded from rendering by the MainCamera.

Loading custom player avatars

It is important to use the correct AvatarConfiguration for the player avatar. We recommend setting the TextureAtlas to none to get a non-atlassed avatar and LOD to High (LOD0), to ensure the hands have the best skinning. You can create your own Avatar Config by Right Click in Project Window -> Create -> Ready Player Me -> Avatar Configuration. Learn more about Avatar Configs here.

You can do the mesh-swap by using a utility function TransferMesh from the Ready Player Me SDK when you load a new avatar. After loading a new Avatar, using the AvatarManager, you can transfer its mesh to the templateAvatar (GameObject which is in your scene) and immediately destroy the loaded Avatar GameObject (newAvatar).

AvatarMeshHelper.TransferMesh(newAvatar, templateAvatar);
Destroy(newAvatar);

Player Height Calibration

Player height calibration is significantly influenced by the overarching game design. The method outlined herein represents one of numerous potential solutions, offering a straightforward approach to height calibration. This is achieved by scaling the avatar based on the height of the player when standing and wearing the headset.

It is important to note that this example employs Final IK, a commercial plugin available on the Asset Store. However, similar scaling results can be achieved using alternative IK systems, whether proprietary or third-party.

public class HeightCalibrator : MonoBehaviour
    {
        private const float maxAllowedHeight = 2.2f;
        private const float minAllowedHeight = 1.35f;

        [SerializeField] private VRIK ik;
        [SerializeField] private InputActionProperty trackedHeadPosition;
        private float lastCalibratedHeight;
        private float scale;

        public void CalibrateHeight()
        {
            // get the player height, by reading it's tracked head position
            var headPosition = trackedHeadPosition.action.ReadValue<Vector3>();

            // Store the tracked head position 
            lastCalibratedHeight = Mathf.Min(maxAllowedHeight, Mathf.Max(minAllowedHeight, headPosition.y));

            CalibrateBody();
        }
        
        public void CalibrateBody()
        {
            scale = lastCalibratedHeight / AvatarComponentReferences.Instance.AvatarDefaultHeight;
            // Scale the avatar game object via VRIK
            ik.references.root.localScale = new Vector3(scale, scale, scale);
            CalibrateHead();
            CalibrateHands();
        }

        // Calibrate the avatar head to the correct size (a bigger avatar 
        // has a slightly smaller head and vice versa).
        private void CalibrateHead()
        {
            const float scaleDivisionConstant = 2f;

            var headScale = 1f + (1f - scale) / scaleDivisionConstant;
            ik.references.head.localScale = new Vector3(headScale, headScale, headScale);
        }
        
        // This method calibrates avatar hands back to one. This means, that the interactions
        // with hands don't need to change, since the hands are bigger/smaller.
        private void CalibrateHands()
        {
            ScaleBoneToOne(ik.references.leftHand);
            ScaleBoneToOne(ik.references.rightHand);
        }

        private void ScaleBoneToOne(Transform hand)
        {
            hand.localScale = Vector3.one;
            var lossyScale = hand.lossyScale;

            hand.localScale = new Vector3(1f / lossyScale.x,
                1f / lossyScale.y, 1f / lossyScale.z);
        }
    }

Last updated