299 lines
11 KiB
C#
299 lines
11 KiB
C#
using FishNet.Utility.Extension;
|
|
using FishNet.Object;
|
|
using System.Runtime.CompilerServices;
|
|
using UnityEngine;
|
|
|
|
namespace FishNet.Component.Prediction
|
|
{
|
|
internal class PredictedObjectOwnerSmoother
|
|
{
|
|
#region Serialized.
|
|
/// <summary>
|
|
/// Transform which holds the graphical features of this object. This transform will be smoothed when desynchronizations occur.
|
|
/// </summary>
|
|
private Transform _graphicalObject;
|
|
/// <summary>
|
|
/// Sets GraphicalObject.
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
public void SetGraphicalObject(Transform value)
|
|
{
|
|
_graphicalObject = value;
|
|
_networkBehaviour.transform.SetTransformOffsets(value, ref _graphicalInstantiatedOffsetPosition, ref _graphicalInstantiatedOffsetRotation);
|
|
}
|
|
/// <summary>
|
|
/// NetworkBehaviour which is using this object.
|
|
/// </summary>
|
|
private NetworkBehaviour _networkBehaviour;
|
|
/// <summary>
|
|
/// How far the transform must travel in a single update to cause a teleport rather than smoothing. Using 0f will teleport every update.
|
|
/// </summary>
|
|
private float _teleportThreshold = 1f;
|
|
/// <summary>
|
|
/// How far in the past to keep the graphical object when owner.
|
|
/// </summary>
|
|
private byte _interpolation = 1;
|
|
/// <summary>
|
|
/// Sets the interpolation value to use when the owner of this object.
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
public void SetInterpolation(byte value) => _interpolation = value;
|
|
#endregion
|
|
|
|
#region Private.
|
|
/// <summary>
|
|
/// World position before transform was predicted or reset.
|
|
/// </summary>
|
|
private Vector3 _graphicalStartPosition;
|
|
/// <summary>
|
|
/// World rotation before transform was predicted or reset.
|
|
/// </summary>
|
|
private Quaternion _graphicalStartRotation;
|
|
/// <summary>
|
|
/// GraphicalObject position difference from the PredictedObject when this is initialized.
|
|
/// </summary>
|
|
private Vector3 _graphicalInstantiatedOffsetPosition;
|
|
/// <summary>
|
|
/// How quickly to move towards TargetPosition.
|
|
/// </summary>
|
|
private float _positionMoveRate = -2;
|
|
/// <summary>
|
|
/// GraphicalObject rotation difference from the PredictedObject when this is initialized.
|
|
/// </summary>
|
|
private Quaternion _graphicalInstantiatedOffsetRotation;
|
|
/// <summary>
|
|
/// How quickly to move towards TargetRotation.
|
|
/// </summary>
|
|
private float _rotationMoveRate = -2;
|
|
/// <summary>
|
|
/// True if OnPreTick was received this frame.
|
|
/// </summary>
|
|
private bool _preTickReceived;
|
|
/// <summary>
|
|
/// True to move towards position goals.
|
|
/// </summary>
|
|
private bool _smoothPosition;
|
|
/// <summary>
|
|
/// True to move towards rotation goals.
|
|
/// </summary>
|
|
private bool _smoothRotation;
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Initializes this script for use.
|
|
/// </summary>
|
|
public void Initialize(NetworkBehaviour nb, Vector3 instantiatedOffsetPosition, Quaternion instantiatedOffsetRotation, Transform graphicalObject
|
|
, bool smoothPosition, bool smoothRotation, byte interpolation, float teleportThreshold)
|
|
{
|
|
_networkBehaviour = nb;
|
|
_graphicalInstantiatedOffsetPosition = instantiatedOffsetPosition;
|
|
_graphicalInstantiatedOffsetRotation = instantiatedOffsetRotation;
|
|
_graphicalObject = graphicalObject;
|
|
|
|
_smoothPosition = smoothPosition;
|
|
_smoothRotation = smoothRotation;
|
|
|
|
_interpolation = interpolation;
|
|
_teleportThreshold = teleportThreshold;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called every frame.
|
|
/// </summary>
|
|
public void ManualUpdate()
|
|
{
|
|
MoveToTarget();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the TimeManager invokes OnPreTick.
|
|
/// </summary>
|
|
public void OnPreTick()
|
|
{
|
|
if (CanSmooth())
|
|
{
|
|
_preTickReceived = true;
|
|
/* Only snap to destination if interpolation is 1.
|
|
* This ensures the graphics will be at the proper location
|
|
* before the next movement rates are calculated. */
|
|
if (_interpolation == 1)
|
|
ResetGraphicalToInstantiatedProperties(true, true);
|
|
|
|
SetGraphicalPreviousProperties();
|
|
}
|
|
}
|
|
|
|
public void OnPostTick()
|
|
{
|
|
if (CanSmooth() && _preTickReceived)
|
|
{
|
|
_preTickReceived = false;
|
|
ResetGraphicalToPreviousProperties();
|
|
SetGraphicalMoveRates();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns if prediction can be used on this rigidbody.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private bool CanSmooth()
|
|
{
|
|
if (_interpolation == 0)
|
|
return false;
|
|
//Only owner needs smoothing.
|
|
if (!_networkBehaviour.IsOwner && !_networkBehaviour.IsHost)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves transform to target values.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void MoveToTarget()
|
|
{
|
|
//Not set, meaning movement doesnt need to happen or completed.
|
|
if (_positionMoveRate == -2f && _rotationMoveRate == -2f)
|
|
return;
|
|
|
|
Vector3 posGoal = GetGraphicalGoalPosition();
|
|
Quaternion rotGoal = GetGraphicalGoalRotation();
|
|
|
|
/* Only try to update properties if they have a valid move rate.
|
|
* Properties may have 0f move rate if they did not change. */
|
|
Transform t = _graphicalObject;
|
|
float delta = Time.deltaTime;
|
|
|
|
//Position.
|
|
if (SmoothPosition())
|
|
{
|
|
if (_positionMoveRate == -1f)
|
|
ResetGraphicalToInstantiatedProperties(true, false);
|
|
else if (_positionMoveRate > 0f)
|
|
t.position = Vector3.MoveTowards(t.position, posGoal, _positionMoveRate * delta);
|
|
}
|
|
|
|
//Rotation.
|
|
if (SmoothRotation())
|
|
{
|
|
if (_rotationMoveRate == -1f)
|
|
ResetGraphicalToInstantiatedProperties(false, true);
|
|
else if (_rotationMoveRate > 0f)
|
|
t.rotation = Quaternion.RotateTowards(t.rotation, rotGoal, _rotationMoveRate * delta);
|
|
}
|
|
|
|
if (GraphicalObjectMatches(posGoal, rotGoal))
|
|
{
|
|
_positionMoveRate = -2f;
|
|
_rotationMoveRate = -2f;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns if this transform matches arguments.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private bool GraphicalObjectMatches(Vector3 position, Quaternion rotation)
|
|
{
|
|
bool positionMatches = (!_smoothPosition || (_graphicalObject.position == position));
|
|
bool rotationMatches = (!_smoothRotation || (_graphicalObject.rotation == rotation));
|
|
return (positionMatches && rotationMatches);
|
|
}
|
|
|
|
/// <summary>
|
|
/// True to smooth position. When false the graphicalObjects property will not be updated.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private bool SmoothPosition() => (_smoothPosition && (_networkBehaviour.IsOwner || _networkBehaviour.IsHost));
|
|
/// <summary>
|
|
/// True to smooth rotation. When false the graphicalObjects property will not be updated.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private bool SmoothRotation() => (_smoothRotation && (_networkBehaviour.IsOwner || _networkBehaviour.IsHost));
|
|
|
|
/// <summary>
|
|
/// Sets Position and Rotation move rates to reach Target datas.
|
|
/// </summary>
|
|
private void SetGraphicalMoveRates()
|
|
{
|
|
float delta = ((float)_networkBehaviour.TimeManager.TickDelta * _interpolation);
|
|
|
|
float distance;
|
|
distance = Vector3.Distance(_graphicalObject.position, GetGraphicalGoalPosition());
|
|
//If qualifies for teleporting.
|
|
if (_teleportThreshold != -1f && distance >= _teleportThreshold)
|
|
{
|
|
_positionMoveRate = -1f;
|
|
_rotationMoveRate = -1f;
|
|
}
|
|
//Smoothing.
|
|
else
|
|
{
|
|
_positionMoveRate = (distance / delta);
|
|
distance = Quaternion.Angle(_graphicalObject.rotation, GetGraphicalGoalRotation());
|
|
if (distance > 0f)
|
|
_rotationMoveRate = (distance / delta);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a goal position for the graphical object.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private Vector3 GetGraphicalGoalPosition()
|
|
{
|
|
if (SmoothPosition())
|
|
return (_networkBehaviour.transform.position + _graphicalInstantiatedOffsetPosition);
|
|
else
|
|
return _graphicalObject.position;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a goal rotation for the graphical object.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private Quaternion GetGraphicalGoalRotation()
|
|
{
|
|
if (SmoothRotation())
|
|
return (_graphicalInstantiatedOffsetRotation * _networkBehaviour.transform.rotation);
|
|
else
|
|
return _graphicalObject.rotation;
|
|
}
|
|
/// <summary>
|
|
/// Caches the graphical object' current position and rotation.
|
|
/// </summary>
|
|
private void SetGraphicalPreviousProperties()
|
|
{
|
|
_graphicalStartPosition = _graphicalObject.position;
|
|
_graphicalStartRotation = _graphicalObject.rotation;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the graphical object to cached position and rotation of the transform.
|
|
/// </summary>
|
|
private void ResetGraphicalToPreviousProperties()
|
|
{
|
|
_graphicalObject.SetPositionAndRotation(_graphicalStartPosition, _graphicalStartRotation);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the graphical object to it's transform offsets during instantiation.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void ResetGraphicalToInstantiatedProperties(bool position, bool rotation)
|
|
{
|
|
if (position)
|
|
_graphicalObject.position = GetGraphicalGoalPosition();
|
|
if (rotation)
|
|
_graphicalObject.rotation = GetGraphicalGoalRotation();
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} |