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 before being set kinematic. /// private struct RigidbodyData { /// /// Rigidbody for data. /// public Rigidbody Rigidbody; /// /// Cached velocity when being set kinematic. /// public Vector3 Velocity; /// /// Cached velocity when being set kinematic. /// public Vector3 AngularVelocity; /// /// True if the rigidbody was kinematic prior to being paused. /// public bool IsKinematic; /// /// Detection mode of the Rigidbody. /// public CollisionDetectionMode CollisionDetectionMode; public RigidbodyData(Rigidbody rb) { Rigidbody = rb; Velocity = Vector3.zero; AngularVelocity = Vector3.zero; IsKinematic = rb.isKinematic; CollisionDetectionMode = rb.collisionDetectionMode; } public void Update(Rigidbody rb) { Velocity = rb.linearVelocity; AngularVelocity = rb.angularVelocity; IsKinematic = rb.isKinematic; CollisionDetectionMode = rb.collisionDetectionMode; } } /// /// Data for a rigidbody2d before being set kinematic. /// private struct Rigidbody2DData { /// /// Rigidbody for data. /// public Rigidbody2D Rigidbody2d; /// /// Cached velocity when being set kinematic. /// public Vector2 Velocity; /// /// Cached velocity when being set kinematic. /// public float AngularVelocity; /// /// True if the rigidbody was kinematic prior to being paused. /// public bool IsKinematic; /// /// True if the rigidbody was simulated prior to being paused. /// public bool Simulated; /// /// Detection mode of the rigidbody. /// public CollisionDetectionMode2D CollisionDetectionMode; public Rigidbody2DData(Rigidbody2D rb) { Rigidbody2d = rb; Velocity = Vector2.zero; AngularVelocity = 0f; Simulated = rb.simulated; IsKinematic = rb.isKinematic; CollisionDetectionMode = rb.collisionDetectionMode; } public void Update(Rigidbody2D rb) { Velocity = rb.linearVelocity; AngularVelocity = rb.angularVelocity; Simulated = rb.simulated; IsKinematic = rb.isKinematic; CollisionDetectionMode = rb.collisionDetectionMode; } } #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(); /// /// 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); } } /// /// Pauses rigidbodies preventing them from interacting. /// 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; rb.isKinematic = true; rb.simulated = false; return true; } } } /// /// Unpauses rigidbodies allowing them to interact normally. /// 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) { rb.linearVelocity = rbData.Velocity; 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; // ReSharper disable once ConditionIsAlwaysTrueOrFalse rb.isKinematic = rbData.IsKinematic; // ReSharper disable once ConditionIsAlwaysTrueOrFalse rb.simulated = rbData.Simulated; rb.collisionDetectionMode = rbData.CollisionDetectionMode; if (!rb.isKinematic) { rb.linearVelocity = rbData.Velocity; 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() { } } }