// standalone utility functions for PredictedRigidbody component. using System; using UnityEngine; namespace Mirror { public static class PredictionUtils2D { // rigidbody /////////////////////////////////////////////////////////// // move a Rigidbody + settings from one GameObject to another. public static void MoveRigidbody(GameObject source, GameObject destination, bool destroySource = true) { // create a new Rigidbody component on destination. // note that adding a Joint automatically adds a Rigidbody. // so first check if one was added yet. Rigidbody2D original = source.GetComponent(); if (original == null) throw new Exception($"Prediction: attempted to move {source}'s Rigidbody to the predicted copy, but there was no component."); Rigidbody2D rigidbodyCopy; if (!destination.TryGetComponent(out rigidbodyCopy)) rigidbodyCopy = destination.AddComponent(); // copy all properties rigidbodyCopy.mass = original.mass; #if UNITY_6000_0_OR_NEWER rigidbodyCopy.linearDamping = original.linearDamping; rigidbodyCopy.angularDamping = original.angularDamping; #else rigidbodyCopy.drag = original.drag; rigidbodyCopy.angularDrag = original.angularDrag; #endif rigidbodyCopy.isKinematic = original.isKinematic; rigidbodyCopy.interpolation = original.interpolation; rigidbodyCopy.collisionDetectionMode = original.collisionDetectionMode; // fix: need to set freezeRotation before constraints: // https://github.com/MirrorNetworking/Mirror/pull/3946 rigidbodyCopy.freezeRotation = original.freezeRotation; rigidbodyCopy.constraints = original.constraints; // moving (Configurable)Joints messes up their range of motion unless // we reset to initial position first (we do this in PredictedRigibody.cs). // so here we don't set the Rigidbody's physics position at all. // rigidbodyCopy.position = original.position; // rigidbodyCopy.rotation = original.rotation; // projects may keep Rigidbodies as kinematic sometimes. in that case, setting velocity would log an error if (!original.isKinematic) { #if UNITY_6000_0_OR_NEWER rigidbodyCopy.linearVelocity = original.linearVelocity; #else rigidbodyCopy.velocity = original.velocity; #endif rigidbodyCopy.angularVelocity = original.angularVelocity; } // destroy original if (destroySource) GameObject.Destroy(original); } // helper function: if a collider is on a child, copy that child first. // this way child's relative position/rotation/scale are preserved. public static GameObject CopyRelativeTransform(GameObject source, Transform sourceChild, GameObject destination) { // is this on the source root? then we want to put it on the destination root. if (sourceChild == source.transform) return destination; // is this on a child? then create the same child with the same transform on destination. // note this is technically only correct for the immediate child since // .localPosition is relative to parent, but this is good enough. GameObject child = new GameObject(sourceChild.name); child.transform.SetParent(destination.transform, true); child.transform.localPosition = sourceChild.localPosition; child.transform.localRotation = sourceChild.localRotation; child.transform.localScale = sourceChild.localScale; // assign the same Layer for the physics copy. // games may use a custom physics collision matrix, layer matters. child.layer = sourceChild.gameObject.layer; return child; } // colliders /////////////////////////////////////////////////////////// // move all BoxColliders + settings from one GameObject to another. public static void MoveBoxColliders(GameObject source, GameObject destination, bool destroySource = true) { // colliders may be on children BoxCollider2D[] sourceColliders = source.GetComponentsInChildren(); foreach (BoxCollider2D sourceCollider in sourceColliders) { // copy the relative transform: // if collider is on root, it returns destination root. // if collider is on a child, it creates and returns a child on destination. GameObject target = CopyRelativeTransform(source, sourceCollider.transform, destination); BoxCollider2D colliderCopy = target.AddComponent(); colliderCopy.offset = sourceCollider.offset; colliderCopy.size = sourceCollider.size; colliderCopy.isTrigger = sourceCollider.isTrigger; if (destroySource) GameObject.Destroy(sourceCollider); } } // move all SphereColliders + settings from one GameObject to another. public static void MoveSphereColliders(GameObject source, GameObject destination, bool destroySource = true) { // colliders may be on children CircleCollider2D[] sourceColliders = source.GetComponentsInChildren(); foreach (CircleCollider2D sourceCollider in sourceColliders) { // copy the relative transform: // if collider is on root, it returns destination root. // if collider is on a child, it creates and returns a child on destination. GameObject target = CopyRelativeTransform(source, sourceCollider.transform, destination); CircleCollider2D colliderCopy = target.AddComponent(); colliderCopy.offset = sourceCollider.offset; colliderCopy.radius = sourceCollider.radius; colliderCopy.isTrigger = sourceCollider.isTrigger; if (destroySource) GameObject.Destroy(sourceCollider); } } // move all CapsuleColliders + settings from one GameObject to another. public static void MoveCapsuleColliders(GameObject source, GameObject destination, bool destroySource = true) { // colliders may be on children CapsuleCollider2D[] sourceColliders = source.GetComponentsInChildren(); foreach (CapsuleCollider2D sourceCollider in sourceColliders) { // copy the relative transform: // if collider is on root, it returns destination root. // if collider is on a child, it creates and returns a child on destination. GameObject target = CopyRelativeTransform(source, sourceCollider.transform, destination); CapsuleCollider2D colliderCopy = target.AddComponent(); colliderCopy.offset = sourceCollider.offset; colliderCopy.size = sourceCollider.size; colliderCopy.direction = sourceCollider.direction; colliderCopy.isTrigger = sourceCollider.isTrigger; if (destroySource) GameObject.Destroy(sourceCollider); } } // move all Colliders + settings from one GameObject to another. public static void MoveAllColliders(GameObject source, GameObject destination, bool destroySource = true) { MoveBoxColliders(source, destination, destroySource); MoveSphereColliders(source, destination, destroySource); MoveCapsuleColliders(source, destination, destroySource); } // all ///////////////////////////////////////////////////////////////// // move all physics components from one GameObject to another. public static void MovePhysicsComponents(GameObject source, GameObject destination, bool destroySource = true) { MoveAllColliders(source, destination, destroySource); MoveRigidbody(source, destination, destroySource); } } }