using FishNet.Utility.Extension; using FishNet.Object; using System.Runtime.CompilerServices; using UnityEngine; namespace FishNet.Component.Prediction { internal class PredictedObjectOwnerSmoother { #region Serialized. /// /// Transform which holds the graphical features of this object. This transform will be smoothed when desynchronizations occur. /// private Transform _graphicalObject; /// /// Sets GraphicalObject. /// /// public void SetGraphicalObject(Transform value) { _graphicalObject = value; _networkBehaviour.transform.SetTransformOffsets(value, ref _graphicalInstantiatedOffsetPosition, ref _graphicalInstantiatedOffsetRotation); } /// /// NetworkBehaviour which is using this object. /// private NetworkBehaviour _networkBehaviour; /// /// How far the transform must travel in a single update to cause a teleport rather than smoothing. Using 0f will teleport every update. /// private float _teleportThreshold = 1f; /// /// How far in the past to keep the graphical object when owner. /// private byte _interpolation = 1; /// /// Sets the interpolation value to use when the owner of this object. /// /// public void SetInterpolation(byte value) => _interpolation = value; #endregion #region Private. /// /// World position before transform was predicted or reset. /// private Vector3 _graphicalStartPosition; /// /// World rotation before transform was predicted or reset. /// private Quaternion _graphicalStartRotation; /// /// GraphicalObject position difference from the PredictedObject when this is initialized. /// private Vector3 _graphicalInstantiatedOffsetPosition; /// /// How quickly to move towards TargetPosition. /// private float _positionMoveRate = -2; /// /// GraphicalObject rotation difference from the PredictedObject when this is initialized. /// private Quaternion _graphicalInstantiatedOffsetRotation; /// /// How quickly to move towards TargetRotation. /// private float _rotationMoveRate = -2; /// /// True if OnPreTick was received this frame. /// private bool _preTickReceived; /// /// True to move towards position goals. /// private bool _smoothPosition; /// /// True to move towards rotation goals. /// private bool _smoothRotation; #endregion /// /// Initializes this script for use. /// 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; } /// /// Called every frame. /// public void ManualUpdate() { MoveToTarget(); } /// /// Called when the TimeManager invokes OnPreTick. /// 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(); } } /// /// Returns if prediction can be used on this rigidbody. /// /// private bool CanSmooth() { if (_interpolation == 0) return false; //Only owner needs smoothing. if (!_networkBehaviour.IsOwner && !_networkBehaviour.IsHost) return false; return true; } /// /// Moves transform to target values. /// [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; } } /// /// Returns if this transform matches arguments. /// /// private bool GraphicalObjectMatches(Vector3 position, Quaternion rotation) { bool positionMatches = (!_smoothPosition || (_graphicalObject.position == position)); bool rotationMatches = (!_smoothRotation || (_graphicalObject.rotation == rotation)); return (positionMatches && rotationMatches); } /// /// True to smooth position. When false the graphicalObjects property will not be updated. /// /// private bool SmoothPosition() => (_smoothPosition && (_networkBehaviour.IsOwner || _networkBehaviour.IsHost)); /// /// True to smooth rotation. When false the graphicalObjects property will not be updated. /// /// private bool SmoothRotation() => (_smoothRotation && (_networkBehaviour.IsOwner || _networkBehaviour.IsHost)); /// /// Sets Position and Rotation move rates to reach Target datas. /// 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); } } /// /// Gets a goal position for the graphical object. /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private Vector3 GetGraphicalGoalPosition() { if (SmoothPosition()) return (_networkBehaviour.transform.position + _graphicalInstantiatedOffsetPosition); else return _graphicalObject.position; } /// /// Gets a goal rotation for the graphical object. /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private Quaternion GetGraphicalGoalRotation() { if (SmoothRotation()) return (_graphicalInstantiatedOffsetRotation * _networkBehaviour.transform.rotation); else return _graphicalObject.rotation; } /// /// Caches the graphical object' current position and rotation. /// private void SetGraphicalPreviousProperties() { _graphicalStartPosition = _graphicalObject.position; _graphicalStartRotation = _graphicalObject.rotation; } /// /// Resets the graphical object to cached position and rotation of the transform. /// private void ResetGraphicalToPreviousProperties() { _graphicalObject.SetPositionAndRotation(_graphicalStartPosition, _graphicalStartRotation); } /// /// Resets the graphical object to it's transform offsets during instantiation. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ResetGraphicalToInstantiatedProperties(bool position, bool rotation) { if (position) _graphicalObject.position = GetGraphicalGoalPosition(); if (rotation) _graphicalObject.rotation = GetGraphicalGoalRotation(); } } }