Skip to content

Instantly share code, notes, and snippets.

@leeprobert
Last active June 25, 2025 21:43
Show Gist options
  • Save leeprobert/815c7d2be754ae6384ce5c351041fd7e to your computer and use it in GitHub Desktop.
Save leeprobert/815c7d2be754ae6384ce5c351041fd7e to your computer and use it in GitHub Desktop.
Unity C# class for loading a Ready Player Me model (avatar) and then setting the relevant Third Person Controller components (like the Starter Assets package that uses a Cinemachine Virtual Camera). This script based on the work of Sarge (RPM) and his tool for converting an older RPM model in the Editor. See this repos: https://github.com/srcnal…
using System;
using Cinemachine;
using ReadyPlayerMe.Core;
using StarterAssets;
using UnityEngine;
using UnityEngine.InputSystem;
namespace com.wealdcreative.unity.readyplayerme {
public class RPMAvatarLoader : MonoBehaviour
{
private const string CAMERA_TARGET_OBJECT_NAME = "CameraTarget";
private readonly Vector3 avatarPositionOffset = new Vector3(0, -0.08f, 0);
[SerializeField][Tooltip("RPM avatar URL or shortcode to load")]
private string avatarUrl;
private GameObject avatar;
private AvatarObjectLoader avatarObjectLoader;
[SerializeField][Tooltip("If true it will try to load avatar from avatarUrl on start")]
private bool loadOnStart = true;
public event Action OnLoadComplete;
[Header("Asset References")]
[SerializeField][Tooltip("Animator to use on loaded avatar")] private GameObject _previewAvatar;
[SerializeField][Tooltip("Animator to use on loaded avatar")] private RuntimeAnimatorController _animatorController;
[SerializeField][Tooltip("Input Actions to control the avatar")] private InputActionAsset _inputActionAsset;
[SerializeField][Tooltip("Sound effect for landing")] private AudioClip _landingAudioClip;
[SerializeField][Tooltip("Footstep sound effects")] private AudioClip[] _footstepAudioClips;
private void Start()
{
avatarObjectLoader = new AvatarObjectLoader();
avatarObjectLoader.OnCompleted += OnLoadCompleted;
avatarObjectLoader.OnFailed += OnLoadFailed;
if (_previewAvatar != null)
{
SetupAvatar(_previewAvatar);
}
if (loadOnStart)
{
LoadAvatar(avatarUrl);
}
}
private void OnLoadFailed(object sender, FailureEventArgs args)
{
OnLoadComplete?.Invoke();
}
private void OnLoadCompleted(object sender, CompletionEventArgs args)
{
if (_previewAvatar != null)
{
Destroy(_previewAvatar);
_previewAvatar = null;
}
SetupAvatar(args.Avatar);
OnLoadComplete?.Invoke();
}
private void SetupAvatar(GameObject targetAvatar)
{
if (avatar != null)
{
Destroy(avatar);
}
avatar = targetAvatar;
// Re-parent and reset transforms
avatar.transform.parent = transform;
avatar.transform.localPosition = avatarPositionOffset;
avatar.transform.localRotation = Quaternion.Euler(0, 0, 0);
avatar.tag = "Player";
// Create camera follow target
GameObject cameraTarget = new GameObject(CAMERA_TARGET_OBJECT_NAME);
cameraTarget.transform.parent = avatar.transform;
cameraTarget.transform.localPosition = new Vector3(0, 1.5f, 0);
cameraTarget.tag = "CinemachineTarget";
// Set the animator controller and disable root motion
Animator animator = avatar.GetComponent<Animator>();
animator.runtimeAnimatorController = _animatorController;
animator.applyRootMotion = false;
// Add tp controller and set values
ThirdPersonController tpsController = avatar.AddComponent<StarterAssets.ThirdPersonController>();
tpsController.GroundedOffset = 0.1f;
tpsController.GroundLayers = 1;
tpsController.JumpTimeout = 0.5f;
tpsController.CinemachineCameraTarget = cameraTarget;
tpsController.LandingAudioClip = _landingAudioClip;
tpsController.FootstepAudioClips = _footstepAudioClips;
// Add character controller and set size
CharacterController characterController = avatar.GetComponent<CharacterController>();
characterController.center = new Vector3(0, 1, 0);
characterController.radius = 0.3f;
characterController.height = 1.9f;
// Add components with default values
avatar.AddComponent<BasicRigidBodyPush>();
avatar.AddComponent<StarterAssetsInputs>();
// Add player input and set actions asset
PlayerInput playerInput = avatar.GetComponent<PlayerInput>();
playerInput.actions = _inputActionAsset;
playerInput.defaultActionMap = "Player";
playerInput.ActivateInput();
var camera = UnityEngine.Object.FindFirstObjectByType<CinemachineVirtualCamera>();
if (camera)
{
camera.Follow = cameraTarget.transform;
camera.LookAt = cameraTarget.transform;
}
}
public void LoadAvatar(string url)
{
//remove any leading or trailing spaces
avatarUrl = url.Trim(' ');
avatarObjectLoader.LoadAvatar(avatarUrl);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment