using FishNet.Managing;
using GameKit.Dependencies.Utilities;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using UnityEngine;
namespace FishNet.Component.Prediction
{
///
/// Pauses and unpauses rigidbodies. While paused rigidbodies cannot be interacted with or simulated.
///
public class RigidbodyPauser : IResettable
{
#region Types.
///
/// Data for a rigidbody, including its transform and simulation state.
///
public struct RigidbodyData
{
///
/// Rigidbody for data.
///
public Rigidbody Rigidbody;
///
/// Cached velocity.
///
public Vector3 Velocity;
///
/// Cached angular velocity.
///
public Vector3 AngularVelocity;
///
/// True if the rigidbody was kinematic.
///
public bool IsKinematic;
///
/// True if the rigidbody was detecting collisions.
///
public bool DetectCollisions;
///
/// Detection mode of the Rigidbody.
///
public CollisionDetectionMode CollisionDetectionMode;
///
/// World position of the rigidbody's transform.
///
public Vector3 Position;
///
/// World rotation of the rigidbody's transform.
///
public Quaternion Rotation;
public RigidbodyData(Rigidbody rb)
{
Rigidbody = rb;
Velocity = Vector3.zero;
AngularVelocity = Vector3.zero;
IsKinematic = rb.isKinematic;
DetectCollisions = rb.detectCollisions;
CollisionDetectionMode = rb.collisionDetectionMode;
Position = rb.transform.position;
Rotation = rb.transform.rotation;
}
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;
Position = rb.transform.position;
Rotation = rb.transform.rotation;
}
}
///
/// Data for a rigidbody2d, including its transform and simulation state.
///
public struct Rigidbody2DData
{
///
/// Rigidbody for data.
///
public Rigidbody2D Rigidbody2d;
///
/// Cached velocity.
///
public Vector2 Velocity;
///
/// Cached angular velocity.
///
public float AngularVelocity;
///
/// True if the rigidbody was kinematic.
///
public bool IsKinematic;
///
/// True if the rigidbody was simulated.
///
public bool Simulated;
///
/// Detection mode of the rigidbody.
///
public CollisionDetectionMode2D CollisionDetectionMode;
///
/// World position of the rigidbody's transform.
///
public Vector2 Position;
///
/// World rotation of the rigidbody's transform, in degrees.
///
public float Rotation;
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;
Position = rb.position;
Rotation = rb.rotation;
}
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;
Position = rb.position;
Rotation = rb.rotation;
}
}
#endregion
#region Public.
///
/// True if the rigidbodies are considered paused.
///
public bool Paused { get; private set; }
#endregion
#region Private.
///
/// Rigidbody datas for found rigidbodies.
///
private List _rigidbodyDatas = new();
///
/// Rigidbody2D datas for found rigidbodies;
///
private List _rigidbody2dDatas = new();
///
/// Snapshot taken when pausing, applied when unpausing.
///
private List _pauseSnapshot = new();
///
/// Snapshot taken when pausing 2D rigidbodies, applied when unpausing.
///
private List _pauseSnapshot2d = new();
///
/// True to get rigidbodies in children of transform.
///
private bool _getInChildren;
///
/// Transform to get rigidbodies on.
///
private Transform _transform;
///
/// Type of prediction movement which is being used.
///
private RigidbodyType _rigidbodyType;
///
/// True if initialized at least once.
///
private bool _initialized;
#endregion
///
/// Assigns rigidbodies using initialized settings.
///
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);
}
///
/// Assigns rigidbodies manually and initializes component.
///
public void UpdateRigidbodies(Rigidbody[] rbs)
{
List rigidbodies = CollectionCaches.RetrieveList();
foreach (Rigidbody rb in rbs)
rigidbodies.Add(rb);
UpdateRigidbodies(rigidbodies);
CollectionCaches.Store(rigidbodies);
}
///
/// Assigns rigidbodies manually and initializes component.
///
private void UpdateRigidbodies(List rbs)
{
_rigidbodyDatas.Clear();
foreach (Rigidbody rb in rbs)
_rigidbodyDatas.Add(new(rb));
_initialized = true;
}
///
/// Assigns rigidbodies manually and initializes component.
///
public void UpdateRigidbodies2D(Rigidbody2D[] rbs)
{
List rigidbodies = CollectionCaches.RetrieveList();
foreach (Rigidbody2D rb in rbs)
rigidbodies.Add(rb);
UpdateRigidbodies2D(rigidbodies);
CollectionCaches.Store(rigidbodies);
}
///
/// Assigns rigidbodies manually and initializes component.
///
private void UpdateRigidbodies2D(List rbs)
{
_rigidbody2dDatas.Clear();
foreach (Rigidbody2D rb in rbs)
_rigidbody2dDatas.Add(new(rb));
_initialized = true;
}
///
/// Assigns rigidbodies.
///
/// Rigidbodies2D to use.
public void UpdateRigidbodies(Transform t, RigidbodyType rbType, bool getInChildren)
{
_rigidbodyType = rbType;
_getInChildren = getInChildren;
// 3D.
if (rbType == RigidbodyType.Rigidbody)
{
List rigidbodies = CollectionCaches.RetrieveList();
if (getInChildren)
{
Rigidbody[] rbs = t.GetComponentsInChildren();
for (int i = 0; i < rbs.Length; i++)
rigidbodies.Add(rbs[i]);
}
else
{
Rigidbody rb = t.GetComponent();
if (rb != null)
rigidbodies.Add(rb);
}
UpdateRigidbodies(rigidbodies);
CollectionCaches.Store(rigidbodies);
}
// 2D.
else
{
List rigidbodies = CollectionCaches.RetrieveList();
if (getInChildren)
{
Rigidbody2D[] rbs = t.GetComponentsInChildren();
for (int i = 0; i < rbs.Length; i++)
rigidbodies.Add(rbs[i]);
}
else
{
Rigidbody2D rb = t.GetComponent();
if (rb != null)
rigidbodies.Add(rb);
}
UpdateRigidbodies2D(rigidbodies);
CollectionCaches.Store(rigidbodies);
}
}
///
/// Fills a list with a snapshot of the current transform and simulation state of every tracked rigidbody.
///
/// List to populate; it is cleared first.
public void GetSnapshot(List snapshot)
{
snapshot.Clear();
for (int i = 0; i < _rigidbodyDatas.Count; i++)
{
Rigidbody rb = _rigidbodyDatas[i].Rigidbody;
if (rb == null)
continue;
RigidbodyData data = new(rb);
data.Update(rb);
snapshot.Add(data);
}
}
///
/// Fills a list with a snapshot of the current transform and simulation state of every tracked 2D rigidbody.
///
/// List to populate; it is cleared first.
public void GetSnapshot(List snapshot)
{
snapshot.Clear();
for (int i = 0; i < _rigidbody2dDatas.Count; i++)
{
Rigidbody2D rb = _rigidbody2dDatas[i].Rigidbody2d;
if (rb == null)
continue;
Rigidbody2DData data = new(rb);
data.Update(rb);
snapshot.Add(data);
}
}
///
/// Applies a snapshot, setting each rigidbody's transform (and optionally its simulation state) back to the stored values.
///
/// Snapshot to apply. Null is ignored.
/// True to restore isKinematic, detectCollisions and collisionDetectionMode (used when unpausing).
/// False to leave those as they are and only restore transform and velocity — the kinematic state is owned by other systems and must not be forced by a reconcile correction.
public void ApplySnapshot(List snapshot, bool restoreSimulationState = true)
{
if (snapshot == null)
return;
for (int i = 0; i < snapshot.Count; i++)
{
RigidbodyData data = snapshot[i];
Rigidbody rb = data.Rigidbody;
if (rb == null)
continue;
rb.position = data.Position;
rb.rotation = data.Rotation;
if (restoreSimulationState)
{
rb.isKinematic = data.IsKinematic;
rb.detectCollisions = data.DetectCollisions;
rb.collisionDetectionMode = data.CollisionDetectionMode;
}
//Velocities cannot be set on a kinematic rigidbody; check the actual current state.
if (!rb.isKinematic)
{
#if UNITY_6000_1_OR_NEWER
rb.linearVelocity = data.Velocity;
#else
rb.velocity = data.Velocity;
#endif
rb.angularVelocity = data.AngularVelocity;
}
}
}
///
/// Applies a snapshot, setting each 2D rigidbody's transform (and optionally its simulation state) back to the stored values.
///
/// Snapshot to apply. Null is ignored.
/// True to restore bodyType, simulated and collisionDetectionMode (used when unpausing).
/// False to leave those as they are and only restore transform and velocity — the kinematic state is owned by other systems and must not be forced by a reconcile correction.
public void ApplySnapshot(List snapshot, bool restoreSimulationState = true)
{
if (snapshot == null)
return;
for (int i = 0; i < snapshot.Count; i++)
{
Rigidbody2DData data = snapshot[i];
Rigidbody2D rb = data.Rigidbody2d;
if (rb == null)
continue;
rb.position = data.Position;
rb.rotation = data.Rotation;
if (restoreSimulationState)
{
#if UNITY_6000_1_OR_NEWER
rb.bodyType = data.IsKinematic ? RigidbodyType2D.Kinematic : RigidbodyType2D.Dynamic;
#else
rb.isKinematic = data.IsKinematic;
#endif
rb.simulated = data.Simulated;
rb.collisionDetectionMode = data.CollisionDetectionMode;
}
//Velocities can only be set on a dynamic body; check the actual current state.
#if UNITY_6000_1_OR_NEWER
bool canSetVelocity = rb.bodyType == RigidbodyType2D.Dynamic;
#else
bool canSetVelocity = !rb.isKinematic;
#endif
if (canSetVelocity)
{
#if UNITY_6000_1_OR_NEWER
rb.linearVelocity = data.Velocity;
#else
rb.velocity = data.Velocity;
#endif
rb.angularVelocity = data.AngularVelocity;
}
}
}
///
/// Pauses rigidbodies preventing them from interacting. The pre-pause state is snapshotted so Unpause can restore it.
///
public void Pause()
{
if (Paused)
return;
Paused = true;
// 3D.
if (_rigidbodyType == RigidbodyType.Rigidbody)
{
//Snapshot the current state, then freeze every rigidbody.
GetSnapshot(_pauseSnapshot);
for (int i = 0; i < _rigidbodyDatas.Count; i++)
{
Rigidbody rb = _rigidbodyDatas[i].Rigidbody;
if (rb == null)
{
_rigidbodyDatas.RemoveAt(i);
i--;
continue;
}
rb.collisionDetectionMode = CollisionDetectionMode.Discrete;
rb.isKinematic = true;
rb.detectCollisions = false;
}
}
// 2D.
else
{
GetSnapshot(_pauseSnapshot2d);
for (int i = 0; i < _rigidbody2dDatas.Count; i++)
{
Rigidbody2D rb = _rigidbody2dDatas[i].Rigidbody2d;
if (rb == null)
{
_rigidbody2dDatas.RemoveAt(i);
i--;
continue;
}
rb.collisionDetectionMode = CollisionDetectionMode2D.Discrete;
#if UNITY_6000_1_OR_NEWER
rb.bodyType = RigidbodyType2D.Kinematic;
#else
rb.isKinematic = true;
#endif
rb.simulated = false;
}
}
}
///
/// Unpauses rigidbodies allowing them to interact normally, restoring the state snapshotted during Pause.
///
public void Unpause()
{
if (!Paused)
return;
Paused = false;
if (_rigidbodyType == RigidbodyType.Rigidbody)
ApplySnapshot(_pauseSnapshot);
else
ApplySnapshot(_pauseSnapshot2d);
}
public void ResetState()
{
_rigidbodyDatas.Clear();
_rigidbody2dDatas.Clear();
_pauseSnapshot.Clear();
_pauseSnapshot2d.Clear();
_getInChildren = default;
_transform = default;
_rigidbodyType = default;
_initialized = default;
Paused = default;
}
public void InitializeState() { }
}
}