170 lines
6.4 KiB
C#
170 lines
6.4 KiB
C#
using UnityEngine;
|
|
using Vector3 = UnityEngine.Vector3;
|
|
|
|
public readonly struct ControlledPlayerCorrectionSettings
|
|
{
|
|
public ControlledPlayerCorrectionSettings(
|
|
float authoritativeCadenceSeconds,
|
|
float moveSpeed,
|
|
float turnSpeedDegreesPerSecond,
|
|
float snapDistanceMultiplier = 3f,
|
|
float snapAngleMultiplier = 15f)
|
|
{
|
|
AuthoritativeCadenceSeconds = Mathf.Max(0f, authoritativeCadenceSeconds);
|
|
MoveSpeed = Mathf.Max(0f, moveSpeed);
|
|
TurnSpeedDegreesPerSecond = Mathf.Max(0f, turnSpeedDegreesPerSecond);
|
|
SnapDistanceMultiplier = Mathf.Max(1f, snapDistanceMultiplier);
|
|
SnapAngleMultiplier = Mathf.Max(1f, snapAngleMultiplier);
|
|
}
|
|
|
|
public float AuthoritativeCadenceSeconds { get; }
|
|
|
|
public float MoveSpeed { get; }
|
|
|
|
public float TurnSpeedDegreesPerSecond { get; }
|
|
|
|
public float SnapDistanceMultiplier { get; }
|
|
|
|
public float SnapAngleMultiplier { get; }
|
|
|
|
public float MaxBoundedPositionCorrection => MoveSpeed * AuthoritativeCadenceSeconds;
|
|
|
|
public float MaxBoundedRotationCorrectionDegrees => TurnSpeedDegreesPerSecond * AuthoritativeCadenceSeconds;
|
|
|
|
public float SnapPositionThreshold => MaxBoundedPositionCorrection * SnapDistanceMultiplier;
|
|
|
|
public float SnapRotationThresholdDegrees => MaxBoundedRotationCorrectionDegrees * SnapAngleMultiplier;
|
|
|
|
public int MaxCorrectionSteps => Mathf.Max(1, Mathf.CeilToInt(SnapDistanceMultiplier));
|
|
}
|
|
|
|
public readonly struct ControlledPlayerVisualCorrectionState
|
|
{
|
|
public static ControlledPlayerVisualCorrectionState None => default;
|
|
|
|
public ControlledPlayerVisualCorrectionState(Vector3 targetPosition, Quaternion targetRotation, int remainingStepBudget)
|
|
{
|
|
TargetPosition = targetPosition;
|
|
TargetRotation = targetRotation;
|
|
RemainingStepBudget = Mathf.Max(0, remainingStepBudget);
|
|
}
|
|
|
|
public Vector3 TargetPosition { get; }
|
|
|
|
public Quaternion TargetRotation { get; }
|
|
|
|
public int RemainingStepBudget { get; }
|
|
|
|
public bool IsActive => RemainingStepBudget > 0;
|
|
}
|
|
|
|
public readonly struct ControlledPlayerCorrectionResult
|
|
{
|
|
public ControlledPlayerCorrectionResult(
|
|
Vector3 position,
|
|
Quaternion rotation,
|
|
bool usedHardSnap,
|
|
ControlledPlayerVisualCorrectionState nextState,
|
|
float positionError,
|
|
float rotationErrorDegrees)
|
|
{
|
|
Position = position;
|
|
Rotation = rotation;
|
|
UsedHardSnap = usedHardSnap;
|
|
NextState = nextState;
|
|
PositionError = positionError;
|
|
RotationErrorDegrees = rotationErrorDegrees;
|
|
}
|
|
|
|
public Vector3 Position { get; }
|
|
|
|
public Quaternion Rotation { get; }
|
|
|
|
public bool UsedHardSnap { get; }
|
|
|
|
public ControlledPlayerVisualCorrectionState NextState { get; }
|
|
|
|
public float PositionError { get; }
|
|
|
|
public float RotationErrorDegrees { get; }
|
|
}
|
|
|
|
public static class ControlledPlayerCorrection
|
|
{
|
|
public static ControlledPlayerCorrectionResult Resolve(
|
|
Vector3 currentPosition,
|
|
Quaternion currentRotation,
|
|
Vector3 targetPosition,
|
|
Quaternion targetRotation,
|
|
ControlledPlayerCorrectionSettings settings)
|
|
{
|
|
return Resolve(
|
|
currentPosition,
|
|
currentRotation,
|
|
targetPosition,
|
|
targetRotation,
|
|
settings,
|
|
ControlledPlayerVisualCorrectionState.None);
|
|
}
|
|
|
|
public static ControlledPlayerCorrectionResult Resolve(
|
|
Vector3 currentPosition,
|
|
Quaternion currentRotation,
|
|
Vector3 targetPosition,
|
|
Quaternion targetRotation,
|
|
ControlledPlayerCorrectionSettings settings,
|
|
ControlledPlayerVisualCorrectionState activeCorrection)
|
|
{
|
|
var positionError = Vector3.Distance(currentPosition, targetPosition);
|
|
var rotationError = Quaternion.Angle(currentRotation, targetRotation);
|
|
var boundedPositionCorrection = settings.MaxBoundedPositionCorrection;
|
|
var boundedRotationCorrection = settings.MaxBoundedRotationCorrectionDegrees;
|
|
|
|
if (positionError <= Mathf.Epsilon && rotationError <= Mathf.Epsilon)
|
|
{
|
|
return new ControlledPlayerCorrectionResult(targetPosition, targetRotation, false, ControlledPlayerVisualCorrectionState.None, positionError, rotationError);
|
|
}
|
|
|
|
if (boundedPositionCorrection <= Mathf.Epsilon && boundedRotationCorrection <= Mathf.Epsilon)
|
|
{
|
|
return new ControlledPlayerCorrectionResult(targetPosition, targetRotation, true, ControlledPlayerVisualCorrectionState.None, positionError, rotationError);
|
|
}
|
|
|
|
if (positionError > settings.SnapPositionThreshold || rotationError > settings.SnapRotationThresholdDegrees)
|
|
{
|
|
return new ControlledPlayerCorrectionResult(targetPosition, targetRotation, true, ControlledPlayerVisualCorrectionState.None, positionError, rotationError);
|
|
}
|
|
|
|
var remainingStepBudget = activeCorrection.IsActive
|
|
? activeCorrection.RemainingStepBudget
|
|
: settings.MaxCorrectionSteps;
|
|
if (remainingStepBudget <= 0)
|
|
{
|
|
return new ControlledPlayerCorrectionResult(targetPosition, targetRotation, true, ControlledPlayerVisualCorrectionState.None, positionError, rotationError);
|
|
}
|
|
|
|
var correctedPosition = Vector3.MoveTowards(currentPosition, targetPosition, boundedPositionCorrection);
|
|
var correctedRotation = Quaternion.RotateTowards(currentRotation, targetRotation, boundedRotationCorrection);
|
|
var nextPositionError = Vector3.Distance(correctedPosition, targetPosition);
|
|
var nextRotationError = Quaternion.Angle(correctedRotation, targetRotation);
|
|
if (nextPositionError <= Mathf.Epsilon && nextRotationError <= Mathf.Epsilon)
|
|
{
|
|
return new ControlledPlayerCorrectionResult(targetPosition, targetRotation, false, ControlledPlayerVisualCorrectionState.None, positionError, rotationError);
|
|
}
|
|
|
|
remainingStepBudget--;
|
|
if (remainingStepBudget <= 0)
|
|
{
|
|
return new ControlledPlayerCorrectionResult(targetPosition, targetRotation, true, ControlledPlayerVisualCorrectionState.None, positionError, rotationError);
|
|
}
|
|
|
|
return new ControlledPlayerCorrectionResult(
|
|
correctedPosition,
|
|
correctedRotation,
|
|
false,
|
|
new ControlledPlayerVisualCorrectionState(targetPosition, targetRotation, remainingStepBudget),
|
|
positionError,
|
|
rotationError);
|
|
}
|
|
}
|