294 lines
10 KiB
C#
294 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.CompilerServices;
|
|
using UnityEngine;
|
|
|
|
namespace KinematicCharacterController
|
|
{
|
|
/// <summary>
|
|
/// The system that manages the simulation of KinematicCharacterMotor and PhysicsMover
|
|
/// </summary>
|
|
[DefaultExecutionOrder(-100)]
|
|
public class KinematicCharacterSystem : MonoBehaviour
|
|
{
|
|
private static KinematicCharacterSystem _instance;
|
|
|
|
public static List<KinematicCharacterMotor> CharacterMotors = new List<KinematicCharacterMotor>();
|
|
public static List<PhysicsMover> PhysicsMovers = new List<PhysicsMover>();
|
|
|
|
private static float _lastCustomInterpolationStartTime = -1f;
|
|
private static float _lastCustomInterpolationDeltaTime = -1f;
|
|
|
|
public static KCCSettings Settings;
|
|
|
|
/// <summary>
|
|
/// Creates a KinematicCharacterSystem instance if there isn't already one
|
|
/// </summary>
|
|
public static void EnsureCreation()
|
|
{
|
|
if (_instance == null)
|
|
{
|
|
GameObject systemGameObject = new GameObject("KinematicCharacterSystem");
|
|
_instance = systemGameObject.AddComponent<KinematicCharacterSystem>();
|
|
|
|
systemGameObject.hideFlags = HideFlags.NotEditable;
|
|
_instance.hideFlags = HideFlags.NotEditable;
|
|
|
|
Settings = ScriptableObject.CreateInstance<KCCSettings>();
|
|
|
|
GameObject.DontDestroyOnLoad(systemGameObject);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the KinematicCharacterSystem instance if any
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public static KinematicCharacterSystem GetInstance()
|
|
{
|
|
return _instance;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the maximum capacity of the character motors list, to prevent allocations when adding characters
|
|
/// </summary>
|
|
/// <param name="capacity"></param>
|
|
public static void SetCharacterMotorsCapacity(int capacity)
|
|
{
|
|
if (capacity < CharacterMotors.Count)
|
|
{
|
|
capacity = CharacterMotors.Count;
|
|
}
|
|
CharacterMotors.Capacity = capacity;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers a KinematicCharacterMotor into the system
|
|
/// </summary>
|
|
public static void RegisterCharacterMotor(KinematicCharacterMotor motor)
|
|
{
|
|
CharacterMotors.Add(motor);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unregisters a KinematicCharacterMotor from the system
|
|
/// </summary>
|
|
public static void UnregisterCharacterMotor(KinematicCharacterMotor motor)
|
|
{
|
|
CharacterMotors.Remove(motor);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the maximum capacity of the physics movers list, to prevent allocations when adding movers
|
|
/// </summary>
|
|
/// <param name="capacity"></param>
|
|
public static void SetPhysicsMoversCapacity(int capacity)
|
|
{
|
|
if (capacity < PhysicsMovers.Count)
|
|
{
|
|
capacity = PhysicsMovers.Count;
|
|
}
|
|
PhysicsMovers.Capacity = capacity;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers a PhysicsMover into the system
|
|
/// </summary>
|
|
public static void RegisterPhysicsMover(PhysicsMover mover)
|
|
{
|
|
PhysicsMovers.Add(mover);
|
|
|
|
mover.Rigidbody.interpolation = RigidbodyInterpolation.None;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unregisters a PhysicsMover from the system
|
|
/// </summary>
|
|
public static void UnregisterPhysicsMover(PhysicsMover mover)
|
|
{
|
|
PhysicsMovers.Remove(mover);
|
|
}
|
|
|
|
// This is to prevent duplicating the singleton gameobject on script recompiles
|
|
private void OnDisable()
|
|
{
|
|
Destroy(this.gameObject);
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
_instance = this;
|
|
}
|
|
|
|
private void FixedUpdate()
|
|
{
|
|
if (Settings.AutoSimulation)
|
|
{
|
|
float deltaTime = Time.deltaTime;
|
|
|
|
if (Settings.Interpolate)
|
|
{
|
|
PreSimulationInterpolationUpdate(deltaTime);
|
|
}
|
|
|
|
Simulate(deltaTime, CharacterMotors, PhysicsMovers);
|
|
|
|
if (Settings.Interpolate)
|
|
{
|
|
PostSimulationInterpolationUpdate(deltaTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
if (Settings.Interpolate)
|
|
{
|
|
CustomInterpolationUpdate();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remembers the point to interpolate from for KinematicCharacterMotors and PhysicsMovers
|
|
/// </summary>
|
|
public static void PreSimulationInterpolationUpdate(float deltaTime)
|
|
{
|
|
// Save pre-simulation poses and place transform at transient pose
|
|
for (int i = 0; i < CharacterMotors.Count; i++)
|
|
{
|
|
KinematicCharacterMotor motor = CharacterMotors[i];
|
|
|
|
motor.InitialTickPosition = motor.TransientPosition;
|
|
motor.InitialTickRotation = motor.TransientRotation;
|
|
|
|
motor.Transform.SetPositionAndRotation(motor.TransientPosition, motor.TransientRotation);
|
|
}
|
|
|
|
for (int i = 0; i < PhysicsMovers.Count; i++)
|
|
{
|
|
PhysicsMover mover = PhysicsMovers[i];
|
|
|
|
mover.InitialTickPosition = mover.TransientPosition;
|
|
mover.InitialTickRotation = mover.TransientRotation;
|
|
|
|
mover.Transform.SetPositionAndRotation(mover.TransientPosition, mover.TransientRotation);
|
|
mover.Rigidbody.position = mover.TransientPosition;
|
|
mover.Rigidbody.rotation = mover.TransientRotation;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ticks characters and/or movers
|
|
/// </summary>
|
|
public static void Simulate(float deltaTime, List<KinematicCharacterMotor> motors, List<PhysicsMover> movers)
|
|
{
|
|
int characterMotorsCount = motors.Count;
|
|
int physicsMoversCount = movers.Count;
|
|
|
|
#pragma warning disable 0162
|
|
// Update PhysicsMover velocities
|
|
for (int i = 0; i < physicsMoversCount; i++)
|
|
{
|
|
movers[i].VelocityUpdate(deltaTime);
|
|
}
|
|
|
|
// Character controller update phase 1
|
|
for (int i = 0; i < characterMotorsCount; i++)
|
|
{
|
|
motors[i].UpdatePhase1(deltaTime);
|
|
}
|
|
|
|
// Simulate PhysicsMover displacement
|
|
for (int i = 0; i < physicsMoversCount; i++)
|
|
{
|
|
PhysicsMover mover = movers[i];
|
|
|
|
mover.Transform.SetPositionAndRotation(mover.TransientPosition, mover.TransientRotation);
|
|
mover.Rigidbody.position = mover.TransientPosition;
|
|
mover.Rigidbody.rotation = mover.TransientRotation;
|
|
}
|
|
|
|
// Character controller update phase 2 and move
|
|
for (int i = 0; i < characterMotorsCount; i++)
|
|
{
|
|
KinematicCharacterMotor motor = motors[i];
|
|
|
|
motor.UpdatePhase2(deltaTime);
|
|
|
|
motor.Transform.SetPositionAndRotation(motor.TransientPosition, motor.TransientRotation);
|
|
}
|
|
#pragma warning restore 0162
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initiates the interpolation for KinematicCharacterMotors and PhysicsMovers
|
|
/// </summary>
|
|
public static void PostSimulationInterpolationUpdate(float deltaTime)
|
|
{
|
|
_lastCustomInterpolationStartTime = Time.time;
|
|
_lastCustomInterpolationDeltaTime = deltaTime;
|
|
|
|
// Return interpolated roots to their initial poses
|
|
for (int i = 0; i < CharacterMotors.Count; i++)
|
|
{
|
|
KinematicCharacterMotor motor = CharacterMotors[i];
|
|
|
|
motor.Transform.SetPositionAndRotation(motor.InitialTickPosition, motor.InitialTickRotation);
|
|
}
|
|
|
|
for (int i = 0; i < PhysicsMovers.Count; i++)
|
|
{
|
|
PhysicsMover mover = PhysicsMovers[i];
|
|
|
|
if (mover.MoveWithPhysics)
|
|
{
|
|
mover.Rigidbody.position = mover.InitialTickPosition;
|
|
mover.Rigidbody.rotation = mover.InitialTickRotation;
|
|
|
|
mover.Rigidbody.MovePosition(mover.TransientPosition);
|
|
mover.Rigidbody.MoveRotation(mover.TransientRotation);
|
|
}
|
|
else
|
|
{
|
|
mover.Rigidbody.position = (mover.TransientPosition);
|
|
mover.Rigidbody.rotation = (mover.TransientRotation);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles per-frame interpolation
|
|
/// </summary>
|
|
private static void CustomInterpolationUpdate()
|
|
{
|
|
float interpolationFactor = Mathf.Clamp01((Time.time - _lastCustomInterpolationStartTime) / _lastCustomInterpolationDeltaTime);
|
|
|
|
// Handle characters interpolation
|
|
for (int i = 0; i < CharacterMotors.Count; i++)
|
|
{
|
|
KinematicCharacterMotor motor = CharacterMotors[i];
|
|
|
|
motor.Transform.SetPositionAndRotation(
|
|
Vector3.Lerp(motor.InitialTickPosition, motor.TransientPosition, interpolationFactor),
|
|
Quaternion.Slerp(motor.InitialTickRotation, motor.TransientRotation, interpolationFactor));
|
|
}
|
|
|
|
// Handle PhysicsMovers interpolation
|
|
for (int i = 0; i < PhysicsMovers.Count; i++)
|
|
{
|
|
PhysicsMover mover = PhysicsMovers[i];
|
|
|
|
mover.Transform.SetPositionAndRotation(
|
|
Vector3.Lerp(mover.InitialTickPosition, mover.TransientPosition, interpolationFactor),
|
|
Quaternion.Slerp(mover.InitialTickRotation, mover.TransientRotation, interpolationFactor));
|
|
|
|
Vector3 newPos = mover.Transform.position;
|
|
Quaternion newRot = mover.Transform.rotation;
|
|
mover.PositionDeltaFromInterpolation = newPos - mover.LatestInterpolationPosition;
|
|
mover.RotationDeltaFromInterpolation = Quaternion.Inverse(mover.LatestInterpolationRotation) * newRot;
|
|
mover.LatestInterpolationPosition = newPos;
|
|
mover.LatestInterpolationRotation = newRot;
|
|
}
|
|
}
|
|
}
|
|
} |