Files
Studio/Assets/Scripts/ExternalAssets/TriLib/TriLibSamples/AvatarLoader/Utils/AvatarController.cs
2025-06-11 16:50:56 +09:00

148 lines
6.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
namespace TriLibCore.Samples
{
/// <summary>
/// Manages avatar movement and camera positioning in TriLib samples.
/// This class extends <see cref="AbstractInputSystem"/> to handle user input,
/// allowing the character to move and rotate smoothly, as well as updating the
/// cameras position and orientation relative to the avatar.
/// </summary>
public class AvatarController : AbstractInputSystem
{
/// <summary>
/// A singleton reference to the active <see cref="AvatarController"/> in the scene.
/// Set in <see cref="Awake"/> to ensure only one active instance is present.
/// </summary>
public static AvatarController Instance { get; private set; }
/// <summary>
/// Maximum movement speed (in Unity units per second) that the avatar can reach.
/// </summary>
private const float MaxSpeed = 2f;
/// <summary>
/// Avatar acceleration (in Unity units per second) applied each frame
/// when the user provides movement input.
/// </summary>
private const float Acceleration = 5f;
/// <summary>
/// Rate at which the avatar slows down (in Unity units per second)
/// when there is no user input (applies friction to movement).
/// </summary>
private const float Friction = 2f;
/// <summary>
/// The rotation speed factor used to smoothly rotate the avatar toward the movement direction.
/// </summary>
private const float RotationSpeed = 60f;
/// <summary>
/// The <see cref="CharacterController"/> responsible for handling collisions
/// and movement for the avatar.
/// </summary>
public CharacterController CharacterController;
/// <summary>
/// The <see cref="Animator"/> that handles animation playback and blending for this avatar.
/// </summary>
public Animator Animator;
/// <summary>
/// The root <see cref="GameObject"/> representing the loaded avatar model.
/// This can be replaced at runtime if a new avatar is loaded.
/// </summary>
public GameObject InnerAvatar;
/// <summary>
/// The cameras offset from the avatars position, used to maintain a consistent view.
/// Calculated during <see cref="Awake"/> and updated in <see cref="Update"/> to reflect rotation.
/// </summary>
private Vector3 _cameraOffset;
/// <summary>
/// The avatars current movement speed, dynamically updated each frame
/// based on <see cref="Acceleration"/>, <see cref="Friction"/>, and user input.
/// </summary>
private float _speed;
/// <summary>
/// A vertical offset to position the camera at the avatars head level,
/// derived from the <see cref="CharacterController"/>'s height.
/// </summary>
private Vector3 _cameraHeightOffset;
/// <summary>
/// Tracks the instantaneous velocity for smooth avatar rotation
/// in <c>Mathf.SmoothDampAngle</c>.
/// </summary>
private float _currentVelocity;
/// <summary>
/// Initializes the singleton instance and calculates the camera offsets
/// based on the avatars height. Called automatically when the script instance awakens.
/// </summary>
private void Awake()
{
Instance = this;
// Determine where the camera should pivot from the avatars head level
_cameraHeightOffset = new Vector3(0f, CharacterController.height * 0.8f, 0f);
// Store the initial offset between the camera and the avatar
_cameraOffset = Camera.main.transform.position - transform.position;
}
/// <summary>
/// Handles user input and updates the avatars position, rotation, speed, and animator parameters.
/// Also updates the camera position and orientation to stay behind the avatar.
/// </summary>
private void Update()
{
// Gather input along the horizontal and vertical axes
var input = new Vector3(GetAxis("Horizontal"), 0f, GetAxis("Vertical"));
// Transform input according to the cameras orientation, ignoring vertical component
var direction = Camera.main.transform.TransformDirection(input);
direction.y = 0f;
direction.Normalize();
// Determine the avatars target orientation based on input direction
var targetEulerAngles = direction.magnitude > 0
? Quaternion.LookRotation(direction).eulerAngles
: transform.rotation.eulerAngles;
// Smoothly rotate the avatar toward the movement direction
var eulerAngles = transform.rotation.eulerAngles;
eulerAngles.y = Mathf.SmoothDampAngle(
eulerAngles.y,
targetEulerAngles.y,
ref _currentVelocity,
Time.deltaTime * RotationSpeed * input.magnitude
);
transform.rotation = Quaternion.Euler(eulerAngles);
// Update speed based on acceleration or friction
_speed += input.magnitude * (Acceleration * MaxSpeed) * Time.deltaTime;
_speed -= Friction * MaxSpeed * Time.deltaTime;
_speed = Mathf.Clamp(_speed, 0f, MaxSpeed);
// Move the avatar using the CharacterController
CharacterController.SimpleMove(transform.forward * _speed);
// Update the animator with the normalized speed
Animator.SetFloat("SpeedFactor", _speed / MaxSpeed);
// Adjust the camera based on the users camera angle (from AssetViewerBase) and stored offsets
var pivotedPosition =
Quaternion.AngleAxis(AvatarLoader.Instance.CameraAngle.x, Vector3.up) *
Quaternion.AngleAxis(-AvatarLoader.Instance.CameraAngle.y, Vector3.right) *
_cameraOffset;
Camera.main.transform.position = transform.position + _cameraHeightOffset + pivotedPosition;
Camera.main.transform.LookAt(transform.position + _cameraHeightOffset);
}
}
}