RUDPClient/Assets/Scripts/ControlledPlayerCorrection.cs

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);
}
}