[Add] FishNet

This commit is contained in:
2026-03-30 20:11:57 +07:00
parent ee793a3361
commit c22c08753a
1797 changed files with 197950 additions and 1 deletions
@@ -0,0 +1,421 @@
using System;
using FishNet.Editing;
using FishNet.Managing;
using FishNet.Managing.Statistic;
using FishNet.Managing.Timing;
using GameKit.Dependencies.Utilities;
using GameKit.Dependencies.Utilities.Types;
using UnityEngine;
namespace FishNet.Component.Utility
{
/// <summary>
/// Add to any object to display current ping(round trip time).
/// </summary>
[AddComponentMenu("FishNet/Component/BandwidthDisplay")]
public class BandwidthDisplay : MonoBehaviour
{
#region Types.
private enum Corner
{
TopLeft,
TopRight,
BottomLeft,
BottomRight
}
public class InOutAverage
{
private RingBuffer<ulong> _in;
private RingBuffer<ulong> _out;
public InOutAverage(int ticks)
{
_in = new(ticks);
_out = new(ticks);
}
public void AddIn(ulong value) => _in.Add(value);
public void AddOut(ulong value) => _out.Add(value);
public float GetAverage(bool inBuffer)
{
RingBuffer<ulong> buffer = GetBuffer(inBuffer);
int bufferCount = buffer.Count;
if (bufferCount == 0)
return 0;
ulong total = GetTotal(inBuffer);
return (float)total / bufferCount;
}
public ulong GetTotal(bool inBuffer)
{
RingBuffer<ulong> buffer = GetBuffer(inBuffer);
ulong total = 0;
foreach (ulong v in buffer)
total += v;
return total;
}
private RingBuffer<ulong> GetBuffer(bool inBuffer) => inBuffer ? _in : _out;
public void ResetState()
{
_in.Clear();
_out.Clear();
}
public void InitializeState(int capacity)
{
_in.Initialize(capacity);
_out.Initialize(capacity);
}
}
#endregion
#region Public.
#if UNITY_EDITOR || !UNITY_SERVER
/// <summary>
/// Averages for client.
/// </summary>
public InOutAverage ClientAverages { get; private set; }
/// <summary>
/// Averages for server.
/// </summary>
public InOutAverage ServerAverages { get; private set; }
#endif
#endregion
#region Serialized.
[Header("Misc")]
/// <summary>
/// True to operate while in release. This may cause allocations and impact performance.
/// </summary>
[Tooltip("True to operate while in release. This may cause allocations and impact performance.")]
[SerializeField]
private bool _runInRelease;
[Header("Timing")]
/// <summary>
/// Number of seconds used to gather data per second. Lower values will show more up to date usage per second while higher values provide a better over-all estimate.
/// </summary>
[Tooltip("Number of seconds used to gather data per second. Lower values will show more up to date usage per second while higher values provide a better over-all estimate.")]
[SerializeField]
[Range(1, byte.MaxValue)]
private byte _secondsAveraged = 1;
/// <summary>
/// How often to update displayed text.
/// </summary>
[Tooltip("How often to update displayed text.")]
[Range(0f, 10f)]
[SerializeField]
private float _updateInterval = 1f;
[Header("Appearance")]
/// <summary>
/// Color for text.
/// </summary>
[Tooltip("Color for text.")]
[SerializeField]
private Color _color = Color.white;
/// <summary>
/// Which corner to display network statistics in.
/// </summary>
[Tooltip("Which corner to display network statistics in.")]
[SerializeField]
private Corner _placement = Corner.TopRight;
/// <summary>
/// rue to show outgoing data bytes.
/// </summary>
[Tooltip("True to show outgoing data bytes.")]
[SerializeField]
private bool _showOutgoing = true;
/// <summary>
/// Sets ShowOutgoing value.
/// </summary>
/// <param name = "value"></param>
public void SetShowOutgoing(bool value) => _showOutgoing = value;
/// <summary>
/// True to show incoming data bytes.
/// </summary>
[Tooltip("True to show incoming data bytes.")]
[SerializeField]
private bool _showIncoming = true;
/// <summary>
/// Sets ShowIncoming value.
/// </summary>
/// <param name = "value"></param>
public void SetShowIncoming(bool value) => _showIncoming = value;
#endregion
#if UNITY_EDITOR || !UNITY_SERVER
#region Private.
/// <summary>
/// Style for drawn ping.
/// </summary>
private readonly GUIStyle _style = new();
/// <summary>
/// Text to show for client in/out data.
/// </summary>
private string _clientText;
/// <summary>
/// Text to show for server in/out data.
/// </summary>
private string _serverText;
/// <summary>
/// First found NetworkTrafficStatistics.
/// </summary>
private NetworkTrafficStatistics _networkTrafficStatistics;
/// <summary>
/// Next time the server text can be updated.
/// </summary>
private float _nextServerTextUpdateTime;
/// <summary>
/// Next time the server text can be updated.
/// </summary>
private float _nextClientTextUpdateTime;
/// <summary>
/// True if component is initialized.
/// </summary>
private bool _initialized;
#endregion
private void Start()
{
// Requires a UI, so exit if server build.
#if UNITY_SERVER
return;
#endif
// If release build, check if able to run in release.
#if !DEVELOPMENT_BUILD && !UNITY_EDITOR
if (!_runInRelease)
return;
#endif
// Not enabled.
if (!InstanceFinder.NetworkManager.StatisticsManager.TryGetNetworkTrafficStatistics(out _networkTrafficStatistics))
return;
if (!_networkTrafficStatistics.UpdateClient && !_networkTrafficStatistics.UpdateServer)
{
Debug.LogWarning($"StatisticsManager.NetworkTraffic is not updating for client nor server. To see results ensure your NetworkManager has a StatisticsManager component added with the NetworkTraffic values configured.");
return;
}
SetSecondsAveraged(_secondsAveraged);
_networkTrafficStatistics.OnNetworkTraffic += NetworkTrafficStatistics_OnNetworkTraffic;
_initialized = true;
}
private void OnDestroy()
{
if (_networkTrafficStatistics != null)
_networkTrafficStatistics.OnNetworkTraffic -= NetworkTrafficStatistics_OnNetworkTraffic;
}
/// <summary>
/// Sets a new number of seconds to average from.
/// </summary>
public void SetSecondsAveraged(byte seconds)
{
// Get to ticks.
NetworkManager manager = InstanceFinder.NetworkManager;
if (manager == null)
return;
if (seconds <= 0)
seconds = 1;
//Convert to milliseconds.
long ms = seconds * 1000;
uint ticks = manager.TimeManager.TimeToTicks(ms, TickRounding.RoundUp);
// Should not ever be possible.
if (ticks <= 0)
ticks = 60;
ClientAverages = new((int)ticks);
ServerAverages = new((int)ticks);
}
/// <summary>
/// Called when new traffic statistics are received.
/// </summary>
private void NetworkTrafficStatistics_OnNetworkTraffic(uint tick, BidirectionalNetworkTraffic serverTraffic, BidirectionalNetworkTraffic clientTraffic)
{
if (!_initialized)
return;
ServerAverages.AddIn(serverTraffic.InboundTraffic.Bytes);
ServerAverages.AddOut(serverTraffic.OutboundTraffic.Bytes);
ClientAverages.AddIn(clientTraffic.InboundTraffic.Bytes);
ClientAverages.AddOut(clientTraffic.OutboundTraffic.Bytes);
if (Time.time < _nextServerTextUpdateTime)
return;
_nextServerTextUpdateTime = Time.time + _updateInterval;
string nl = System.Environment.NewLine;
string result = string.Empty;
if (_showIncoming)
result += $"Server In: {NetworkTrafficStatistics.FormatBytesToLargest(ServerAverages.GetTotal(inBuffer: true))}/s{nl}";
if (_showOutgoing)
result += $"Server Out: {NetworkTrafficStatistics.FormatBytesToLargest(ServerAverages.GetTotal(inBuffer: false))}/s{nl}";
_serverText = result;
result = string.Empty;
if (_showIncoming)
result += $"Client In: {NetworkTrafficStatistics.FormatBytesToLargest(ClientAverages.GetTotal(inBuffer: true))}/s{nl}";
if (_showOutgoing)
result += $"Client Out: {NetworkTrafficStatistics.FormatBytesToLargest(ClientAverages.GetTotal(inBuffer: false))}/s{nl}";
_clientText = result;
}
/// <summary>
/// Called when client network traffic is updated.
/// </summary>
private void NetworkTraffic_OnClientNetworkTraffic(BidirectionalNetworkTraffic traffic)
{
if (!_initialized)
return;
ClientAverages.AddIn(traffic.InboundTraffic.Bytes);
ClientAverages.AddOut(traffic.OutboundTraffic.Bytes);
if (Time.time < _nextClientTextUpdateTime)
return;
_nextClientTextUpdateTime = Time.time + _updateInterval;
string nl = System.Environment.NewLine;
string result = string.Empty;
if (_showIncoming)
result += $"Client In: {NetworkTrafficStatistics.FormatBytesToLargest(ClientAverages.GetAverage(inBuffer: true))}/s{nl}";
if (_showOutgoing)
result += $"Client Out: {NetworkTrafficStatistics.FormatBytesToLargest(ClientAverages.GetAverage(inBuffer: false))}/s{nl}";
_clientText = result;
}
/// <summary>
/// Called when server network traffic is updated.
/// </summary>
private void NetworkTraffic_OnServerNetworkTraffic(BidirectionalNetworkTraffic traffic)
{
if (!_initialized)
return;
ServerAverages.AddIn(traffic.InboundTraffic.Bytes);
ServerAverages.AddOut(traffic.OutboundTraffic.Bytes);
if (Time.time < _nextServerTextUpdateTime)
return;
_nextServerTextUpdateTime = Time.time + _updateInterval;
string nl = System.Environment.NewLine;
string result = string.Empty;
if (_showIncoming)
result += $"Server In: {NetworkTrafficStatistics.FormatBytesToLargest(ServerAverages.GetAverage(inBuffer: true))}/s{nl}";
if (_showOutgoing)
result += $"Server Out: {NetworkTrafficStatistics.FormatBytesToLargest(ServerAverages.GetAverage(inBuffer: false))}/s{nl}";
_serverText = result;
}
private void OnGUI()
{
_style.normal.textColor = _color;
_style.fontSize = 15;
float width = 100f;
float height = 0f;
if (_showIncoming)
height += 15f;
if (_showOutgoing)
height += 15f;
bool isClient = InstanceFinder.IsClientStarted;
bool isServer = InstanceFinder.IsServerStarted;
if (!isClient)
ResetCalculationsAndDisplay(forServer: false);
if (!isServer)
ResetCalculationsAndDisplay(forServer: true);
if (isServer && isClient)
height *= 2f;
float edge = 10f;
float horizontal;
float vertical;
if (_placement == Corner.TopLeft)
{
horizontal = 10f;
vertical = 10f;
_style.alignment = TextAnchor.UpperLeft;
}
else if (_placement == Corner.TopRight)
{
horizontal = Screen.width - width - edge;
vertical = 10f;
_style.alignment = TextAnchor.UpperRight;
}
else if (_placement == Corner.BottomLeft)
{
horizontal = 10f;
vertical = Screen.height - height - edge;
_style.alignment = TextAnchor.LowerLeft;
}
else
{
horizontal = Screen.width - width - edge;
vertical = Screen.height - height - edge;
_style.alignment = TextAnchor.LowerRight;
}
GUI.Label(new(horizontal, vertical, width, height), _clientText + _serverText, _style);
}
[ContextMenu("Reset Averages")]
public void ResetAverages()
{
ResetCalculationsAndDisplay(forServer: true);
ResetCalculationsAndDisplay(forServer: false);
}
private void ResetCalculationsAndDisplay(bool forServer)
{
if (!_initialized)
return;
if (forServer)
{
_serverText = string.Empty;
ServerAverages.ResetState();
}
else
{
_clientText = string.Empty;
ClientAverages.ResetState();
}
}
#endif
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 8bc8f0363ddc75946a958043c5e49a83
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/Utility/BandwidthDisplay.cs
uploadId: 866910
@@ -0,0 +1,251 @@
using FishNet.Connection;
using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Managing.Scened;
using FishNet.Transporting;
using FishNet.Utility;
using GameKit.Dependencies.Utilities;
using GameKit.Dependencies.Utilities.Types;
using System.IO;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Serialization;
using UnitySceneManager = UnityEngine.SceneManagement.SceneManager;
namespace FishNet.Component.Scenes
{
/// <summary>
/// Add to a NetworkManager object to change between Online and Offline scene based on connection states of the server or client.
/// </summary>
[AddComponentMenu("FishNet/Component/DefaultScene")]
public class DefaultScene : MonoBehaviour
{
#region Serialized.
[Tooltip("True to load the online scene as global, false to load it as connection.")]
[SerializeField]
private bool _enableGlobalScenes = true;
/// <summary>
/// True to replace all scenes with the offline scene immediately.
/// </summary>
[Tooltip("True to replace all scenes with the offline scene immediately.")]
[SerializeField]
private bool _startInOffline;
/// <summary>
/// </summary>
[Tooltip("Scene to load when disconnected. Server and client will load this scene.")]
[SerializeField]
[Scene]
private string _offlineScene;
/// <summary>
/// Sets which offline scene to use.
/// </summary>
/// <param name = "sceneName">Scene name to use as the offline scene.</param>
public void SetOfflineScene(string sceneName) => _offlineScene = sceneName;
/// <summary>
/// Scene to load when disconnected. Server and client will load this scene.
/// </summary>
/// <returns></returns>
public string GetOfflineScene() => _offlineScene;
/// <summary>
/// </summary>
[Tooltip("Scene to load when connected. Server and client will load this scene.")]
[SerializeField]
[Scene]
private string _onlineScene;
/// <summary>
/// Sets which online scene to use.
/// </summary>
/// <param name = "sceneName">Scene name to use as the online scene.</param>
public void SetOnlineScene(string sceneName) => _onlineScene = sceneName;
/// <summary>
/// Scene to load when connected. Server and client will load this scene.
/// </summary>
/// <returns></returns>
public string GetOnlineScene() => _onlineScene;
/// <summary>
/// Which scenes to replace when loading into OnlineScene.
/// </summary>
[Tooltip("Which scenes to replace when loading into OnlineScene.")]
[SerializeField]
private ReplaceOption _replaceScenes = ReplaceOption.All;
#endregion
#region Private.
/// <summary>
/// NetworkManager for this component.
/// </summary>
private NetworkManager _networkManager;
#endregion
private void OnEnable()
{
Initialize();
}
private void OnDestroy()
{
Deinitialize();
}
/// <summary>
/// Initializes this script for use.
/// </summary>
private void Initialize()
{
_networkManager = GetComponentInParent<NetworkManager>();
if (_networkManager == null)
{
_networkManager.LogError($"NetworkManager not found on {gameObject.name} or any parent objects. DefaultScene will not work.");
return;
}
// A NetworkManager won't be initialized if it's being destroyed.
if (!_networkManager.Initialized)
return;
if (_onlineScene == string.Empty || _offlineScene == string.Empty)
{
_networkManager.LogWarning("Online or Offline scene is not specified. Default scenes will not load.");
return;
}
_networkManager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState;
_networkManager.ServerManager.OnServerConnectionState += ServerManager_OnServerConnectionState;
_networkManager.SceneManager.OnLoadEnd += SceneManager_OnLoadEnd;
_networkManager.ServerManager.OnAuthenticationResult += ServerManager_OnAuthenticationResult;
if (_startInOffline)
LoadOfflineScene();
}
private void Deinitialize()
{
if (!ApplicationState.IsQuitting() && _networkManager != null && _networkManager.Initialized)
{
_networkManager.ClientManager.OnClientConnectionState -= ClientManager_OnClientConnectionState;
_networkManager.ServerManager.OnServerConnectionState -= ServerManager_OnServerConnectionState;
_networkManager.SceneManager.OnLoadEnd -= SceneManager_OnLoadEnd;
_networkManager.ServerManager.OnAuthenticationResult -= ServerManager_OnAuthenticationResult;
}
}
/// <summary>
/// Called when a scene load ends.
/// </summary>
private void SceneManager_OnLoadEnd(SceneLoadEndEventArgs obj)
{
bool onlineLoaded = false;
foreach (Scene s in obj.LoadedScenes)
{
if (s.name == GetSceneName(_onlineScene))
{
onlineLoaded = true;
break;
}
}
// If online scene was loaded then unload offline.
if (onlineLoaded)
UnloadOfflineScene();
}
/// <summary>
/// Called after the local server connection state changes.
/// </summary>
private void ServerManager_OnServerConnectionState(ServerConnectionStateArgs obj)
{
/* When server starts load online scene as global.
* Since this is a global scene clients will automatically
* join it when connecting. */
if (obj.ConnectionState == LocalConnectionState.Started)
{
/* If not exactly one server is started then
* that means either none are started, which isnt true because
* we just got a started callback, or two+ are started.
* When a server has already started there's no reason to load
* scenes again. */
if (!_networkManager.ServerManager.IsOnlyOneServerStarted())
return;
// If here can load scene.
SceneLoadData sld = new(GetSceneName(_onlineScene));
sld.ReplaceScenes = _replaceScenes;
if (_enableGlobalScenes)
_networkManager.SceneManager.LoadGlobalScenes(sld);
else
_networkManager.SceneManager.LoadConnectionScenes(sld);
}
// When server stops load offline scene.
else if (obj.ConnectionState == LocalConnectionState.Stopped && !_networkManager.ServerManager.IsAnyServerStarted())
{
LoadOfflineScene();
}
}
/// <summary>
/// Called after the local client connection state changes.
/// </summary>
private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs obj)
{
if (obj.ConnectionState == LocalConnectionState.Stopped)
{
// Only load offline scene if not also server.
if (!_networkManager.IsServerStarted)
LoadOfflineScene();
}
}
/// <summary>
/// Called when a client completes authentication.
/// </summary>
private void ServerManager_OnAuthenticationResult(NetworkConnection arg1, bool authenticated)
{
/* This is only for loading connection scenes.
* If using global there is no need to continue. */
if (_enableGlobalScenes)
return;
if (!authenticated)
return;
SceneLoadData sld = new(GetSceneName(_onlineScene));
_networkManager.SceneManager.LoadConnectionScenes(arg1, sld);
}
/// <summary>
/// Loads offlineScene as single.
/// </summary>
private void LoadOfflineScene()
{
// Already in offline scene.
if (UnitySceneManager.GetActiveScene().name == GetSceneName(_offlineScene))
return;
// Only use scene manager if networking scenes. I may add something in later to do both local and networked.
UnitySceneManager.LoadScene(_offlineScene);
}
/// <summary>
/// Unloads the offline scene.
/// </summary>
private void UnloadOfflineScene()
{
Scene s = UnitySceneManager.GetSceneByName(GetSceneName(_offlineScene));
if (string.IsNullOrEmpty(s.name))
return;
UnitySceneManager.UnloadSceneAsync(s);
}
/// <summary>
/// Returns a scene name from fullPath.
/// </summary>
/// <param name = "fullPath"></param>
/// <returns></returns>
private string GetSceneName(string fullPath)
{
return Path.GetFileNameWithoutExtension(fullPath);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 57ce8bbb58966cb45a7140f32da5327a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/Utility/DefaultScene.cs
uploadId: 866910
@@ -0,0 +1,251 @@
using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Managing.Timing;
using FishNet.Object;
using FishNet.Object.Prediction;
using FishNet.Utility.Extension;
using GameKit.Dependencies.Utilities;
using Unity.Profiling;
using UnityEngine;
namespace FishNet.Component.Transforming
{
/// <summary>
/// Detaches the object which this component resides and follows another.
/// </summary>
public class DetachableNetworkTickSmoother : NetworkBehaviour
{
#region Serialized.
/// <summary>
/// True to attach the object to it's original parent when OnStopClient is called.
/// </summary>
[Tooltip("True to attach the object to it's original parent when OnStopClient is called.")]
[SerializeField]
private bool _attachOnStop = true;
/// <summary>
/// Object to follow, and smooth towards.
/// </summary>
[Tooltip("Object to follow, and smooth towards.")]
[SerializeField]
private Transform _followObject;
/// <summary>
/// How many ticks to interpolate over.
/// </summary>
[Tooltip("How many ticks to interpolate over.")]
[Range(1, byte.MaxValue)]
[SerializeField]
private byte _interpolation = 1;
/// <summary>
/// True to enable teleport threshhold.
/// </summary>
[Tooltip("True to enable teleport threshold.")]
[SerializeField]
private bool _enableTeleport;
/// <summary>
/// How far the object must move between ticks to teleport rather than smooth.
/// </summary>
[Tooltip("How far the object must move between ticks to teleport rather than smooth.")]
[Range(0f, ushort.MaxValue)]
[SerializeField]
private float _teleportThreshold;
/// <summary>
/// True to synchronize the position of the followObject.
/// </summary>
[Tooltip("True to synchronize the position of the followObject.")]
[SerializeField]
private bool _synchronizePosition = true;
/// <summary>
/// True to synchronize the rotation of the followObject.
/// </summary>
[Tooltip("True to synchronize the rotation of the followObject.")]
[SerializeField]
private bool _synchronizeRotation;
/// <summary>
/// True to synchronize the scale of the followObject.
/// </summary>
[Tooltip("True to synchronize the scale of the followObject.")]
[SerializeField]
private bool _synchronizeScale;
#endregion
#region Private.
/// <summary>
/// TimeManager subscribed to.
/// </summary>
private TimeManager _timeManager;
/// <summary>
/// Parent of the object prior to detaching.
/// </summary>
private Transform _parent;
/// <summary>
/// Local properties of the graphical during instantation.
/// </summary>
private TransformProperties _transformInstantiatedLocalProperties;
/// <summary>
/// World properties of the followObject during post tick.
/// </summary>
private TransformProperties _postTickFollowObjectWorldProperties;
/// <summary>
/// How quickly to move towards target.
/// </summary>
private MoveRates _moveRates = new(MoveRates.INSTANT_VALUE);
/// <summary>
/// True if initialized.
/// </summary>
private bool _initialized;
/// <summary>
/// Cached TickDelta of the TimeManager.
/// </summary>
private float _tickDelta;
private static readonly ProfilerMarker _pm_OnPostTick = new("DetachableNetworkTickSmoother._timeManager_OnPostTick()");
#endregion
private void Awake()
{
_transformInstantiatedLocalProperties = transform.GetLocalProperties();
}
private void OnDestroy()
{
ChangeSubscription(false);
}
public override void OnStartClient()
{
bool error = false;
if (transform.parent == null)
{
NetworkManager.LogError($"{GetType().Name} on gameObject {gameObject.name} requires a parent to detach from.");
error = true;
}
if (_followObject == null)
{
NetworkManager.LogError($"{GetType().Name} on gameObject {gameObject}, root {transform.root} requires followObject to be set.");
error = true;
}
if (error)
return;
_parent = transform.parent;
transform.SetParent(null);
SetTimeManager(TimeManager);
// Unsub first in the rare chance we already subbed such as a stop callback issue.
ChangeSubscription(false);
ChangeSubscription(true);
_postTickFollowObjectWorldProperties = _followObject.GetWorldProperties();
_tickDelta = (float)TimeManager.TickDelta;
_initialized = true;
}
public override void OnStopClient()
{
#if UNITY_EDITOR
if (ApplicationState.IsQuitting())
return;
#endif
// Reattach to parent.
if (_attachOnStop && _parent != null)
{
// Reparent
transform.SetParent(_parent);
// Set to instantiated local values.
transform.SetLocalProperties(_transformInstantiatedLocalProperties);
}
_postTickFollowObjectWorldProperties.ResetState();
ChangeSubscription(false);
_initialized = false;
}
[Client(Logging = LoggingType.Off)]
private void Update()
{
MoveTowardsFollowTarget();
}
/// <summary>
/// Called after a tick completes.
/// </summary>
private void _timeManager_OnPostTick()
{
using (_pm_OnPostTick.Auto())
{
if (!_initialized)
return;
_postTickFollowObjectWorldProperties.Update(_followObject);
// Unset values if not following the transform property.
if (!_synchronizePosition)
_postTickFollowObjectWorldProperties.Position = transform.position;
if (!_synchronizeRotation)
_postTickFollowObjectWorldProperties.Rotation = transform.rotation;
if (!_synchronizeScale)
_postTickFollowObjectWorldProperties.Scale = transform.localScale;
SetMoveRates();
}
}
/// <summary>
/// Sets a new PredictionManager to use.
/// </summary>
/// <param name = "tm"></param>
private void SetTimeManager(TimeManager tm)
{
if (tm == _timeManager)
return;
// Unsub from current.
ChangeSubscription(false);
// Sub to newest.
_timeManager = tm;
ChangeSubscription(true);
}
/// <summary>
/// Changes the subscription to the TimeManager.
/// </summary>
private void ChangeSubscription(bool subscribe)
{
if (_timeManager == null)
return;
if (subscribe)
_timeManager.OnPostTick += _timeManager_OnPostTick;
else
_timeManager.OnPostTick -= _timeManager_OnPostTick;
}
/// <summary>
/// Moves towards targetObject.
/// </summary>
private void MoveTowardsFollowTarget()
{
if (!_initialized)
return;
_moveRates.Move(transform, _postTickFollowObjectWorldProperties, Time.deltaTime, useWorldSpace: true);
}
private void SetMoveRates()
{
if (!_initialized)
return;
float duration = _tickDelta * _interpolation;
/* If interpolation is 1 then add on a tiny amount
* of more time to compensate for frame time, so that
* the smoothing does not complete before the next tick,
* as this would result in jitter. */
if (_interpolation == 1)
duration += Mathf.Max(Time.deltaTime, 1f / 50f);
float teleportT = _enableTeleport ? _teleportThreshold : MoveRates.UNSET_VALUE;
_moveRates = MoveRates.GetWorldMoveRates(transform, _followObject, duration, teleportT);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c631fa10037fa844292bacd140d7c7f9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/Utility/DetachableNetworkTickSmoother.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: edb137ec1a2c56540a187929d6b97b54
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,75 @@
#if UNITY_EDITOR
using GameKit.Dependencies.Utilities;
using UnityEditor;
using LayoutTools = GameKit.Dependencies.Utilities.EditorGuiLayoutTools;
namespace FishNet.Component.Transforming.Editing
{
[CustomEditor(typeof(DetachableNetworkTickSmoother), true)]
[CanEditMultipleObjects]
public class DetachableNetworkTickSmootherEditor : Editor
{
private SerializedProperty _attachOnStop;
private SerializedProperty _followObject;
private SerializedProperty _interpolation;
private SerializedProperty _enableTeleport;
private SerializedProperty _teleportThreshold;
private SerializedProperty _synchronizePosition;
private SerializedProperty _synchronizeRotation;
private SerializedProperty _synchronizeScale;
protected virtual void OnEnable()
{
_attachOnStop = serializedObject.FindProperty(nameof(_attachOnStop));
_followObject = serializedObject.FindProperty(nameof(_followObject));
_interpolation = serializedObject.FindProperty(nameof(_interpolation));
_enableTeleport = serializedObject.FindProperty(nameof(_enableTeleport));
_teleportThreshold = serializedObject.FindProperty(nameof(_teleportThreshold));
_synchronizePosition = serializedObject.FindProperty(nameof(_synchronizePosition));
_synchronizeRotation = serializedObject.FindProperty(nameof(_synchronizeRotation));
_synchronizeScale = serializedObject.FindProperty(nameof(_synchronizeScale));
}
public override void OnInspectorGUI()
{
serializedObject.Update();
LayoutTools.AddObjectField("Script:", MonoScript.FromMonoBehaviour((DetachableNetworkTickSmoother)target), typeof(DetachableNetworkTickSmoother), false, EditorLayoutEnableType.Disabled);
EditorGUILayout.HelpBox("This component will be obsoleted soon. Use NetworkTickSmoother or OfflineTickSmoother.", MessageType.Warning);
// Misc.
EditorGUILayout.LabelField("Misc", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_attachOnStop);
EditorGUI.indentLevel--;
EditorGUILayout.Space();
// Smoothing.
EditorGUILayout.LabelField("Smoothing", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_followObject);
EditorGUILayout.PropertyField(_interpolation);
EditorGUILayout.PropertyField(_enableTeleport);
if (_enableTeleport.boolValue)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_teleportThreshold);
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
EditorGUILayout.Space();
//Authority.
EditorGUILayout.LabelField("Synchronizing", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_synchronizePosition);
EditorGUILayout.PropertyField(_synchronizeRotation);
EditorGUILayout.PropertyField(_synchronizeScale);
EditorGUI.indentLevel--;
serializedObject.ApplyModifiedProperties();
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 342594fe005d75a4985e1a8ca218f822
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/Utility/Editor/DetachableNetworkTickSmootherEditor.cs
uploadId: 866910
@@ -0,0 +1,164 @@
using FishNet.Managing.Logging;
using FishNet.Managing.Timing;
using FishNet.Object;
using FishNet.Object.Prediction;
using GameKit.Dependencies.Utilities;
using Unity.Profiling;
using UnityEngine;
#pragma warning disable CS0618 // Type or member is obsolete
namespace FishNet.Component.Transforming
{
/// <summary>
/// Smoothes an object between ticks.
/// This can be used on objects without NetworkObject components.
/// </summary>
public class MonoTickSmoother : MonoBehaviour
{
// Lazy way to display obsolete message w/o using a custom editor.
[Header("This component will be obsoleted soon.")]
[Header("Use NetworkTickSmoother or OfflineTickSmoother.")]
[Header(" ")]
#region Serialized.
/// <summary>
/// True to use InstanceFinder to locate the TimeManager. When false specify which TimeManager to use by calling SetTimeManager.
/// </summary>
[Tooltip("True to use InstanceFinder to locate the TimeManager. When false specify which TimeManager to use by calling SetTimeManager.")]
[SerializeField]
private bool _useInstanceFinder = true;
/// <summary>
/// GraphicalObject you wish to smooth.
/// </summary>
[Tooltip("GraphicalObject you wish to smooth.")]
[SerializeField]
private Transform _graphicalObject;
/// <summary>
/// True to enable teleport threshhold.
/// </summary>
[Tooltip("True to enable teleport threshold.")]
[SerializeField]
private bool _enableTeleport;
/// <summary>
/// How far the object must move between ticks to teleport rather than smooth.
/// </summary>
[Tooltip("How far the object must move between ticks to teleport rather than smooth.")]
[Range(0f, ushort.MaxValue)]
[SerializeField]
private float _teleportThreshold;
#endregion
#region Private.
/// <summary>
/// TimeManager subscribed to.
/// </summary>
private TimeManager _timeManager;
/// <summary>
/// BasicTickSmoother for this script.
/// </summary>
private LocalTransformTickSmoother _tickSmoother;
#endregion
#region Private Profiler Markers
private static readonly ProfilerMarker _pm_OnPreTick = new("MonoTickSmoother._timeManager_OnPreTick()");
private static readonly ProfilerMarker _pm_OnPostTick = new("MonoTickSmoother._timeManager_OnPostTick()");
#endregion
private void OnEnable()
{
Initialize();
}
private void OnDisable()
{
_tickSmoother.ResetState();
ChangeSubscription(false);
ObjectCaches<LocalTransformTickSmoother>.StoreAndDefault(ref _tickSmoother);
}
[Client(Logging = LoggingType.Off)]
private void Update()
{
_tickSmoother?.Update();
}
/// <summary>
/// Initializes this script for use.
/// </summary>
private void Initialize()
{
_tickSmoother = ObjectCaches<LocalTransformTickSmoother>.Retrieve();
if (_useInstanceFinder)
{
_timeManager = InstanceFinder.TimeManager;
ChangeSubscription(true);
}
}
/// <summary>
/// Sets a new PredictionManager to use.
/// </summary>
/// <param name = "tm"></param>
public void SetTimeManager(TimeManager tm)
{
if (tm == _timeManager)
return;
// Unsub from current.
ChangeSubscription(false);
// Sub to newest.
_timeManager = tm;
ChangeSubscription(true);
}
/// <summary>
/// Changes the subscription to the TimeManager.
/// </summary>
private void ChangeSubscription(bool subscribe)
{
if (_timeManager == null)
return;
if (subscribe)
{
if (_tickSmoother != null)
{
float tDistance = _enableTeleport ? _teleportThreshold : MoveRates.UNSET_VALUE;
_tickSmoother.InitializeOnce(_graphicalObject, tDistance, (float)_timeManager.TickDelta, 1);
}
_timeManager.OnPreTick += _timeManager_OnPreTick;
_timeManager.OnPostTick += _timeManager_OnPostTick;
}
else
{
_timeManager.OnPreTick -= _timeManager_OnPreTick;
_timeManager.OnPostTick -= _timeManager_OnPostTick;
}
}
/// <summary>
/// Called before a tick starts.
/// </summary>
private void _timeManager_OnPreTick()
{
using (_pm_OnPreTick.Auto())
{
_tickSmoother.OnPreTick();
}
}
/// <summary>
/// Called after a tick completes.
/// </summary>
private void _timeManager_OnPostTick()
{
using (_pm_OnPostTick.Auto())
{
_tickSmoother.OnPostTick();
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: b3c0f5e921f9d784986ee1c8d311ccba
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/Utility/MonoTickSmoother.cs
uploadId: 866910
@@ -0,0 +1,108 @@
using FishNet.Managing.Timing;
using UnityEngine;
namespace FishNet.Component.Utility
{
/// <summary>
/// Add to any object to display current ping(round trip time).
/// </summary>
[AddComponentMenu("FishNet/Component/PingDisplay")]
public class PingDisplay : MonoBehaviour
{
#region Types.
private enum Corner
{
TopLeft,
TopRight,
BottomLeft,
BottomRight
}
#endregion
#region Serialized.
/// <summary>
/// Color for text.
/// </summary>
[Tooltip("Color for text.")]
[SerializeField]
private Color _color = Color.white;
/// <summary>
/// Which corner to display ping in.
/// </summary>
[Tooltip("Which corner to display ping in.")]
[SerializeField]
private Corner _placement = Corner.TopRight;
/// <summary>
/// True to show the real ping. False to include tick rate latency within the ping.
/// </summary>
[Tooltip("True to show the real ping. False to include tick rate latency within the ping.")]
[SerializeField]
private bool _hideTickRate = true;
#endregion
#if UNITY_EDITOR || !UNITY_SERVER
#region Private.
/// <summary>
/// Style for drawn ping.
/// </summary>
private GUIStyle _style = new();
#endregion
private void OnGUI()
{
// Only clients can see pings.
if (!InstanceFinder.IsClientStarted)
return;
_style.normal.textColor = _color;
_style.fontSize = 15;
float width = 85f;
float height = 15f;
float edge = 10f;
float horizontal;
float vertical;
if (_placement == Corner.TopLeft)
{
horizontal = 10f;
vertical = 10f;
}
else if (_placement == Corner.TopRight)
{
horizontal = Screen.width - width - edge;
vertical = 10f;
}
else if (_placement == Corner.BottomLeft)
{
horizontal = 10f;
vertical = Screen.height - height - edge;
}
else
{
horizontal = Screen.width - width - edge;
vertical = Screen.height - height - edge;
}
long ping;
TimeManager tm = InstanceFinder.TimeManager;
if (tm == null)
{
ping = 0;
}
else
{
ping = tm.RoundTripTime;
long deduction = 0;
if (_hideTickRate)
deduction = (long)(tm.TickDelta * 2000d);
ping = (long)Mathf.Max(1, ping - deduction);
}
GUI.Label(new(horizontal, vertical, width, height), $"Ping: {ping}ms", _style);
}
#endif
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: f9b6b565cd9533c4ebc18003f0fc18a2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/Utility/PingDisplay.cs
uploadId: 866910