[Add] FishNet
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user