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() { } } }