上传YomovSDK
This commit is contained in:
@@ -0,0 +1,887 @@
|
||||
//using GameKit.Utilities;
|
||||
//using FishNet.Transporting;
|
||||
//using FishNet.Utility.Extension;
|
||||
//using System.Runtime.CompilerServices;
|
||||
//using UnityEngine;
|
||||
//using FishNet.Managing.Timing;
|
||||
//using System.Collections.Generic;
|
||||
//using FishNet.Managing.Scened;
|
||||
|
||||
//namespace FishNet.Object.Prediction
|
||||
//{
|
||||
// internal class AdaptiveInterpolationSmoother
|
||||
// {
|
||||
// #if PREDICTION_V2
|
||||
|
||||
// #region Types.
|
||||
|
||||
// /// <summary>
|
||||
// /// Data on a goal to move towards.
|
||||
// /// </summary>
|
||||
// private class GoalData : IResettable
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// True if this GoalData is valid.
|
||||
// /// </summary>
|
||||
// public bool IsValid;
|
||||
|
||||
// /// <summary>
|
||||
// /// Tick of the data this GoalData is for.
|
||||
// /// </summary>
|
||||
// public uint DataTick;
|
||||
|
||||
// /// <summary>
|
||||
// /// Data on how fast to move to transform values.
|
||||
// /// </summary>
|
||||
// public RateData MoveRates = new RateData();
|
||||
|
||||
// /// <summary>
|
||||
// /// Transform values to move towards.
|
||||
// /// </summary>
|
||||
// public TransformPropertiesCls TransformProperties = new TransformPropertiesCls();
|
||||
|
||||
// public GoalData() { }
|
||||
|
||||
// public void InitializeState() { }
|
||||
|
||||
// public void ResetState()
|
||||
// {
|
||||
// DataTick = 0;
|
||||
// TransformProperties.ResetState();
|
||||
// MoveRates.ResetState();
|
||||
// IsValid = false;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Updates values using a GoalData.
|
||||
// /// </summary>
|
||||
// public void Update(GoalData gd)
|
||||
// {
|
||||
// DataTick = gd.DataTick;
|
||||
// MoveRates.Update(gd.MoveRates);
|
||||
// TransformProperties.Update(gd.TransformProperties);
|
||||
// IsValid = true;
|
||||
// }
|
||||
|
||||
// public void Update(uint dataTick, RateData rd, TransformPropertiesCls tp)
|
||||
// {
|
||||
// DataTick = dataTick;
|
||||
// MoveRates = rd;
|
||||
// TransformProperties = tp;
|
||||
// IsValid = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// How fast to move to values.
|
||||
// /// </summary>
|
||||
// private class RateData : IResettable
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// Rate for position after smart calculations.
|
||||
// /// </summary>
|
||||
// public float Position;
|
||||
|
||||
// /// <summary>
|
||||
// /// Rate for rotation after smart calculations.
|
||||
// /// </summary>
|
||||
// public float Rotation;
|
||||
|
||||
// /// <summary>
|
||||
// /// Number of ticks the rates are calculated for.
|
||||
// /// If TickSpan is 2 then the rates are calculated under the assumption the transform changed over 2 ticks.
|
||||
// /// </summary>
|
||||
// public uint TickSpan;
|
||||
|
||||
// /// <summary>
|
||||
// /// Time remaining until transform is expected to reach it's goal.
|
||||
// /// </summary>
|
||||
// internal float TimeRemaining;
|
||||
|
||||
// public RateData() { }
|
||||
|
||||
// public void InitializeState() { }
|
||||
|
||||
// /// <summary>
|
||||
// /// Resets values for re-use.
|
||||
// /// </summary>
|
||||
// public void ResetState()
|
||||
// {
|
||||
// Position = 0f;
|
||||
// Rotation = 0f;
|
||||
// TickSpan = 0;
|
||||
// TimeRemaining = 0f;
|
||||
// }
|
||||
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// public void Update(RateData rd)
|
||||
// {
|
||||
// Update(rd.Position, rd.Rotation, rd.TickSpan, rd.TimeRemaining);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Updates rates.
|
||||
// /// </summary>
|
||||
// public void Update(float position, float rotation, uint tickSpan, float timeRemaining)
|
||||
// {
|
||||
// Position = position;
|
||||
// Rotation = rotation;
|
||||
// TickSpan = tickSpan;
|
||||
// TimeRemaining = timeRemaining;
|
||||
// }
|
||||
// }
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Private.
|
||||
|
||||
// /// <summary>
|
||||
// /// Offsets of the graphical object when this was initialized.
|
||||
// /// </summary>
|
||||
// private TransformProperties _graphicalInitializedValues;
|
||||
|
||||
// /// <summary>
|
||||
// /// Offsets of the graphical object during PreTick. This could also be the offset PreReplay.
|
||||
// /// </summary>
|
||||
// private TransformProperties _graphicalPretickValues;
|
||||
|
||||
// /// <summary>
|
||||
// /// Offsets of the root transform before simulating. This could be PreTick, or PreReplay.
|
||||
// /// </summary>
|
||||
// private TransformProperties _rootPreSimulateValues;
|
||||
|
||||
// /// <summary>
|
||||
// /// SmoothingData to use.
|
||||
// /// </summary>
|
||||
// private AdaptiveInterpolationSmoothingData _smoothingData;
|
||||
|
||||
// /// <summary>
|
||||
// /// Current interpolation value. This changes based on ping and settings.
|
||||
// /// </summary>
|
||||
// private long _currentInterpolation = 2;
|
||||
|
||||
// /// <summary>
|
||||
// /// Target interpolation when collision is exited. This changes based on ping and settings.
|
||||
// /// </summary>
|
||||
// private uint _targetInterpolation;
|
||||
|
||||
// /// <summary>
|
||||
// /// Target interpolation when collision is entered. This changes based on ping and settings.
|
||||
// /// </summary>
|
||||
// private uint _targetCollisionInterpolation;
|
||||
|
||||
// /// <summary>
|
||||
// /// Current GoalData being used.
|
||||
// /// </summary>
|
||||
// private GoalData _currentGoalData = new GoalData();
|
||||
|
||||
// /// <summary>
|
||||
// /// GoalDatas to move towards.
|
||||
// /// </summary>
|
||||
// //private RingBuffer<GoalData> _goalDatas = new RingBuffer<GoalData>();
|
||||
// private List<GoalData> _goalDatas = new List<GoalData>();
|
||||
|
||||
// /// <summary>
|
||||
// /// Last ping value when it was checked.
|
||||
// /// </summary>
|
||||
// private long _lastPing = long.MinValue;
|
||||
|
||||
// /// <summary>
|
||||
// /// Cached NetworkObject reference in SmoothingData for performance.
|
||||
// /// </summary>
|
||||
// private NetworkObject _networkObject;
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Const.
|
||||
|
||||
// /// <summary>
|
||||
// /// Multiplier to apply to movement speed when buffer is over interpolation.
|
||||
// /// </summary>
|
||||
// private const float OVERFLOW_MULTIPLIER = 0.1f;
|
||||
|
||||
// /// <summary>
|
||||
// /// Multiplier to apply to movement speed when buffer is under interpolation.
|
||||
// /// </summary>
|
||||
// private const float UNDERFLOW_MULTIPLIER = 0.02f;
|
||||
|
||||
// #endregion
|
||||
|
||||
// public AdaptiveInterpolationSmoother()
|
||||
// {
|
||||
// /* Initialize for up to 50
|
||||
// * goal datas. Anything beyond that
|
||||
// * is unreasonable. */
|
||||
// //_goalDatas.Initialize(50);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Initializes this for use.
|
||||
// /// </summary>
|
||||
// internal void Initialize(AdaptiveInterpolationSmoothingData data)
|
||||
// {
|
||||
// _smoothingData = data;
|
||||
// _networkObject = data.NetworkObject;
|
||||
// SetGraphicalObject(data.GraphicalObject);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// <summary>
|
||||
// /// Called every frame.
|
||||
// /// </summary>
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// public void Update()
|
||||
// {
|
||||
// if (CanSmooth())
|
||||
// MoveToTarget();
|
||||
// }
|
||||
|
||||
// private string GetGoalDataTicks()
|
||||
// {
|
||||
// string result = string.Empty;
|
||||
// foreach (var item in _goalDatas)
|
||||
// result += item.DataTick + ", ";
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// private string _preTickGoalDatas = string.Empty;
|
||||
|
||||
// /// <summary>
|
||||
// /// Called when the TimeManager invokes OnPreTick.
|
||||
// /// </summary>
|
||||
// public void OnPreTick()
|
||||
// {
|
||||
// _preTickGoalDatas = GetGoalDataTicks();
|
||||
|
||||
// if (CanSmooth())
|
||||
// {
|
||||
// UpdatePingInterpolation();
|
||||
// _graphicalPretickValues.Update(_smoothingData.GraphicalObject);
|
||||
|
||||
// //Update the last post simulate data.
|
||||
// UpdateRootPreSimulateOffsets();
|
||||
// //UpdateRootPreSimulateOffsets(_networkObject.ReplicateTick.Value(_networkObject.TimeManager) - 1);
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Called when the TimeManager invokes OnPostTick.
|
||||
// /// </summary>
|
||||
// public void OnPostTick()
|
||||
// {
|
||||
// if (CanSmooth())
|
||||
// {
|
||||
// //Move towards target interpolation.
|
||||
// UpdateCurrentInterpolation();
|
||||
// //Reset graphics to start graphicals transforms properties.
|
||||
// _smoothingData.GraphicalObject.SetPositionAndRotation(_graphicalPretickValues.Position, _graphicalPretickValues.Rotation);
|
||||
// //Create a goal data for new transform position.
|
||||
// uint tick = _networkObject.LastUnorderedReplicateTick;
|
||||
// //Debug.Log($"GoalDatas count {_goalDatas.Count}. Tick {tick}. LocalTick {_networkObject.TimeManager.LocalTick}");
|
||||
// CreatePostSimulateGoalData(tick, true);
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Called before a reconcile runs a replay.
|
||||
// /// </summary>
|
||||
// public void OnPreReplicateReplay(uint clientTick, uint serverTick)
|
||||
// {
|
||||
// //Update the last post simulate data.
|
||||
// if (CanSmooth())
|
||||
// {
|
||||
// UpdateRootPreSimulateOffsets();
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Called after a reconcile runs a replay.
|
||||
// /// </summary>
|
||||
// public void OnPostReplicateReplay(uint clientTick, uint serverTick)
|
||||
// {
|
||||
// if (CanSmooth())
|
||||
// {
|
||||
// /* Create new goal data from the replay.
|
||||
// * This must be done every replay. If a desync
|
||||
// * did occur then the goaldatas would be different
|
||||
// * from what they were previously. */
|
||||
// uint tick = _networkObject.LastUnorderedReplicateTick;
|
||||
// CreatePostSimulateGoalData(tick, false);
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Updates rootPostSimulateOffsets value with root transform's current values.
|
||||
// /// </summary>
|
||||
// private void UpdateRootPreSimulateOffsets()
|
||||
// {
|
||||
// Transform t = _networkObject.transform;
|
||||
// _rootPreSimulateValues.Update(t.position, t.rotation);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Moves current interpolation to target interpolation.
|
||||
// /// </summary>
|
||||
// private void UpdateCurrentInterpolation()
|
||||
// {
|
||||
// AdaptiveInterpolationSmoothingData data = _smoothingData;
|
||||
// bool colliding = _networkObject.CollidingWithLocalClient();
|
||||
// if (colliding)
|
||||
// _currentInterpolation -= data.InterpolationDecreaseStep;
|
||||
// else
|
||||
// _currentInterpolation += data.InterpolationIncreaseStep;
|
||||
|
||||
// _currentInterpolation = (long)Mathf.Clamp(_currentInterpolation, _targetCollisionInterpolation, _targetInterpolation);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Updates interpolation values based on ping.
|
||||
// /// </summary>
|
||||
// private void UpdatePingInterpolation()
|
||||
// {
|
||||
// /* Only update if ping has changed considerably.
|
||||
// * This will prevent random lag spikes from throwing
|
||||
// * off the interpolation. */
|
||||
// long ping = _networkObject.TimeManager.RoundTripTime;
|
||||
// ulong difference = (ulong)Mathf.Abs(ping - _lastPing);
|
||||
// _lastPing = ping;
|
||||
// //Allow update if ping jump is large enough.
|
||||
// if (difference > 25)
|
||||
// SetTargetSmoothing(ping, false);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Sets target smoothing values.
|
||||
// /// </summary>
|
||||
// /// <param name="setImmediately">True to set current values to targets immediately.</param>
|
||||
// private void SetTargetSmoothing(long ping, bool setImmediately)
|
||||
// {
|
||||
// AdaptiveInterpolationSmoothingData data = _smoothingData;
|
||||
// TimeManager tm = _networkObject.TimeManager;
|
||||
// double interpolationTime = (ping / 1000d) * data.InterpolationPercent;
|
||||
// _targetInterpolation = tm.TimeToTicks(interpolationTime, TickRounding.RoundUp);
|
||||
// double collisionInterpolationTime = (ping / 1000d) * data.CollisionInterpolationPercent;
|
||||
// _targetCollisionInterpolation = tm.TimeToTicks(collisionInterpolationTime, TickRounding.RoundUp);
|
||||
|
||||
// //If to apply values to targets immediately.
|
||||
// if (setImmediately)
|
||||
// _currentInterpolation = (_networkObject.CollidingWithLocalClient()) ? _targetCollisionInterpolation : _targetInterpolation;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Sets GraphicalObject.
|
||||
// /// </summary>
|
||||
// /// <param name="value"></param>
|
||||
// public void SetGraphicalObject(Transform value)
|
||||
// {
|
||||
// _smoothingData.GraphicalObject = value;
|
||||
// _graphicalInitializedValues = _networkObject.transform.GetTransformOffsets(_smoothingData.GraphicalObject);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Returns if the graphics can be smoothed.
|
||||
// /// </summary>
|
||||
// /// <returns></returns>
|
||||
// private bool CanSmooth()
|
||||
// {
|
||||
// if (_networkObject.IsOwner)
|
||||
// return false;
|
||||
|
||||
// if (_networkObject.IsServerOnly)
|
||||
// return false;
|
||||
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Returns if this transform matches arguments.
|
||||
// /// </summary>
|
||||
// /// <returns></returns>
|
||||
// private bool GraphicalObjectMatches(Vector3 position, Quaternion rotation)
|
||||
// {
|
||||
// bool positionMatches = (!_smoothingData.SmoothPosition || (_smoothingData.GraphicalObject.position == position));
|
||||
// bool rotationMatches = (!_smoothingData.SmoothRotation || (_smoothingData.GraphicalObject.rotation == rotation));
|
||||
|
||||
// return (positionMatches && rotationMatches);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Returns if there is any change between two datas.
|
||||
// /// </summary>
|
||||
// private bool HasChanged(TransformPropertiesCls a, TransformPropertiesCls b)
|
||||
// {
|
||||
// return (a.Position != b.Position) ||
|
||||
// (a.Rotation != b.Rotation);
|
||||
// }
|
||||
|
||||
// ///// <summary>
|
||||
// ///// Returns if the transform differs from td.
|
||||
// ///// </summary>
|
||||
// //private bool HasChanged(TransformPropertiesCls tp)
|
||||
// //{
|
||||
// // Transform t = _networkObject.transform;
|
||||
// // bool changed = (tp.Position != t.position) || (tp.Rotation != t.rotation);
|
||||
|
||||
// // return changed;
|
||||
// //}
|
||||
|
||||
// /// <summary>
|
||||
// /// Sets CurrentGoalData to the next in queue. Returns if was set successfully.
|
||||
// /// </summary>
|
||||
// private bool SetCurrentGoalData()
|
||||
// {
|
||||
// if (_goalDatas.Count == 0)
|
||||
// {
|
||||
// _currentGoalData.IsValid = false;
|
||||
|
||||
// return false;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// /* Update to the next goal data.
|
||||
// * We could assign _goalDatas[0] as the
|
||||
// * current and then just remove it from
|
||||
// * the collection. But if did that _currentGoalData
|
||||
// * would have to be disposed first. So all the same,
|
||||
// * we're using the Update then dispose because it's
|
||||
// * a little easier to follow. */
|
||||
// _currentGoalData.Update(_goalDatas[0]);
|
||||
// Vector3 offset = new Vector3(0f, 10f, 0f);
|
||||
// Debug.DrawLine(_goalDatas[0].TransformProperties.Position + offset, _goalDatas[0].TransformProperties.Position - offset, Color.red, 2f);
|
||||
// //Store old and remove it.
|
||||
// ResettableObjectCaches<GoalData>.Store(_goalDatas[0]);
|
||||
// //_goalDatas.RemoveRange(true, 1);
|
||||
// _goalDatas.RemoveRange(0, 1);
|
||||
|
||||
// //Debug.LogWarning($"Frame {Time.frameCount}. CurrentGoalData set to Tick {_currentGoalData.DataTick}. PosX/Y {_currentGoalData.TransformProperties.Position.x}, {_currentGoalData.TransformProperties.Position.y}. Rate {_currentGoalData.MoveRates.Position}");
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Moves to a GoalData. Automatically determins if to use data from server or client.
|
||||
// /// </summary>
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// private void MoveToTarget(float deltaOverride = -1f)
|
||||
// {
|
||||
// /* If the current goal data is not valid then
|
||||
// * try to set a new one. If none are available
|
||||
// * it will remain inactive. */
|
||||
// if (!_currentGoalData.IsValid)
|
||||
// {
|
||||
// if (!SetCurrentGoalData())
|
||||
// return;
|
||||
// }
|
||||
|
||||
// float delta = (deltaOverride != -1f) ? deltaOverride : Time.deltaTime;
|
||||
// /* Once here it's safe to assume the object will be moving.
|
||||
// * Any checks which would stop it from moving be it client
|
||||
// * auth and owner, or server controlled and server, ect,
|
||||
// * would have already been run. */
|
||||
// TransformPropertiesCls td = _currentGoalData.TransformProperties;
|
||||
// RateData rd = _currentGoalData.MoveRates;
|
||||
|
||||
// int queueCount = _goalDatas.Count;
|
||||
// /* Begin moving even if interpolation buffer isn't
|
||||
// * met to provide more real-time interactions but
|
||||
// * speed up when buffer is too large. This should
|
||||
// * provide a good balance of accuracy. */
|
||||
|
||||
// float multiplier;
|
||||
// int countOverInterpolation = (queueCount - (int)_currentInterpolation - (int)_currentInterpolation);
|
||||
|
||||
// if (countOverInterpolation > 0)
|
||||
// {
|
||||
// float overflowMultiplier = OVERFLOW_MULTIPLIER;
|
||||
// overflowMultiplier = 0f;
|
||||
// multiplier = 1f + overflowMultiplier; //(overflowMultiplier * countOverInterpolation);
|
||||
// }
|
||||
// else if (countOverInterpolation < 0)
|
||||
// {
|
||||
// float value = (UNDERFLOW_MULTIPLIER * Mathf.Abs(countOverInterpolation));
|
||||
// const float maximum = 0.9f;
|
||||
// if (value > maximum)
|
||||
// value = maximum;
|
||||
|
||||
// multiplier = 1f - value;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// multiplier = 1f;
|
||||
// }
|
||||
|
||||
// //Rate to update. Changes per property.
|
||||
// float rate;
|
||||
// Transform t = _smoothingData.GraphicalObject;
|
||||
|
||||
// //Position.
|
||||
// if (_smoothingData.SmoothPosition)
|
||||
// {
|
||||
// rate = rd.Position;
|
||||
// Vector3 posGoal = td.Position;
|
||||
// //Debug.Log($"Rate {rate}. PosY {posGoal.y}. Multiplier {multiplier}. QueueCount {queueCount}");
|
||||
// if (rate == -1f)
|
||||
// t.position = td.Position;
|
||||
// else if (rate > 0f)
|
||||
// t.position = Vector3.MoveTowards(t.position, posGoal, rate * delta * multiplier);
|
||||
// }
|
||||
|
||||
// //Rotation.
|
||||
// if (_smoothingData.SmoothRotation)
|
||||
// {
|
||||
// rate = rd.Rotation;
|
||||
// if (rate == -1f)
|
||||
// t.rotation = td.Rotation;
|
||||
// else if (rate > 0f)
|
||||
// t.rotation = Quaternion.RotateTowards(t.rotation, td.Rotation, rate * delta);
|
||||
// }
|
||||
|
||||
// //Subtract time remaining for movement to complete.
|
||||
// if (rd.TimeRemaining > 0f)
|
||||
// {
|
||||
// float subtractionAmount = (delta * multiplier);
|
||||
// float timeRemaining = rd.TimeRemaining - subtractionAmount;
|
||||
// rd.TimeRemaining = timeRemaining;
|
||||
// }
|
||||
|
||||
// //If movement shoudl be complete.
|
||||
// if (rd.TimeRemaining <= 0f)
|
||||
// {
|
||||
// //_smoothingData.GraphicalObject.transform.position = _currentGoalData.TransformProperties.Position;
|
||||
// //_smoothingData.GraphicalObject.transform.rotation = _currentGoalData.TransformProperties.Rotation;
|
||||
// float leftOver = Mathf.Abs(rd.TimeRemaining);
|
||||
|
||||
// if (SetCurrentGoalData())
|
||||
// {
|
||||
// if (leftOver > 0f)
|
||||
// MoveToTarget(leftOver);
|
||||
// }
|
||||
// //No more in buffer, see if can extrapolate.
|
||||
// else
|
||||
// {
|
||||
// /* Everything should line up when
|
||||
// * time remaining is <= 0f but incase it's not,
|
||||
// * such as if the user manipulated the grapihc object
|
||||
// * somehow, then set goaldata active again to continue
|
||||
// * moving it until it lines up with the goal. */
|
||||
// if (!GraphicalObjectMatches(td.Position, td.Rotation))
|
||||
// _currentGoalData.IsValid = true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// #region Rates.
|
||||
|
||||
// /// <summary>
|
||||
// /// Sets move rates which will occur instantly.
|
||||
// /// </summary>
|
||||
// private void SetInstantRates(RateData rd)
|
||||
// {
|
||||
// Debug.LogError($"Instant rates set.");
|
||||
// rd.Update(MoveRates.INSTANT_VALUE, MoveRates.INSTANT_VALUE, 1, MoveRates.INSTANT_VALUE);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Sets move rates which will occur over time.
|
||||
// /// </summary>
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// private void SetCalculatedRates(GoalData prevGd, GoalData nextGd, bool datasCleared, Channel channel)
|
||||
// {
|
||||
// datasCleared = false;
|
||||
|
||||
// /* Only update rates if data has changed.
|
||||
// * When data comes in reliably for eventual consistency
|
||||
// * it's possible that it will be the same as the last
|
||||
// * unreliable packet. When this happens no change has occurred
|
||||
// * and the distance of change would also be 0; this prevents
|
||||
// * the object from moving. Only need to compare data if channel is reliable. */
|
||||
// if (channel == Channel.Reliable && !HasChanged(prevGd.TransformProperties, nextGd.TransformProperties))
|
||||
// {
|
||||
// Debug.LogError("Reliable and unchanged.");
|
||||
// nextGd.MoveRates.Update(prevGd.MoveRates);
|
||||
// //Set to 0 to indicate settled.
|
||||
// nextGd.DataTick = 0;
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
// uint lastTick = prevGd.DataTick;
|
||||
// /* How much time has passed between last update and current.
|
||||
// * If set to 0 then that means the transform has
|
||||
// * settled. */
|
||||
// if (lastTick == 0)
|
||||
// lastTick = (nextGd.DataTick - 1);
|
||||
|
||||
// uint tickDifference = (nextGd.DataTick - lastTick);
|
||||
// float timePassed = (float)_networkObject.TimeManager.TicksToTime(tickDifference);
|
||||
// RateData nextRd = nextGd.MoveRates;
|
||||
|
||||
// float rateMultiplier;
|
||||
|
||||
// if (!datasCleared)
|
||||
// {
|
||||
// rateMultiplier = 1f;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// float tickDelta = (float)_networkObject.TimeManager.TickDelta;
|
||||
// rateMultiplier = (_currentGoalData.MoveRates.TimeRemaining / tickDelta);
|
||||
// }
|
||||
|
||||
// //Distance between properties.
|
||||
// float distance;
|
||||
// //Position.
|
||||
// Vector3 lastPosition = prevGd.TransformProperties.Position;
|
||||
// distance = Vector3.Distance(lastPosition, nextGd.TransformProperties.Position);
|
||||
|
||||
// if (tickDifference == 0)
|
||||
// Debug.LogError($"0 tick difference");
|
||||
|
||||
// //If distance teleports assume rest do.
|
||||
// if (_smoothingData.TeleportThreshold != MoveRates.UNSET_VALUE && distance >= _smoothingData.TeleportThreshold)
|
||||
// {
|
||||
// SetInstantRates(nextRd);
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
// //Position distance already calculated.
|
||||
// float positionRate = (distance / timePassed);
|
||||
// if (positionRate <= 0f || positionRate > 5.6f && !_networkObject.IsServer && !_networkObject.IsOwner)
|
||||
// //Debug.LogError($"Position Rate {positionRate} for tick {nextGd.LocalTick}. PrevY {prevGd.TransformProperties.Position.y}. NextY {nextGd.TransformProperties.Position.y}");
|
||||
// //Rotation.
|
||||
// distance = prevGd.TransformProperties.Rotation.Angle(nextGd.TransformProperties.Rotation, true);
|
||||
|
||||
// float rotationRate = (distance / timePassed);
|
||||
|
||||
// //if (positionRate > 5.1f || positionRate <= 0.05f)
|
||||
// //Debug.Log($"Rate {positionRate}. Distance {distance}. TickDifference {tickDifference}.");
|
||||
// /* If no speed then snap just in case.
|
||||
// * 0f could be from floating errors. */
|
||||
// if (positionRate == 0f)
|
||||
// positionRate = MoveRates.INSTANT_VALUE;
|
||||
|
||||
// if (rotationRate == 0f)
|
||||
// rotationRate = MoveRates.INSTANT_VALUE;
|
||||
|
||||
// nextRd.Update(positionRate * rateMultiplier, rotationRate * rateMultiplier, tickDifference, timePassed);
|
||||
// }
|
||||
|
||||
// #endregion
|
||||
|
||||
// /// <summary>
|
||||
// /// Removes GoalDatas which make the queue excessive.
|
||||
// /// This could cause teleportation but would rarely occur, only potentially during sever network issues.
|
||||
// /// </summary>
|
||||
// private void RemoveExcessiveGoalDatas()
|
||||
// {
|
||||
// if (_goalDatas.Count > 100)
|
||||
// Debug.LogError($"Whoa getting kind of high with count of {_goalDatas.Count}");
|
||||
// ///* Remove entries which are excessive to the buffer.
|
||||
// //* This could create a starting jitter but it will ensure
|
||||
// //* the buffer does not fill too much. The buffer next should
|
||||
// //* actually get unreasonably high but rather safe than sorry. */
|
||||
// //int maximumBufferAllowance = ((int)_currentInterpolation * 8);
|
||||
// //int removedBufferCount = (_goalDatas.Count - maximumBufferAllowance);
|
||||
// ////If there are some to remove.
|
||||
// //if (removedBufferCount > 0)
|
||||
// //{
|
||||
// // for (int i = 0; i < removedBufferCount; i++)
|
||||
// // ResettableObjectCaches<GoalData>.Store(_goalDatas[0 + i]);
|
||||
// // //_goalDatas.RemoveRange(true, removedBufferCount);
|
||||
// // _goalDatas.RemoveRange(0, removedBufferCount);
|
||||
// //}
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Returns if a tick is older than or equal to the current GoalData and outputs current GoalData tick.
|
||||
// /// </summary>
|
||||
// private bool OldGoalDataTick(uint tick, out uint currentGoalDataTick)
|
||||
// {
|
||||
// currentGoalDataTick = _currentGoalData.DataTick;
|
||||
|
||||
// return (tick <= currentGoalDataTick);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Creates the next GoalData using previous goalData and tick.
|
||||
// /// </summary>
|
||||
// /// <param name="tick">Tick to apply for the next goal data.</param>
|
||||
// private GoalData CreateNextGoalData(uint tick, GoalData prevGoalData, bool datasCleared)
|
||||
// {
|
||||
// //Debug.Log($"Creating next GoalData for tick {tick}. PrevGoalData tick {prevGoalData.LocalTick}");
|
||||
// //Begin building next goal data.
|
||||
// GoalData nextGoalData = ResettableObjectCaches<GoalData>.Retrieve();
|
||||
// nextGoalData.DataTick = tick;
|
||||
// //Set next transform data.
|
||||
// TransformPropertiesCls nextTp = nextGoalData.TransformProperties;
|
||||
// nextTp.Update(_networkObject.transform);
|
||||
// /* Reset properties if smoothing is not enabled
|
||||
// * for them. It's less checks and easier to do it
|
||||
// * after the nextGoalData is populated. */
|
||||
// if (!_smoothingData.SmoothPosition)
|
||||
// nextTp.Position = _graphicalPretickValues.Position;
|
||||
|
||||
// if (!_smoothingData.SmoothRotation)
|
||||
// nextTp.Rotation = _graphicalPretickValues.Rotation;
|
||||
|
||||
// // Debug.Log($"Creating NextGd X {nextTp.Position.x} for tick {tick}.");
|
||||
// //Calculate rates for prev vs next data.
|
||||
// SetCalculatedRates(prevGoalData, nextGoalData, datasCleared, Channel.Unreliable);
|
||||
|
||||
// return nextGoalData;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Makes a GoalData using transform values from rootPostSimulateOffsets.
|
||||
// /// </summary>
|
||||
// /// <returns></returns>
|
||||
// private GoalData CreateGoalDataFromRootPreSimulate(uint tick)
|
||||
// {
|
||||
// GoalData gd = ResettableObjectCaches<GoalData>.Retrieve();
|
||||
// //RigidbodyData contains the data from preTick.
|
||||
// // Debug.Log($"Creating goalData from X {_rootPostSimulateValues.Position.x}. Tick {tick}");
|
||||
// gd.TransformProperties.Update(_rootPreSimulateValues);
|
||||
// gd.DataTick = tick;
|
||||
|
||||
// //No need to update rates because this is just a starting point reference for interpolation.
|
||||
// return gd;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Clears all goalDatas.
|
||||
// /// </summary>
|
||||
// private void ClearGoalData(bool clearCurrent)
|
||||
// {
|
||||
// if (clearCurrent)
|
||||
// ResettableObjectCaches<GoalData>.Store(_currentGoalData);
|
||||
|
||||
// int count = _goalDatas.Count;
|
||||
// for (int i = 0; i < count; i++)
|
||||
// ResettableObjectCaches<GoalData>.Store(_goalDatas[i]);
|
||||
|
||||
// _goalDatas.Clear();
|
||||
// }
|
||||
|
||||
// private uint _jumpTick;
|
||||
|
||||
// private uint _lastPostTickDataTick;
|
||||
|
||||
// /// <summary>
|
||||
// /// Creates a GoalData after a simulate.
|
||||
// /// </summary>
|
||||
// /// <param name="postTick">True if being created for OnPostTick.</param>
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// private void CreatePostSimulateGoalData(uint tick, bool postTick)
|
||||
// {
|
||||
// bool jumping = (_networkObject.transform.position.y > 0.5f);
|
||||
// bool dataCleared = false;
|
||||
// int dataIndex = -1;
|
||||
// bool useUpdate = false;
|
||||
// RemoveExcessiveGoalDatas();
|
||||
|
||||
// if (tick <= _currentGoalData.DataTick)
|
||||
// {
|
||||
// if (!postTick)
|
||||
// {
|
||||
// if (jumping)
|
||||
// Debug.LogWarning($"Frame {Time.frameCount}. Old tick. Tick {tick}. Current {_currentGoalData.DataTick}. QueueCount {_goalDatas.Count}");
|
||||
|
||||
// return;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// dataCleared = true;
|
||||
// ClearGoalData(false);
|
||||
// Debug.LogWarning($"Frame {Time.frameCount}. Tick {tick}. Current {_currentGoalData.DataTick}. CLEARING!");
|
||||
// }
|
||||
// }
|
||||
|
||||
// uint prevArrTick = 0;
|
||||
|
||||
// for (int i = 0; i < _goalDatas.Count; i++)
|
||||
// {
|
||||
// uint arrTick = _goalDatas[i].DataTick;
|
||||
|
||||
// if (tick == arrTick)
|
||||
// {
|
||||
// dataIndex = i;
|
||||
// useUpdate = true;
|
||||
|
||||
// break;
|
||||
// }
|
||||
// else if (i > 0 && tick > prevArrTick && tick < arrTick)
|
||||
// {
|
||||
// dataIndex = i;
|
||||
|
||||
// break;
|
||||
// }
|
||||
|
||||
// prevArrTick = arrTick;
|
||||
// }
|
||||
|
||||
// if (dataIndex == -1)
|
||||
// {
|
||||
// if (_goalDatas.Count > 0 && tick < _goalDatas[0].DataTick)
|
||||
// {
|
||||
// // Insert at the beginning.
|
||||
// dataIndex = 0;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // Insert at the end.
|
||||
// dataIndex = _goalDatas.Count;
|
||||
// }
|
||||
// }
|
||||
|
||||
// GoalData prevGd;
|
||||
|
||||
// if (dataCleared)
|
||||
// {
|
||||
// prevGd = ResettableObjectCaches<GoalData>.Retrieve();
|
||||
// prevGd.Update(_currentGoalData);
|
||||
// prevGd.DataTick = (tick - 1);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// prevGd = CreateGoalDataFromRootPreSimulate(tick - 1);
|
||||
// }
|
||||
|
||||
// GoalData nextGd = CreateNextGoalData(tick, prevGd, dataCleared);
|
||||
|
||||
// if (jumping)
|
||||
// {
|
||||
// Debug.Log($"Frame {Time.frameCount}. CreateGoalData. Tick {tick}. Next Rate {nextGd.MoveRates.Position}. Next PosY {nextGd.TransformProperties.Position.y}");
|
||||
// SceneLoadData sld = new SceneLoadData(_networkObject.gameObject.scene);
|
||||
// _jumpTick = nextGd.DataTick;
|
||||
// }
|
||||
|
||||
// if (useUpdate && _goalDatas[dataIndex].DataTick == _jumpTick)
|
||||
// {
|
||||
// Debug.LogError($"Frame {Time.frameCount}. Overwriting jump. Tick {tick}. IndexTick {_goalDatas[dataIndex].DataTick}. CurrentGoalY {_goalDatas[dataIndex].TransformProperties.Position.y}. Next Rate {nextGd.MoveRates.Position}. Next PosY {nextGd.TransformProperties.Position.y}.");
|
||||
// }
|
||||
|
||||
// if (useUpdate)
|
||||
// _goalDatas[dataIndex].Update(nextGd);
|
||||
// else
|
||||
// _goalDatas.Insert(dataIndex, nextGd);
|
||||
|
||||
// //Debug.
|
||||
// if (postTick)
|
||||
// {
|
||||
// Vector3 offset = new Vector3(0.15f, 4f, 0f);
|
||||
// Debug.DrawLine(nextGd.TransformProperties.Position + offset, nextGd.TransformProperties.Position - offset, Color.green, 2f);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Vector3 offset = new Vector3(-0.15f, 4f, 0f);
|
||||
// Debug.DrawLine(nextGd.TransformProperties.Position + offset, nextGd.TransformProperties.Position - offset, Color.cyan, 2f);
|
||||
// }
|
||||
// }
|
||||
|
||||
// uint _postTickGdCount;
|
||||
|
||||
// #endif
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 28460ed39f6c7e44498b2332d72f1aaf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,73 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
/// <summary>
|
||||
/// How to favor smoothing for predicted objects.
|
||||
/// </summary>
|
||||
internal enum AdaptiveSmoothingType
|
||||
{
|
||||
/// <summary>
|
||||
/// Favor accurate collisions. With fast moving objects this may result in some jitter with higher latencies.
|
||||
/// </summary>
|
||||
Accuracy = 0,
|
||||
/// <summary>
|
||||
/// A mix between Accuracy and Smoothness.
|
||||
/// </summary>
|
||||
Mixed = 1,
|
||||
/// <summary>
|
||||
/// Prefer smooth movement and corrections. Fast moving objects may collide before the graphical representation catches up.
|
||||
/// </summary>
|
||||
Gradual = 2,
|
||||
/// <summary>
|
||||
/// Configure values to your preference.
|
||||
/// </summary>
|
||||
Custom = 3,
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public struct AdaptiveInterpolationSmoothingData
|
||||
{
|
||||
[HideInInspector, System.NonSerialized]
|
||||
public bool SmoothPosition;
|
||||
[HideInInspector, System.NonSerialized]
|
||||
public bool SmoothRotation;
|
||||
[HideInInspector, System.NonSerialized]
|
||||
public bool SmoothScale;
|
||||
[HideInInspector,System.NonSerialized]
|
||||
public Transform GraphicalObject;
|
||||
[HideInInspector,System.NonSerialized]
|
||||
public NetworkObject NetworkObject;
|
||||
[HideInInspector, System.NonSerialized]
|
||||
public float TeleportThreshold;
|
||||
/// <summary>
|
||||
/// Percentage of ping to use as interpolation. Higher values will result in more interpolation.
|
||||
/// </summary>
|
||||
[Tooltip("Percentage of ping to use as interpolation. Higher values will result in more interpolation.")]
|
||||
[Range(0.01f, 5f)]
|
||||
public float InterpolationPercent;
|
||||
/// <summary>
|
||||
/// Percentage of ping to use as interpolation when colliding with an object local client owns.
|
||||
/// This is used to speed up local interpolation when predicted objects collide with a player as well keep graphics closer to the objects root while colliding.
|
||||
/// </summary>
|
||||
[Tooltip("Percentage of ping to use as interpolation when colliding with an object local client owns." +
|
||||
"This is used to speed up local interpolation when predicted objects collide with a player as well keep graphics closer to the objects root while colliding.")]
|
||||
[Range(0.01f, 5f)]
|
||||
public float CollisionInterpolationPercent;
|
||||
/// <summary>
|
||||
/// How much per tick to decrease to collision interpolation when colliding with a local player object.
|
||||
/// Higher values will set interpolation to collision settings faster.
|
||||
/// </summary>
|
||||
[Tooltip("How much per tick to decrease to collision interpolation when colliding with a local player object. Higher values will set interpolation to collision settings faster.")]
|
||||
[Range(1, byte.MaxValue)]
|
||||
public byte InterpolationDecreaseStep;
|
||||
/// <summary>
|
||||
/// How much per tick to increase to normal interpolation when not colliding with a local player object.
|
||||
/// Higher values will set interpolation to normal settings faster.
|
||||
/// </summary>
|
||||
[Tooltip("How much per tick to increase to normal interpolation when not colliding with a local player object. Higher values will set interpolation to normal settings faster.")]
|
||||
[Range(1, byte.MaxValue)]
|
||||
public byte InterpolationIncreaseStep;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9fcf00526b5e1bf40a6565c3f521d926
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,589 @@
|
||||
using GameKit.Utilities;
|
||||
using FishNet.Utility.Extension;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
internal class AdaptiveInterpolationSmootherFixed
|
||||
{
|
||||
#if PREDICTION_V2
|
||||
|
||||
#region Types.
|
||||
/// <summary>
|
||||
/// Data on a goal to move towards.
|
||||
/// </summary>
|
||||
private class GoalData : IResettable
|
||||
{
|
||||
/// <summary>
|
||||
/// True if this GoalData is valid.
|
||||
/// </summary>
|
||||
public bool IsValid;
|
||||
/// <summary>
|
||||
/// Tick of the data this GoalData is for.
|
||||
/// </summary>
|
||||
public uint DataTick;
|
||||
/// <summary>
|
||||
/// Transform values to move towards.
|
||||
/// </summary>
|
||||
public TransformPropertiesCls TransformProperties = new TransformPropertiesCls();
|
||||
/// <summary>
|
||||
/// Time remaining to move towards goal.
|
||||
/// </summary>
|
||||
public float TimeRemaining;
|
||||
|
||||
public GoalData() { }
|
||||
|
||||
public void InitializeState() { }
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
DataTick = 0;
|
||||
TimeRemaining = 0f;
|
||||
TransformProperties.ResetState();
|
||||
IsValid = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates values using a GoalData.
|
||||
/// </summary>
|
||||
public void Update(GoalData gd)
|
||||
{
|
||||
DataTick = gd.DataTick;
|
||||
TransformProperties.Update(gd.TransformProperties);
|
||||
TimeRemaining = gd.TimeRemaining;
|
||||
IsValid = true;
|
||||
}
|
||||
|
||||
public void Update(uint dataTick, TransformPropertiesCls tp, float timeRemaining)
|
||||
{
|
||||
DataTick = dataTick;
|
||||
TransformProperties = tp;
|
||||
TimeRemaining = timeRemaining;
|
||||
IsValid = true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Offsets of the root object during PreTick or PreReplicateReplay.
|
||||
/// </summary>
|
||||
private TransformProperties _rootPreSimulateWorldValues;
|
||||
/// <summary>
|
||||
/// Offsets of the graphical object during PreTick or PreReplicateReplay.
|
||||
/// </summary>
|
||||
private TransformProperties _graphicalPreSimulateWorldValues;
|
||||
/// <summary>
|
||||
/// SmoothingData to use.
|
||||
/// </summary>
|
||||
private AdaptiveInterpolationSmoothingData _smoothingData;
|
||||
/// <summary>
|
||||
/// Current interpolation value. This changes based on ping and settings.
|
||||
/// </summary>
|
||||
public long _currentInterpolation = 15;
|
||||
/// <summary>
|
||||
/// Current GoalData being used.
|
||||
/// </summary>
|
||||
private GoalData _currentGoalData = new GoalData();
|
||||
/// <summary>
|
||||
/// MoveRates for currentGoalData.
|
||||
/// </summary>
|
||||
private MoveRates _currentMoveRates;
|
||||
/// <summary>
|
||||
/// GoalDatas to move towards.
|
||||
/// </summary>
|
||||
//private RingBuffer<GoalData> _goalDatas = new RingBuffer<GoalData>();
|
||||
private List<GoalData> _goalDatas = new List<GoalData>();
|
||||
/// <summary>
|
||||
/// Cached NetworkObject reference in SmoothingData for performance.
|
||||
/// </summary>
|
||||
private NetworkObject _networkObject;
|
||||
/// <summary>
|
||||
/// Cached tickDelta on the TimeManager.
|
||||
/// </summary>
|
||||
private float _tickDelta;
|
||||
/// <summary>
|
||||
/// Multiplier to apply towards movements. This is used to speed up and slow down buffer as needed.
|
||||
/// </summary>
|
||||
private float _rateMultiplier = 1f;
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
/// <summary>
|
||||
/// Multiplier to apply to movement speed when buffer is over interpolation.
|
||||
/// </summary>
|
||||
private const float OVERFLOW_MULTIPLIER = 10f;
|
||||
/// <summary>
|
||||
/// Multiplier to apply to movement speed when buffer is under interpolation.
|
||||
/// </summary>
|
||||
private const float UNDERFLOW_MULTIPLIER = 1f;
|
||||
#endregion
|
||||
|
||||
public AdaptiveInterpolationSmootherFixed()
|
||||
{
|
||||
/* Initialize for up to 50
|
||||
* goal datas. Anything beyond that
|
||||
* is unreasonable. */
|
||||
//_goalDatas.Initialize(50);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this for use.
|
||||
/// </summary>
|
||||
internal void Initialize(AdaptiveInterpolationSmoothingData data)
|
||||
{
|
||||
_smoothingData = data;
|
||||
_networkObject = data.NetworkObject;
|
||||
_tickDelta = (float)_networkObject.TimeManager.TickDelta;
|
||||
SetGraphicalObject(data.GraphicalObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// Called every frame.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update()
|
||||
{
|
||||
if (CanSmooth())
|
||||
MoveToTarget();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the TimeManager invokes OnPreTick.
|
||||
/// </summary>
|
||||
public void OnPreTick()
|
||||
{
|
||||
if (CanSmooth())
|
||||
{
|
||||
_graphicalPreSimulateWorldValues = _smoothingData.GraphicalObject.GetWorldProperties();
|
||||
_rootPreSimulateWorldValues.Update(_networkObject.transform);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the TimeManager invokes OnPostTick.
|
||||
/// </summary>
|
||||
public void OnPostTick()
|
||||
{
|
||||
if (CanSmooth())
|
||||
{
|
||||
//Reset graphics to start graphicals transforms properties.
|
||||
_smoothingData.GraphicalObject.SetPositionAndRotation(_graphicalPreSimulateWorldValues.Position, _graphicalPreSimulateWorldValues.Rotation);
|
||||
//Create a goal data for new transform position.
|
||||
uint tick = _networkObject.LastUnorderedReplicateTick;
|
||||
CreatePostSimulateGoalData(tick, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before a reconcile runs a replay.
|
||||
/// </summary>
|
||||
public void OnPreReplicateReplay(uint clientTick, uint serverTick)
|
||||
{
|
||||
//Update the last post simulate data.
|
||||
if (CanSmooth())
|
||||
_rootPreSimulateWorldValues.Update(_networkObject.transform);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after a reconcile runs a replay.
|
||||
/// </summary>
|
||||
public void OnPostReplicateReplay(uint clientTick, uint serverTick)
|
||||
{
|
||||
if (CanSmooth())
|
||||
{
|
||||
/* Create new goal data from the replay.
|
||||
* This must be done every replay. If a desync
|
||||
* did occur then the goaldatas would be different
|
||||
* from what they were previously. */
|
||||
uint tick = _networkObject.LastUnorderedReplicateTick;
|
||||
CreatePostSimulateGoalData(tick, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPostReconcile(uint clientReconcileTick, uint serverReconcileTick)
|
||||
{
|
||||
if (CanSmooth())
|
||||
{
|
||||
_rootPreSimulateWorldValues.Update(_networkObject.transform);
|
||||
int countOverInterpolation = (_goalDatas.Count - (int)_currentInterpolation);
|
||||
//Debug.Log($"{Time.frameCount}. CountOver {countOverInterpolation}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets GraphicalObject.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public void SetGraphicalObject(Transform value)
|
||||
{
|
||||
_smoothingData.GraphicalObject = value;
|
||||
_graphicalPreSimulateWorldValues.Update(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if the graphics can be smoothed.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool CanSmooth()
|
||||
{
|
||||
if (_networkObject.IsOwner)
|
||||
return false;
|
||||
if (_networkObject.IsServerOnly || _networkObject.IsHost)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if this transform matches arguments.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool GraphicalObjectMatches(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
bool positionMatches = (!_smoothingData.SmoothPosition || (_smoothingData.GraphicalObject.position == position));
|
||||
bool rotationMatches = (!_smoothingData.SmoothRotation || (_smoothingData.GraphicalObject.rotation == rotation));
|
||||
|
||||
return (positionMatches && rotationMatches);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets CurrentGoalData to the next in queue. Returns if was set successfully.
|
||||
/// </summary>
|
||||
private bool SetCurrentGoalData()
|
||||
{
|
||||
if (_goalDatas.Count == 0)
|
||||
{
|
||||
_currentGoalData.IsValid = false;
|
||||
Debug.LogError("No more goal datas.");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Previous will always be current since
|
||||
* we are getting next in queue. We
|
||||
* later check if current is valid to determine
|
||||
* if instant rates should be set or normal rates.
|
||||
* If current is not valie then instant rates are set
|
||||
* to teleport graphics to their starting position, and
|
||||
* future sets will have a valid current. */
|
||||
GoalData prev = _currentGoalData;
|
||||
//Set next and make valid.
|
||||
GoalData next = _goalDatas[0];
|
||||
//Remove from goalDatas.
|
||||
_goalDatas.RemoveAt(0);
|
||||
|
||||
if (prev != null && prev.IsValid)
|
||||
SetCurrentMoveRates(prev.DataTick, next.DataTick, prev.TransformProperties, next.TransformProperties);
|
||||
else
|
||||
_currentMoveRates.SetInstantRates();
|
||||
|
||||
//Store previous.
|
||||
if (prev != null)
|
||||
ResettableObjectCaches<GoalData>.Store(prev);
|
||||
//Assign new current.
|
||||
_currentGoalData = next;
|
||||
Debug.LogWarning($"Set CurrentGoalData on tick {_currentGoalData.DataTick}, remaining {_goalDatas.Count}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves to a GoalData. Automatically determins if to use data from server or client.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void MoveToTarget(float deltaOverride = -1f)
|
||||
{
|
||||
/* If the current goal data is not valid then
|
||||
* try to set a new one. If none are available
|
||||
* it will remain inactive. */
|
||||
if (!_currentGoalData.IsValid)
|
||||
{
|
||||
if (!SetCurrentGoalData())
|
||||
return;
|
||||
}
|
||||
|
||||
GoalData currentGd = _currentGoalData;
|
||||
|
||||
float delta = (deltaOverride != -1f) ? deltaOverride : Time.deltaTime;
|
||||
/* Once here it's safe to assume the object will be moving.
|
||||
* Any checks which would stop it from moving be it client
|
||||
* auth and owner, or server controlled and server, ect,
|
||||
* would have already been run. */
|
||||
TransformPropertiesCls td = currentGd.TransformProperties;
|
||||
MoveRates mr = _currentMoveRates;
|
||||
|
||||
//How much multiplier should change in either direction over a second.
|
||||
float multiplierChangeRate = 0.3f;
|
||||
|
||||
int queueCount = _goalDatas.Count;
|
||||
/* Begin moving even if interpolation buffer isn't
|
||||
* met to provide more real-time interactions but
|
||||
* speed up when buffer is too large. This should
|
||||
* provide a good balance of accuracy. */
|
||||
int countOverInterpolation = (queueCount - (int)_currentInterpolation);
|
||||
string debugPrint = string.Empty;
|
||||
//Really high over interpolation, snap to datas.
|
||||
if (countOverInterpolation > (_currentInterpolation * 30))
|
||||
{
|
||||
debugPrint = $"OverInterpolation {countOverInterpolation}. Teleporting.";
|
||||
mr.SetInstantRates();
|
||||
//Setting to -1 will force it to go negative, which will clear next goal data for teleport as well.
|
||||
currentGd.TimeRemaining = -1f;
|
||||
}
|
||||
else if (countOverInterpolation > 0)
|
||||
{
|
||||
debugPrint = $"OverInterpolation {countOverInterpolation}. Increasing.";
|
||||
_rateMultiplier += (multiplierChangeRate * delta);
|
||||
}
|
||||
else if (countOverInterpolation < 0)
|
||||
{
|
||||
debugPrint = $"OverInterpolation {countOverInterpolation}. Slowing.";
|
||||
_rateMultiplier -= (multiplierChangeRate * delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
_rateMultiplier = Mathf.MoveTowards(_rateMultiplier, 1f, (multiplierChangeRate * delta));
|
||||
}
|
||||
|
||||
//Clamp multiplier.
|
||||
const float maximumMultiplier = 1.1f;
|
||||
const float minimumMultiplier = 0.95f;
|
||||
_rateMultiplier = Mathf.Clamp(_rateMultiplier, minimumMultiplier, maximumMultiplier);
|
||||
//Apply multiplier to delta.
|
||||
delta *= _rateMultiplier;
|
||||
|
||||
// if (debugPrint != string.Empty && _networkObject.TimeManager.FrameTicked)
|
||||
// Debug.Log($"{debugPrint}. Multiplier {_rateMultiplier}");
|
||||
//multiplier = 1f;
|
||||
//delta = Time.deltaTime;
|
||||
//Rate to update. Changes per property.
|
||||
float rate;
|
||||
Transform t = _smoothingData.GraphicalObject;
|
||||
|
||||
//Position.
|
||||
if (_smoothingData.SmoothPosition)
|
||||
{
|
||||
rate = mr.Position;
|
||||
Vector3 posGoal = td.Position;
|
||||
if (rate == MoveRatesCls.INSTANT_VALUE)
|
||||
t.position = td.Position;
|
||||
else if (rate > 0f)
|
||||
t.position = Vector3.MoveTowards(t.position, posGoal, rate * delta);
|
||||
}
|
||||
|
||||
//Rotation.
|
||||
if (_smoothingData.SmoothRotation)
|
||||
{
|
||||
rate = mr.Rotation;
|
||||
if (rate == MoveRatesCls.INSTANT_VALUE)
|
||||
t.rotation = td.Rotation;
|
||||
else if (rate > 0f)
|
||||
t.rotation = Quaternion.RotateTowards(t.rotation, td.Rotation, rate * delta);
|
||||
}
|
||||
|
||||
if (currentGd.TimeRemaining > 0f)
|
||||
currentGd.TimeRemaining -= delta;
|
||||
|
||||
if (currentGd.TimeRemaining <= 0f)
|
||||
{
|
||||
bool graphicsMatch = GraphicalObjectMatches(td.Position, td.Rotation);
|
||||
if (graphicsMatch)
|
||||
{
|
||||
float leftOver = Mathf.Abs(currentGd.TimeRemaining);
|
||||
if (SetCurrentGoalData())
|
||||
MoveToTarget(leftOver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Rates.
|
||||
/// <summary>
|
||||
/// Sets move rates which will occur over time.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetCurrentMoveRates(uint prevTick, uint tick, TransformPropertiesCls prevTp, TransformPropertiesCls nextTp)
|
||||
{
|
||||
long lngTicksPassed = (tick - prevTick);
|
||||
//Should not happen.
|
||||
if (lngTicksPassed <= 0)
|
||||
{
|
||||
_networkObject.NetworkManager.LogError($"Ticks passed returned negative as {lngTicksPassed}. Instant rates are being set.");
|
||||
_currentMoveRates.SetInstantRates();
|
||||
return;
|
||||
}
|
||||
//More than 1 tick, also unusual.
|
||||
else if (lngTicksPassed > 1)
|
||||
{
|
||||
_networkObject.NetworkManager.LogError($"Ticks passed are not equal to 1, passed value is {lngTicksPassed}");
|
||||
// lngTicksPassed = 1;
|
||||
}
|
||||
|
||||
|
||||
uint ticksPassed = (uint)lngTicksPassed;
|
||||
float delta = _tickDelta;
|
||||
|
||||
float distance;
|
||||
float rate;
|
||||
const float v3Tolerance = 0.0001f;
|
||||
const float qTolerance = 0.2f;
|
||||
|
||||
//Position.
|
||||
rate = prevTp.Position.GetRate(nextTp.Position, delta, out distance, ticksPassed);
|
||||
//If distance teleports assume rest do.
|
||||
if (_smoothingData.TeleportThreshold != MoveRates.UNSET_VALUE && distance >= _smoothingData.TeleportThreshold)
|
||||
{
|
||||
Debug.Log($"Teleporting threshhold.");
|
||||
_currentMoveRates.SetInstantRates();
|
||||
return;
|
||||
}
|
||||
float positionRate = rate.SetIfUnderTolerance(v3Tolerance, MoveRates.INSTANT_VALUE);
|
||||
|
||||
//Rotation.
|
||||
rate = prevTp.Rotation.GetRate(nextTp.Rotation, delta, out _, ticksPassed);
|
||||
float rotationRate = rate.SetIfUnderTolerance(qTolerance, MoveRates.INSTANT_VALUE);
|
||||
|
||||
_currentMoveRates.Update(positionRate, rotationRate, MoveRates.INSTANT_VALUE);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Removes GoalDatas which make the queue excessive.
|
||||
/// This could cause teleportation but would rarely occur, only potentially during sever network issues.
|
||||
/// </summary>
|
||||
private void RemoveExcessiveGoalDatas()
|
||||
{
|
||||
if (_goalDatas.Count > 100)
|
||||
Debug.LogError($"Whoa getting kind of high with count of {_goalDatas.Count}");
|
||||
///* Remove entries which are excessive to the buffer.
|
||||
//* This could create a starting jitter but it will ensure
|
||||
//* the buffer does not fill too much. The buffer next sho0..uld
|
||||
//* actually get unreasonably high but rather safe than sorry. */
|
||||
//int maximumBufferAllowance = ((int)_currentInterpolation * 8);
|
||||
//int removedBufferCount = (_goalDatas.Count - maximumBufferAllowance);
|
||||
////If there are some to remove.
|
||||
//if (removedBufferCount > 0)
|
||||
//{
|
||||
// for (int i = 0; i < removedBufferCount; i++)
|
||||
// ResettableObjectCaches<GoalData>.Store(_goalDatas[0 + i]);
|
||||
// //_goalDatas.RemoveRange(true, removedBufferCount);
|
||||
// _goalDatas.RemoveRange(0, removedBufferCount);
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a GoalData after a simulate.
|
||||
/// </summary>
|
||||
/// <param name="postTick">True if being created for OnPostTick.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void CreatePostSimulateGoalData(uint tick, bool postTick)
|
||||
{
|
||||
RemoveExcessiveGoalDatas();
|
||||
|
||||
int dataIndex = -1;
|
||||
bool useUpdate = false;
|
||||
|
||||
/* Post ticks always go on the end.
|
||||
* The tick will be wrong for the post tick, so set it
|
||||
* to the last entry tick + 1. */
|
||||
|
||||
int datasCount = _goalDatas.Count;
|
||||
|
||||
|
||||
if (postTick)
|
||||
{
|
||||
if (datasCount > 0)
|
||||
tick = _goalDatas[datasCount - 1].DataTick + 1;
|
||||
else
|
||||
tick = _currentGoalData.DataTick + 1;
|
||||
|
||||
dataIndex = datasCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* There is no need to create a goaldata
|
||||
* if the tick is previous to currentGoalData.
|
||||
* This would indicate the graphics have already
|
||||
* moved past tick. */
|
||||
if (tick < _currentGoalData.DataTick)
|
||||
{
|
||||
//Debug.LogWarning($"Frame {Time.frameCount}. Skipping tick {tick}. Current {_currentGoalData.DataTick}. PostTick? {postTick}. QueueCount {_goalDatas.Count}. StatesCount {_networkObject.PredictionManager._recievedStates.Count}");
|
||||
return;
|
||||
}
|
||||
//If current tick then let current play out and do nothing.
|
||||
else if (tick == _currentGoalData.DataTick)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
uint prevArrTick = 0;
|
||||
for (int i = 0; i < datasCount; i++)
|
||||
{
|
||||
uint arrTick = _goalDatas[i].DataTick;
|
||||
if (tick == arrTick)
|
||||
{
|
||||
dataIndex = i;
|
||||
useUpdate = true;
|
||||
break;
|
||||
}
|
||||
else if (i > 0 && tick > prevArrTick && tick < arrTick)
|
||||
{
|
||||
dataIndex = i;
|
||||
break;
|
||||
}
|
||||
|
||||
prevArrTick = arrTick;
|
||||
}
|
||||
|
||||
if (dataIndex == -1)
|
||||
{
|
||||
//Insert at beginning.
|
||||
if (datasCount > 0 && tick < _goalDatas[0].DataTick)
|
||||
dataIndex = 0;
|
||||
//Insert at end.
|
||||
else
|
||||
dataIndex = datasCount;
|
||||
}
|
||||
}
|
||||
|
||||
Transform rootT = _networkObject.transform;
|
||||
//Begin building next goal data.
|
||||
GoalData nextGd = ResettableObjectCaches<GoalData>.Retrieve();
|
||||
nextGd.DataTick = tick;
|
||||
nextGd.TimeRemaining = _tickDelta;
|
||||
nextGd.IsValid = true;
|
||||
//Set next transform data.
|
||||
TransformPropertiesCls nextTp = nextGd.TransformProperties;
|
||||
//Position.
|
||||
if (!_smoothingData.SmoothPosition)
|
||||
nextTp.Position = _graphicalPreSimulateWorldValues.Position;
|
||||
else
|
||||
nextTp.Position = rootT.position;
|
||||
//ROtation.
|
||||
if (!_smoothingData.SmoothRotation)
|
||||
nextTp.Rotation = _graphicalPreSimulateWorldValues.Rotation;
|
||||
else
|
||||
nextTp.Rotation = rootT.rotation;
|
||||
|
||||
|
||||
//Vector3 lineDist = new Vector3(0f, 3f, 0f);
|
||||
//if (!postTick)
|
||||
// Debug.DrawLine(rootT.position + lineDist, rootT.position, Color.red, 2f);
|
||||
//else
|
||||
// Debug.DrawLine(rootT.position + lineDist + new Vector3(1f, 0f, 0f), rootT.position, Color.blue, 2f);
|
||||
|
||||
if (useUpdate)
|
||||
_goalDatas[dataIndex].Update(nextGd);
|
||||
else
|
||||
_goalDatas.Insert(dataIndex, nextGd);
|
||||
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8444905b030752941a0f1562fd44a782
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,887 @@
|
||||
//using GameKit.Utilities; //Remove on 04/01/01
|
||||
//using FishNet.Transporting;
|
||||
//using FishNet.Utility.Extension;
|
||||
//using System.Runtime.CompilerServices;
|
||||
//using UnityEngine;
|
||||
//using FishNet.Managing.Timing;
|
||||
//using System.Collections.Generic;
|
||||
//using FishNet.Managing.Scened;
|
||||
|
||||
//namespace FishNet.Object.Prediction
|
||||
//{
|
||||
// internal class AdaptiveInterpolationSmoother
|
||||
// {
|
||||
// #if PREDICTION_V2
|
||||
|
||||
// #region Types.
|
||||
|
||||
// /// <summary>
|
||||
// /// Data on a goal to move towards.
|
||||
// /// </summary>
|
||||
// private class GoalData : IResettable
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// True if this GoalData is valid.
|
||||
// /// </summary>
|
||||
// public bool IsValid;
|
||||
|
||||
// /// <summary>
|
||||
// /// Tick of the data this GoalData is for.
|
||||
// /// </summary>
|
||||
// public uint DataTick;
|
||||
|
||||
// /// <summary>
|
||||
// /// Data on how fast to move to transform values.
|
||||
// /// </summary>
|
||||
// public RateData MoveRates = new RateData();
|
||||
|
||||
// /// <summary>
|
||||
// /// Transform values to move towards.
|
||||
// /// </summary>
|
||||
// public TransformPropertiesCls TransformProperties = new TransformPropertiesCls();
|
||||
|
||||
// public GoalData() { }
|
||||
|
||||
// public void InitializeState() { }
|
||||
|
||||
// public void ResetState()
|
||||
// {
|
||||
// DataTick = 0;
|
||||
// TransformProperties.ResetState();
|
||||
// MoveRates.ResetState();
|
||||
// IsValid = false;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Updates values using a GoalData.
|
||||
// /// </summary>
|
||||
// public void Update(GoalData gd)
|
||||
// {
|
||||
// DataTick = gd.DataTick;
|
||||
// MoveRates.Update(gd.MoveRates);
|
||||
// TransformProperties.Update(gd.TransformProperties);
|
||||
// IsValid = true;
|
||||
// }
|
||||
|
||||
// public void Update(uint dataTick, RateData rd, TransformPropertiesCls tp)
|
||||
// {
|
||||
// DataTick = dataTick;
|
||||
// MoveRates = rd;
|
||||
// TransformProperties = tp;
|
||||
// IsValid = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// How fast to move to values.
|
||||
// /// </summary>
|
||||
// private class RateData : IResettable
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// Rate for position after smart calculations.
|
||||
// /// </summary>
|
||||
// public float Position;
|
||||
|
||||
// /// <summary>
|
||||
// /// Rate for rotation after smart calculations.
|
||||
// /// </summary>
|
||||
// public float Rotation;
|
||||
|
||||
// /// <summary>
|
||||
// /// Number of ticks the rates are calculated for.
|
||||
// /// If TickSpan is 2 then the rates are calculated under the assumption the transform changed over 2 ticks.
|
||||
// /// </summary>
|
||||
// public uint TickSpan;
|
||||
|
||||
// /// <summary>
|
||||
// /// Time remaining until transform is expected to reach it's goal.
|
||||
// /// </summary>
|
||||
// internal float TimeRemaining;
|
||||
|
||||
// public RateData() { }
|
||||
|
||||
// public void InitializeState() { }
|
||||
|
||||
// /// <summary>
|
||||
// /// Resets values for re-use.
|
||||
// /// </summary>
|
||||
// public void ResetState()
|
||||
// {
|
||||
// Position = 0f;
|
||||
// Rotation = 0f;
|
||||
// TickSpan = 0;
|
||||
// TimeRemaining = 0f;
|
||||
// }
|
||||
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// public void Update(RateData rd)
|
||||
// {
|
||||
// Update(rd.Position, rd.Rotation, rd.TickSpan, rd.TimeRemaining);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Updates rates.
|
||||
// /// </summary>
|
||||
// public void Update(float position, float rotation, uint tickSpan, float timeRemaining)
|
||||
// {
|
||||
// Position = position;
|
||||
// Rotation = rotation;
|
||||
// TickSpan = tickSpan;
|
||||
// TimeRemaining = timeRemaining;
|
||||
// }
|
||||
// }
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Private.
|
||||
|
||||
// /// <summary>
|
||||
// /// Offsets of the graphical object when this was initialized.
|
||||
// /// </summary>
|
||||
// private TransformProperties _graphicalInitializedValues;
|
||||
|
||||
// /// <summary>
|
||||
// /// Offsets of the graphical object during PreTick. This could also be the offset PreReplay.
|
||||
// /// </summary>
|
||||
// private TransformProperties _graphicalPretickValues;
|
||||
|
||||
// /// <summary>
|
||||
// /// Offsets of the root transform before simulating. This could be PreTick, or PreReplay.
|
||||
// /// </summary>
|
||||
// private TransformProperties _rootPreSimulateValues;
|
||||
|
||||
// /// <summary>
|
||||
// /// SmoothingData to use.
|
||||
// /// </summary>
|
||||
// private AdaptiveInterpolationSmoothingData _smoothingData;
|
||||
|
||||
// /// <summary>
|
||||
// /// Current interpolation value. This changes based on ping and settings.
|
||||
// /// </summary>
|
||||
// private long _currentInterpolation = 2;
|
||||
|
||||
// /// <summary>
|
||||
// /// Target interpolation when collision is exited. This changes based on ping and settings.
|
||||
// /// </summary>
|
||||
// private uint _targetInterpolation;
|
||||
|
||||
// /// <summary>
|
||||
// /// Target interpolation when collision is entered. This changes based on ping and settings.
|
||||
// /// </summary>
|
||||
// private uint _targetCollisionInterpolation;
|
||||
|
||||
// /// <summary>
|
||||
// /// Current GoalData being used.
|
||||
// /// </summary>
|
||||
// private GoalData _currentGoalData = new GoalData();
|
||||
|
||||
// /// <summary>
|
||||
// /// GoalDatas to move towards.
|
||||
// /// </summary>
|
||||
// //private RingBuffer<GoalData> _goalDatas = new RingBuffer<GoalData>();
|
||||
// private List<GoalData> _goalDatas = new List<GoalData>();
|
||||
|
||||
// /// <summary>
|
||||
// /// Last ping value when it was checked.
|
||||
// /// </summary>
|
||||
// private long _lastPing = long.MinValue;
|
||||
|
||||
// /// <summary>
|
||||
// /// Cached NetworkObject reference in SmoothingData for performance.
|
||||
// /// </summary>
|
||||
// private NetworkObject _networkObject;
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Const.
|
||||
|
||||
// /// <summary>
|
||||
// /// Multiplier to apply to movement speed when buffer is over interpolation.
|
||||
// /// </summary>
|
||||
// private const float OVERFLOW_MULTIPLIER = 0.1f;
|
||||
|
||||
// /// <summary>
|
||||
// /// Multiplier to apply to movement speed when buffer is under interpolation.
|
||||
// /// </summary>
|
||||
// private const float UNDERFLOW_MULTIPLIER = 0.02f;
|
||||
|
||||
// #endregion
|
||||
|
||||
// public AdaptiveInterpolationSmoother()
|
||||
// {
|
||||
// /* Initialize for up to 50
|
||||
// * goal datas. Anything beyond that
|
||||
// * is unreasonable. */
|
||||
// //_goalDatas.Initialize(50);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Initializes this for use.
|
||||
// /// </summary>
|
||||
// internal void Initialize(AdaptiveInterpolationSmoothingData data)
|
||||
// {
|
||||
// _smoothingData = data;
|
||||
// _networkObject = data.NetworkObject;
|
||||
// SetGraphicalObject(data.GraphicalObject);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// <summary>
|
||||
// /// Called every frame.
|
||||
// /// </summary>
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// public void Update()
|
||||
// {
|
||||
// if (CanSmooth())
|
||||
// MoveToTarget();
|
||||
// }
|
||||
|
||||
// private string GetGoalDataTicks()
|
||||
// {
|
||||
// string result = string.Empty;
|
||||
// foreach (var item in _goalDatas)
|
||||
// result += item.DataTick + ", ";
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// private string _preTickGoalDatas = string.Empty;
|
||||
|
||||
// /// <summary>
|
||||
// /// Called when the TimeManager invokes OnPreTick.
|
||||
// /// </summary>
|
||||
// public void OnPreTick()
|
||||
// {
|
||||
// _preTickGoalDatas = GetGoalDataTicks();
|
||||
|
||||
// if (CanSmooth())
|
||||
// {
|
||||
// UpdatePingInterpolation();
|
||||
// _graphicalPretickValues.Update(_smoothingData.GraphicalObject);
|
||||
|
||||
// //Update the last post simulate data.
|
||||
// UpdateRootPreSimulateOffsets();
|
||||
// //UpdateRootPreSimulateOffsets(_networkObject.ReplicateTick.Value(_networkObject.TimeManager) - 1);
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Called when the TimeManager invokes OnPostTick.
|
||||
// /// </summary>
|
||||
// public void OnPostTick()
|
||||
// {
|
||||
// if (CanSmooth())
|
||||
// {
|
||||
// //Move towards target interpolation.
|
||||
// UpdateCurrentInterpolation();
|
||||
// //Reset graphics to start graphicals transforms properties.
|
||||
// _smoothingData.GraphicalObject.SetPositionAndRotation(_graphicalPretickValues.Position, _graphicalPretickValues.Rotation);
|
||||
// //Create a goal data for new transform position.
|
||||
// uint tick = _networkObject.LastUnorderedReplicateTick;
|
||||
// //Debug.Log($"GoalDatas count {_goalDatas.Count}. Tick {tick}. LocalTick {_networkObject.TimeManager.LocalTick}");
|
||||
// CreatePostSimulateGoalData(tick, true);
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Called before a reconcile runs a replay.
|
||||
// /// </summary>
|
||||
// public void OnPreReplicateReplay(uint clientTick, uint serverTick)
|
||||
// {
|
||||
// //Update the last post simulate data.
|
||||
// if (CanSmooth())
|
||||
// {
|
||||
// UpdateRootPreSimulateOffsets();
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Called after a reconcile runs a replay.
|
||||
// /// </summary>
|
||||
// public void OnPostReplicateReplay(uint clientTick, uint serverTick)
|
||||
// {
|
||||
// if (CanSmooth())
|
||||
// {
|
||||
// /* Create new goal data from the replay.
|
||||
// * This must be done every replay. If a desync
|
||||
// * did occur then the goaldatas would be different
|
||||
// * from what they were previously. */
|
||||
// uint tick = _networkObject.LastUnorderedReplicateTick;
|
||||
// CreatePostSimulateGoalData(tick, false);
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Updates rootPostSimulateOffsets value with root transform's current values.
|
||||
// /// </summary>
|
||||
// private void UpdateRootPreSimulateOffsets()
|
||||
// {
|
||||
// Transform t = _networkObject.transform;
|
||||
// _rootPreSimulateValues.Update(t.position, t.rotation);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Moves current interpolation to target interpolation.
|
||||
// /// </summary>
|
||||
// private void UpdateCurrentInterpolation()
|
||||
// {
|
||||
// AdaptiveInterpolationSmoothingData data = _smoothingData;
|
||||
// bool colliding = _networkObject.CollidingWithLocalClient();
|
||||
// if (colliding)
|
||||
// _currentInterpolation -= data.InterpolationDecreaseStep;
|
||||
// else
|
||||
// _currentInterpolation += data.InterpolationIncreaseStep;
|
||||
|
||||
// _currentInterpolation = (long)Mathf.Clamp(_currentInterpolation, _targetCollisionInterpolation, _targetInterpolation);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Updates interpolation values based on ping.
|
||||
// /// </summary>
|
||||
// private void UpdatePingInterpolation()
|
||||
// {
|
||||
// /* Only update if ping has changed considerably.
|
||||
// * This will prevent random lag spikes from throwing
|
||||
// * off the interpolation. */
|
||||
// long ping = _networkObject.TimeManager.RoundTripTime;
|
||||
// ulong difference = (ulong)Mathf.Abs(ping - _lastPing);
|
||||
// _lastPing = ping;
|
||||
// //Allow update if ping jump is large enough.
|
||||
// if (difference > 25)
|
||||
// SetTargetSmoothing(ping, false);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Sets target smoothing values.
|
||||
// /// </summary>
|
||||
// /// <param name="setImmediately">True to set current values to targets immediately.</param>
|
||||
// private void SetTargetSmoothing(long ping, bool setImmediately)
|
||||
// {
|
||||
// AdaptiveInterpolationSmoothingData data = _smoothingData;
|
||||
// TimeManager tm = _networkObject.TimeManager;
|
||||
// double interpolationTime = (ping / 1000d) * data.InterpolationPercent;
|
||||
// _targetInterpolation = tm.TimeToTicks(interpolationTime, TickRounding.RoundUp);
|
||||
// double collisionInterpolationTime = (ping / 1000d) * data.CollisionInterpolationPercent;
|
||||
// _targetCollisionInterpolation = tm.TimeToTicks(collisionInterpolationTime, TickRounding.RoundUp);
|
||||
|
||||
// //If to apply values to targets immediately.
|
||||
// if (setImmediately)
|
||||
// _currentInterpolation = (_networkObject.CollidingWithLocalClient()) ? _targetCollisionInterpolation : _targetInterpolation;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Sets GraphicalObject.
|
||||
// /// </summary>
|
||||
// /// <param name="value"></param>
|
||||
// public void SetGraphicalObject(Transform value)
|
||||
// {
|
||||
// _smoothingData.GraphicalObject = value;
|
||||
// _graphicalInitializedValues = _networkObject.transform.GetTransformOffsets(_smoothingData.GraphicalObject);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Returns if the graphics can be smoothed.
|
||||
// /// </summary>
|
||||
// /// <returns></returns>
|
||||
// private bool CanSmooth()
|
||||
// {
|
||||
// if (_networkObject.IsOwner)
|
||||
// return false;
|
||||
|
||||
// if (_networkObject.IsServerOnly)
|
||||
// return false;
|
||||
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Returns if this transform matches arguments.
|
||||
// /// </summary>
|
||||
// /// <returns></returns>
|
||||
// private bool GraphicalObjectMatches(Vector3 position, Quaternion rotation)
|
||||
// {
|
||||
// bool positionMatches = (!_smoothingData.SmoothPosition || (_smoothingData.GraphicalObject.position == position));
|
||||
// bool rotationMatches = (!_smoothingData.SmoothRotation || (_smoothingData.GraphicalObject.rotation == rotation));
|
||||
|
||||
// return (positionMatches && rotationMatches);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Returns if there is any change between two datas.
|
||||
// /// </summary>
|
||||
// private bool HasChanged(TransformPropertiesCls a, TransformPropertiesCls b)
|
||||
// {
|
||||
// return (a.Position != b.Position) ||
|
||||
// (a.Rotation != b.Rotation);
|
||||
// }
|
||||
|
||||
// ///// <summary>
|
||||
// ///// Returns if the transform differs from td.
|
||||
// ///// </summary>
|
||||
// //private bool HasChanged(TransformPropertiesCls tp)
|
||||
// //{
|
||||
// // Transform t = _networkObject.transform;
|
||||
// // bool changed = (tp.Position != t.position) || (tp.Rotation != t.rotation);
|
||||
|
||||
// // return changed;
|
||||
// //}
|
||||
|
||||
// /// <summary>
|
||||
// /// Sets CurrentGoalData to the next in queue. Returns if was set successfully.
|
||||
// /// </summary>
|
||||
// private bool SetCurrentGoalData()
|
||||
// {
|
||||
// if (_goalDatas.Count == 0)
|
||||
// {
|
||||
// _currentGoalData.IsValid = false;
|
||||
|
||||
// return false;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// /* Update to the next goal data.
|
||||
// * We could assign _goalDatas[0] as the
|
||||
// * current and then just remove it from
|
||||
// * the collection. But if did that _currentGoalData
|
||||
// * would have to be disposed first. So all the same,
|
||||
// * we're using the Update then dispose because it's
|
||||
// * a little easier to follow. */
|
||||
// _currentGoalData.Update(_goalDatas[0]);
|
||||
// Vector3 offset = new Vector3(0f, 10f, 0f);
|
||||
// Debug.DrawLine(_goalDatas[0].TransformProperties.Position + offset, _goalDatas[0].TransformProperties.Position - offset, Color.red, 2f);
|
||||
// //Store old and remove it.
|
||||
// ResettableObjectCaches<GoalData>.Store(_goalDatas[0]);
|
||||
// //_goalDatas.RemoveRange(true, 1);
|
||||
// _goalDatas.RemoveRange(0, 1);
|
||||
|
||||
// //Debug.LogWarning($"Frame {Time.frameCount}. CurrentGoalData set to Tick {_currentGoalData.DataTick}. PosX/Y {_currentGoalData.TransformProperties.Position.x}, {_currentGoalData.TransformProperties.Position.y}. Rate {_currentGoalData.MoveRates.Position}");
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Moves to a GoalData. Automatically determins if to use data from server or client.
|
||||
// /// </summary>
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// private void MoveToTarget(float deltaOverride = -1f)
|
||||
// {
|
||||
// /* If the current goal data is not valid then
|
||||
// * try to set a new one. If none are available
|
||||
// * it will remain inactive. */
|
||||
// if (!_currentGoalData.IsValid)
|
||||
// {
|
||||
// if (!SetCurrentGoalData())
|
||||
// return;
|
||||
// }
|
||||
|
||||
// float delta = (deltaOverride != -1f) ? deltaOverride : Time.deltaTime;
|
||||
// /* Once here it's safe to assume the object will be moving.
|
||||
// * Any checks which would stop it from moving be it client
|
||||
// * auth and owner, or server controlled and server, ect,
|
||||
// * would have already been run. */
|
||||
// TransformPropertiesCls td = _currentGoalData.TransformProperties;
|
||||
// RateData rd = _currentGoalData.MoveRates;
|
||||
|
||||
// int queueCount = _goalDatas.Count;
|
||||
// /* Begin moving even if interpolation buffer isn't
|
||||
// * met to provide more real-time interactions but
|
||||
// * speed up when buffer is too large. This should
|
||||
// * provide a good balance of accuracy. */
|
||||
|
||||
// float multiplier;
|
||||
// int countOverInterpolation = (queueCount - (int)_currentInterpolation - (int)_currentInterpolation);
|
||||
|
||||
// if (countOverInterpolation > 0)
|
||||
// {
|
||||
// float overflowMultiplier = OVERFLOW_MULTIPLIER;
|
||||
// overflowMultiplier = 0f;
|
||||
// multiplier = 1f + overflowMultiplier; //(overflowMultiplier * countOverInterpolation);
|
||||
// }
|
||||
// else if (countOverInterpolation < 0)
|
||||
// {
|
||||
// float value = (UNDERFLOW_MULTIPLIER * Mathf.Abs(countOverInterpolation));
|
||||
// const float maximum = 0.9f;
|
||||
// if (value > maximum)
|
||||
// value = maximum;
|
||||
|
||||
// multiplier = 1f - value;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// multiplier = 1f;
|
||||
// }
|
||||
|
||||
// //Rate to update. Changes per property.
|
||||
// float rate;
|
||||
// Transform t = _smoothingData.GraphicalObject;
|
||||
|
||||
// //Position.
|
||||
// if (_smoothingData.SmoothPosition)
|
||||
// {
|
||||
// rate = rd.Position;
|
||||
// Vector3 posGoal = td.Position;
|
||||
// //Debug.Log($"Rate {rate}. PosY {posGoal.y}. Multiplier {multiplier}. QueueCount {queueCount}");
|
||||
// if (rate == -1f)
|
||||
// t.position = td.Position;
|
||||
// else if (rate > 0f)
|
||||
// t.position = Vector3.MoveTowards(t.position, posGoal, rate * delta * multiplier);
|
||||
// }
|
||||
|
||||
// //Rotation.
|
||||
// if (_smoothingData.SmoothRotation)
|
||||
// {
|
||||
// rate = rd.Rotation;
|
||||
// if (rate == -1f)
|
||||
// t.rotation = td.Rotation;
|
||||
// else if (rate > 0f)
|
||||
// t.rotation = Quaternion.RotateTowards(t.rotation, td.Rotation, rate * delta);
|
||||
// }
|
||||
|
||||
// //Subtract time remaining for movement to complete.
|
||||
// if (rd.TimeRemaining > 0f)
|
||||
// {
|
||||
// float subtractionAmount = (delta * multiplier);
|
||||
// float timeRemaining = rd.TimeRemaining - subtractionAmount;
|
||||
// rd.TimeRemaining = timeRemaining;
|
||||
// }
|
||||
|
||||
// //If movement shoudl be complete.
|
||||
// if (rd.TimeRemaining <= 0f)
|
||||
// {
|
||||
// //_smoothingData.GraphicalObject.transform.position = _currentGoalData.TransformProperties.Position;
|
||||
// //_smoothingData.GraphicalObject.transform.rotation = _currentGoalData.TransformProperties.Rotation;
|
||||
// float leftOver = Mathf.Abs(rd.TimeRemaining);
|
||||
|
||||
// if (SetCurrentGoalData())
|
||||
// {
|
||||
// if (leftOver > 0f)
|
||||
// MoveToTarget(leftOver);
|
||||
// }
|
||||
// //No more in buffer, see if can extrapolate.
|
||||
// else
|
||||
// {
|
||||
// /* Everything should line up when
|
||||
// * time remaining is <= 0f but incase it's not,
|
||||
// * such as if the user manipulated the grapihc object
|
||||
// * somehow, then set goaldata active again to continue
|
||||
// * moving it until it lines up with the goal. */
|
||||
// if (!GraphicalObjectMatches(td.Position, td.Rotation))
|
||||
// _currentGoalData.IsValid = true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// #region Rates.
|
||||
|
||||
// /// <summary>
|
||||
// /// Sets move rates which will occur instantly.
|
||||
// /// </summary>
|
||||
// private void SetInstantRates(RateData rd)
|
||||
// {
|
||||
// Debug.LogError($"Instant rates set.");
|
||||
// rd.Update(MoveRates.INSTANT_VALUE, MoveRates.INSTANT_VALUE, 1, MoveRates.INSTANT_VALUE);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Sets move rates which will occur over time.
|
||||
// /// </summary>
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// private void SetCalculatedRates(GoalData prevGd, GoalData nextGd, bool datasCleared, Channel channel)
|
||||
// {
|
||||
// datasCleared = false;
|
||||
|
||||
// /* Only update rates if data has changed.
|
||||
// * When data comes in reliably for eventual consistency
|
||||
// * it's possible that it will be the same as the last
|
||||
// * unreliable packet. When this happens no change has occurred
|
||||
// * and the distance of change would also be 0; this prevents
|
||||
// * the object from moving. Only need to compare data if channel is reliable. */
|
||||
// if (channel == Channel.Reliable && !HasChanged(prevGd.TransformProperties, nextGd.TransformProperties))
|
||||
// {
|
||||
// Debug.LogError("Reliable and unchanged.");
|
||||
// nextGd.MoveRates.Update(prevGd.MoveRates);
|
||||
// //Set to 0 to indicate settled.
|
||||
// nextGd.DataTick = 0;
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
// uint lastTick = prevGd.DataTick;
|
||||
// /* How much time has passed between last update and current.
|
||||
// * If set to 0 then that means the transform has
|
||||
// * settled. */
|
||||
// if (lastTick == 0)
|
||||
// lastTick = (nextGd.DataTick - 1);
|
||||
|
||||
// uint tickDifference = (nextGd.DataTick - lastTick);
|
||||
// float timePassed = (float)_networkObject.TimeManager.TicksToTime(tickDifference);
|
||||
// RateData nextRd = nextGd.MoveRates;
|
||||
|
||||
// float rateMultiplier;
|
||||
|
||||
// if (!datasCleared)
|
||||
// {
|
||||
// rateMultiplier = 1f;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// float tickDelta = (float)_networkObject.TimeManager.TickDelta;
|
||||
// rateMultiplier = (_currentGoalData.MoveRates.TimeRemaining / tickDelta);
|
||||
// }
|
||||
|
||||
// //Distance between properties.
|
||||
// float distance;
|
||||
// //Position.
|
||||
// Vector3 lastPosition = prevGd.TransformProperties.Position;
|
||||
// distance = Vector3.Distance(lastPosition, nextGd.TransformProperties.Position);
|
||||
|
||||
// if (tickDifference == 0)
|
||||
// Debug.LogError($"0 tick difference");
|
||||
|
||||
// //If distance teleports assume rest do.
|
||||
// if (_smoothingData.TeleportThreshold != MoveRates.UNSET_VALUE && distance >= _smoothingData.TeleportThreshold)
|
||||
// {
|
||||
// SetInstantRates(nextRd);
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
// //Position distance already calculated.
|
||||
// float positionRate = (distance / timePassed);
|
||||
// if (positionRate <= 0f || positionRate > 5.6f && !_networkObject.IsServer && !_networkObject.IsOwner)
|
||||
// //Debug.LogError($"Position Rate {positionRate} for tick {nextGd.LocalTick}. PrevY {prevGd.TransformProperties.Position.y}. NextY {nextGd.TransformProperties.Position.y}");
|
||||
// //Rotation.
|
||||
// distance = prevGd.TransformProperties.Rotation.Angle(nextGd.TransformProperties.Rotation, true);
|
||||
|
||||
// float rotationRate = (distance / timePassed);
|
||||
|
||||
// //if (positionRate > 5.1f || positionRate <= 0.05f)
|
||||
// //Debug.Log($"Rate {positionRate}. Distance {distance}. TickDifference {tickDifference}.");
|
||||
// /* If no speed then snap just in case.
|
||||
// * 0f could be from floating errors. */
|
||||
// if (positionRate == 0f)
|
||||
// positionRate = MoveRates.INSTANT_VALUE;
|
||||
|
||||
// if (rotationRate == 0f)
|
||||
// rotationRate = MoveRates.INSTANT_VALUE;
|
||||
|
||||
// nextRd.Update(positionRate * rateMultiplier, rotationRate * rateMultiplier, tickDifference, timePassed);
|
||||
// }
|
||||
|
||||
// #endregion
|
||||
|
||||
// /// <summary>
|
||||
// /// Removes GoalDatas which make the queue excessive.
|
||||
// /// This could cause teleportation but would rarely occur, only potentially during sever network issues.
|
||||
// /// </summary>
|
||||
// private void RemoveExcessiveGoalDatas()
|
||||
// {
|
||||
// if (_goalDatas.Count > 100)
|
||||
// Debug.LogError($"Whoa getting kind of high with count of {_goalDatas.Count}");
|
||||
// ///* Remove entries which are excessive to the buffer.
|
||||
// //* This could create a starting jitter but it will ensure
|
||||
// //* the buffer does not fill too much. The buffer next should
|
||||
// //* actually get unreasonably high but rather safe than sorry. */
|
||||
// //int maximumBufferAllowance = ((int)_currentInterpolation * 8);
|
||||
// //int removedBufferCount = (_goalDatas.Count - maximumBufferAllowance);
|
||||
// ////If there are some to remove.
|
||||
// //if (removedBufferCount > 0)
|
||||
// //{
|
||||
// // for (int i = 0; i < removedBufferCount; i++)
|
||||
// // ResettableObjectCaches<GoalData>.Store(_goalDatas[0 + i]);
|
||||
// // //_goalDatas.RemoveRange(true, removedBufferCount);
|
||||
// // _goalDatas.RemoveRange(0, removedBufferCount);
|
||||
// //}
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Returns if a tick is older than or equal to the current GoalData and outputs current GoalData tick.
|
||||
// /// </summary>
|
||||
// private bool OldGoalDataTick(uint tick, out uint currentGoalDataTick)
|
||||
// {
|
||||
// currentGoalDataTick = _currentGoalData.DataTick;
|
||||
|
||||
// return (tick <= currentGoalDataTick);
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Creates the next GoalData using previous goalData and tick.
|
||||
// /// </summary>
|
||||
// /// <param name="tick">Tick to apply for the next goal data.</param>
|
||||
// private GoalData CreateNextGoalData(uint tick, GoalData prevGoalData, bool datasCleared)
|
||||
// {
|
||||
// //Debug.Log($"Creating next GoalData for tick {tick}. PrevGoalData tick {prevGoalData.LocalTick}");
|
||||
// //Begin building next goal data.
|
||||
// GoalData nextGoalData = ResettableObjectCaches<GoalData>.Retrieve();
|
||||
// nextGoalData.DataTick = tick;
|
||||
// //Set next transform data.
|
||||
// TransformPropertiesCls nextTp = nextGoalData.TransformProperties;
|
||||
// nextTp.Update(_networkObject.transform);
|
||||
// /* Reset properties if smoothing is not enabled
|
||||
// * for them. It's less checks and easier to do it
|
||||
// * after the nextGoalData is populated. */
|
||||
// if (!_smoothingData.SmoothPosition)
|
||||
// nextTp.Position = _graphicalPretickValues.Position;
|
||||
|
||||
// if (!_smoothingData.SmoothRotation)
|
||||
// nextTp.Rotation = _graphicalPretickValues.Rotation;
|
||||
|
||||
// // Debug.Log($"Creating NextGd X {nextTp.Position.x} for tick {tick}.");
|
||||
// //Calculate rates for prev vs next data.
|
||||
// SetCalculatedRates(prevGoalData, nextGoalData, datasCleared, Channel.Unreliable);
|
||||
|
||||
// return nextGoalData;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Makes a GoalData using transform values from rootPostSimulateOffsets.
|
||||
// /// </summary>
|
||||
// /// <returns></returns>
|
||||
// private GoalData CreateGoalDataFromRootPreSimulate(uint tick)
|
||||
// {
|
||||
// GoalData gd = ResettableObjectCaches<GoalData>.Retrieve();
|
||||
// //RigidbodyData contains the data from preTick.
|
||||
// // Debug.Log($"Creating goalData from X {_rootPostSimulateValues.Position.x}. Tick {tick}");
|
||||
// gd.TransformProperties.Update(_rootPreSimulateValues);
|
||||
// gd.DataTick = tick;
|
||||
|
||||
// //No need to update rates because this is just a starting point reference for interpolation.
|
||||
// return gd;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Clears all goalDatas.
|
||||
// /// </summary>
|
||||
// private void ClearGoalData(bool clearCurrent)
|
||||
// {
|
||||
// if (clearCurrent)
|
||||
// ResettableObjectCaches<GoalData>.Store(_currentGoalData);
|
||||
|
||||
// int count = _goalDatas.Count;
|
||||
// for (int i = 0; i < count; i++)
|
||||
// ResettableObjectCaches<GoalData>.Store(_goalDatas[i]);
|
||||
|
||||
// _goalDatas.Clear();
|
||||
// }
|
||||
|
||||
// private uint _jumpTick;
|
||||
|
||||
// private uint _lastPostTickDataTick;
|
||||
|
||||
// /// <summary>
|
||||
// /// Creates a GoalData after a simulate.
|
||||
// /// </summary>
|
||||
// /// <param name="postTick">True if being created for OnPostTick.</param>
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// private void CreatePostSimulateGoalData(uint tick, bool postTick)
|
||||
// {
|
||||
// bool jumping = (_networkObject.transform.position.y > 0.5f);
|
||||
// bool dataCleared = false;
|
||||
// int dataIndex = -1;
|
||||
// bool useUpdate = false;
|
||||
// RemoveExcessiveGoalDatas();
|
||||
|
||||
// if (tick <= _currentGoalData.DataTick)
|
||||
// {
|
||||
// if (!postTick)
|
||||
// {
|
||||
// if (jumping)
|
||||
// Debug.LogWarning($"Frame {Time.frameCount}. Old tick. Tick {tick}. Current {_currentGoalData.DataTick}. QueueCount {_goalDatas.Count}");
|
||||
|
||||
// return;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// dataCleared = true;
|
||||
// ClearGoalData(false);
|
||||
// Debug.LogWarning($"Frame {Time.frameCount}. Tick {tick}. Current {_currentGoalData.DataTick}. CLEARING!");
|
||||
// }
|
||||
// }
|
||||
|
||||
// uint prevArrTick = 0;
|
||||
|
||||
// for (int i = 0; i < _goalDatas.Count; i++)
|
||||
// {
|
||||
// uint arrTick = _goalDatas[i].DataTick;
|
||||
|
||||
// if (tick == arrTick)
|
||||
// {
|
||||
// dataIndex = i;
|
||||
// useUpdate = true;
|
||||
|
||||
// break;
|
||||
// }
|
||||
// else if (i > 0 && tick > prevArrTick && tick < arrTick)
|
||||
// {
|
||||
// dataIndex = i;
|
||||
|
||||
// break;
|
||||
// }
|
||||
|
||||
// prevArrTick = arrTick;
|
||||
// }
|
||||
|
||||
// if (dataIndex == -1)
|
||||
// {
|
||||
// if (_goalDatas.Count > 0 && tick < _goalDatas[0].DataTick)
|
||||
// {
|
||||
// // Insert at the beginning.
|
||||
// dataIndex = 0;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // Insert at the end.
|
||||
// dataIndex = _goalDatas.Count;
|
||||
// }
|
||||
// }
|
||||
|
||||
// GoalData prevGd;
|
||||
|
||||
// if (dataCleared)
|
||||
// {
|
||||
// prevGd = ResettableObjectCaches<GoalData>.Retrieve();
|
||||
// prevGd.Update(_currentGoalData);
|
||||
// prevGd.DataTick = (tick - 1);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// prevGd = CreateGoalDataFromRootPreSimulate(tick - 1);
|
||||
// }
|
||||
|
||||
// GoalData nextGd = CreateNextGoalData(tick, prevGd, dataCleared);
|
||||
|
||||
// if (jumping)
|
||||
// {
|
||||
// Debug.Log($"Frame {Time.frameCount}. CreateGoalData. Tick {tick}. Next Rate {nextGd.MoveRates.Position}. Next PosY {nextGd.TransformProperties.Position.y}");
|
||||
// SceneLoadData sld = new SceneLoadData(_networkObject.gameObject.scene);
|
||||
// _jumpTick = nextGd.DataTick;
|
||||
// }
|
||||
|
||||
// if (useUpdate && _goalDatas[dataIndex].DataTick == _jumpTick)
|
||||
// {
|
||||
// Debug.LogError($"Frame {Time.frameCount}. Overwriting jump. Tick {tick}. IndexTick {_goalDatas[dataIndex].DataTick}. CurrentGoalY {_goalDatas[dataIndex].TransformProperties.Position.y}. Next Rate {nextGd.MoveRates.Position}. Next PosY {nextGd.TransformProperties.Position.y}.");
|
||||
// }
|
||||
|
||||
// if (useUpdate)
|
||||
// _goalDatas[dataIndex].Update(nextGd);
|
||||
// else
|
||||
// _goalDatas.Insert(dataIndex, nextGd);
|
||||
|
||||
// //Debug.
|
||||
// if (postTick)
|
||||
// {
|
||||
// Vector3 offset = new Vector3(0.15f, 4f, 0f);
|
||||
// Debug.DrawLine(nextGd.TransformProperties.Position + offset, nextGd.TransformProperties.Position - offset, Color.green, 2f);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Vector3 offset = new Vector3(-0.15f, 4f, 0f);
|
||||
// Debug.DrawLine(nextGd.TransformProperties.Position + offset, nextGd.TransformProperties.Position - offset, Color.cyan, 2f);
|
||||
// }
|
||||
// }
|
||||
|
||||
// uint _postTickGdCount;
|
||||
|
||||
// #endif
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10df9daae891dda488c9fa8715b68556
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
/// <summary>
|
||||
/// Replicated methods are to be called from clients and will run the same data and logic on the server.
|
||||
/// Only data used as method arguments will be serialized.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
|
||||
public class ReplicateAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// How many past datas to resend.
|
||||
/// </summary>
|
||||
[Obsolete("Use PredictionManager.RedundancyCount.")] //Remove on 2023/06/01
|
||||
public byte Resends = 5;
|
||||
/// <summary>
|
||||
/// True to allow running input passed in with asServer true when there is no owner.
|
||||
/// </summary>
|
||||
public bool AllowServerControl = false;
|
||||
}
|
||||
/// <summary>
|
||||
/// Reconcile methods indicate how to reset your script or object after the server has replicated user data.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
|
||||
public class ReconcileAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// How many times to resend reconcile.
|
||||
/// </summary>
|
||||
[Obsolete("Use PredictionManager.RedundancyCount.")] //Remove on 2023/06/01
|
||||
public byte Resends = 3;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Replicated methods are to be called from clients and will run the same data and logic on the server.
|
||||
/// Only data used as method arguments will be serialized.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
|
||||
public class ReplicateV2Attribute : Attribute { }
|
||||
/// <summary>
|
||||
/// Reconcile methods indicate how to reset your script or object after the server has replicated user data.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
|
||||
public class ReconcileV2Attribute : Attribute { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b082d36535ce0404d8438bc1b0499e53
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,27 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Constant;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)]
|
||||
namespace FishNet.Object.Prediction.Delegating
|
||||
{
|
||||
[APIExclude]
|
||||
public delegate void ReplicateRpcDelegate(PooledReader reader, NetworkConnection sender, Channel channel);
|
||||
[APIExclude]
|
||||
public delegate void ReconcileRpcDelegate(PooledReader reader, Channel channel);
|
||||
|
||||
#if !PREDICTION_V2
|
||||
[APIExclude]
|
||||
public delegate void ReplicateUserLogicDelegate<T>(T data, bool asServer, Channel channel, bool replaying);
|
||||
[APIExclude]
|
||||
public delegate void ReconcileUserLogicDelegate<T>(T data, bool asServer, Channel channel);
|
||||
#else
|
||||
[APIExclude]
|
||||
public delegate void ReplicateUserLogicDelegate<T>(T data, ReplicateState state, Channel channel);
|
||||
[APIExclude]
|
||||
public delegate void ReconcileUserLogicDelegate<T>(T data, Channel channel);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9904192dacd41a4ba7b29bc3199ec3a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
public interface IReplicateData
|
||||
{
|
||||
/// <summary>
|
||||
/// Local tick when the data was created.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
uint GetTick();
|
||||
/// <summary>
|
||||
/// Sets the local tick when data was created.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
void SetTick(uint value);
|
||||
/// <summary>
|
||||
/// Allows for any cleanup when the data is being discarded.
|
||||
/// </summary>
|
||||
void Dispose();
|
||||
}
|
||||
|
||||
public interface IReconcileData
|
||||
{
|
||||
/// <summary>
|
||||
/// Local tick when the data was created.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
uint GetTick();
|
||||
/// <summary>
|
||||
/// Sets the local tick when data was created.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
void SetTick(uint value);
|
||||
/// <summary>
|
||||
/// Allows for any cleanup when the data is being discarded.
|
||||
/// </summary>
|
||||
void Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 515754257f85574438408c7f5b268590
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,232 @@
|
||||
|
||||
using GameKit.Utilities;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
/// <summary>
|
||||
/// Data to be used to configure smoothing for an owned predicted object.
|
||||
/// </summary>
|
||||
internal struct MoveRates
|
||||
{
|
||||
public float Position;
|
||||
public float Rotation;
|
||||
public float Scale;
|
||||
|
||||
public MoveRates(float value)
|
||||
{
|
||||
Position = value;
|
||||
Rotation = value;
|
||||
Scale = value;
|
||||
}
|
||||
public MoveRates(float position, float rotation)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = INSTANT_VALUE;
|
||||
}
|
||||
public MoveRates(float position, float rotation, float scale)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = scale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if a positional move rate is set.
|
||||
/// </summary>
|
||||
public bool PositionSet => (Position != UNSET_VALUE);
|
||||
/// <summary>
|
||||
/// True if rotation move rate is set.
|
||||
/// </summary>
|
||||
public bool RotationSet => (Rotation != UNSET_VALUE);
|
||||
/// <summary>
|
||||
/// True if a scale move rate is set.
|
||||
/// </summary>
|
||||
public bool ScaleSet => (Scale != UNSET_VALUE);
|
||||
/// <summary>
|
||||
/// True if any move rate is set.
|
||||
/// </summary>
|
||||
public bool AnySet => (PositionSet || RotationSet || ScaleSet);
|
||||
|
||||
/// <summary>
|
||||
/// True if position move rate should be instant.
|
||||
/// </summary>
|
||||
public bool InstantPosition => (Position == INSTANT_VALUE);
|
||||
/// <summary>
|
||||
/// True if rotation move rate should be instant.
|
||||
/// </summary>
|
||||
public bool InstantRotation => (Rotation == INSTANT_VALUE);
|
||||
/// <summary>
|
||||
/// True if scale move rate should be instant.
|
||||
/// </summary>
|
||||
public bool InstantScale => (Scale == INSTANT_VALUE);
|
||||
|
||||
/// <summary>
|
||||
/// Sets all rates to instant.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetInstantRates()
|
||||
{
|
||||
Update(INSTANT_VALUE);
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets all rates to the same value.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(float value)
|
||||
{
|
||||
Update(value, value, value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets rates for each property.
|
||||
/// </summary>
|
||||
public void Update(float position, float rotation, float scale)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = scale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Value used when data is not set.
|
||||
/// </summary>
|
||||
public const float UNSET_VALUE = float.NegativeInfinity;
|
||||
/// <summary>
|
||||
/// Value used when move rate should be instant.
|
||||
/// </summary>
|
||||
public const float INSTANT_VALUE = float.PositiveInfinity;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Data to be used to configure smoothing for an owned predicted object.
|
||||
/// </summary>
|
||||
internal class MoveRatesCls : IResettable
|
||||
{
|
||||
public float Position;
|
||||
public float Rotation;
|
||||
public float Scale;
|
||||
|
||||
public float LastMultiplier { get; private set; } = 1f;
|
||||
|
||||
public MoveRatesCls(float value)
|
||||
{
|
||||
Position = value;
|
||||
Rotation = value;
|
||||
Scale = value;
|
||||
}
|
||||
public MoveRatesCls(float position, float rotation)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = INSTANT_VALUE;
|
||||
}
|
||||
public MoveRatesCls(float position, float rotation, float scale)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = scale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if a positional move rate is set.
|
||||
/// </summary>
|
||||
public bool PositionSet => (Position != UNSET_VALUE);
|
||||
/// <summary>
|
||||
/// True if rotation move rate is set.
|
||||
/// </summary>
|
||||
public bool RotationSet => (Rotation != UNSET_VALUE);
|
||||
/// <summary>
|
||||
/// True if a scale move rate is set.
|
||||
/// </summary>
|
||||
public bool ScaleSet => (Scale != UNSET_VALUE);
|
||||
/// <summary>
|
||||
/// True if any move rate is set.
|
||||
/// </summary>
|
||||
public bool AnySet => (PositionSet || RotationSet || ScaleSet);
|
||||
|
||||
/// <summary>
|
||||
/// True if position move rate should be instant.
|
||||
/// </summary>
|
||||
public bool InstantPosition => (Position == INSTANT_VALUE);
|
||||
/// <summary>
|
||||
/// True if rotation move rate should be instant.
|
||||
/// </summary>
|
||||
public bool InstantRotation => (Rotation == INSTANT_VALUE);
|
||||
/// <summary>
|
||||
/// True if scale move rate should be instant.
|
||||
/// </summary>
|
||||
public bool InstantScale => (Scale == INSTANT_VALUE);
|
||||
|
||||
public MoveRatesCls()
|
||||
{
|
||||
Update(UNSET_VALUE, UNSET_VALUE, UNSET_VALUE);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies all rates by value.
|
||||
/// </summary>
|
||||
public void Multiply(float value)
|
||||
{
|
||||
LastMultiplier = value;
|
||||
Position *= value;
|
||||
Rotation *= value;
|
||||
Scale *= value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all rates to instant.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetInstantRates()
|
||||
{
|
||||
Update(INSTANT_VALUE);
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets all rates to the same value.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(float value)
|
||||
{
|
||||
Update(value, value, value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Updaes values.
|
||||
/// </summary>
|
||||
public void Update(float position, float rotation, float scale)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = scale;
|
||||
}
|
||||
/// <summary>
|
||||
/// Updaes values.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(MoveRatesCls mr)
|
||||
{
|
||||
Update(mr.Position, mr.Rotation, mr.Scale);
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
Position = UNSET_VALUE;
|
||||
Rotation = UNSET_VALUE;
|
||||
Scale = UNSET_VALUE;
|
||||
}
|
||||
|
||||
public void InitializeState() { }
|
||||
|
||||
/// <summary>
|
||||
/// Value used when data is not set.
|
||||
/// </summary>
|
||||
public const float UNSET_VALUE = float.NegativeInfinity;
|
||||
/// <summary>
|
||||
/// Value used when move rate should be instant.
|
||||
/// </summary>
|
||||
public const float INSTANT_VALUE = float.PositiveInfinity;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bddf57861232884ca21f7e97a6d662d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,243 @@
|
||||
using FishNet.Utility.Extension;
|
||||
using GameKit.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
internal class SetInterpolationSmoother
|
||||
{
|
||||
#if PREDICTION_V2
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// How quickly to move towards goal values.
|
||||
/// </summary>
|
||||
private MoveRates _moveRates;
|
||||
/// <summary>
|
||||
/// Local values of the graphical object when this was initialized.
|
||||
/// </summary>
|
||||
private TransformProperties _graphicalInitializedLocalValues;
|
||||
/// <summary>
|
||||
/// Values of the graphical object during PreTick or PreReplay.
|
||||
/// </summary>
|
||||
private TransformProperties _graphicalPreSimulateWorldValues;
|
||||
/// <summary>
|
||||
/// SmoothingData to use.
|
||||
/// </summary>
|
||||
private SetInterpolationSmootherData _smoothingData;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this smoother; should only be completed once.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
internal void InitializeOnce(SetInterpolationSmootherData data)
|
||||
{
|
||||
_smoothingData = data;
|
||||
SetGraphicalObject(data.GraphicalObject);
|
||||
_moveRates = new MoveRates(MoveRates.UNSET_VALUE);
|
||||
_graphicalPreSimulateWorldValues = _smoothingData.GraphicalObject.GetWorldProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets GraphicalObject; can be changed at runtime.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
internal void SetGraphicalObject(Transform value)
|
||||
{
|
||||
_smoothingData.GraphicalObject = value;
|
||||
_graphicalInitializedLocalValues.Update(value.localPosition, value.localRotation, value.localScale);
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets the interpolation value to use when the owner of this object.
|
||||
/// </summary>
|
||||
/// <param name="value">New interpolation value.</param>
|
||||
internal void SetInterpolation(byte value)
|
||||
{
|
||||
if (value < 1)
|
||||
value = 1;
|
||||
|
||||
_smoothingData.Interpolation = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called every frame.
|
||||
/// </summary>
|
||||
internal void Update()
|
||||
{
|
||||
if (CanSmooth())
|
||||
MoveToTarget();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when the TimeManager invokes OnPreTick.
|
||||
/// </summary>
|
||||
internal void OnPreTick()
|
||||
{
|
||||
if (CanSmooth())
|
||||
{
|
||||
/* 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 (_smoothingData.Interpolation == 1)
|
||||
ResetGraphicalToInitializedLocalOffsets(true, true);
|
||||
|
||||
_graphicalPreSimulateWorldValues = _smoothingData.GraphicalObject.GetWorldProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when TimeManager invokes OnPostTick.
|
||||
/// </summary>
|
||||
internal void OnPostTick()
|
||||
{
|
||||
if (CanSmooth())
|
||||
{
|
||||
_smoothingData.GraphicalObject.SetPositionAndRotation(_graphicalPreSimulateWorldValues.Position, _graphicalPreSimulateWorldValues.Rotation);
|
||||
SetGraphicalMoveRates();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if prediction can be used on this rigidbody.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool CanSmooth()
|
||||
{
|
||||
NetworkObject nob = _smoothingData.NetworkObject;
|
||||
if (nob == null)
|
||||
return false;
|
||||
if (nob.IsServerOnly)
|
||||
return false;
|
||||
if (!nob.IsHost && nob.SpectatorAdaptiveInterpolation && !nob.IsOwner)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves transform to target values.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void MoveToTarget()
|
||||
{
|
||||
//No rates are set.
|
||||
if (!_moveRates.AnySet)
|
||||
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 = _smoothingData.GraphicalObject;
|
||||
float delta = Time.deltaTime;
|
||||
|
||||
//Position.
|
||||
if (_smoothingData.SmoothPosition)
|
||||
{
|
||||
if (_moveRates.InstantPosition)
|
||||
ResetGraphicalToInitializedLocalOffsets(true, false);
|
||||
else if (_moveRates.PositionSet)
|
||||
t.localPosition = Vector3.MoveTowards(t.localPosition, posGoal, _moveRates.Position * delta);
|
||||
}
|
||||
|
||||
//Rotation.
|
||||
if (_smoothingData.SmoothRotation)
|
||||
{
|
||||
if (_moveRates.InstantRotation)
|
||||
ResetGraphicalToInitializedLocalOffsets(false, true);
|
||||
else if (_moveRates.RotationSet)
|
||||
t.localRotation = Quaternion.RotateTowards(t.localRotation, rotGoal, _moveRates.Rotation * delta);
|
||||
}
|
||||
|
||||
if (GraphicalObjectMatchesLocalValues(posGoal, rotGoal))
|
||||
_moveRates.Update(MoveRates.UNSET_VALUE);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if this transform matches arguments.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool GraphicalObjectMatchesLocalValues(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
bool positionMatches = (!_smoothingData.SmoothPosition || (_smoothingData.GraphicalObject.localPosition == position));
|
||||
bool rotationMatches = (!_smoothingData.SmoothRotation || (_smoothingData.GraphicalObject.localRotation == rotation));
|
||||
return (positionMatches && rotationMatches);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets Position and Rotation move rates to reach Target datas.
|
||||
/// </summary>
|
||||
private void SetGraphicalMoveRates()
|
||||
{
|
||||
uint interval = _smoothingData.Interpolation;
|
||||
float delta = (float)_smoothingData.NetworkObject?.TimeManager.TickDelta;
|
||||
|
||||
float rate;
|
||||
float distance;
|
||||
Transform t = _smoothingData.GraphicalObject;
|
||||
/* Position. */
|
||||
rate = t.localPosition.GetRate(_graphicalInitializedLocalValues.Position, delta, out distance, interval);
|
||||
//If qualifies for teleporting.
|
||||
if (_smoothingData.TeleportThreshold != MoveRates.UNSET_VALUE && distance >= _smoothingData.TeleportThreshold)
|
||||
{
|
||||
_moveRates.Update(MoveRates.INSTANT_VALUE);
|
||||
}
|
||||
//Smoothing.
|
||||
else
|
||||
{
|
||||
float positionRate = rate.SetIfUnderTolerance(0.0001f, MoveRates.INSTANT_VALUE);
|
||||
rate = t.localRotation.GetRate(_graphicalInitializedLocalValues.Rotation, delta, out _, interval);
|
||||
float rotationRate = rate.SetIfUnderTolerance(0.2f, MoveRates.INSTANT_VALUE);
|
||||
_moveRates.Update(positionRate, rotationRate, MoveRates.UNSET_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a goal position for the graphical object.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Vector3 GetGraphicalGoalPosition()
|
||||
{
|
||||
if (_smoothingData.SmoothPosition)
|
||||
return _graphicalInitializedLocalValues.Position;
|
||||
else
|
||||
return _smoothingData.GraphicalObject.position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a goal rotation for the graphical object.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Quaternion GetGraphicalGoalRotation()
|
||||
{
|
||||
if (_smoothingData.SmoothRotation)
|
||||
return _graphicalInitializedLocalValues.Rotation;
|
||||
else
|
||||
return _smoothingData.GraphicalObject.rotation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the graphical object to it's transform offsets during instantiation.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ResetGraphicalToInitializedLocalOffsets(bool position, bool rotation)
|
||||
{
|
||||
Transform graphical = _smoothingData.GraphicalObject;
|
||||
if (position)
|
||||
graphical.localPosition = GetGraphicalGoalPosition();
|
||||
if (rotation)
|
||||
graphical.localRotation = GetGraphicalGoalRotation();
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2adf11043050fb4dbd1c3893c66c9f3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
/// <summary>
|
||||
/// Data to be used to configure smoothing for an owned predicted object.
|
||||
/// </summary>
|
||||
internal struct SetInterpolationSmootherData
|
||||
{
|
||||
public Transform GraphicalObject;
|
||||
public byte Interpolation;
|
||||
public NetworkObject NetworkObject;
|
||||
public bool SmoothPosition;
|
||||
public bool SmoothRotation;
|
||||
public bool SmoothScale;
|
||||
public float TeleportThreshold;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5fecbbc171a81f4a8aaf30aebb1caef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user