[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,8 @@
fileFormatVersion: 2
guid: bc7d71f1ce4ba474ba6eed2c30b44aec
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f6bca334cff41934c987b04a7cfe77ee
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 89043e9d4033c29428eb797d22d85626
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,181 @@
#if UNITY_EDITOR
using FishNet.Editing;
using System.Collections.Generic;
using FishNet.Managing;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
namespace FishNet.Component.Animating.Editing
{
[CustomEditor(typeof(NetworkAnimator), true)]
[CanEditMultipleObjects]
public class NetworkAnimatorEditor : Editor
{
private SerializedProperty _animator;
private SerializedProperty _interpolation;
private SerializedProperty _synchronizeWhenDisabled;
private SerializedProperty _smoothFloats;
private SerializedProperty _clientAuthoritative;
private SerializedProperty _sendToOwner;
private RuntimeAnimatorController _lastRuntimeAnimatorController;
private AnimatorController _lastAnimatorController;
protected virtual void OnEnable()
{
_animator = serializedObject.FindProperty(nameof(_animator));
_interpolation = serializedObject.FindProperty(nameof(_interpolation));
_synchronizeWhenDisabled = serializedObject.FindProperty(nameof(_synchronizeWhenDisabled));
_smoothFloats = serializedObject.FindProperty(nameof(_smoothFloats));
_clientAuthoritative = serializedObject.FindProperty(nameof(_clientAuthoritative));
_sendToOwner = serializedObject.FindProperty(nameof(_sendToOwner));
}
public override void OnInspectorGUI()
{
serializedObject.Update();
NetworkAnimator na = (NetworkAnimator)target;
GUI.enabled = false;
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour(na), typeof(NetworkAnimator), false);
GUI.enabled = true;
#pragma warning disable CS0162 // Unreachable code detected
EditorGUILayout.HelpBox(EditingConstants.PRO_ASSETS_LOCKED_TEXT, MessageType.Warning);
#pragma warning restore CS0162 // Unreachable code detected
// Animator
EditorGUILayout.LabelField("Animator", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_animator);
EditorGUILayout.PropertyField(_synchronizeWhenDisabled);
EditorGUI.indentLevel--;
EditorGUILayout.Space();
// Synchronization Processing.
EditorGUILayout.LabelField("Synchronization Processing", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_interpolation);
EditorGUILayout.PropertyField(_smoothFloats);
EditorGUI.indentLevel--;
EditorGUILayout.Space();
// Authority.
EditorGUILayout.LabelField("Authority", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_clientAuthoritative);
if (_clientAuthoritative.boolValue == false)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_sendToOwner);
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
EditorGUILayout.Space();
DrawParameters(na);
serializedObject.ApplyModifiedProperties();
}
private void DrawParameters(NetworkAnimator na)
{
EditorGUILayout.LabelField("* Synchronized Parameters", EditorStyles.boldLabel);
EditorGUILayout.HelpBox("This setting allows you to optionally completely prevent the synchronization of certain parameters. Both Fish-Networking free and Pro will only synchronize changes as they occur.", MessageType.Info);
if (Application.isPlaying)
{
EditorGUILayout.HelpBox("This feature can only be configured while out of play mode.", MessageType.Info);
return;
}
if (na == null)
return;
Animator animator = na.Animator;
if (animator == null)
return;
RuntimeAnimatorController runtimeController = animator.runtimeAnimatorController is AnimatorOverrideController aoc ? aoc.runtimeAnimatorController : animator.runtimeAnimatorController;
if (runtimeController == null)
{
na.IgnoredParameters.Clear();
return;
}
/* If runtime controller changed
* or editor controller is null
* then get new editor controller. */
if (runtimeController != _lastRuntimeAnimatorController || _lastAnimatorController == null)
_lastAnimatorController = (AnimatorController)AssetDatabase.LoadAssetAtPath(AssetDatabase.GetAssetPath(runtimeController), typeof(AnimatorController));
_lastRuntimeAnimatorController = runtimeController;
Color defaultColor = GUI.backgroundColor;
float width = Screen.width;
float spacePerEntry = 125f;
// Buttons seem to be longer than spacePerEntry. Why, because who knows...
float extraSpaceJustBecause = 60;
float spacer = 20f;
width -= spacer;
int entriesPerWidth = Mathf.Max(1, Mathf.FloorToInt(width / (spacePerEntry + extraSpaceJustBecause)));
List<AnimatorControllerParameter> aps = new();
// Create a parameter detail for each parameter that can be synchronized.
int count = 0;
foreach (AnimatorControllerParameter item in _lastAnimatorController.parameters)
{
count++;
// Over 240 parameters; who would do this!?
if (count >= 240)
continue;
aps.Add(item);
}
int apsCount = aps.Count;
for (int i = 0; i < apsCount; i++)
{
using (GUILayout.HorizontalScope hs = new())
{
GUILayout.Space(spacer);
int z = 0;
while (z < entriesPerWidth && z + i < apsCount)
{
// If this z+i would exceed entries then break.
if (z + i >= apsCount)
break;
AnimatorControllerParameter item = aps[i + z];
string parameterName = item.name;
bool ignored = na.IgnoredParameters.Contains(parameterName);
Color c = ignored ? Color.gray : Color.green;
GUI.backgroundColor = c;
if (GUILayout.Button(item.name, GUILayout.Width(spacePerEntry)))
{
if (Application.isPlaying)
{
NetworkManagerExtensions.Log("Synchronized parameters may not be changed while playing.");
}
else
{
if (ignored)
na.IgnoredParameters.Remove(parameterName);
else
na.IgnoredParameters.Add(parameterName);
}
EditorUtility.SetDirty(target);
}
z++;
}
i += z - 1;
}
GUI.backgroundColor = defaultColor;
}
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 65609e99a0823a347a2f615b0e6f736e
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/NetworkAnimator/Editor/NetworkAnimatorEditor.cs
uploadId: 866910
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e8cac635f24954048aad3a6ff9110beb
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/NetworkAnimator/NetworkAnimator.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c69d0773e094d8442b66666dc0b49caa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1746873c72693e54fbf636563628b50f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,149 @@
#if UNITY_EDITOR
using FishNet.Editing;
using GameKit.Dependencies.Utilities;
using UnityEditor;
using UnityEngine;
using LayoutTools = GameKit.Dependencies.Utilities.EditorGuiLayoutTools;
namespace FishNet.Component.Transforming.Editing
{
[CustomEditor(typeof(NetworkTransform), true)]
[CanEditMultipleObjects]
public class NetworkTransformEditor : Editor
{
private SerializedProperty _componentConfiguration;
private SerializedProperty _synchronizeParent;
private SerializedProperty _packing;
private SerializedProperty _useScaledTime;
private SerializedProperty _interpolation;
private SerializedProperty _extrapolation;
private SerializedProperty _enableTeleport;
private SerializedProperty _teleportThreshold;
private SerializedProperty _clientAuthoritative;
private SerializedProperty _sendToOwner;
private SerializedProperty _interval;
private SerializedProperty _synchronizePosition;
private SerializedProperty _positionSensitivity;
private SerializedProperty _positionSnapping;
private SerializedProperty _synchronizeRotation;
private SerializedProperty _rotationSnapping;
private SerializedProperty _synchronizeScale;
private SerializedProperty _scaleSensitivity;
private SerializedProperty _scaleSnapping;
protected virtual void OnEnable()
{
_componentConfiguration = serializedObject.FindProperty(nameof(_componentConfiguration));
_synchronizeParent = serializedObject.FindProperty(nameof(_synchronizeParent));
_packing = serializedObject.FindProperty(nameof(_packing));
_useScaledTime = serializedObject.FindProperty(nameof(_useScaledTime));
_interpolation = serializedObject.FindProperty(nameof(_interpolation));
_extrapolation = serializedObject.FindProperty(nameof(_extrapolation));
_enableTeleport = serializedObject.FindProperty(nameof(_enableTeleport));
_teleportThreshold = serializedObject.FindProperty(nameof(_teleportThreshold));
_clientAuthoritative = serializedObject.FindProperty(nameof(_clientAuthoritative));
_sendToOwner = serializedObject.FindProperty(nameof(_sendToOwner));
_interval = serializedObject.FindProperty(nameof(_interval));
_synchronizePosition = serializedObject.FindProperty(nameof(_synchronizePosition));
_positionSensitivity = serializedObject.FindProperty(nameof(_positionSensitivity));
_positionSnapping = serializedObject.FindProperty(nameof(_positionSnapping));
_synchronizeRotation = serializedObject.FindProperty(nameof(_synchronizeRotation));
_rotationSnapping = serializedObject.FindProperty(nameof(_rotationSnapping));
_synchronizeScale = serializedObject.FindProperty(nameof(_synchronizeScale));
_scaleSensitivity = serializedObject.FindProperty(nameof(_scaleSensitivity));
_scaleSnapping = serializedObject.FindProperty(nameof(_scaleSnapping));
}
public override void OnInspectorGUI()
{
serializedObject.Update();
LayoutTools.AddObjectField("Script:", MonoScript.FromMonoBehaviour((NetworkTransform)target), typeof(NetworkTransform), false, EditorLayoutEnableType.Disabled);
bool isPro = false;
if (isPro)
EditorGUILayout.HelpBox(EditingConstants.PRO_ASSETS_UNLOCKED_TEXT, MessageType.None);
else
EditorGUILayout.HelpBox(EditingConstants.PRO_ASSETS_LOCKED_TEXT, MessageType.Warning);
//Misc.
EditorGUILayout.LabelField("Misc", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_componentConfiguration);
EditorGUILayout.PropertyField(_synchronizeParent, new GUIContent("Synchronize Parent"));
EditorGUILayout.PropertyField(_packing);
EditorGUI.indentLevel--;
EditorGUILayout.Space();
//Smoothing.
EditorGUILayout.LabelField("Smoothing", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_useScaledTime);
EditorGUILayout.PropertyField(_interpolation);
EditorGUILayout.PropertyField(_extrapolation, new GUIContent("* Extrapolation"));
EditorGUILayout.PropertyField(_enableTeleport);
if (_enableTeleport.boolValue)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_teleportThreshold);
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
EditorGUILayout.Space();
//Authority.
EditorGUILayout.LabelField("Authority", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_clientAuthoritative);
if (!_clientAuthoritative.boolValue)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_sendToOwner);
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
EditorGUILayout.Space();
//Synchronizing.
EditorGUILayout.LabelField("Synchronizing.", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
//Interval.
EditorGUILayout.PropertyField(_interval, new GUIContent("Send Interval"));
//Position.
EditorGUILayout.PropertyField(_synchronizePosition);
if (_synchronizePosition.boolValue)
{
EditorGUI.indentLevel += 2;
EditorGUILayout.PropertyField(_positionSnapping);
EditorGUILayout.PropertyField(_positionSensitivity);
EditorGUI.indentLevel -= 2;
}
//Rotation.
EditorGUILayout.PropertyField(_synchronizeRotation);
if (_synchronizeRotation.boolValue)
{
EditorGUI.indentLevel += 2;
EditorGUILayout.PropertyField(_rotationSnapping);
EditorGUI.indentLevel -= 2;
}
//Scale.
EditorGUILayout.PropertyField(_synchronizeScale);
if (_synchronizeScale.boolValue)
{
EditorGUI.indentLevel += 2;
EditorGUILayout.PropertyField(_scaleSnapping);
EditorGUILayout.PropertyField(_scaleSensitivity);
EditorGUI.indentLevel -= 2;
}
EditorGUI.indentLevel--;
serializedObject.ApplyModifiedProperties();
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3ed56c899b8ecf241a2bc3e40a2dabc1
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/NetworkTransform/Editor/NetworkTransformEditor.cs
uploadId: 866910
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: a2836e36774ca1c4bbbee976e17b649c
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/NetworkTransform/NetworkTransform.cs
uploadId: 866910
@@ -0,0 +1,12 @@
namespace FishNet.Component.Transforming
{
[System.Flags]
public enum SynchronizedProperty : byte
{
None = 0,
Parent = 1,
Position = 2,
Rotation = 4,
Scale = 8
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3e6005ee9abfdd542ad27023114bbe04
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/NetworkTransform/SynchronizedProperty.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a68eb7a82e2c73847beb5b90cd65acc1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,291 @@
using FishNet.Object;
using GameKit.Dependencies.Utilities;
using GameKit.Dependencies.Utilities.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using UnityEngine;
using TimeManagerCls = FishNet.Managing.Timing.TimeManager;
namespace FishNet.Component.Prediction
{
public abstract class NetworkCollider : NetworkColliderBase
{
/// <summary>
/// Called when another collider enters this collider.
/// </summary>
public event Action<Collider> OnEnter;
/// <summary>
/// Called when another collider stays in this collider.
/// </summary>
public event Action<Collider> OnStay;
/// <summary>
/// Called when another collider exits this collider.
/// </summary>
public event Action<Collider> OnExit;
/// <summary>
/// The colliders on this object.
/// </summary>
private Collider[] _colliders;
/// <summary>
/// The hits from the last check.
/// </summary>
private Collider[] _hits;
/// <summary>
/// The history of collider data.
/// </summary>
private Dictionary<Collider, CollisionData> _enteredColliders;
protected override void Awake()
{
base.Awake();
_enteredColliders = CollectionCaches<Collider, CollisionData>.RetrieveDictionary();
_hits = CollectionCaches<Collider>.RetrieveArray();
if (_hits.Length < MaximumSimultaneousHits)
_hits = new Collider[MaximumSimultaneousHits];
}
private void OnDestroy()
{
CollectionCaches<Collider, CollisionData>.StoreAndDefault(ref _enteredColliders);
CollectionCaches<Collider>.StoreAndDefault(ref _hits, _hits.Length);
}
/// <summary>
/// Called by the PredictionManager immediately before a reconcile begins.
/// </summary>
protected override void PredictionManager_OnPreReconcile(uint clientTick, uint serverTick)
{
/* Remove entries older than the reconcile clientTick, if
* the entry is exited - as in the collider is no longer occupied. */
if (_enteredColliders.Count > 0)
{
List<Collider> entriesToRemove = CollectionCaches<Collider>.RetrieveList();
foreach (KeyValuePair<Collider, CollisionData> kvp in _enteredColliders)
{
uint exitTick = kvp.Value.ExitTick;
if (exitTick != TimeManagerCls.UNSET_TICK && exitTick < clientTick)
entriesToRemove.Add(kvp.Key);
}
foreach (Collider entry in entriesToRemove)
_enteredColliders.Remove(entry);
CollectionCaches<Collider>.Store(entriesToRemove);
}
/* Call base only after removing old entries. This ensures old entries are removed
* before CheckColliders is called. */
base.PredictionManager_OnPreReconcile(clientTick, serverTick);
}
/// <summary>
/// Checks for any collider changes;
/// </summary>
protected override void CheckColliders(uint clientTick)
{
// Initial checks failed.
if (!TryPrepareColliderCheck(clientTick))
return;
HashSet<Collider> current = CollectionCaches<Collider>.RetrieveHashSet();
Dictionary<Collider, CollisionData> entered = _enteredColliders;
/* Previous may not be set here if there were
* no collisions during the previous tick. */
// The rotation of the object for box colliders.
Quaternion rotation = transform.rotation;
// Check each collider for triggers.
foreach (Collider col in _colliders)
{
if (!col.enabled)
continue;
if (IsTrigger != col.isTrigger)
continue;
// Number of hits from the checks.
int hits;
if (col is SphereCollider sphereCollider)
hits = GetSphereColliderHits(sphereCollider, InteractableLayers);
else if (col is CapsuleCollider capsuleCollider)
hits = GetCapsuleColliderHits(capsuleCollider, InteractableLayers);
else if (col is BoxCollider boxCollider)
hits = GetBoxColliderHits(boxCollider, rotation, InteractableLayers);
else
hits = 0;
/* Check hits for enter/exit callbacks. */
for (int i = 0; i < hits; i++)
{
Collider hit = _hits[i];
if (hit == null || hit == col)
continue;
current.Add(hit);
// Already entered.
if (entered.TryGetValueIL2CPP(hit, out CollisionData collisionData))
{
/* If entered tick is beyond the tick being checked then
* that means the collider entered at a later time, and something
* is not aligning. Invoke OnExit and OnEnter again. */
if (collisionData.EnterTick >= clientTick || collisionData.ExitTick != TimeManagerCls.UNSET_TICK)
{
OnExit?.Invoke(hit);
OnEnter?.Invoke(hit);
// Also update position in collection.
entered[hit] = new(clientTick);
}
}
// Not yet in entered state.
else
{
OnEnter?.Invoke(hit);
// Also update position in collection.
entered[hit] = new(clientTick);
}
// Always invoke OnStay when collider hits.
OnStay?.Invoke(hit);
}
List<Collider> collidersExited = CollectionCaches<Collider>.RetrieveList();
/* Check to invoke exit on any colliders which are no longer
* in the entered state. */
foreach (Collider c in entered.Keys)
{
// Collider was still entered, no need to check exit.
if (current.Contains(c))
continue;
/* Entered tick will be the same as tick if first
* entering for this tick. It's not possible for Unity physics
* to invoke Enter/Exit on the same tick, as it doesn't make sense
* to anyway. When the same tick, continue. */
if (entered[c].EnterTick == clientTick)
continue;
collidersExited.Add(c);
}
// Invoke for exited and remove from entered.
foreach (Collider c in collidersExited)
{
/* If here then the entered collider was not hit
* this trace. Invoke exit and remove from entered. */
OnExit?.Invoke(c);
if (IsServerStarted)
{
entered.Remove(c);
}
else
{
/* Only re-add if the entered tick is beyond
* the current tick; this would indicate a new enter.
* Otherwise, we are at an exit only. */
uint enteredTick = entered[c].EnterTick;
if (enteredTick > clientTick)
entered[c] = new(entered[c].EnterTick, clientTick);
else
entered.Remove(c);
}
}
CollectionCaches<Collider>.Store(collidersExited);
}
CollectionCaches<Collider>.Store(current);
}
/// <summary>
/// Checks for Sphere collisions.
/// </summary>
/// <returns>Number of colliders hit.</returns>
private int GetSphereColliderHits(SphereCollider sphereCollider, int layerMask)
{
sphereCollider.GetSphereOverlapParams(out Vector3 center, out float radius);
radius += AdditionalSize;
return gameObject.scene.GetPhysicsScene().OverlapSphere(center, radius, _hits, layerMask, QueryTriggerInteraction.UseGlobal);
}
/// <summary>
/// Checks for Capsule collisions.
/// </summary>
/// <returns>Number of colliders hit.</returns>
private int GetCapsuleColliderHits(CapsuleCollider capsuleCollider, int layerMask)
{
capsuleCollider.GetCapsuleCastParams(out Vector3 start, out Vector3 end, out float radius);
radius += AdditionalSize;
return gameObject.scene.GetPhysicsScene().OverlapCapsule(start, end, radius, _hits, layerMask, QueryTriggerInteraction.UseGlobal);
}
/// <summary>
/// Checks for Box collisions.
/// </summary>
/// <returns>Number of colliders hit.</returns>
private int GetBoxColliderHits(BoxCollider boxCollider, Quaternion rotation, int layerMask)
{
boxCollider.GetBoxOverlapParams(out Vector3 center, out Vector3 halfExtents);
Vector3 additional = Vector3.one * AdditionalSize;
halfExtents += additional;
return gameObject.scene.GetPhysicsScene().OverlapBox(center, halfExtents, _hits, rotation, layerMask, QueryTriggerInteraction.UseGlobal);
}
/// <summary>
/// Finds colliders on this object to check.
/// </summary>
/// <param name = "force">True to set colliders again even if already found. This action will clear stored collider states.</param>
/// <returns>True if colliders should be found again.</returns>
public override bool TryFindColliders(bool force = false)
{
if (!base.TryFindColliders(force))
return false;
ClearColliderDataHistory(invokeOnExit: true);
_colliders = GetComponents<Collider>();
return true;
}
/// <summary>
/// Resets this NetworkBehaviour so that it may be added to an object pool.
/// </summary>
public override void ResetState(bool asServer)
{
ClearColliderDataHistory(invokeOnExit: true);
base.ResetState(asServer);
}
/// <summary>
/// Clears stored collider states.
/// </summary>
/// <param name = "invokeOnExit">True to invoke OnExit if a collider is stored in the OnEntered state. When called during a reconcile this used the current ClientReplayTick, otherwise uses LocalTick.</param>
protected override void ClearColliderDataHistory(bool invokeOnExit)
{
if (_enteredColliders == null)
return;
/* Data needs to exist to iterate, and managers are needed
* to get the proper tick to invoke. */
if (invokeOnExit)
{
foreach (KeyValuePair<Collider, CollisionData> kvp in _enteredColliders)
{
/* This indicates an exit has not yet invoked.
* It's possible for an item to invoked an exit and still
* have its state cached for properly executing events during
* a reconcile. */
if (kvp.Value.ExitTick == TimeManagerCls.UNSET_TICK)
OnExit?.Invoke(kvp.Key);
}
}
_enteredColliders.Clear();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 4f977181495644345a4d0d821c31579f
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/Prediction/NetworkCollider.cs
uploadId: 866910
@@ -0,0 +1,277 @@
using FishNet.Managing;
using FishNet.Object;
using GameKit.Dependencies.Utilities;
using GameKit.Dependencies.Utilities.Types;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
using TimeManagerCls = FishNet.Managing.Timing.TimeManager;
namespace FishNet.Component.Prediction
{
public abstract class NetworkCollider2D : NetworkColliderBase
{
/// <summary>
/// Called when another collider enters this collider.
/// </summary>
public event Action<Collider2D> OnEnter;
/// <summary>
/// Called when another collider stays in this collider.
/// </summary>
public event Action<Collider2D> OnStay;
/// <summary>
/// Called when another collider exits this collider.
/// </summary>
public event Action<Collider2D> OnExit;
/// <summary>
/// The colliders on this object.
/// </summary>
private Collider2D[] _colliders;
/// <summary>
/// The hits from the last check.
/// </summary>
private Collider2D[] _hits;
/// <summary>
/// The history of collider data.
/// </summary>
private Dictionary<Collider2D, CollisionData> _enteredColliders;
protected override void Awake()
{
base.Awake();
_enteredColliders = CollectionCaches<Collider2D, CollisionData>.RetrieveDictionary();
_hits = CollectionCaches<Collider2D>.RetrieveArray();
if (_hits.Length < MaximumSimultaneousHits)
_hits = new Collider2D[MaximumSimultaneousHits];
}
private void OnDestroy()
{
CollectionCaches<Collider2D, CollisionData>.StoreAndDefault(ref _enteredColliders);
CollectionCaches<Collider2D>.StoreAndDefault(ref _hits, _hits.Length);
}
/// <summary>
/// Called by the PredictionManager immediately before a reconcile begins.
/// </summary>
protected override void PredictionManager_OnPreReconcile(uint clientTick, uint serverTick)
{
/* Remove entries older than the reconcile clientTick, if
* the entry is exited - as in the collider is no longer occupied. */
if (_enteredColliders.Count > 0)
{
List<Collider2D> entriesToRemove = CollectionCaches<Collider2D>.RetrieveList();
foreach (KeyValuePair<Collider2D, CollisionData> kvp in _enteredColliders)
{
uint exitTick = kvp.Value.ExitTick;
if (exitTick != TimeManagerCls.UNSET_TICK && exitTick < clientTick)
entriesToRemove.Add(kvp.Key);
}
foreach (Collider2D entry in entriesToRemove)
_enteredColliders.Remove(entry);
CollectionCaches<Collider2D>.Store(entriesToRemove);
}
/* Call base only after removing old entries. This ensures old entries are removed
* before CheckColliders is called. */
base.PredictionManager_OnPreReconcile(clientTick, serverTick);
}
/// <summary>
/// Checks for any collider changes;
/// </summary>
protected override void CheckColliders(uint clientTick)
{
// Initial checks failed.
if (!TryPrepareColliderCheck(clientTick))
return;
HashSet<Collider2D> current = CollectionCaches<Collider2D>.RetrieveHashSet();
Dictionary<Collider2D, CollisionData> entered = _enteredColliders;
/* Previous may not be set here if there were
* no collisions during the previous tick. */
// The rotation of the object for box colliders.
Quaternion rotation = transform.rotation;
// Check each collider for triggers.
foreach (Collider2D col in _colliders)
{
if (!col.enabled)
continue;
if (IsTrigger != col.isTrigger)
continue;
// Number of hits from the checks.
int hits;
if (col is CircleCollider2D circleCollider)
hits = GetCircleCollider2DHits(circleCollider, InteractableLayers);
else if (col is BoxCollider2D boxCollider)
hits = GetBoxCollider2DHits(boxCollider, rotation, InteractableLayers);
else
hits = 0;
/* Check hits for enter/exit callbacks. */
for (int i = 0; i < hits; i++)
{
Collider2D hit = _hits[i];
if (hit == null || hit == col)
continue;
current.Add(hit);
// Already entered.
if (entered.TryGetValueIL2CPP(hit, out CollisionData collisionData))
{
/* If entered tick is beyond the tick being checked then
* that means the collider entered at a later time, and something
* is not aligning. Invoke OnExit and OnEnter again. */
if (collisionData.EnterTick >= clientTick || collisionData.ExitTick != TimeManagerCls.UNSET_TICK)
{
OnExit?.Invoke(hit);
OnEnter?.Invoke(hit);
// Also update position in collection.
entered[hit] = new(clientTick);
}
}
// Not yet in entered state.
else
{
OnEnter?.Invoke(hit);
// Also update position in collection.
entered[hit] = new(clientTick);
}
// Always invoke OnStay when collider hits.
OnStay?.Invoke(hit);
}
List<Collider2D> collidersExited = CollectionCaches<Collider2D>.RetrieveList();
/* Check to invoke exit on any colliders which are no longer
* in the entered state. */
foreach (Collider2D c in entered.Keys)
{
// Collider was still entered, no need to check exit.
if (current.Contains(c))
continue;
/* Entered tick will be the same as tick if first
* entering for this tick. It's not possible for Unity physics
* to invoke Enter/Exit on the same tick, as it doesn't make sense
* to anyway. When the same tick, continue. */
if (entered[c].EnterTick == clientTick)
continue;
collidersExited.Add(c);
}
// Invoke for exited and remove from entered.
foreach (Collider2D c in collidersExited)
{
/* If here then the entered collider was not hit
* this trace. Invoke exit and remove from entered. */
OnExit?.Invoke(c);
if (IsServerStarted)
{
entered.Remove(c);
}
else
{
/* Only re-add if the entered tick is beyond
* the current tick; this would indicate a new enter.
* Otherwise, we are at an exit only. */
uint enteredTick = entered[c].EnterTick;
if (enteredTick > clientTick)
entered[c] = new(entered[c].EnterTick, clientTick);
else
entered.Remove(c);
}
// entered.Remove(c);
}
}
CollectionCaches<Collider2D>.Store(current);
}
/// <summary>
/// Checks for circle collisions.
/// </summary>
/// <returns>Number of colliders hit.</returns>
private int GetCircleCollider2DHits(CircleCollider2D circleCollider, int layerMask)
{
circleCollider.GetCircleOverlapParams(out Vector3 center, out float radius);
radius += AdditionalSize;
return gameObject.scene.GetPhysicsScene2D().OverlapCircle(center, radius, _hits, layerMask);
}
/// <summary>
/// Checks for Box collisions.
/// </summary>
/// <returns>Number of colliders hit.</returns>
private int GetBoxCollider2DHits(BoxCollider2D boxCollider, Quaternion rotation, int layerMask)
{
boxCollider.GetBox2DOverlapParams(out Vector3 center, out Vector3 halfExtents);
Vector3 additional = Vector3.one * AdditionalSize;
halfExtents += additional;
return gameObject.scene.GetPhysicsScene2D().OverlapBox(center, halfExtents, rotation.z, _hits, layerMask);
}
/// <summary>
/// Finds colliders on this object to check.
/// </summary>
/// <param name = "force">True to set colliders again even if already found. This action will clear stored collider states.</param>
/// <returns>True if colliders should be found again.</returns>
public override bool TryFindColliders(bool force = false)
{
if (!base.TryFindColliders(force))
return false;
ClearColliderDataHistory(invokeOnExit: true);
_colliders = GetComponents<Collider2D>();
return true;
}
/// <summary>
/// Resets this NetworkBehaviour so that it may be added to an object pool.
/// </summary>
public override void ResetState(bool asServer)
{
ClearColliderDataHistory(invokeOnExit: true);
base.ResetState(asServer);
}
/// <summary>
/// Clears stored collider states.
/// </summary>
/// <param name = "invokeOnExit">True to invoke OnExit if a collider is stored in the OnEntered state. When called during a reconcile this used the current ClientReplayTick, otherwise uses LocalTick.</param>
protected override void ClearColliderDataHistory(bool invokeOnExit)
{
if (_enteredColliders == null)
return;
/* Data needs to exist to iterate, and managers are needed
* to get the proper tick to invoke. */
if (invokeOnExit)
{
foreach (KeyValuePair<Collider2D, CollisionData> kvp in _enteredColliders)
{
/* This indicates an exit has not yet invoked.
* It's possible for an item to invoked an exit and still
* have its state cached for properly executing events during
* a reconcile. */
if (kvp.Value.ExitTick == TimeManagerCls.UNSET_TICK)
OnExit?.Invoke(kvp.Key);
}
}
_enteredColliders.Clear();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: df45081143314fd469a062670a0fe1ea
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/Prediction/NetworkCollider2D.cs
uploadId: 866910
@@ -0,0 +1,177 @@
using System;
using FishNet.Object;
using GameKit.Dependencies.Utilities;
using UnityEngine;
using UnityEngine.Serialization;
using TimeManagerCls = FishNet.Managing.Timing.TimeManager;
namespace FishNet.Component.Prediction
{
public abstract class NetworkColliderBase : NetworkBehaviour
{
#region Types.
protected struct CollisionData
{
/// <summary>
/// Tick when entering collision.
/// </summary>
public uint EnterTick;
/// <summary>
/// Tick when exiting collision.
/// </summary>
public uint ExitTick;
public CollisionData(uint enterTick) : this()
{
EnterTick = enterTick;
ExitTick = Managing.Timing.TimeManager.UNSET_TICK;
}
public CollisionData(uint enterTick, uint exitTick) : this()
{
EnterTick = enterTick;
ExitTick = exitTick;
}
}
#endregion
/// <summary>
/// True to run collisions for colliders which are triggers, false to run collisions for colliders which are not triggers.
/// </summary>
[HideInInspector]
protected bool IsTrigger;
/// <summary>
/// Maximum number of simultaneous hits to check for. Larger values decrease performance but allow detection to work for more overlapping colliders. Typically the default value of 16 is more than sufficient.
/// </summary>
[FormerlySerializedAs("_maximumSimultaneousHits")]
[Tooltip("Maximum number of simultaneous hits to check for. Larger values decrease performance but allow detection to work for more overlapping colliders. Typically the default value of 16 is more than sufficient.")]
[SerializeField]
protected ushort MaximumSimultaneousHits = 16;
/// <summary>
/// Units to extend collision traces by. This is used to prevent missed overlaps when colliders do not intersect enough.
/// </summary>
[FormerlySerializedAs("_additionalSize")]
[Tooltip("Units to extend collision traces by. This is used to prevent missed overlaps when colliders do not intersect enough.")]
[Range(0f, 100f)]
[SerializeField]
protected float AdditionalSize = 0.1f;
/// <summary>
/// Layers to trace on. This is used when value is not nothing.
/// </summary>
[FormerlySerializedAs("_layers")]
[Tooltip("Layers to trace on. This is used when value is not nothing.")]
[SerializeField]
protected LayerMask Layers = (LayerMask)0;
/// <summary>
/// True if colliders have been searched for at least once.
/// We cannot check the null state on _colliders because Unity has a habit of initializing collections on it's own.
/// </summary>
private bool _collidersFound;
/// <summary>
/// Last layer of the gameObject.
/// </summary>
private int _lastGameObjectLayer = -1;
/// <summary>
/// Interactable layers for the layer of this gameObject.
/// </summary>
[HideInInspector]
protected int InteractableLayers;
protected virtual void Awake()
{
TryFindColliders(force: true);
;
}
public override void OnStartNetwork()
{
// Events needed by server and client.
TimeManager.OnPostPhysicsSimulation += TimeManager_OnPostPhysicsSimulation;
}
public override void OnStartClient()
{
// Events only needed by the client.
PredictionManager.OnPostReconcileSyncTransforms += PredictionManager_OnPreReconcile;
}
public override void OnStopClient()
{
// Events only needed by the client.
PredictionManager.OnPostReconcileSyncTransforms -= PredictionManager_OnPreReconcile;
}
public override void OnStopNetwork()
{
TimeManager.OnPostPhysicsSimulation -= TimeManager_OnPostPhysicsSimulation;
}
/// <summary>
/// Called by the PredictionManager immediately before a reconcile begins.
/// </summary>
protected virtual void PredictionManager_OnPreReconcile(uint clientTick, uint serverTick)
{
CheckColliders(clientTick);
}
/// <summary>
/// When using TimeManager for physics timing, this is called immediately after the physics simulation has occured for the tick.
/// While using Unity for physics timing, this is called during Update, only if a physics frame.
/// This may be useful if you wish to run physics differently for stacked scenes.
private void TimeManager_OnPostPhysicsSimulation(float delta)
{
uint tick = PredictionManager.IsReconciling && !IsServerStarted ? PredictionManager.ClientReplayTick : TimeManager.LocalTick;
CheckColliders(tick);
}
/// <summary>
/// Returns if colliders should be checked. If colliders can be checked data needed by all collider checks (2D and 3D) is set.
/// </summary>
/// <returns>True if collision checking should proceed, false if not.</returns>
protected bool TryPrepareColliderCheck(uint tick)
{
// Should not be possible as tick always starts on 1.
if (tick == TimeManagerCls.UNSET_TICK)
return false;
/* Previous may not be set here if there were
* no collisions during the previous tick. */
// If layers are specified then do not use GOs layers, use specified.
if (Layers != (LayerMask)0)
{
InteractableLayers = Layers;
}
// Use GOs layers.
else
{
int currentLayer = gameObject.layer;
if (_lastGameObjectLayer != currentLayer)
{
_lastGameObjectLayer = currentLayer;
InteractableLayers = GameKit.Dependencies.Utilities.Layers.GetInteractableLayersValue(currentLayer);
}
}
return true;
}
/// <summary>
/// Implement collider checking logic within this method.
/// </summary>
protected abstract void CheckColliders(uint clientTick);
/// <summary>
/// Clears stored collider states.
/// </summary>
/// <param name = "invokeOnExit">True to invoke OnExit if a collider is stored in the OnEntered state. When called during a reconcile this used the current ClientReplayTick, otherwise uses LocalTick.</param>
protected abstract void ClearColliderDataHistory(bool invokeOnExit);
/// <summary>
/// Finds colliders on this object to check.
/// </summary>
/// <param name = "force">True to set colliders again even if already found. This action will clear stored collider states.</param>
/// <returns>True if colliders should be found again.</returns>
public virtual bool TryFindColliders(bool force = false) => !_collidersFound || force;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 4fa87b975da969046b596d2669737932
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/Prediction/NetworkColliderBase.cs
uploadId: 866910
@@ -0,0 +1,13 @@
using UnityEngine;
namespace FishNet.Component.Prediction
{
public sealed class NetworkCollision : NetworkCollider
{
protected override void Awake()
{
IsTrigger = false;
base.Awake();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 40bcde17fa5e03f44b87f9963ac281b3
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/Prediction/NetworkCollision.cs
uploadId: 866910
@@ -0,0 +1,13 @@
using UnityEngine;
namespace FishNet.Component.Prediction
{
public sealed class NetworkCollision2D : NetworkCollider2D
{
protected override void Awake()
{
IsTrigger = false;
base.Awake();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: be3cb2952c351714c963b0766f53cef3
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/Prediction/NetworkCollision2D.cs
uploadId: 866910
@@ -0,0 +1,11 @@
namespace FishNet.Component.Prediction
{
public sealed class NetworkTrigger : NetworkCollider
{
protected override void Awake()
{
IsTrigger = true;
base.Awake();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3dc97a8b9e628044e83004c93095c14d
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/Prediction/NetworkTrigger.cs
uploadId: 866910
@@ -0,0 +1,11 @@
namespace FishNet.Component.Prediction
{
public sealed class NetworkTrigger2D : NetworkCollider2D
{
protected override void Awake()
{
IsTrigger = true;
base.Awake();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: a60424afcf0b5984dbe878a1f21c10b2
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/Prediction/NetworkTrigger2D.cs
uploadId: 866910
@@ -0,0 +1,108 @@
using FishNet.Managing.Predicting;
using UnityEngine;
namespace FishNet.Component.Prediction
{
public partial class OfflineRigidbody : MonoBehaviour
{
#region Serialized.
/// <summary>
/// Type of prediction movement which is being used.
/// </summary>
[Tooltip("Type of prediction movement which is being used.")]
[SerializeField]
private RigidbodyType _rigidbodyType;
/// <summary>
/// True to also get rigidbody components within children.
/// </summary>
[Tooltip("True to also get rigidbody components within children.")]
[SerializeField]
private bool _getInChildren;
#endregion
#region Private.
/// <summary>
/// Pauser for rigidbodies.
/// </summary>
private RigidbodyPauser _rigidbodyPauser = new();
/// <summary>
/// TimeManager subscribed to.
/// </summary>
private PredictionManager _predictionManager;
#endregion
private void Awake()
{
InitializeOnce();
}
private void OnDestroy()
{
ChangeSubscription(false);
}
/// <summary>
/// Initializes this script for use.
/// </summary>
private void InitializeOnce()
{
_predictionManager = InstanceFinder.PredictionManager;
UpdateRigidbodies();
ChangeSubscription(true);
}
/// <summary>
/// Sets a new PredictionManager to use.
/// </summary>
/// <param name = "tm"></param>
public void SetPredictionManager(PredictionManager pm)
{
if (pm == _predictionManager)
return;
// Unsub from current.
ChangeSubscription(false);
// Sub to newest.
_predictionManager = pm;
ChangeSubscription(true);
}
/// <summary>
/// Finds and assigns rigidbodie using configured settings.
/// </summary>
public void UpdateRigidbodies()
{
_rigidbodyPauser.UpdateRigidbodies(transform, _rigidbodyType, _getInChildren);
}
/// <summary>
/// Changes the subscription to the TimeManager.
/// </summary>
private void ChangeSubscription(bool subscribe)
{
if (_predictionManager == null)
return;
if (subscribe)
{
_predictionManager.OnPreReconcile += _predictionManager_OnPreReconcile;
_predictionManager.OnPostReconcile += _predictionManager_OnPostReconcile;
}
else
{
_predictionManager.OnPreReconcile -= _predictionManager_OnPreReconcile;
_predictionManager.OnPostReconcile -= _predictionManager_OnPostReconcile;
}
}
private void _predictionManager_OnPreReconcile(uint clientTick, uint serverTick)
{
_rigidbodyPauser.Pause();
}
private void _predictionManager_OnPostReconcile(uint clientTick, uint serverTick)
{
_rigidbodyPauser.Unpause();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: b749f90d4c9961c4991179db1130fa4d
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/Prediction/OfflineRigidbody.cs
uploadId: 866910
@@ -0,0 +1,479 @@
using FishNet.Managing;
using GameKit.Dependencies.Utilities;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using UnityEngine;
namespace FishNet.Component.Prediction
{
/// <summary>
/// Pauses and unpauses rigidbodies. While paused rigidbodies cannot be interacted with or simulated.
/// </summary>
public class RigidbodyPauser : IResettable
{
#region Types.
/// <summary>
/// Data for a rigidbody before being set kinematic.
/// </summary>
private struct RigidbodyData
{
/// <summary>
/// Rigidbody for data.
/// </summary>
public Rigidbody Rigidbody;
/// <summary>
/// Cached velocity when being set kinematic.
/// </summary>
public Vector3 Velocity;
/// <summary>
/// Cached velocity when being set kinematic.
/// </summary>
public Vector3 AngularVelocity;
/// <summary>
/// True if the rigidbody was kinematic prior to being paused.
/// </summary>
public bool IsKinematic;
/// <summary>
/// True if the rigidbody was detecting collisions prior to being paused.
/// </summary>
public bool DetectCollisions;
/// <summary>
/// Detection mode of the Rigidbody.
/// </summary>
public CollisionDetectionMode CollisionDetectionMode;
public RigidbodyData(Rigidbody rb)
{
Rigidbody = rb;
Velocity = Vector3.zero;
AngularVelocity = Vector3.zero;
IsKinematic = rb.isKinematic;
DetectCollisions = rb.detectCollisions;
CollisionDetectionMode = rb.collisionDetectionMode;
}
public void Update(Rigidbody rb)
{
#if UNITY_6000_1_OR_NEWER
Velocity = rb.linearVelocity;
#else
Velocity = rb.velocity;
#endif
AngularVelocity = rb.angularVelocity;
IsKinematic = rb.isKinematic;
DetectCollisions = rb.detectCollisions;
CollisionDetectionMode = rb.collisionDetectionMode;
}
}
/// <summary>
/// Data for a rigidbody2d before being set kinematic.
/// </summary>
private struct Rigidbody2DData
{
/// <summary>
/// Rigidbody for data.
/// </summary>
public Rigidbody2D Rigidbody2d;
/// <summary>
/// Cached velocity when being set kinematic.
/// </summary>
public Vector2 Velocity;
/// <summary>
/// Cached velocity when being set kinematic.
/// </summary>
public float AngularVelocity;
/// <summary>
/// True if the rigidbody was kinematic prior to being paused.
/// </summary>
public bool IsKinematic;
/// <summary>
/// True if the rigidbody was simulated prior to being paused.
/// </summary>
public bool Simulated;
/// <summary>
/// Detection mode of the rigidbody.
/// </summary>
public CollisionDetectionMode2D CollisionDetectionMode;
public Rigidbody2DData(Rigidbody2D rb)
{
Rigidbody2d = rb;
Velocity = Vector2.zero;
AngularVelocity = 0f;
Simulated = rb.simulated;
#if UNITY_6000_1_OR_NEWER
IsKinematic = rb.bodyType == RigidbodyType2D.Kinematic;
#else
IsKinematic = rb.isKinematic;
#endif
CollisionDetectionMode = rb.collisionDetectionMode;
}
public void Update(Rigidbody2D rb)
{
#if UNITY_6000_1_OR_NEWER
Velocity = rb.linearVelocity;
#else
Velocity = rb.velocity;
#endif
AngularVelocity = rb.angularVelocity;
Simulated = rb.simulated;
#if UNITY_6000_1_OR_NEWER
IsKinematic = rb.bodyType == RigidbodyType2D.Kinematic;
#else
IsKinematic = rb.isKinematic;
#endif
CollisionDetectionMode = rb.collisionDetectionMode;
}
}
#endregion
#region Public.
/// <summary>
/// True if the rigidbodies are considered paused.
/// </summary>
public bool Paused { get; private set; }
#endregion
#region Private.
/// <summary>
/// Rigidbody datas for found rigidbodies.
/// </summary>
private List<RigidbodyData> _rigidbodyDatas = new();
/// <summary>
/// Rigidbody2D datas for found rigidbodies;
/// </summary>
private List<Rigidbody2DData> _rigidbody2dDatas = new();
/// <summary>
/// True to get rigidbodies in children of transform.
/// </summary>
private bool _getInChildren;
/// <summary>
/// Transform to get rigidbodies on.
/// </summary>
private Transform _transform;
/// <summary>
/// Type of prediction movement which is being used.
/// </summary>
private RigidbodyType _rigidbodyType;
/// <summary>
/// True if initialized at least once.
/// </summary>
private bool _initialized;
#endregion
/// <summary>
/// Assigns rigidbodies using initialized settings.
/// </summary>
public void UpdateRigidbodies()
{
if (!_initialized)
{
InstanceFinder.NetworkManager.LogError($"T{GetType().Name} has not been initialized yet. This method cannot be used.");
return;
}
UpdateRigidbodies(_transform, _rigidbodyType, _getInChildren);
}
/// <summary>
/// Assigns rigidbodies manually and initializes component.
/// </summary>
public void UpdateRigidbodies(Rigidbody[] rbs)
{
List<Rigidbody> rigidbodies = CollectionCaches<Rigidbody>.RetrieveList();
foreach (Rigidbody rb in rbs)
rigidbodies.Add(rb);
UpdateRigidbodies(rigidbodies);
CollectionCaches<Rigidbody>.Store(rigidbodies);
}
/// <summary>
/// Assigns rigidbodies manually and initializes component.
/// </summary>
private void UpdateRigidbodies(List<Rigidbody> rbs)
{
_rigidbodyDatas.Clear();
foreach (Rigidbody rb in rbs)
_rigidbodyDatas.Add(new(rb));
_initialized = true;
}
/// <summary>
/// Assigns rigidbodies manually and initializes component.
/// </summary>
public void UpdateRigidbodies2D(Rigidbody2D[] rbs)
{
List<Rigidbody2D> rigidbodies = CollectionCaches<Rigidbody2D>.RetrieveList();
foreach (Rigidbody2D rb in rbs)
rigidbodies.Add(rb);
UpdateRigidbodies2D(rigidbodies);
CollectionCaches<Rigidbody2D>.Store(rigidbodies);
}
/// <summary>
/// Assigns rigidbodies manually and initializes component.
/// </summary>
private void UpdateRigidbodies2D(List<Rigidbody2D> rbs)
{
_rigidbody2dDatas.Clear();
foreach (Rigidbody2D rb in rbs)
_rigidbody2dDatas.Add(new(rb));
_initialized = true;
}
/// <summary>
/// Assigns rigidbodies.
/// </summary>
/// <param name = "rbs">Rigidbodies2D to use.</param>
public void UpdateRigidbodies(Transform t, RigidbodyType rbType, bool getInChildren)
{
_rigidbodyType = rbType;
_getInChildren = getInChildren;
// 3D.
if (rbType == RigidbodyType.Rigidbody)
{
List<Rigidbody> rigidbodies = CollectionCaches<Rigidbody>.RetrieveList();
if (getInChildren)
{
Rigidbody[] rbs = t.GetComponentsInChildren<Rigidbody>();
for (int i = 0; i < rbs.Length; i++)
rigidbodies.Add(rbs[i]);
}
else
{
Rigidbody rb = t.GetComponent<Rigidbody>();
if (rb != null)
rigidbodies.Add(rb);
}
UpdateRigidbodies(rigidbodies);
CollectionCaches<Rigidbody>.Store(rigidbodies);
}
// 2D.
else
{
List<Rigidbody2D> rigidbodies = CollectionCaches<Rigidbody2D>.RetrieveList();
if (getInChildren)
{
Rigidbody2D[] rbs = t.GetComponentsInChildren<Rigidbody2D>();
for (int i = 0; i < rbs.Length; i++)
rigidbodies.Add(rbs[i]);
}
else
{
Rigidbody2D rb = t.GetComponent<Rigidbody2D>();
if (rb != null)
rigidbodies.Add(rb);
}
UpdateRigidbodies2D(rigidbodies);
CollectionCaches<Rigidbody2D>.Store(rigidbodies);
}
}
/// <summary>
/// Pauses rigidbodies preventing them from interacting.
/// </summary>
public void Pause()
{
if (Paused)
return;
Paused = true;
/* Iterate move after pausing.
* This ensures when the children RBs update values
* they are not updating from a new scene, where the root
* may have moved them */
// 3D.
if (_rigidbodyType == RigidbodyType.Rigidbody)
{
for (int i = 0; i < _rigidbodyDatas.Count; i++)
{
if (!PauseRigidbody(i))
{
_rigidbodyDatas.RemoveAt(i);
i--;
}
}
// Sets isKinematic status and returns if successful.
bool PauseRigidbody(int index)
{
RigidbodyData rbData = _rigidbodyDatas[index];
Rigidbody rb = rbData.Rigidbody;
if (rb == null)
return false;
rbData.Update(rb);
_rigidbodyDatas[index] = rbData;
rb.collisionDetectionMode = CollisionDetectionMode.Discrete;
rb.isKinematic = true;
rb.detectCollisions = false;
return true;
}
}
// 2D.
else
{
for (int i = 0; i < _rigidbody2dDatas.Count; i++)
{
if (!PauseRigidbody(i))
{
_rigidbody2dDatas.RemoveAt(i);
i--;
}
}
// Sets isKinematic status and returns if successful.
bool PauseRigidbody(int index)
{
Rigidbody2DData rbData = _rigidbody2dDatas[index];
Rigidbody2D rb = rbData.Rigidbody2d;
if (rb == null)
return false;
rbData.Update(rb);
_rigidbody2dDatas[index] = rbData;
rb.collisionDetectionMode = CollisionDetectionMode2D.Discrete;
#if UNITY_6000_1_OR_NEWER
rb.bodyType = RigidbodyType2D.Kinematic;
#else
rb.isKinematic = true;
#endif
rb.simulated = false;
return true;
}
}
}
/// <summary>
/// Unpauses rigidbodies allowing them to interact normally.
/// </summary>
public void Unpause()
{
if (!Paused)
return;
Paused = false;
// 3D.
if (_rigidbodyType == RigidbodyType.Rigidbody)
{
for (int i = 0; i < _rigidbodyDatas.Count; i++)
{
if (!UnpauseRigidbody(i))
{
_rigidbodyDatas.RemoveAt(i);
i--;
}
}
// Sets isKinematic status and returns if successful.
bool UnpauseRigidbody(int index)
{
RigidbodyData rbData = _rigidbodyDatas[index];
Rigidbody rb = rbData.Rigidbody;
if (rb == null)
return false;
/* If data has RB updated as kinematic then
* do not unpause. This means either something else
* is handling the kinematic state of the dev
* made it kinematic. */
// if (rbData.IsKinematic)
// return true;
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
rb.isKinematic = rbData.IsKinematic;
rb.detectCollisions = rbData.DetectCollisions;
rb.collisionDetectionMode = rbData.CollisionDetectionMode;
if (!rb.isKinematic)
{
#if UNITY_6000_1_OR_NEWER
rb.linearVelocity = rbData.Velocity;
#else
rb.velocity = rbData.Velocity;
#endif
rb.angularVelocity = rbData.AngularVelocity;
}
return true;
}
}
// 2D.
else
{
for (int i = 0; i < _rigidbody2dDatas.Count; i++)
{
if (!UnpauseRigidbody(i))
{
_rigidbody2dDatas.RemoveAt(i);
i--;
}
}
// Sets isKinematic status and returns if successful.
bool UnpauseRigidbody(int index)
{
Rigidbody2DData rbData = _rigidbody2dDatas[index];
Rigidbody2D rb = rbData.Rigidbody2d;
if (rb == null)
return false;
//Same as RB, only unpause if data is stored in an unpaused state.
if (rbData.IsKinematic || !rbData.Simulated)
return true;
#if UNITY_6000_1_OR_NEWER
rb.bodyType = RigidbodyType2D.Dynamic;
#else
rb.isKinematic = false;
#endif
rb.simulated = true;
rb.collisionDetectionMode = rbData.CollisionDetectionMode;
#if UNITY_6000_1_OR_NEWER
rb.linearVelocity = rbData.Velocity;
#else
rb.velocity = rbData.Velocity;
#endif
rb.angularVelocity = rbData.AngularVelocity;
return true;
}
}
}
public void ResetState()
{
_rigidbodyDatas.Clear();
_rigidbody2dDatas.Clear();
_getInChildren = default;
_transform = default;
_rigidbodyType = default;
_initialized = default;
Paused = default;
}
public void InitializeState() { }
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 9536377524ca5db43aae431f983ab21f
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/Prediction/RigidbodyPauser.cs
uploadId: 866910
@@ -0,0 +1,226 @@
using FishNet.CodeGenerating;
using FishNet.Serializing;
using UnityEngine;
using UnityEngine.Scripting;
namespace FishNet.Component.Prediction
{
[UseGlobalCustomSerializer]
[Preserve]
public struct RigidbodyState
{
public Vector3 Position;
public Quaternion Rotation;
[ExcludeSerialization]
public readonly AutoPackType RotationPacking;
public bool IsKinematic;
public Vector3 Velocity;
public Vector3 AngularVelocity;
public RigidbodyState(Rigidbody rb, AutoPackType rotationPacking = AutoPackType.Packed)
{
Position = rb.transform.position;
Rotation = rb.transform.rotation;
IsKinematic = rb.isKinematic;
#if UNITY_6000_1_OR_NEWER
Velocity = rb.linearVelocity;
#else
Velocity = rb.velocity;
#endif
AngularVelocity = rb.angularVelocity;
RotationPacking = rotationPacking;
}
}
[UseGlobalCustomSerializer]
[Preserve]
public struct Rigidbody2DState
{
public Vector3 Position;
public Quaternion Rotation;
[ExcludeSerialization]
public readonly AutoPackType RotationPacking;
public Vector2 Velocity;
public float AngularVelocity;
public bool Simulated;
public bool IsKinematic;
public Rigidbody2DState(Rigidbody2D rb, AutoPackType rotationPacking = AutoPackType.Packed)
{
Position = rb.transform.position;
Rotation = rb.transform.rotation;
RotationPacking = rotationPacking;
#if UNITY_6000_1_OR_NEWER
Velocity = rb.linearVelocity;
#else
Velocity = rb.velocity;
#endif
AngularVelocity = rb.angularVelocity;
Simulated = rb.simulated;
#if UNITY_6000_1_OR_NEWER
IsKinematic = rb.bodyType == RigidbodyType2D.Kinematic;
#else
IsKinematic = rb.isKinematic;
#endif
}
}
[Preserve]
public static class RigidbodyStateSerializers
{
public static void WriteRigidbodyState(this Writer writer, RigidbodyState value)
{
writer.WriteVector3(value.Position);
writer.WriteAutoPackType(value.RotationPacking);
writer.WriteQuaternion(value.Rotation, value.RotationPacking);
writer.WriteBoolean(value.IsKinematic);
if (!value.IsKinematic)
{
writer.WriteVector3(value.Velocity);
writer.WriteVector3(value.AngularVelocity);
}
}
public static RigidbodyState ReadRigidbodyState(this Reader reader)
{
Vector3 position = reader.ReadVector3();
AutoPackType rotationPacking = reader.ReadAutoPackType();
Quaternion rotation = reader.ReadQuaternion(rotationPacking);
bool isKinematic = reader.ReadBoolean();
RigidbodyState state = new()
{
Position = position,
Rotation = rotation,
IsKinematic = isKinematic,
};
if (!state.IsKinematic)
{
state.Velocity = reader.ReadVector3();
state.AngularVelocity = reader.ReadVector3();
}
return state;
}
public static void WriteRigidbody2DState(this Writer writer, Rigidbody2DState value)
{
writer.WriteVector3(value.Position);
writer.WriteAutoPackType(value.RotationPacking);
writer.WriteQuaternion(value.Rotation, value.RotationPacking);
writer.WriteBoolean(value.Simulated);
writer.WriteBoolean(value.IsKinematic);
if (value.Simulated)
{
writer.WriteVector2(value.Velocity);
writer.WriteSingle(value.AngularVelocity);
}
}
public static Rigidbody2DState ReadRigidbody2DState(this Reader reader)
{
Vector3 position = reader.ReadVector3();
AutoPackType rotationPacking = reader.ReadAutoPackType();
Quaternion rotation = reader.ReadQuaternion(rotationPacking);
bool simulated = reader.ReadBoolean();
bool isKinematic = reader.ReadBoolean();
Rigidbody2DState state = new()
{
Position = position,
Rotation = rotation,
Simulated = simulated,
IsKinematic = isKinematic,
};
if (state.Simulated)
{
state.Velocity = reader.ReadVector2();
state.AngularVelocity = reader.ReadSingle();
}
return state;
}
}
[Preserve]
public static class RigidbodyStateExtensions
{
/// <summary>
/// Gets a RigidbodyState.
/// </summary>
public static RigidbodyState GetState(this Rigidbody rb, AutoPackType rotationPacking = AutoPackType.Packed)
{
return new(rb, rotationPacking);
}
/// <summary>
/// Sets a state to a rigidbody.
/// </summary>
public static void SetState(this Rigidbody rb, RigidbodyState state)
{
Transform t = rb.transform;
t.position = state.Position;
t.rotation = state.Rotation;
rb.isKinematic = state.IsKinematic;
if (!state.IsKinematic)
{
#if UNITY_6000_1_OR_NEWER
rb.linearVelocity = state.Velocity;
#else
rb.velocity = state.Velocity;
#endif
rb.angularVelocity = state.AngularVelocity;
}
}
/// <summary>
/// Gets a Rigidbody2DState.
/// </summary>
public static Rigidbody2DState GetState(this Rigidbody2D rb, AutoPackType rotationPacking = AutoPackType.Packed)
{
return new(rb, rotationPacking);
}
/// <summary>
/// Sets a state to a rigidbody.
/// </summary>
public static void SetState(this Rigidbody2D rb, Rigidbody2DState state)
{
Transform t = rb.transform;
t.position = state.Position;
t.rotation = state.Rotation;
#if UNITY_6000_1_OR_NEWER
rb.bodyType = state.IsKinematic ? RigidbodyType2D.Kinematic : RigidbodyType2D.Dynamic;
#else
rb.isKinematic = state.IsKinematic;
#endif
if (!state.IsKinematic)
{
#if UNITY_6000_1_OR_NEWER
rb.linearVelocity = state.Velocity;
#else
rb.velocity = state.Velocity;
#endif
rb.angularVelocity = state.AngularVelocity;
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 05dbb585c2bc6bf4dbbc592bea73d2fe
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/Prediction/RigidbodyState.cs
uploadId: 866910
@@ -0,0 +1,11 @@
namespace FishNet.Component.Prediction
{
/// <summary>
/// Type of prediction movement being used.
/// </summary>
public enum RigidbodyType : byte
{
Rigidbody = 0,
Rigidbody2D = 1
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7cf2d3fc2ff7a9042b4b7618db15b482
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/Prediction/RigidbodyType.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ae5df816b1e348a4681e184662e59bb5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,160 @@
using FishNet.Connection;
using FishNet.Managing;
using FishNet.Object;
using System;
using UnityEngine;
using UnityEngine.Serialization;
namespace FishNet.Component.Spawning
{
/// <summary>
/// Spawns a player object for clients when they connect.
/// </summary>
[AddComponentMenu("FishNet/Component/PlayerSpawner")]
public class PlayerSpawner : MonoBehaviour
{
#region Public.
/// <summary>
/// Called on the server when a player is spawned.
/// </summary>
public event Action<NetworkObject> OnSpawned;
#endregion
#region Serialized.
/// <summary>
/// Prefab to spawn for the player.
/// </summary>
[Tooltip("Prefab to spawn for the player.")]
[SerializeField]
private NetworkObject _playerPrefab;
/// <summary>
/// Sets the PlayerPrefab to use.
/// </summary>
/// <param name = "nob"></param>
public void SetPlayerPrefab(NetworkObject nob) => _playerPrefab = nob;
/// <summary>
/// True to add player to the active scene when no global scenes are specified through the SceneManager.
/// </summary>
[Tooltip("True to add player to the active scene when no global scenes are specified through the SceneManager.")]
[SerializeField]
private bool _addToDefaultScene = true;
/// <summary>
/// Areas in which players may spawn.
/// </summary>
[Tooltip("Areas in which players may spawn.")]
public Transform[] Spawns = new Transform[0];
#endregion
#region Private.
/// <summary>
/// First instance of the NetworkManager found. This will be either the NetworkManager on or above this object, or InstanceFinder.NetworkManager.
/// </summary>
private NetworkManager _networkManager;
/// <summary>
/// Next spawns to use.
/// </summary>
private int _nextSpawn;
#endregion
private void Awake()
{
InitializeOnce();
}
private void OnDestroy()
{
if (_networkManager != null)
_networkManager.SceneManager.OnClientLoadedStartScenes -= SceneManager_OnClientLoadedStartScenes;
}
/// <summary>
/// Initializes this script for use.
/// </summary>
private void InitializeOnce()
{
_networkManager = GetComponentInParent<NetworkManager>();
if (_networkManager == null)
_networkManager = InstanceFinder.NetworkManager;
if (_networkManager == null)
{
_networkManager.LogWarning($"PlayerSpawner on {gameObject.name} cannot work as NetworkManager wasn't found on this object or within parent objects.");
return;
}
_networkManager.SceneManager.OnClientLoadedStartScenes += SceneManager_OnClientLoadedStartScenes;
}
/// <summary>
/// Called when a client loads initial scenes after connecting.
/// </summary>
private void SceneManager_OnClientLoadedStartScenes(NetworkConnection conn, bool asServer)
{
if (!asServer)
return;
if (_playerPrefab == null)
{
_networkManager.LogWarning($"Player prefab is empty and cannot be spawned for connection {conn.ClientId}.");
return;
}
Vector3 position;
Quaternion rotation;
SetSpawn(_playerPrefab.transform, out position, out rotation);
NetworkObject nob = _networkManager.GetPooledInstantiated(_playerPrefab, position, rotation, true);
_networkManager.ServerManager.Spawn(nob, conn);
// If there are no global scenes
if (_addToDefaultScene)
_networkManager.SceneManager.AddOwnerToDefaultScene(nob);
OnSpawned?.Invoke(nob);
}
/// <summary>
/// Sets a spawn position and rotation.
/// </summary>
/// <param name = "pos"></param>
/// <param name = "rot"></param>
private void SetSpawn(Transform prefab, out Vector3 pos, out Quaternion rot)
{
// No spawns specified.
if (Spawns.Length == 0)
{
SetSpawnUsingPrefab(prefab, out pos, out rot);
return;
}
Transform result = Spawns[_nextSpawn];
if (result == null)
{
SetSpawnUsingPrefab(prefab, out pos, out rot);
}
else
{
pos = result.position;
rot = result.rotation;
}
// Increase next spawn and reset if needed.
_nextSpawn++;
if (_nextSpawn >= Spawns.Length)
_nextSpawn = 0;
}
/// <summary>
/// Sets spawn using values from prefab.
/// </summary>
/// <param name = "prefab"></param>
/// <param name = "pos"></param>
/// <param name = "rot"></param>
private void SetSpawnUsingPrefab(Transform prefab, out Vector3 pos, out Quaternion rot)
{
pos = prefab.position;
rot = prefab.rotation;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 211a9f6ec51ddc14f908f5acc0cd0423
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/Spawning/PlayerSpawner.cs
uploadId: 866910
@@ -0,0 +1,101 @@
using FishNet.Managing;
using FishNet.Object;
using System.Collections.Generic;
using FishNet.Managing.Server;
using FishNet.Transporting;
using UnityEngine;
namespace FishNet.Component.Spawning
{
/// <summary>
/// Spawns network objects when the server starts.
/// </summary>
[AddComponentMenu("FishNet/Component/ServerSpawner")]
public class ServerSpawner : MonoBehaviour
{
#region Serialized
[Tooltip("True to spawn the objects as soon as the server starts. False if you wish to call Spawn manually.")]
[SerializeField]
private bool _automaticallySpawn = true;
/// <summary>
/// NetworkObjects to spawn when the server starts.
/// </summary>
[Tooltip("NetworkObjects to spawn when the server starts.")]
[SerializeField]
private List<NetworkObject> _networkObjects = new();
#endregion
#region Private.
/// <summary>
/// First instance of the ServerManager found. This will be either the ServerManager on or above this object, or InstanceFinder.ServerManager.
/// </summary>
private ServerManager _serverManager;
#endregion
private void Awake()
{
InitializeOnce();
}
private void OnDestroy()
{
if (_serverManager == null)
return;
// Unsubscribe even if not automatically spawning; this is to protect against the user unchecking during play mode.
_serverManager.OnServerConnectionState -= ServerManager_OnServerConnectionState;
}
/// <summary>
/// Initializes this script for use.
/// </summary>
private void InitializeOnce()
{
_serverManager = GetComponentInParent<ServerManager>();
if (_serverManager == null)
_serverManager = InstanceFinder.ServerManager;
if (_serverManager == null)
{
NetworkManagerExtensions.LogWarning($"{nameof(ServerSpawner)} on {gameObject.name} cannot work as NetworkManager wasn't found on this object or within parent objects.");
return;
}
// Only subscribe if to automatically spawn.
if (_automaticallySpawn)
_serverManager.OnServerConnectionState += ServerManager_OnServerConnectionState;
}
private void ServerManager_OnServerConnectionState(ServerConnectionStateArgs args)
{
// If not started then exit.
if (args.ConnectionState != LocalConnectionState.Started)
return;
// If more than 1 server is started then exit. This means the user is using multipass and another server already started.
if (!_serverManager.IsOnlyOneServerStarted())
return;
Spawn_Internally();
}
private void Spawn_Internally()
{
if (_serverManager == null)
return;
// Spawn the objects now.
foreach (NetworkObject networkObject in _networkObjects)
{
NetworkObject nob = _serverManager.NetworkManager.GetPooledInstantiated(networkObject, asServer: true);
_serverManager.Spawn(nob);
}
}
/// <summary>
/// Spawns all provided NetworkObjects.
/// </summary>
/// <remarks>This will spawn the objects again even if they were already spawned automatically or manually before.</remarks>
public void Spawn() => Spawn_Internally();
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 859294e5d2aaf9a47b1e00bd20772f2b
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/Spawning/ServerSpawner.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3bb69ba1a673375489a94fa875c3aa98
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using FishNet.CodeGenerating;
using FishNet.Connection;
using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Managing.Server;
using FishNet.Object;
using FishNet.Object.Synchronizing;
using GameKit.Dependencies.Utilities;
using UnityEngine;
namespace FishNet.Component.Ownership
{
/// <summary>
/// Adding this component allows any client to take ownership of the object and begin modifying it immediately.
/// </summary>
public class PredictedOwner : NetworkBehaviour
{
#region Public.
/// <summary>
/// True if the local client used TakeOwnership and is awaiting an ownership change.
/// </summary>
public bool TakingOwnership { get; private set; }
/// <summary>
/// Owner on client prior to taking ownership. This can be used to reverse a failed ownership attempt.
/// </summary>
public NetworkConnection PreviousOwner { get; private set; } = NetworkManager.EmptyConnection;
#endregion
#region Serialized.
/// <summary>
/// True if to enable this component.
/// </summary>
[Tooltip("True if to enable this component.")]
[SerializeField]
private bool _allowTakeOwnership = true;
private readonly SyncVar<bool> _allowTakeOwnershipSyncVar = new();
/// <summary>
/// Sets the next value for AllowTakeOwnership and synchronizes it.
/// Only the server may use this method.
/// </summary>
/// <param name = "value">Next value to use.</param>
[Server]
public void SetAllowTakeOwnership(bool value) => _allowTakeOwnershipSyncVar.Value = value;
#endregion
protected virtual void Awake()
{
_allowTakeOwnershipSyncVar.Value = _allowTakeOwnership;
_allowTakeOwnershipSyncVar.UpdateSendRate(0f);
_allowTakeOwnershipSyncVar.OnChange += _allowTakeOwnershipSyncVar_OnChange;
}
/// <summary>
/// Called when SyncVar value changes for AllowTakeOwnership.
/// </summary>
private void _allowTakeOwnershipSyncVar_OnChange(bool prev, bool next, bool asServer)
{
if (asServer || !IsHostStarted)
_allowTakeOwnership = next;
}
/// <summary>
/// Called on the client after gaining or losing ownership.
/// </summary>
/// <param name = "prevOwner">Previous owner of this object.</param>
public override void OnOwnershipClient(NetworkConnection prevOwner)
{
/* Unset taken ownership either way.
* If the new owner it won't be used,
* if no longer owner then another client
* took it. */
TakingOwnership = false;
PreviousOwner = Owner;
}
[Client]
[Obsolete("Use TakeOwnership(bool).")]
public virtual void TakeOwnership() => TakeOwnership(includeNested: true);
/// <summary>
/// Gives ownership of this to the local client and allows immediate control.
/// </summary>
/// <param name = "includeNested">True to also take ownership of nested objects.</param>
public virtual void TakeOwnership(bool includeNested)
{
if (!_allowTakeOwnershipSyncVar.Value)
return;
// Already owner.
if (IsOwner)
return;
NetworkConnection c = ClientManager.Connection;
TakingOwnership = true;
//If not server go through the server.
if (!IsServerStarted)
{
NetworkObject.SetLocalOwnership(c, includeNested);
ServerTakeOwnership(includeNested);
}
//Otherwise take directly without rpcs.
else
{
OnTakeOwnership(c, includeNested);
}
}
/// <summary>
/// Takes ownership of this object.
/// </summary>
[ServerRpc(RequireOwnership = false)]
private void ServerTakeOwnership(bool includeNested, NetworkConnection caller = null)
{
OnTakeOwnership(caller, includeNested);
}
[Server]
[Obsolete("Use OnTakeOwnership(bool).")]
protected virtual void OnTakeOwnership(NetworkConnection caller) => OnTakeOwnership(caller, recursive: false);
/// <summary>
/// Called on the server when a client tries to take ownership of this object.
/// </summary>
/// <param name = "caller">Connection trying to take ownership.</param>
[Server]
protected virtual void OnTakeOwnership(NetworkConnection caller, bool recursive)
{
//Client somehow disconnected between here and there.
if (!caller.IsActive)
return;
//Feature is not enabled.
if (!_allowTakeOwnershipSyncVar.Value)
return;
//Already owner.
if (caller == Owner)
return;
GiveOwnership(caller);
if (recursive)
{
List<NetworkObject> allNested = NetworkObject.GetNetworkObjects(GetNetworkObjectOption.AllNestedRecursive);
foreach (NetworkObject nob in allNested)
{
PredictedOwner po = nob.PredictedOwner;
if (po != null)
po.OnTakeOwnership(caller, recursive: true);
}
CollectionCaches<NetworkObject>.Store(allNested);
}
/* No need to send a response back because an ownershipchange will handle changes.
* Although if you were to override with this your own behavior
* you could send responses for approved/denied. */
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 03002f7d324007e41b10a9dc87ed3c38
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/TakeOwnership/PredictedOwner.cs
uploadId: 866910
@@ -0,0 +1,84 @@
using FishNet.Connection;
using FishNet.Object;
using UnityEngine;
namespace FishNet.Component.Ownership
{
/// <summary>
/// Adding this component allows any client to use predictive spawning on this prefab.
/// </summary>
public class PredictedSpawn : NetworkBehaviour
{
#region Serialized.
/// <summary>
/// True to allow clients to predicted spawn this object.
/// </summary>
public bool GetAllowSpawning() => _allowSpawning;
/// <summary>
/// Sets to allow predicted spawning. This must be set on client and server.
/// </summary>
/// <param name = "value">New value.</param>
public void SetAllowSpawning(bool value) => _allowSpawning = value;
[Tooltip("True to allow clients to predicted spawn this object.")]
[SerializeField]
private bool _allowSpawning = true;
/// <summary>
/// True to allow clients to predicted despawn this object.
/// </summary>
public bool GetAllowDespawning() => _allowDespawning;
/// <summary>
/// Sets to allow predicted despawning. This must be set on client and server.
/// </summary>
/// <param name = "value">New value.</param>
public void SetAllowDespawning(bool value) => _allowDespawning = value;
[Tooltip("True to allow clients to predicted despawn this object.")]
[SerializeField]
private bool _allowDespawning = true;
#endregion
/// <summary>
/// Called on the client when trying to predicted spawn this object.
/// </summary>
/// <param name = "owner">Owner specified to spawn with.</param>
/// <returns>True if able to spawn.</returns>
public virtual bool OnTrySpawnClient(NetworkConnection owner = null)
{
return GetAllowSpawning();
}
/// <summary>
/// Called on the server when a client tries to predicted spawn this object.
/// </summary>
/// <param name = "spawner">Connection trying to predicted spawn this object.</param>
/// <param name = "owner">Owner specified to spawn with.</param>
/// <returns>True if able to spawn.</returns>
public virtual bool OnTrySpawnServer(NetworkConnection spawner, NetworkConnection owner = null)
{
return GetAllowSpawning();
}
/// <summary>
/// Called on the client when trying to predicted spawn this object.
/// </summary>
/// <returns>True if able to despawn.</returns>
public virtual bool OnTryDespawnClient()
{
return GetAllowDespawning();
}
/// <summary>
/// Called on the server when a client tries to predicted despawn this object.
/// </summary>
/// <param name = "despawner">Connection trying to predicted despawn this object.</param>
/// <returns>True if able to despawn.</returns>
public virtual bool OnTryDespawnServer(NetworkConnection despawner)
{
return GetAllowDespawning();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e2b597e1828355a4d994a69cbb11ef85
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/TakeOwnership/PredictedSpawn.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dd9e54c58fb9c984ba69d3525a8f8d53
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,34 @@
namespace FishNet.Component.Transforming
{
public enum AdaptiveInterpolationType
{
/// <summary>
/// Adaptive interpolation is disabled. An exact interpolation value is used.
/// </summary>
Off = 0,
/// <summary>
/// Visual disturbances caused by desynchronization are definite without predicting future states.
/// </summary>
ExtremelyLow = 1,
/// <summary>
/// Visual disturbances caused by desynchronization are likely without predicting future states.
/// </summary>
VeryLow = 2,
/// <summary>
/// Visual disturbances caused by desynchronization are still possible but less likely.
/// </summary>
Low = 3,
/// <summary>
/// Visual disturbances caused by desynchronization are likely without predicting a small amount of future states.
/// </summary>
Moderate = 4,
/// <summary>
/// Visual disturbances caused by desynchronization are very unlikely. Graphics are using a generous amount interpolation.
/// </summary>
High = 5,
/// <summary>
/// Visual disturbances caused by desynchronization are extremely unlikely. Graphics are using a generous amount interpolation.
/// </summary>
VeryHigh = 6
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 612b76499aa863b4b8a1b773a466cc7d
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/TickSmoothing/AdaptiveInterpolationType.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 43bf94bbbaa20b6489f07cd1abe42972
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,51 @@
#if UNITY_EDITOR && THREADED_TICKSMOOTHERS
using FishNet.Object;
using GameKit.Dependencies.Utilities;
using UnityEditor;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta.Editing
{
[CustomPropertyDrawer(typeof(MovementSettings))]
public class MovementSettingsDrawer : PropertyDrawer
{
private PropertyDrawerTool _propertyDrawer;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
_propertyDrawer = new(position);
// _propertyDrawer.DrawLabel(label, FontStyle.Bold);
EditorGUI.indentLevel++;
SerializedProperty enableTeleport = property.FindPropertyRelative("EnableTeleport");
SerializedProperty teleportThreshold = property.FindPropertyRelative("TeleportThreshold");
SerializedProperty adaptiveInterpolationValue = property.FindPropertyRelative("AdaptiveInterpolationValue");
SerializedProperty interpolationValue = property.FindPropertyRelative("InterpolationValue");
SerializedProperty smoothedProperties = property.FindPropertyRelative("SmoothedProperties");
SerializedProperty snapNonSmoothedProperties = property.FindPropertyRelative("SnapNonSmoothedProperties");
_propertyDrawer.DrawProperty(enableTeleport, "Enable Teleport");
if (enableTeleport.boolValue == true)
_propertyDrawer.DrawProperty(teleportThreshold, "Teleport Threshold", indent: 1);
_propertyDrawer.DrawProperty(adaptiveInterpolationValue, "Adaptive Interpolation");
if ((AdaptiveInterpolationType)adaptiveInterpolationValue.intValue == AdaptiveInterpolationType.Off)
_propertyDrawer.DrawProperty(interpolationValue, "Interpolation Value", indent: 1);
_propertyDrawer.DrawProperty(smoothedProperties, "Smoothed Properties");
if ((uint)smoothedProperties.intValue != (uint)TransformPropertiesFlag.Everything)
_propertyDrawer.DrawProperty(snapNonSmoothedProperties, "Snap Non-Smoothed Properties", indent: 1);
_propertyDrawer.SetIndentToStarting();
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) => _propertyDrawer.GetPropertyHeight();
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 4cb0116961f72144f83da97d61e3aa2d
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/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs
uploadId: 866910
@@ -0,0 +1,51 @@
#if UNITY_EDITOR && !THREADED_TICKSMOOTHERS
using FishNet.Object;
using GameKit.Dependencies.Utilities;
using UnityEditor;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta.Editing
{
[CustomPropertyDrawer(typeof(MovementSettings))]
public class MovementSettingsDrawer : PropertyDrawer
{
private PropertyDrawerTool _propertyDrawer;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
_propertyDrawer = new(position);
// _propertyDrawer.DrawLabel(label, FontStyle.Bold);
EditorGUI.indentLevel++;
SerializedProperty enableTeleport = property.FindPropertyRelative("EnableTeleport");
SerializedProperty teleportThreshold = property.FindPropertyRelative("TeleportThreshold");
SerializedProperty adaptiveInterpolationValue = property.FindPropertyRelative("AdaptiveInterpolationValue");
SerializedProperty interpolationValue = property.FindPropertyRelative("InterpolationValue");
SerializedProperty smoothedProperties = property.FindPropertyRelative("SmoothedProperties");
SerializedProperty snapNonSmoothedProperties = property.FindPropertyRelative("SnapNonSmoothedProperties");
_propertyDrawer.DrawProperty(enableTeleport, "Enable Teleport");
if (enableTeleport.boolValue == true)
_propertyDrawer.DrawProperty(teleportThreshold, "Teleport Threshold", indent: 1);
_propertyDrawer.DrawProperty(adaptiveInterpolationValue, "Adaptive Interpolation");
if ((AdaptiveInterpolationType)adaptiveInterpolationValue.intValue == AdaptiveInterpolationType.Off)
_propertyDrawer.DrawProperty(interpolationValue, "Interpolation Value", indent: 1);
_propertyDrawer.DrawProperty(smoothedProperties, "Smoothed Properties");
if ((uint)smoothedProperties.intValue != (uint)TransformPropertiesFlag.Everything)
_propertyDrawer.DrawProperty(snapNonSmoothedProperties, "Snap Non-Smoothed Properties", indent: 1);
_propertyDrawer.SetIndentToStarting();
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) => _propertyDrawer.GetPropertyHeight();
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: aa57faea7c2b2ce4b95e4865a64f3551
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/TickSmoothing/Editor/MovementSettingsDrawer.cs
uploadId: 866910
@@ -0,0 +1,54 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta
{
[CustomEditor(typeof(NetworkTickSmoother), true)]
[CanEditMultipleObjects]
public class NetworkTickSmootherEditor : Editor
{
private SerializedProperty _initializationSettings;
private SerializedProperty _controllerMovementSettings;
private SerializedProperty _favorPredictionNetworkTransform;
private SerializedProperty _spectatorMovementSettings;
private bool _showControllerSmoothingSettings;
private bool _showSpectatorSmoothingSettings;
protected virtual void OnEnable()
{
_initializationSettings = serializedObject.FindProperty(nameof(_initializationSettings));
_favorPredictionNetworkTransform = serializedObject.FindProperty(nameof(_favorPredictionNetworkTransform));
_controllerMovementSettings = serializedObject.FindProperty(nameof(_controllerMovementSettings));
_spectatorMovementSettings = serializedObject.FindProperty(nameof(_spectatorMovementSettings));
}
public override void OnInspectorGUI()
{
serializedObject.Update();
GUI.enabled = false;
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((NetworkTickSmoother)target), typeof(NetworkTickSmoother), false);
GUI.enabled = true;
EditorGUILayout.LabelField("Initialization Settings", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_initializationSettings);
_showControllerSmoothingSettings = EditorGUILayout.Foldout(_showControllerSmoothingSettings, new GUIContent("Controller", "Smoothing applied when object controller. This would be the owner, or if there is no owner and are also server."));
if (_showControllerSmoothingSettings)
EditorGUILayout.PropertyField(_controllerMovementSettings);
_showSpectatorSmoothingSettings = EditorGUILayout.Foldout(_showSpectatorSmoothingSettings, new GUIContent("Spectator Smoothing", "Smoothing applied when object not the owner. This is when server and there is an owner, or when client and not the owner."));
if (_showSpectatorSmoothingSettings)
{
EditorGUILayout.PropertyField(_spectatorMovementSettings);
}
// EditorGUI.indentLevel--;
serializedObject.ApplyModifiedProperties();
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: f51697e093480ab429a4a2f47f545f92
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/TickSmoothing/Editor/NetworkTickSmootherEditor.cs
uploadId: 866910
@@ -0,0 +1,46 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta
{
[CustomEditor(typeof(OfflineTickSmoother), true)]
[CanEditMultipleObjects]
public class OfflineTickSmootherEditor : Editor
{
private SerializedProperty _automaticallyInitialize;
private SerializedProperty _initializationSettings;
private SerializedProperty _movementSettings;
private bool _showMovementSettings;
protected virtual void OnEnable()
{
_automaticallyInitialize = serializedObject.FindProperty(nameof(_automaticallyInitialize));
_initializationSettings = serializedObject.FindProperty(nameof(_initializationSettings));
_movementSettings = serializedObject.FindProperty(nameof(_movementSettings));
}
public override void OnInspectorGUI()
{
serializedObject.Update();
GUI.enabled = false;
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((OfflineTickSmoother)target), typeof(OfflineTickSmoother), false);
GUI.enabled = true;
// EditorGUILayout.LabelField("Initialization Settings", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_automaticallyInitialize);
EditorGUILayout.PropertyField(_initializationSettings);
_showMovementSettings = EditorGUILayout.Foldout(_showMovementSettings, "Smoothing");
if (_showMovementSettings)
EditorGUILayout.PropertyField(_movementSettings);
// EditorGUI.indentLevel--;
serializedObject.ApplyModifiedProperties();
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 80812403004e23f44ae42bceba1b6733
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/TickSmoothing/Editor/OfflineTickSmootherEditor.cs
uploadId: 866910
@@ -0,0 +1,75 @@
using FishNet.Managing.Timing;
using FishNet.Object;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta
{
[System.Serializable]
public struct InitializationSettings
{
/// <summary>
/// While this script is typically placed on a nested graphical object, the targetTransform would be the object which moves every tick; the TargetTransform can be the same object this script resides but may not be a rigidbody if true;
/// </summary>
[Tooltip("While this script is typically placed on a nested graphical object, the targetTransform would be the object which moves every tick; the TargetTransform can be the same object this script resides but may not be a rigidbody if true;")]
[SerializeField]
public Transform TargetTransform;
/// <summary>
/// The transform which is smoothed.
/// </summary>
[Tooltip("The transform which is smoothed.")]
[System.NonSerialized]
internal Transform GraphicalTransform;
/// <summary>
/// True to detacth this object from its parent on client start.
/// </summary>
[Tooltip("True to detach this object from it's parent on client start.")]
public bool DetachOnStart;
/// <summary>
/// True to re-attach this object to it's parent on client stop.
/// </summary>
[Tooltip("True to re-attach this object to it's parent on client stop.")]
public bool AttachOnStop;
/// <summary>
/// True to begin moving soon as movement data becomes available. Movement will ease in until at interpolation value. False to prevent movement until movement data count meet interpolation.
/// </summary>
/// <remarks>This is not yet used.</remarks>
[Tooltip("True to begin moving soon as movement data becomes available. Movement will ease in until at interpolation value. False to prevent movement until movement data count meet interpolation.")]
public bool MoveImmediately => false;
/// <summary>
/// NetworkBehaviour which initialized these settings. This value may be null if not initialized from a NetworkBehaviour.
/// </summary>
[System.NonSerialized]
internal NetworkBehaviour InitializingNetworkBehaviour;
/// <summary>
/// TimeManager initializing these settings.
/// </summary>
[System.NonSerialized]
internal TimeManager InitializingTimeManager;
/// <summary>
/// True to disable smoothing when the NetworkObject enables prediction, specifies a NetworkTransform to use, and that NetworkTransform is currently smoothing.
/// </summary>
[System.NonSerialized]
internal bool FavorPredictionNetworkTransform;
public void SetNetworkedRuntimeValues(NetworkBehaviour initializingNetworkBehaviour, Transform graphicalTransform, bool favorPredictionNetworkTransform)
{
InitializingNetworkBehaviour = initializingNetworkBehaviour;
InitializingTimeManager = initializingNetworkBehaviour.TimeManager;
GraphicalTransform = graphicalTransform;
FavorPredictionNetworkTransform = favorPredictionNetworkTransform;
}
/// <summary>
/// Sets values used at runtime. NetworkBehaviour is nullified when calling this method.
/// </summary>
public void SetOfflineRuntimeValues(TimeManager timeManager, Transform graphicalTransform)
{
GraphicalTransform = graphicalTransform;
InitializingTimeManager = timeManager;
InitializingNetworkBehaviour = null;
FavorPredictionNetworkTransform = false;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 0c6e8313aa3aa964585e4ec54b733627
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/TickSmoothing/InitializationSettings.cs
uploadId: 866910
@@ -0,0 +1,60 @@
#if FISHNET_THREADED_TICKSMOOTHERS
using FishNet.Object;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta
{
[System.Serializable]
public struct MovementSettings
{
/// <summary>
/// True to enable teleport threshold.
/// </summary>
[Tooltip("True to enable teleport threshold.")]
public 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)]
public float TeleportThreshold;
/// <summary>
/// Amount of adaptive interpolation to use. Adaptive interpolation increases interpolation with the local client's latency. Lower values of adaptive interpolation results in smaller interpolation increases.
/// In most cases adaptive interpolation is only used with prediction where objects might be affected by other moving objects.
/// </summary>
[Tooltip("Amount of adaptive interpolation to use. Adaptive interpolation increases interpolation with the local client's latency. Lower values of adaptive interpolation results in smaller interpolation increases. In most cases adaptive interpolation is only used with prediction where objects might be affected by other moving objects.")]
public AdaptiveInterpolationType AdaptiveInterpolationValue;
/// <summary>
/// Number of ticks to smooth over when not using adaptive interpolation.
/// </summary>
[Tooltip("Number of ticks to smooth over when not using adaptive interpolation.")]
public byte InterpolationValue;
/// <summary>
/// Properties to smooth. Any value not selected will become offset with every movement.
/// </summary>
[Tooltip("Properties to smooth. Any value not selected will become offset with every movement.")]
public TransformPropertiesFlag SmoothedProperties;
/// <summary>
/// True to apply smoothing in local space for position and rotation. False to use world space.
/// </summary>
[Tooltip("True to apply smoothing in local space for position and rotation. False to use world space.")]
public bool UseLocalSpace;
/// <summary>
/// True to keep non-smoothed properties at their original localspace every tick. A false value will keep the properties in the same world space as they were before each tick.
/// </summary>
[Tooltip("True to keep non-smoothed properties at their original localspace every tick. A false value will keep the properties in the same world space as they were before each tick.")]
public bool SnapNonSmoothedProperties;
public MovementSettings(bool unityReallyNeedsToSupportParameterlessInitializersOnStructsAlready)
{
EnableTeleport = false;
TeleportThreshold = 0f;
AdaptiveInterpolationValue = AdaptiveInterpolationType.Off;
InterpolationValue = 2;
SmoothedProperties = TransformPropertiesFlag.Everything;
UseLocalSpace = false;
SnapNonSmoothedProperties = false;
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7186c545fe7ae6148bec3847de098c65
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/TickSmoothing/MovementSettings.Threaded.cs
uploadId: 866910
@@ -0,0 +1,54 @@
#if !FISHNET_THREADED_TICKSMOOTHERS
using FishNet.Object;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta
{
[System.Serializable]
public struct MovementSettings
{
/// <summary>
/// True to enable teleport threshold.
/// </summary>
[Tooltip("True to enable teleport threshold.")]
public 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)]
public float TeleportThreshold;
/// <summary>
/// Amount of adaptive interpolation to use. Adaptive interpolation increases interpolation with the local client's latency. Lower values of adaptive interpolation results in smaller interpolation increases.
/// In most cases adaptive interpolation is only used with prediction where objects might be affected by other moving objects.
/// </summary>
[Tooltip("Amount of adaptive interpolation to use. Adaptive interpolation increases interpolation with the local client's latency. Lower values of adaptive interpolation results in smaller interpolation increases. In most cases adaptive interpolation is only used with prediction where objects might be affected by other moving objects.")]
public AdaptiveInterpolationType AdaptiveInterpolationValue;
/// <summary>
/// Number of ticks to smooth over when not using adaptive interpolation.
/// </summary>
[Tooltip("Number of ticks to smooth over when not using adaptive interpolation.")]
public byte InterpolationValue;
/// <summary>
/// Properties to smooth. Any value not selected will become offset with every movement.
/// </summary>
[Tooltip("Properties to smooth. Any value not selected will become offset with every movement.")]
public TransformPropertiesFlag SmoothedProperties;
/// <summary>
/// True to keep non-smoothed properties at their original localspace every tick. A false value will keep the properties in the same world space as they were before each tick.
/// </summary>
[Tooltip("True to keep non-smoothed properties at their original localspace every tick. A false value will keep the properties in the same world space as they were before each tick.")]
public bool SnapNonSmoothedProperties;
public MovementSettings(bool unityReallyNeedsToSupportParameterlessInitializersOnStructsAlready)
{
EnableTeleport = false;
TeleportThreshold = 0f;
AdaptiveInterpolationValue = AdaptiveInterpolationType.Off;
InterpolationValue = 2;
SmoothedProperties = TransformPropertiesFlag.Everything;
SnapNonSmoothedProperties = false;
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c01e6529f1dd28f4fa6d97ba1a480e22
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/TickSmoothing/MovementSettings.cs
uploadId: 866910
@@ -0,0 +1,91 @@
using FishNet.Object;
using GameKit.Dependencies.Utilities;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta
{
/// <summary>
/// Smoothes this object between ticks.
/// </summary>
/// <remarks>This can be configured to smooth over a set interval of time, or to smooth adaptively and make path corrections for prediction.</remarks>
public class NetworkTickSmoother : NetworkBehaviour
{
#region Public.
/// <summary>
/// Logic for owner smoothing.
/// </summary>
public TickSmootherController SmootherController { get; private set; }
#endregion
/// <summary>
/// Settings required to initialize the smoother.
/// </summary>
[Tooltip("Settings required to initialize the smoother.")]
[SerializeField]
private InitializationSettings _initializationSettings = new();
/// <summary>
/// How smoothing occurs when the controller of the object.
/// </summary>
[Tooltip("How smoothing occurs when the controller of the object.")]
[SerializeField]
private MovementSettings _controllerMovementSettings = new(true);
/// <summary>
/// True to disable smoothing when the NetworkObject enables prediction, specifies a NetworkTransform to use, and that NetworkTransform is currently smoothing.
/// </summary>
[Tooltip("True to disable smoothing when the NetworkObject enables prediction, specifies a NetworkTransform to use, and that NetworkTransform is currently smoothing.")]
[SerializeField]
private bool _favorPredictionNetworkTransform = true;
/// <summary>
/// How smoothing occurs when spectating the object.
/// </summary>
[Tooltip("How smoothing occurs when spectating the object.")]
[SerializeField]
private MovementSettings _spectatorMovementSettings = new(true);
private void OnDestroy()
{
if (SmootherController != null)
SmootherController.OnDestroy();
StoreControllers();
}
public override void OnStartClient()
{
RetrieveControllers();
_initializationSettings.SetNetworkedRuntimeValues(initializingNetworkBehaviour: this, graphicalTransform: transform, _favorPredictionNetworkTransform);
SmootherController.Initialize(_initializationSettings, _controllerMovementSettings, _spectatorMovementSettings);
SmootherController.StartSmoother();
}
public override void OnStopClient()
{
if (SmootherController == null)
return;
SmootherController.StopSmoother();
}
/// <summary>
/// Stores smoothers if they have value.
/// </summary>
private void StoreControllers()
{
if (SmootherController == null)
return;
ResettableObjectCaches<TickSmootherController>.Store(SmootherController);
SmootherController = null;
}
/// <summary>
/// Stores current smoothers and retrieves new ones.
/// </summary>
private void RetrieveControllers()
{
StoreControllers();
SmootherController = ResettableObjectCaches<TickSmootherController>.Retrieve();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 453481867b26f7c43b5bf38802a5f50e
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/TickSmoothing/NetworkTickSmoother.cs
uploadId: 866910
@@ -0,0 +1,143 @@
using FishNet.Managing;
using FishNet.Managing.Timing;
using GameKit.Dependencies.Utilities;
using UnityEngine;
using UnityEngine.Serialization;
namespace FishNet.Component.Transforming.Beta
{
/// <summary>
/// Smoothes this object between ticks.
/// </summary>
/// <remarks>This can be configured to smooth over a set interval of time, or to smooth adaptively and make path corrections for prediction.</remarks>
public class OfflineTickSmoother : MonoBehaviour
{
#region Public.
/// <summary>
/// Logic for owner smoothing.
/// </summary>
public TickSmootherController SmootherController { get; private set; }
/// <summary>
/// True if this component is initialized.
/// </summary>
/// <remarks>This API is for internal use and may change at any time.</remarks>
public bool IsInitialized { get; private set; }
#endregion
#region Serialized.
/// <summary>
/// True to automatically initialize in Awake using InstanceFinder. When false you will need to manually call Initialize.
/// </summary>
[Tooltip("True to automatically initialize in Awake using InstanceFinder. When false you will need to manually call Initialize.")]
[SerializeField]
private bool _automaticallyInitialize = true;
/// <summary>
/// Settings required to initialize the smoother.
/// </summary>
[Tooltip("Settings required to initialize the smoother.")]
[SerializeField]
private InitializationSettings _initializationSettings = new();
/// <summary>
/// How smoothing occurs when the controller of the object.
/// </summary>
[FormerlySerializedAs("_controllerMovementSettings")]
[Tooltip("How smoothing occurs when the controller of the object.")]
[SerializeField]
private MovementSettings _movementSettings = new(true);
#endregion
private void Awake()
{
RetrieveControllers();
AutomaticallyInitialize();
}
private void OnDestroy()
{
if (SmootherController != null)
{
SmootherController.StopSmoother();
SmootherController.OnDestroy();
}
StoreControllers();
IsInitialized = false;
}
/// <summary>
/// Automatically initializes if feature is enabled.
/// </summary>
private void AutomaticallyInitialize()
{
if (!_automaticallyInitialize)
return;
TimeManager tm = InstanceFinder.TimeManager;
if (tm == null)
{
NetworkManagerExtensions.LogWarning($"Automatic initialization failed on {gameObject.name}. You must manually call Initialize.");
return;
}
Initialize(tm);
}
/// <summary>
/// Initializes using a specified TimeManager.
/// </summary>
/// <param name = "timeManager"></param>
public void Initialize(TimeManager timeManager)
{
if (timeManager == null)
{
NetworkManagerExtensions.LogError($"TimeManager cannot be null when initializing.");
return;
}
SmootherController.SetTimeManager(timeManager);
_initializationSettings.SetOfflineRuntimeValues(timeManager, graphicalTransform: transform);
SmootherController.Initialize(_initializationSettings, _movementSettings, default);
SmootherController.StartSmoother();
IsInitialized = true;
}
/// <summary>
/// Sets a transform as the target to follow.
/// </summary>
/// <param name = "value">New value.</param>
public void SetTargetTransform(Transform value)
{
if (IsInitialized)
{
NetworkManagerExtensions.LogError($"Target can only be set before Initialize is called.");
return;
}
_initializationSettings.TargetTransform = value;
}
/// <summary>
/// Stores smoothers if they have value.
/// </summary>
private void StoreControllers()
{
if (SmootherController == null)
return;
ResettableObjectCaches<TickSmootherController>.Store(SmootherController);
SmootherController = null;
}
/// <summary>
/// Stores current smoothers and retrieves new ones.
/// </summary>
private void RetrieveControllers()
{
StoreControllers();
SmootherController = ResettableObjectCaches<TickSmootherController>.Retrieve();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 2361c564b0f77b94dbc549ff9caa72a3
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/TickSmoothing/OfflineTickSmoother.cs
uploadId: 866910
@@ -0,0 +1,332 @@
#if FISHNET_THREADED_TICKSMOOTHERS
using FishNet.Managing.Timing;
using FishNet.Object;
using GameKit.Dependencies.Utilities;
using UnityEngine;
using Unity.Profiling;
namespace FishNet.Component.Transforming.Beta
{
/// <summary>
/// Smoothes this object between ticks.
/// </summary>
/// <remarks>This can be configured to smooth over a set interval of time, or to smooth adaptively and make path corrections for prediction.</remarks>
public class TickSmootherController : IResettable
{
#region Public.
// /// <summary>
// /// Logic for owner smoothing.
// /// </summary>
// public UniversalTickSmoother UniversalSmoother { get; private set; }
#endregion
#region Private.
#region Private Profiler Markers
private static readonly ProfilerMarker _pm_OnUpdate = new("TickSmootherController.TimeManager_OnUpdate()");
private static readonly ProfilerMarker _pm_OnPreTick = new("TickSmootherController.TimeManager_OnPreTick()");
private static readonly ProfilerMarker _pm_OnPostTick = new("TickSmootherController.TimeManager_OnPostTick()");
#endregion
/// <summary>
/// </summary>
private InitializationSettings _initializationSettings = new();
/// <summary>
/// </summary>
private MovementSettings _ownerMovementSettings = new();
/// <summary>
/// </summary>
private MovementSettings _spectatorMovementSettings = new();
/// <summary>
/// True if OnDestroy has been called.
/// </summary>
private bool _destroyed;
/// <summary>
/// Cached timeManager reference.
/// </summary>
private TimeManager _timeManager;
/// <summary>
/// NetworkBehaviour which initialized this object. Value may be null when initialized for an Offline smoother.
/// </summary>
private NetworkBehaviour _initializingNetworkBehaviour;
/// <summary>
/// TickSmoothingManager.
/// </summary>
private TickSmoothingManager _tickSmoothingManager;
/// <summary>
/// Transform which initialized this object.
/// </summary>
private Transform _graphicalTransform;
/// <summary>
/// True if initialized with a null NetworkBehaviour.
/// </summary>
private bool _initializedOffline;
/// <summary>
/// True if subscribed to events used for adaptiveInterpolation.
/// </summary>
private bool _subscribedToAdaptiveEvents;
/// <summary>
/// True if currently subscribed to events.
/// </summary>
private bool _subscribed;
/// <summary>
/// True if initialized.
/// </summary>
private bool _isInitialized;
#endregion
public void Initialize(InitializationSettings initializationSettings, MovementSettings ownerSettings, MovementSettings spectatorSettings)
{
_tickSmoothingManager =
initializationSettings.InitializingNetworkBehaviour?.NetworkManager.TickSmoothingManager ??
InstanceFinder.NetworkManager.TickSmoothingManager;
_initializingNetworkBehaviour = initializationSettings.InitializingNetworkBehaviour;
_graphicalTransform = initializationSettings.GraphicalTransform;
_initializationSettings = initializationSettings;
_ownerMovementSettings = ownerSettings;
_spectatorMovementSettings = spectatorSettings;
_initializedOffline = initializationSettings.InitializingNetworkBehaviour == null;
_isInitialized = true;
}
public void OnDestroy()
{
TickSmoothingManager tsm = _tickSmoothingManager;
if (tsm != null)
tsm.Unregister(this);
// ChangeSubscriptions(false);
// StoreSmoother();
_destroyed = true;
_isInitialized = false;
}
public void StartSmoother()
{
if (!_isInitialized)
return;
bool canStart = _initializedOffline ? StartOffline() : StartOnline();
if (!canStart)
return;
// RetrieveSmoothers();
//
// UniversalSmoother.Initialize(_initializationSettings, _ownerMovementSettings, _spectatorMovementSettings);
//
// UniversalSmoother.StartSmoother();
TickSmoothingManager tsm = _tickSmoothingManager;
if (tsm != null)
tsm.Register(this, _initializationSettings, _ownerMovementSettings, _spectatorMovementSettings);
bool StartOnline()
{
NetworkBehaviour nb = _initializingNetworkBehaviour;
SetTimeManager(nb.TimeManager);
return true;
}
bool StartOffline()
{
if (_timeManager == null)
return false;
return true;
}
}
public void StopSmoother()
{
TickSmoothingManager tsm = _tickSmoothingManager;
if (tsm != null)
tsm.Unregister(this);
if (!_initializedOffline)
StopOnline();
// if (UniversalSmoother != null)
// UniversalSmoother.StopSmoother();
void StopOnline()
{
SetTimeManager(tm: null);
}
// Intentionally left blank.
// void StopOffline() { }
}
// public void TimeManager_OnUpdate()
// {
// using (_pm_OnUpdate.Auto())
// {
// UniversalSmoother.OnUpdate(Time.deltaTime);
// }
// }
//
// public void TimeManager_OnPreTick()
// {
// using (_pm_OnPreTick.Auto())
// {
// UniversalSmoother.OnPreTick();
// }
// }
//
// /// <summary>
// /// Called after a tick completes.
// /// </summary>
// public void TimeManager_OnPostTick()
// {
// using (_pm_OnPostTick.Auto())
// {
// if (_timeManager != null)
// UniversalSmoother.OnPostTick(_timeManager.LocalTick);
// }
// }
//
// private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick)
// {
// UniversalSmoother.OnPostReplicateReplay(clientTick);
// }
//
// private void TimeManager_OnRoundTripTimeUpdated(long rttMs)
// {
// UniversalSmoother.UpdateRealtimeInterpolation();
// }
//
// /// <summary>
// /// Stores smoothers if they have value.
// /// </summary>
// private void StoreSmoother()
// {
// if (UniversalSmoother == null)
// return;
//
// ResettableObjectCaches<UniversalTickSmoother>.Store(UniversalSmoother);
// UniversalSmoother = null;
// }
//
// /// <summary>
// /// Stores current smoothers and retrieves new ones.
// /// </summary>
// private void RetrieveSmoothers()
// {
// StoreSmoother();
// UniversalSmoother = ResettableObjectCaches<UniversalTickSmoother>.Retrieve();
// }
// /// <summary>
// /// Sets a target transform to follow.
// /// </summary>
// public void SetTargetTransform(Transform value)
// {
// Transform currentTargetTransform = _initializationSettings.TargetTransform;
//
// if (value == currentTargetTransform)
// return;
//
// bool clientStartCalled = (_initializedOffline && _timeManager != null) || (_initializingNetworkBehaviour != null && _initializingNetworkBehaviour.OnStartClientCalled);
//
// bool previousTargetTransformIsValid = (currentTargetTransform != null);
//
// // If target is different and old is not null then reset.
// if (previousTargetTransformIsValid && clientStartCalled)
// OnStopClient();
//
// _initializationSettings.TargetTransform = value;
// if (previousTargetTransformIsValid && clientStartCalled)
// OnStartClient();
// }
/// <summary>
/// Sets a new PredictionManager to use.
/// </summary>
public void SetTimeManager(TimeManager tm)
{
if (tm == _timeManager)
return;
// Unsub from current.
// ChangeSubscriptions(false);
//Sub to newest.
_timeManager = tm;
// ChangeSubscriptions(true);
}
// /// <summary>
// /// Changes the subscription to the TimeManager.
// /// </summary>
// private void ChangeSubscriptions(bool subscribe)
// {
// if (_destroyed)
// return;
// TimeManager tm = _timeManager;
// if (tm == null)
// return;
//
// if (subscribe == _subscribed)
// return;
// _subscribed = subscribe;
//
// bool adaptiveIsOff = _ownerMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off && _spectatorMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off;
//
// if (subscribe)
// {
// tm.OnUpdate += TimeManager_OnUpdate;
// tm.OnPreTick += TimeManager_OnPreTick;
// tm.OnPostTick += TimeManager_OnPostTick;
//
// if (!adaptiveIsOff)
// {
// tm.OnRoundTripTimeUpdated += TimeManager_OnRoundTripTimeUpdated;
// PredictionManager pm = tm.NetworkManager.PredictionManager;
// pm.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay;
// _subscribedToAdaptiveEvents = true;
// }
// }
// else
// {
// tm.OnUpdate -= TimeManager_OnUpdate;
// tm.OnPreTick -= TimeManager_OnPreTick;
// tm.OnPostTick -= TimeManager_OnPostTick;
//
// if (_subscribedToAdaptiveEvents)
// {
// tm.OnRoundTripTimeUpdated -= TimeManager_OnRoundTripTimeUpdated;
// PredictionManager pm = tm.NetworkManager.PredictionManager;
// pm.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay;
// }
// }
// }
public void ResetState()
{
_initializationSettings = default;
_ownerMovementSettings = default;
_spectatorMovementSettings = default;
_destroyed = false;
_timeManager = null;
_initializingNetworkBehaviour = null;
_graphicalTransform = null;
_subscribed = false;
_subscribedToAdaptiveEvents = false;
_isInitialized = false;
}
public void InitializeState() { }
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 08a69e2de19910a4c848f7c34af11e41
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/TickSmoothing/TickSmootherController.Threaded.cs
uploadId: 866910
@@ -0,0 +1,309 @@
#if !FISHNET_THREADED_TICKSMOOTHERS
using FishNet.Managing.Predicting;
using FishNet.Managing.Timing;
using FishNet.Object;
using GameKit.Dependencies.Utilities;
using Unity.Profiling;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta
{
/// <summary>
/// Smoothes this object between ticks.
/// </summary>
/// <remarks>This can be configured to smooth over a set interval of time, or to smooth adaptively and make path corrections for prediction.</remarks>
public class TickSmootherController : IResettable
{
#region Public.
/// <summary>
/// Logic for owner smoothing.
/// </summary>
public UniversalTickSmoother UniversalSmoother { get; private set; }
#endregion
#region Private.
/// <summary>
/// </summary>
private InitializationSettings _initializationSettings = new();
/// <summary>
/// </summary>
private MovementSettings _controllerMovementSettings = new();
/// <summary>
/// </summary>
private MovementSettings _spectatorMovementSettings = new();
/// <summary>
/// True if OnDestroy has been called.
/// </summary>
private bool _destroyed;
/// <summary>
/// Cached timeManager reference.
/// </summary>
private TimeManager _timeManager;
/// <summary>
/// NetworkBehaviour which initialized this object. Value may be null when initialized for an Offline smoother.
/// </summary>
private NetworkBehaviour _initializingNetworkBehaviour;
/// <summary>
/// Transform which initialized this object.
/// </summary>
private Transform _graphicalTransform;
/// <summary>
/// True if initialized with a null NetworkBehaviour.
/// </summary>
private bool _initializedOffline;
/// <summary>
/// True if subscribed to events used for adaptiveInterpolation.
/// </summary>
private bool _subscribedToAdaptiveEvents;
/// <summary>
/// True if currently subscribed to events.
/// </summary>
private bool _subscribed;
/// <summary>
/// True if initialized.
/// </summary>
private bool _isInitialized;
private static readonly ProfilerMarker _pm_OnUpdate = new("TickSmootherController.TimeManager_OnUpdate()");
private static readonly ProfilerMarker _pm_OnPreTick = new("TickSmootherController.TimeManager_OnPreTick()");
private static readonly ProfilerMarker _pm_OnPostTick = new("TickSmootherController.TimeManager_OnPostTick()");
#endregion
public void Initialize(InitializationSettings initializationSettings, MovementSettings controllerSettings, MovementSettings spectatorSettings)
{
_initializingNetworkBehaviour = initializationSettings.InitializingNetworkBehaviour;
_graphicalTransform = initializationSettings.GraphicalTransform;
_initializationSettings = initializationSettings;
_controllerMovementSettings = controllerSettings;
_spectatorMovementSettings = spectatorSettings;
_initializedOffline = initializationSettings.InitializingNetworkBehaviour == null;
_isInitialized = true;
}
public void OnDestroy()
{
ChangeSubscriptions(false);
StoreSmoother();
_destroyed = true;
_isInitialized = false;
}
public void StartSmoother()
{
if (!_isInitialized)
return;
bool canStart = _initializedOffline ? StartOffline() : StartOnline();
if (!canStart)
return;
RetrieveSmoothers();
UniversalSmoother.Initialize(_initializationSettings, _controllerMovementSettings, _spectatorMovementSettings);
UniversalSmoother.StartSmoother();
bool StartOnline()
{
NetworkBehaviour nb = _initializingNetworkBehaviour;
SetTimeManager(nb.TimeManager);
return true;
}
bool StartOffline()
{
if (_timeManager == null)
return false;
return true;
}
}
public void StopSmoother()
{
ChangeSubscriptions(subscribe: false);
if (!_initializedOffline)
StopOnline();
if (UniversalSmoother != null)
UniversalSmoother.StopSmoother();
void StopOnline()
{
SetTimeManager(tm: null);
}
// Intentionally left blank.
// void StopOffline() { }
}
public void TimeManager_OnUpdate()
{
using (_pm_OnUpdate.Auto())
{
UniversalSmoother.OnUpdate(Time.deltaTime);
}
}
public void TimeManager_OnPreTick()
{
using (_pm_OnPreTick.Auto())
{
UniversalSmoother.OnPreTick();
}
}
/// <summary>
/// Called after a tick completes.
/// </summary>
public void TimeManager_OnPostTick()
{
using (_pm_OnPostTick.Auto())
{
if (_timeManager != null)
UniversalSmoother.OnPostTick(_timeManager.LocalTick);
}
}
private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick)
{
UniversalSmoother.OnPostReplicateReplay(clientTick);
}
private void TimeManager_OnRoundTripTimeUpdated(long rttMs)
{
UniversalSmoother.UpdateRealtimeInterpolation();
}
/// <summary>
/// Stores smoothers if they have value.
/// </summary>
private void StoreSmoother()
{
if (UniversalSmoother == null)
return;
ResettableObjectCaches<UniversalTickSmoother>.Store(UniversalSmoother);
UniversalSmoother = null;
}
/// <summary>
/// Stores current smoothers and retrieves new ones.
/// </summary>
private void RetrieveSmoothers()
{
StoreSmoother();
UniversalSmoother = ResettableObjectCaches<UniversalTickSmoother>.Retrieve();
}
// /// <summary>
// /// Sets a target transform to follow.
// /// </summary>
// public void SetTargetTransform(Transform value)
// {
// Transform currentTargetTransform = _initializationSettings.TargetTransform;
//
// if (value == currentTargetTransform)
// return;
//
// bool clientStartCalled = (_initializedOffline && _timeManager != null) || (_initializingNetworkBehaviour != null && _initializingNetworkBehaviour.OnStartClientCalled);
//
// bool previousTargetTransformIsValid = (currentTargetTransform != null);
//
// // If target is different and old is not null then reset.
// if (previousTargetTransformIsValid && clientStartCalled)
// OnStopClient();
//
// _initializationSettings.TargetTransform = value;
// if (previousTargetTransformIsValid && clientStartCalled)
// OnStartClient();
// }
/// <summary>
/// Sets a new PredictionManager to use.
/// </summary>
public void SetTimeManager(TimeManager tm)
{
if (tm == _timeManager)
return;
// Unsub from current.
ChangeSubscriptions(false);
//Sub to newest.
_timeManager = tm;
ChangeSubscriptions(true);
}
/// <summary>
/// Changes the subscription to the TimeManager.
/// </summary>
private void ChangeSubscriptions(bool subscribe)
{
if (_destroyed)
return;
TimeManager tm = _timeManager;
if (tm == null)
return;
if (subscribe == _subscribed)
return;
_subscribed = subscribe;
bool adaptiveIsOff = _controllerMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off && _spectatorMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off;
if (subscribe)
{
tm.OnUpdate += TimeManager_OnUpdate;
tm.OnPreTick += TimeManager_OnPreTick;
tm.OnPostTick += TimeManager_OnPostTick;
if (!adaptiveIsOff)
{
tm.OnRoundTripTimeUpdated += TimeManager_OnRoundTripTimeUpdated;
PredictionManager pm = tm.NetworkManager.PredictionManager;
pm.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay;
_subscribedToAdaptiveEvents = true;
}
}
else
{
tm.OnUpdate -= TimeManager_OnUpdate;
tm.OnPreTick -= TimeManager_OnPreTick;
tm.OnPostTick -= TimeManager_OnPostTick;
if (_subscribedToAdaptiveEvents)
{
tm.OnRoundTripTimeUpdated -= TimeManager_OnRoundTripTimeUpdated;
PredictionManager pm = tm.NetworkManager.PredictionManager;
pm.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay;
}
}
}
public void ResetState()
{
_initializationSettings = default;
_controllerMovementSettings = default;
_spectatorMovementSettings = default;
_destroyed = false;
_timeManager = null;
_initializingNetworkBehaviour = null;
_graphicalTransform = null;
_subscribed = false;
_subscribedToAdaptiveEvents = false;
_isInitialized = false;
}
public void InitializeState() { }
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c189fd5371510434a9a879e928422705
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/TickSmoothing/TickSmootherController.cs
uploadId: 866910
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 59458bff2dda41a46a1b86c5512f9ff4
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/TickSmoothing/TickSmoothingManager.Types.cs
uploadId: 866910
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 2c0b6e6e7dbd4bb4f8352f0507f62bdd
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/TickSmoothing/TickSmoothingManager.cs
uploadId: 866910
@@ -0,0 +1,983 @@
#if FISHNET_THREADED_TICKSMOOTHERS
using System;
using FishNet.Managing;
using FishNet.Managing.Timing;
using FishNet.Object;
using FishNet.Object.Prediction;
using FishNet.Utility.Extension;
using GameKit.Dependencies.Utilities;
using UnityEngine;
using UnityEngine.Profiling;
using Unity.Profiling;
using UnityEngine.Scripting;
namespace FishNet.Component.Transforming.Beta
{
/// <summary>
/// This class is under regular development and it's API may change at any time.
/// </summary>
public sealed class UniversalTickSmoother : IResettable
{
#region Public.
/// <summary>
/// True if currently initialized.
/// </summary>
public bool IsInitialized { get; private set; }
#endregion
#region Private.
#region Private Profiler Markers
private static readonly ProfilerMarker _pm_UpdateRealtimeInterpolation = new ProfilerMarker("UniversalTickSmoother.UpdateRealtimeInterpolation()");
private static readonly ProfilerMarker _pm_OnUpdate = new ProfilerMarker("UniversalTickSmoother.OnUpdate(float)");
private static readonly ProfilerMarker _pm_OnPreTick = new ProfilerMarker("UniversalTickSmoother.OnPreTick()");
private static readonly ProfilerMarker _pm_OnPostReplicateReplay = new ProfilerMarker("UniversalTickSmoother.OnPostReplicateReplay(uint)");
private static readonly ProfilerMarker _pm_OnPostTick = new ProfilerMarker("UniversalTickSmoother.OnPostTick(uint)");
private static readonly ProfilerMarker _pm_ClearTPQ = new ProfilerMarker("UniversalTickSmoother.ClearTransformPropertiesQueue()");
private static readonly ProfilerMarker _pm_DiscardTPQ = new ProfilerMarker("UniversalTickSmoother.DiscardExcessiveTransformPropertiesQueue()");
private static readonly ProfilerMarker _pm_AddTP = new ProfilerMarker("UniversalTickSmoother.AddTransformProperties(uint, TransformProperties, TransformProperties)");
private static readonly ProfilerMarker _pm_ModifyTP = new ProfilerMarker("UniversalTickSmoother.ModifyTransformProperties(uint, uint)");
private static readonly ProfilerMarker _pm_SetMoveRates = new ProfilerMarker("UniversalTickSmoother.SetMoveRates(in TransformProperties)");
private static readonly ProfilerMarker _pm_MoveToTarget = new ProfilerMarker("UniversalTickSmoother.MoveToTarget(float)");
#endregion
/// <summary>
/// How quickly to move towards goal values.
/// </summary>
private MoveRates _moveRates = new();
/// <summary>
/// True if a pretick occurred since last postTick.
/// </summary>
private bool _preTicked;
/// <summary>
/// World values of the graphical after it's been aligned to initialized values in PreTick.
/// </summary>
private TransformProperties _trackerPreTickWorldValues;
/// <summary>
/// World values of the graphical after it's been aligned to initialized values in PreTick.
/// </summary>
private TransformProperties _graphicsPreTickWorldValues;
/// <summary>
/// Cached value of adaptive interpolation value.
/// </summary>
private AdaptiveInterpolationType _cachedAdaptiveInterpolationValue;
/// <summary>
/// Cached value of flat interpolation value.
/// </summary>
private byte _cachedInterpolationValue;
/// <summary>
/// Cached properties to smooth of the graphical.
/// </summary>
private TransformPropertiesFlag _cachedSmoothedProperties;
/// <summary>
/// Cached value of snapping non-smoothed properties.
/// </summary>
private bool _cachedSnapNonSmoothedProperties;
/// <summary>
/// Squared distance target must travel to cause a teleport.
/// </summary>
private float _cachedTeleportThreshold;
/// <summary>
/// True if to detach on network start.
/// </summary>
private bool _detachOnStart;
/// <summary>
/// True to re-attach on network stop.
/// </summary>
private bool _attachOnStop;
/// <summary>
/// True to begin moving soon as movement data becomes available. Movement will ease in until at interpolation value. False to prevent movement until movement data count meet interpolation.
/// </summary>
private bool _moveImmediately;
/// <summary>
/// Transform the graphics should follow.
/// </summary>
private Transform _targetTransform;
/// <summary>
/// Cached value of the object to smooth.
/// </summary>
private Transform _graphicalTransform;
/// <summary>
/// Empty gameObject containing a transform which has properties checked after each simulation.
/// If the graphical starts off as nested of targetTransform then this object is created where the graphical object is.
/// Otherwise, this object is placed directly beneath targetTransform.
/// </summary>
private Transform _trackerTransform;
/// <summary>
/// TimeManager tickDelta.
/// </summary>
private float _tickDelta;
/// <summary>
/// NetworkBehaviour this is initialized for. Value may be null.
/// </summary>
private NetworkBehaviour _initializingNetworkBehaviour;
/// <summary>
/// TimeManager this is initialized for.
/// </summary>
private TimeManager _initializingTimeManager;
/// <summary>
/// Value to multiply movement by. This is used to reduce or increase the rate the movement buffer is consumed.
/// </summary>
private float _movementMultiplier = 1f;
/// <summary>
/// TransformProperties to move towards.
/// </summary>
private BasicQueue<TickSmoothingManager.TickTransformProperties> _transformProperties;
/// <summary>
/// True if to smooth using owner settings, false for spectator settings.
/// This is only used for performance gains.
/// </summary>
private bool _useOwnerSettings;
/// <summary>
/// Last tick this was teleported on.
/// </summary>
private uint _teleportedTick = TimeManager.UNSET_TICK;
/// <summary>
/// Current interpolation value, be it a flat value or adaptive.
/// </summary>
private byte _realtimeInterpolation;
/// <summary>
/// Settings to use for owners.
/// </summary>
private MovementSettings _controllerMovementSettings;
/// <summary>
/// Settings to use for spectators.
/// </summary>
private MovementSettings _spectatorMovementSettings;
/// <summary>
/// True if moving has started and has not been stopped.
/// </summary>
private bool _isMoving;
/// <summary>
/// NetworkTransform used when prediction type is set to other.
/// </summary>
private NetworkTransform _predictionNetworkTransform;
#endregion
#region Const.
/// <summary>
/// Maximum allowed entries to be queued over the interpolation amount.
/// </summary>
private const int MAXIMUM_QUEUED_OVER_INTERPOLATION = 3;
#endregion
[Preserve]
public UniversalTickSmoother() { }
~UniversalTickSmoother()
{
// This is a last resort for if something didnt deinitialize right.
ResetState();
}
[Obsolete("This method is no longer used. Use TrySetGraphicalTrackerLocalProperties(TransformProperties).")] // Remove V5
public void SetGraphicalInitializedOffsetValues(TransformProperties value) { }
[Obsolete("This method is no longer used. Use GetGraphicalTrackerLocalProperties.")] // Remove V5
public TransformProperties GetGraphicalInitializedOffsetValues() => default;
/// <summary>
/// Tries to set local properties for the graphical tracker transform.
/// </summary>
/// <param name = "localValues">New values.</param>
/// <returns>Returns true if the tracker has been setup and values have been applied to teh tracker transform.</returns>
/// <remarks>When false is returned the values are cached and will be set when tracker is created. A cached value will be used every time the tracker is setup; to disable this behavior call this method with null value.</remarks>
public bool TrySetGraphicalTrackerLocalProperties(TransformProperties? localValues)
{
if (_trackerTransform == null || localValues == null)
{
_queuedTrackerProperties = localValues;
return false;
}
_trackerTransform.SetLocalProperties(localValues.Value);
return true;
}
[Obsolete("This method is no longer used. Use TrySetGraphicalTrackerLocalProperties(TransformProperties).")] // Remove V5
public void SetAdditionalGraphicalOffsetValues(TransformProperties localValues) { }
[Obsolete("This method is no longer used. Use GetGraphicalTrackerLocalProperties.")] // Remove V5
public TransformProperties GetAdditionalGraphicalOffsetValues() => default;
public TransformProperties GetGraphicalTrackerLocalProperties()
{
if (_trackerTransform != null)
return new(_trackerTransform.localPosition, _trackerTransform.localRotation, _trackerTransform.localScale);
if (_queuedTrackerProperties != null)
return _queuedTrackerProperties.Value;
// Fall through.
NetworkManager manager = _initializingNetworkBehaviour == null ? null : _initializingNetworkBehaviour.NetworkManager;
manager.LogWarning($"Graphical tracker properties cannot be returned because tracker is not setup yet, and no setup properties have been specified. Use TrySetGraphicalTrackerProperties to set setup properties or call this method after IsInitialized is true.");
return default;
}
/// <summary>
/// Properties for the tracker which are queued to be set when the tracker is setup.
/// </summary>
private TransformProperties? _queuedTrackerProperties;
/// <summary>
/// Updates the smoothedProperties value.
/// </summary>
/// <param name = "value">New value.</param>
/// <param name = "forController">True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings</param>
public void SetSmoothedProperties(TransformPropertiesFlag value, bool forController)
{
_controllerMovementSettings.SmoothedProperties = value;
SetCaches(forController);
}
/// <summary>
/// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation.
/// </summary>
/// <param name = "value"></param>
public void SetInterpolationValue(byte value, bool forOwnerOrOfflineSmoother) => SetInterpolationValue(value, forOwnerOrOfflineSmoother, unsetAdaptiveInterpolation: true);
/// <summary>
/// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation.
/// </summary>
private void SetInterpolationValue(byte value, bool forOwnerOrOfflineSmoother, bool unsetAdaptiveInterpolation)
{
if (value < 1)
value = 1;
if (forOwnerOrOfflineSmoother)
_controllerMovementSettings.InterpolationValue = value;
else
_spectatorMovementSettings.InterpolationValue = value;
if (unsetAdaptiveInterpolation)
SetAdaptiveInterpolation(AdaptiveInterpolationType.Off, forOwnerOrOfflineSmoother);
}
/// <summary>
/// Updates the adaptiveInterpolation value.
/// </summary>
/// <param name = "adaptiveInterpolation">New value.</param>
public void SetAdaptiveInterpolation(AdaptiveInterpolationType value, bool forOwnerOrOfflineSmoother)
{
if (forOwnerOrOfflineSmoother)
_controllerMovementSettings.AdaptiveInterpolationValue = value;
else
_spectatorMovementSettings.AdaptiveInterpolationValue = value;
UpdateRealtimeInterpolation();
}
public void Initialize(InitializationSettings initializationSettings, MovementSettings ownerSettings, MovementSettings spectatorSettings)
{
ResetState();
Transform graphicalTransform = initializationSettings.GraphicalTransform;
Transform targetTransform = initializationSettings.TargetTransform;
if (!TransformsAreValid(graphicalTransform, targetTransform))
return;
_transformProperties = CollectionCaches<TickSmoothingManager.TickTransformProperties>.RetrieveBasicQueue();
_controllerMovementSettings = ownerSettings;
_spectatorMovementSettings = spectatorSettings;
/* Unset scale smoothing if not detaching. This is to prevent
* the scale from changing with the parent if nested, as that
* would result in the scale being modified twice, once on the parent
* and once on the graphical. Thanks deo_wh for find! */
if (!initializationSettings.DetachOnStart)
{
_controllerMovementSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale;
_spectatorMovementSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale;
}
_initializingNetworkBehaviour = initializationSettings.InitializingNetworkBehaviour;
_initializingTimeManager = initializationSettings.InitializingTimeManager;
_targetTransform = targetTransform;
_graphicalTransform = graphicalTransform;
_tickDelta = (float)initializationSettings.InitializingTimeManager.TickDelta;
_detachOnStart = initializationSettings.DetachOnStart;
_attachOnStop = initializationSettings.AttachOnStop;
_moveImmediately = initializationSettings.MoveImmediately;
if (initializationSettings.FavorPredictionNetworkTransform && _initializingNetworkBehaviour != null)
{
NetworkObject networkObject = _initializingNetworkBehaviour.NetworkObject;
if (!networkObject.IsRigidbodyPredictionType)
_predictionNetworkTransform = networkObject.PredictionNetworkTransform;
else
_predictionNetworkTransform = null;
}
else
{
_predictionNetworkTransform = null;
}
SetCaches(GetUseOwnerSettings());
//Use set method as it has sanity checks.
SetInterpolationValue(_controllerMovementSettings.InterpolationValue, forOwnerOrOfflineSmoother: true, unsetAdaptiveInterpolation: false);
SetInterpolationValue(_spectatorMovementSettings.InterpolationValue, forOwnerOrOfflineSmoother: false, unsetAdaptiveInterpolation: false);
SetAdaptiveInterpolation(_controllerMovementSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: true);
SetAdaptiveInterpolation(_spectatorMovementSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: false);
SetupTrackerTransform();
/* This is called after setting up the tracker transform in the scenario
* the user set additional offsets before this was initialized. */
if (_queuedTrackerProperties != null)
TrySetGraphicalTrackerLocalProperties(_queuedTrackerProperties.Value);
void SetupTrackerTransform()
{
_trackerTransform = new GameObject($"{_graphicalTransform.name}_Tracker").transform;
if (_detachOnStart)
{
_trackerTransform.SetParent(_targetTransform);
}
else
{
Transform trackerParent = _graphicalTransform.IsChildOf(targetTransform) ? _graphicalTransform.parent : targetTransform;
_trackerTransform.SetParent(trackerParent);
}
_trackerTransform.SetWorldPositionRotationAndScale(_targetTransform.position, _targetTransform.rotation, graphicalTransform.localScale);
}
IsInitialized = true;
}
/// <summary>
/// Returns if configured transforms are valid.
/// </summary>
/// <returns></returns>
private bool TransformsAreValid(Transform graphicalTransform, Transform targetTransform)
{
if (graphicalTransform == null)
{
NetworkManagerExtensions.LogError($"Graphical transform cannot be null.");
return false;
}
if (targetTransform == null)
{
NetworkManagerExtensions.LogError($"Target transform on {graphicalTransform} cannot be null.");
return false;
}
if (targetTransform == graphicalTransform)
{
NetworkManagerExtensions.LogError($"Target transform cannot be the same as graphical transform on {graphicalTransform}.");
return false;
}
return true;
}
/// <summary>
/// Returns true if to use adaptive interpolation.
/// </summary>
/// <returns></returns>
private bool GetUseAdaptiveInterpolation()
{
if (_cachedAdaptiveInterpolationValue == AdaptiveInterpolationType.Off || _initializingTimeManager.NetworkManager.IsServerOnlyStarted)
return false;
return true;
}
/// <summary>
/// Gets if to use owner values.
/// </summary>
/// <remarks>OwnerSettings can be used to read determine this as both owner and spectator settings will have the name InitializingNetworkBehaviour.</remarks>
/// <returns></returns>
private bool GetUseOwnerSettings()
{
/* No networkBehaviour indicates an offline smoother.
* The offline smoothers use owner settings. */
if (_initializingNetworkBehaviour == null)
return true;
if (_initializingNetworkBehaviour.IsController)
return true;
return false;
// return _initializingNetworkBehaviour.IsOwner || !_initializingNetworkBehaviour.Owner.IsValid;
}
/// <summary>
/// Updates OwnerDuringPreTick value and caches if needed.
/// </summary>
private void SetCaches(bool useOwnerSettings)
{
MovementSettings movementSettings = useOwnerSettings ? _controllerMovementSettings : _spectatorMovementSettings;
_cachedSmoothedProperties = movementSettings.SmoothedProperties;
_cachedSnapNonSmoothedProperties = movementSettings.SnapNonSmoothedProperties;
_cachedAdaptiveInterpolationValue = movementSettings.AdaptiveInterpolationValue;
_cachedInterpolationValue = movementSettings.InterpolationValue;
_cachedTeleportThreshold = movementSettings.EnableTeleport ? movementSettings.TeleportThreshold * movementSettings.TeleportThreshold : MoveRates.UNSET_VALUE;
}
/// <summary>
/// Deinitializes this smoother resetting values.
/// </summary>
public void Deinitialize()
{
ResetState();
IsInitialized = false;
}
/// <summary>
/// Updates interpolation based on localClient latency when using adaptive interpolation, or uses set value when adaptive interpolation is off.
/// </summary>
public void UpdateRealtimeInterpolation()
{
using (_pm_UpdateRealtimeInterpolation.Auto())
{
/* If not networked, server is started, or if not
* using adaptive interpolation then use
* flat interpolation.*/
if (!GetUseAdaptiveInterpolation())
{
_realtimeInterpolation = _cachedInterpolationValue;
return;
}
/* If here then adaptive interpolation is being calculated. */
TimeManager tm = _initializingTimeManager;
//Calculate roughly what client state tick would be.
uint localTick = tm.LocalTick;
//This should never be the case; this is a precautionary against underflow.
if (localTick == TimeManager.UNSET_TICK)
return;
//Ensure at least 1 tick.
long rttTime = tm.RoundTripTime;
uint rttTicks = tm.TimeToTicks(rttTime) + 1;
uint clientStateTick = localTick - rttTicks;
float interpolation = localTick - clientStateTick;
//Minimum interpolation is that of adaptive interpolation level.
interpolation += (byte)_cachedAdaptiveInterpolationValue;
//Ensure interpolation is not more than a second.
if (interpolation > tm.TickRate)
interpolation = tm.TickRate;
else if (interpolation > byte.MaxValue)
interpolation = byte.MaxValue;
/* Only update realtime interpolation if it changed more than 1
* tick. This is to prevent excessive changing of interpolation value, which
* could result in noticeable speed ups/slow downs given movement multiplier
* may change when buffer is too full or short. */
if (_realtimeInterpolation == 0 || Math.Abs(_realtimeInterpolation - interpolation) > 1)
_realtimeInterpolation = (byte)Math.Ceiling(interpolation);
}
}
/// <summary>
/// This should be called when OnStartClient is invoked on the initializing NetworkBehaviour.
/// </summary>
/// <remarks>This does not need to be called if there is no initializing NetworkBehaviour.</remarks>
public void StartSmoother()
{
DetachOnStart();
}
/// <summary>
/// This should be called when OnStopClient is invoked on the initializing NetworkBehaviour.
/// </summary>
/// <remarks>This does not need to be called if there is no initializing NetworkBehaviour.</remarks>
internal void StopSmoother()
{
AttachOnStop();
}
/// <summary>
/// Called every frame.
/// </summary>
public void OnUpdate(float delta)
{
using (_pm_OnUpdate.Auto())
{
if (!CanSmooth())
return;
MoveToTarget(delta);
}
}
/// <summary>
/// Called when the TimeManager invokes OnPreTick.
/// </summary>
public void OnPreTick()
{
using (_pm_OnPreTick.Auto())
{
if (!CanSmooth())
return;
SetCaches(GetUseOwnerSettings());
_preTicked = true;
DiscardExcessiveTransformPropertiesQueue();
_graphicsPreTickWorldValues = _graphicalTransform.GetWorldProperties();
_trackerPreTickWorldValues = GetTrackerWorldProperties();
}
}
/// <summary>
/// Called when the TimeManager invokes OnPostReplay.
/// </summary>
/// <param name = "clientTick">Replay tick for the local client.</param>
/// <remarks>This is dependent on the initializing NetworkBehaviour being set.</remarks>
public void OnPostReplicateReplay(uint clientTick)
{
using (_pm_OnPostReplicateReplay.Auto())
{
if (!NetworkObjectIsReconciling())
return;
if (_transformProperties.Count == 0)
return;
if (clientTick <= _teleportedTick)
return;
uint firstTick = _transformProperties.Peek().Tick;
//Already in motion to first entry, or first entry passed tick.
if (clientTick <= firstTick)
return;
ModifyTransformProperties(clientTick, firstTick);
}
}
/// <summary>
/// Called when TimeManager invokes OnPostTick.
/// </summary>
/// <param name = "clientTick">Local tick of the client.</param>
public void OnPostTick(uint clientTick)
{
using (_pm_OnPostTick.Auto())
{
if (!CanSmooth())
return;
if (clientTick <= _teleportedTick)
return;
//If preticked then previous transform values are known.
if (_preTicked)
{
var trackerProps = GetTrackerWorldProperties();
//Only needs to be put to pretick position if not detached.
if (!_detachOnStart)
_graphicalTransform.SetWorldProperties(_graphicsPreTickWorldValues);
DiscardExcessiveTransformPropertiesQueue();
//SnapNonSmoothedProperties();
AddTransformProperties(clientTick, trackerProps);
}
//If did not pretick then the only thing we can do is snap to instantiated values.
else
{
//Only set to position if not to detach.
if (!_detachOnStart)
_graphicalTransform.SetWorldProperties(GetTrackerWorldProperties());
}
}
}
/// <summary>
/// Snaps non-smoothed properties to original positoin if setting is enabled.
/// </summary>
private void SnapNonSmoothedProperties()
{
//Feature is not enabled.
if (!_cachedSnapNonSmoothedProperties)
return;
TransformPropertiesFlag smoothedProperties = _cachedSmoothedProperties;
//Everything is smoothed.
if (smoothedProperties == TransformPropertiesFlag.Everything)
return;
TransformProperties goalValeus = GetTrackerWorldProperties();
if (!smoothedProperties.FastContains(TransformPropertiesFlag.Position))
_graphicalTransform.position = goalValeus.Position;
if (!smoothedProperties.FastContains(TransformPropertiesFlag.Rotation))
_graphicalTransform.rotation = goalValeus.Rotation;
if (!smoothedProperties.FastContains(TransformPropertiesFlag.Scale))
_graphicalTransform.localScale = goalValeus.Scale;
}
/// <summary>
/// Returns if the initialized NetworkBehaviour's NetworkObject is reconcilling.
/// </summary>
private bool NetworkObjectIsReconciling() => _initializingNetworkBehaviour == null || _initializingNetworkBehaviour.NetworkObject.IsObjectReconciling;
/// <summary>
/// Teleports the graphical to it's starting position and clears the internal movement queue.
/// </summary>
public void Teleport()
{
if (_initializingTimeManager == null)
return;
//If using adaptive interpolation then set the tick which was teleported.
if (_controllerMovementSettings.AdaptiveInterpolationValue != AdaptiveInterpolationType.Off)
{
TimeManager tm = _initializingTimeManager == null ? InstanceFinder.TimeManager : _initializingTimeManager;
if (tm != null)
_teleportedTick = tm.LocalTick;
}
ClearTransformPropertiesQueue();
_graphicalTransform.SetWorldProperties(_trackerTransform.GetWorldProperties());
}
/// <summary>
/// Clears the pending movement queue.
/// </summary>
private void ClearTransformPropertiesQueue()
{
using (_pm_ClearTPQ.Auto())
{
_transformProperties.Clear();
//Also unset move rates since there is no more queue.
_moveRates = new(MoveRates.UNSET_VALUE);
}
}
/// <summary>
/// Discards datas over interpolation limit from movement queue.
/// </summary>
private void DiscardExcessiveTransformPropertiesQueue()
{
using (_pm_DiscardTPQ.Auto())
{
int propertiesCount = _transformProperties.Count;
int dequeueCount = propertiesCount - (_realtimeInterpolation + MAXIMUM_QUEUED_OVER_INTERPOLATION);
//If there are entries to dequeue.
if (dequeueCount > 0)
{
TickSmoothingManager.TickTransformProperties ttp = default;
for (int i = 0; i < dequeueCount; i++)
{
ttp = _transformProperties.Dequeue();
}
var nextValues = ttp.Properties;
SetMoveRates(nextValues);
}
}
}
/// <summary>
/// Adds a new transform properties and sets move rates if needed.
/// </summary>
private void AddTransformProperties(uint tick, TransformProperties properties)
{
using (_pm_AddTP.Auto())
{
TickSmoothingManager.TickTransformProperties ttp = new(tick, properties);
_transformProperties.Enqueue(ttp);
//If first entry then set move rates.
if (_transformProperties.Count == 1)
{
TransformProperties gfxWorldProperties = _graphicalTransform.GetWorldProperties();
SetMoveRates(gfxWorldProperties);
}
}
}
/// <summary>
/// Modifies a transform property for a tick. This does not error check for empty collections.
/// </summary>
/// <param name = "firstTick">First tick in the queue. If 0 this will be looked up.</param>
private void ModifyTransformProperties(uint clientTick, uint firstTick)
{
using (_pm_ModifyTP.Auto())
{
int queueCount = _transformProperties.Count;
uint tick = clientTick;
/*Ticks will always be added incremental by 1 so it's safe to jump ahead the difference
* of tick and firstTick. */
int index = (int)(tick - firstTick);
//Replace with new data.
if (index < queueCount)
{
if (tick != _transformProperties[index].Tick)
{
//Should not be possible.
}
else
{
TransformProperties newProperties = GetTrackerWorldProperties();
/* Adjust transformProperties to ease into any corrections.
* The corrected value is used the more the index is to the end
* of the queue. */
/* We want to be fully eased in by the last entry of the queue. */
int lastPossibleIndex = queueCount - 1;
int adjustedQueueCount = lastPossibleIndex - 1;
if (adjustedQueueCount < 1)
adjustedQueueCount = 1;
float easePercent = (float)index / adjustedQueueCount;
//If easing.
if (easePercent < 1f)
{
if (easePercent < 1f)
easePercent = (float)Math.Pow(easePercent, adjustedQueueCount - index);
TransformProperties oldProperties = _transformProperties[index].Properties;
newProperties.Position = Vector3.Lerp(oldProperties.Position, newProperties.Position, easePercent);
newProperties.Rotation = Quaternion.Lerp(oldProperties.Rotation, newProperties.Rotation, easePercent);
newProperties.Scale = Vector3.Lerp(oldProperties.Scale, newProperties.Scale, easePercent);
}
_transformProperties[index] = new(tick, newProperties);
}
}
else
{
//This should never happen.
}
}
}
/// <summary>
/// Gets properties of the tracker.
/// </summary>
private TransformProperties GetTrackerWorldProperties()
{
/* Return lossyScale if graphical is not attached. Otherwise,
* graphical should retain the tracker localScale so it changes
* with root. */
Vector3 scale = _detachOnStart ? _trackerTransform.lossyScale : _trackerTransform.localScale;
return new(_trackerTransform.position, _trackerTransform.rotation, scale);
}
/// <summary>
/// Returns if prediction can be used on this rigidbody.
/// </summary>
/// <returns></returns>
private bool CanSmooth()
{
//No graphical object is set.
if (_graphicalTransform == null)
return false;
/* When this is the case the prediction networkTransform exist and is
* configured in a way to smooth the object, therefor this component should not be smoothing. */
if (_predictionNetworkTransform != null && _predictionNetworkTransform.DoSettingsAllowSmoothing())
return false;
return _initializingTimeManager.NetworkManager.IsClientStarted;
}
/// <summary>
/// Sets new rates based on next entries in transformProperties queue, against a supplied TransformProperties.
/// </summary>
private void SetMoveRates(in TransformProperties prevValues)
{
using (_pm_SetMoveRates.Auto())
{
if (_transformProperties.Count == 0)
{
_moveRates = new(MoveRates.UNSET_VALUE);
return;
}
TransformProperties nextValues = _transformProperties.Peek().Properties;
float duration = _tickDelta;
_moveRates = MoveRates.GetMoveRates(prevValues, nextValues, duration, _cachedTeleportThreshold);
_moveRates.TimeRemaining = duration;
SetMovementMultiplier();
}
}
private void SetMovementMultiplier()
{
if (_moveImmediately)
{
float percent = Mathf.InverseLerp(0, _realtimeInterpolation, _transformProperties.Count);
_movementMultiplier = percent;
_movementMultiplier = Mathf.Clamp(_movementMultiplier, 0.5f, 1.05f);
}
//For the time being, not moving immediately uses these multiplier calculations.
else
{
/* If there's more in queue than interpolation then begin to move faster based on overage.
* Move 5% faster for every overage. */
int overInterpolation = _transformProperties.Count - _realtimeInterpolation;
//If needs to be adjusted.
if (overInterpolation != 0)
{
_movementMultiplier += 0.015f * overInterpolation;
}
//If does not need to be adjusted.
else
{
//If interpolation is 1 then slow down just barely to accomodate for frame delta variance.
if (_realtimeInterpolation == 1)
_movementMultiplier = 1f;
}
_movementMultiplier = Mathf.Clamp(_movementMultiplier, 0.95f, 1.05f);
}
}
/// <summary>
/// Moves transform to target values.
/// </summary>
private void MoveToTarget(float delta)
{
using (_pm_MoveToTarget.Auto())
{
int tpCount = _transformProperties.Count;
//No data.
if (tpCount == 0)
return;
if (_moveImmediately)
{
_isMoving = true;
}
else
{
//Enough in buffer to move.
if (tpCount >= _realtimeInterpolation)
{
_isMoving = true;
}
else if (!_isMoving)
{
return;
}
/* If buffer is considerably under goal then halt
* movement. This will allow the buffer to grow. */
else if (tpCount - _realtimeInterpolation < -4)
{
_isMoving = false;
return;
}
}
TickSmoothingManager.TickTransformProperties ttp = _transformProperties.Peek();
TransformPropertiesFlag smoothedProperties = _cachedSmoothedProperties;
_moveRates.Move(_graphicalTransform, ttp.Properties, smoothedProperties, delta * _movementMultiplier, useWorldSpace: true);
float tRemaining = _moveRates.TimeRemaining;
//if TimeLeft is <= 0f then transform is at goal. Grab a new goal if possible.
if (tRemaining <= 0f)
{
//Dequeue current entry and if there's another call a move on it.
_transformProperties.Dequeue();
//If there are entries left then setup for the next.
if (_transformProperties.Count > 0)
{
SetMoveRates(ttp.Properties);
//If delta is negative then call move again with abs.
if (tRemaining < 0f)
MoveToTarget(Mathf.Abs(tRemaining));
}
//No remaining, set to snap.
else
{
ClearTransformPropertiesQueue();
}
}
}
}
private void DetachOnStart()
{
if (!_detachOnStart)
return;
TransformProperties gfxWorldProperties = _graphicalTransform.GetWorldProperties();
_graphicalTransform.SetParent(null);
_graphicalTransform.SetWorldProperties(gfxWorldProperties);
}
/// <summary>
/// Attachs to Target transform is possible.
/// </summary>
private void AttachOnStop()
{
//Never detached.
if (!_detachOnStart)
return;
//Graphical is null, nothing can be moved.
if (_graphicalTransform == null)
return;
if (ApplicationState.IsQuitting())
return;
/* If not to re-attach or if there's no target to reference
* then the graphical must be destroyed. */
bool destroy = !_attachOnStop || _targetTransform == null;
//If not to re-attach then destroy graphical if needed.
if (destroy)
{
UnityEngine.Object.Destroy(_graphicalTransform.gameObject);
return;
}
_graphicalTransform.SetParent(_targetTransform.parent);
_graphicalTransform.SetLocalProperties(_trackerTransform.GetLocalProperties());
}
public void ResetState()
{
if (!IsInitialized)
return;
AttachOnStop();
_initializingNetworkBehaviour = null;
_initializingTimeManager = null;
_graphicalTransform = null;
_targetTransform = null;
_teleportedTick = TimeManager.UNSET_TICK;
_movementMultiplier = 1f;
CollectionCaches<TickSmoothingManager.TickTransformProperties>.StoreAndDefault(ref _transformProperties);
_moveRates = default;
_preTicked = default;
_queuedTrackerProperties = null;
_trackerPreTickWorldValues = default;
_graphicsPreTickWorldValues = default;
_realtimeInterpolation = default;
_isMoving = default;
_predictionNetworkTransform = null;
if (_trackerTransform != null)
UnityEngine.Object.Destroy(_trackerTransform.gameObject);
}
public void InitializeState() { }
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 5dff0ab5f613e944b9ca9c62e6783e7f
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/TickSmoothing/UniversalTickSmoother.Threaded.cs
uploadId: 866910
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3c9b47c92bed992428c9dbcb60f80916
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/TickSmoothing/UniversalTickSmoother.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a658654fb5a277445af5f1628ae61d88
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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