using UnityEngine; using System.Collections; using System.Collections.Generic; using System; using UnityEngine.Events; public class genericRagdollBuilder : MonoBehaviour { [Header ("Main Settings")] [Space] public string ragdollName = "New Ragdoll"; public bool setMassOnRagdoll; public float totalMass = 20; public float strength = 0.0F; public bool flipForward = false; public float rotationYOffset; [Space] [Header ("Bones List")] [Space] public Transform pelvis; public Transform leftHips = null; public Transform leftKnee = null; public Transform rightHips = null; public Transform rightKnee = null; public Transform leftArm = null; public Transform leftElbow = null; public Transform rightArm = null; public Transform rightElbow = null; public Transform middleSpine = null; public Transform head = null; [Space] public bool ignoreEmptyLimbFieldsEnabled; [Space] [Header ("Other Settings")] [Space] public bool useListToIgnoreOnRagdollActivated; public List colliderListToIgnoreOnRagdollActivated = new List (); [Space] [Header ("Debug")] [Space] public List bones = new List (); public bool ragdollActivated; [Space] [Header ("Event Settings")] [Space] public eventParameters.eventToCallWithTransformList eventToSentBonesList; [Space] [Header ("Components")] [Space] public ragdollActivator mainRagdollActivator; public GameObject characterBody; BoneInfo rootBone; Vector3 right = Vector3.right; Vector3 up = Vector3.up; Vector3 forward = Vector3.forward; Vector3 worldRight = Vector3.right; Vector3 worldUp = Vector3.up; Vector3 worldForward = Vector3.forward; string CheckConsistency () { PrepareBones (); Hashtable map = new Hashtable (); foreach (BoneInfo bone in bones) { if (bone.anchor != null) { if (map [bone.anchor] != null) { BoneInfo oldBone = (BoneInfo)map [bone.anchor]; return String.Format ("{0} and {1} may not be assigned to the same bone.", bone.name, oldBone.name); } map [bone.anchor] = bone; } } foreach (BoneInfo bone in bones) { if (bone.anchor == null) return String.Format ("{0} has not been assigned yet.\n", bone.name); } return ""; } void DecomposeVector (out Vector3 normalCompo, out Vector3 tangentCompo, Vector3 outwardDir, Vector3 outwardNormal) { outwardNormal = outwardNormal.normalized; normalCompo = outwardNormal * Vector3.Dot (outwardDir, outwardNormal); tangentCompo = outwardDir - normalCompo; } void CalculateAxes () { if (head != null && pelvis != null) up = CalculateDirectionAxis (pelvis.InverseTransformPoint (head.position)); if (rightElbow != null && pelvis != null) { Vector3 removed, temp; DecomposeVector (out temp, out removed, pelvis.InverseTransformPoint (rightElbow.position), up); right = CalculateDirectionAxis (removed); } forward = Vector3.Cross (right, up); if (flipForward) forward = -forward; } void OnWizardUpdate () { string errorString = CheckConsistency (); CalculateAxes (); bool isValid = errorString.Length == 0; print (isValid); } void PrepareBones () { if (pelvis != null) { worldRight = pelvis.TransformDirection (right); worldUp = pelvis.TransformDirection (up); worldForward = pelvis.TransformDirection (forward); } bones = new List (); rootBone = new BoneInfo (); rootBone.name = "Pelvis"; rootBone.anchor = pelvis; rootBone.parent = null; rootBone.density = 2.5F; bones.Add (rootBone); AddMirroredJoint ("Hips", leftHips, rightHips, "Pelvis", worldRight, worldForward, -20, 70, 30, typeof (CapsuleCollider), 0.3F, 1.5F); AddMirroredJoint ("Knee", leftKnee, rightKnee, "Hips", worldRight, worldForward, -80, 0, 0, typeof (CapsuleCollider), 0.25F, 1.5F); AddJoint ("Middle Spine", middleSpine, "Pelvis", worldRight, worldForward, -20, 20, 10, null, 1, 2.5F); AddMirroredJoint ("Arm", leftArm, rightArm, "Middle Spine", worldUp, worldForward, -70, 10, 50, typeof (CapsuleCollider), 0.25F, 1.0F); AddMirroredJoint ("Elbow", leftElbow, rightElbow, "Arm", worldForward, worldUp, -90, 0, 0, typeof (CapsuleCollider), 0.20F, 1.0F); AddJoint ("Head", head, "Middle Spine", worldRight, worldForward, -40, 25, 25, null, 1, 1.0F); } void OnWizardCreate () { removeRagdoll (); OnWizardUpdate (); BuildCapsules (); AddSpineAndChestColliders (); AddHeadCollider (); BuildBodies (); BuildJoints (); CalculateMass (); } BoneInfo FindBone (string name) { foreach (BoneInfo bone in bones) { if (bone.name == name) return bone; } return null; } void AddMirroredJoint (string name, Transform leftAnchor, Transform rightAnchor, string parent, Vector3 worldTwistAxis, Vector3 worldSwingAxis, float minLimit, float maxLimit, float swingLimit, Type colliderType, float radiusScale, float density) { if (leftAnchor != null) { AddJoint ("Left " + name, leftAnchor, parent, worldTwistAxis, worldSwingAxis, minLimit, maxLimit, swingLimit, colliderType, radiusScale, density); } if (rightAnchor != null) { AddJoint ("Right " + name, rightAnchor, parent, worldTwistAxis, worldSwingAxis, minLimit, maxLimit, swingLimit, colliderType, radiusScale, density); } } void AddJoint (string name, Transform anchor, string parent, Vector3 worldTwistAxis, Vector3 worldSwingAxis, float minLimit, float maxLimit, float swingLimit, Type colliderType, float radiusScale, float density) { BoneInfo bone = new BoneInfo (); bone.name = name; bone.anchor = anchor; bone.axis = worldTwistAxis; bone.normalAxis = worldSwingAxis; bone.minLimit = minLimit; bone.maxLimit = maxLimit; bone.swingLimit = swingLimit; bone.density = density; bone.colliderType = colliderType; bone.radiusScale = radiusScale; if (FindBone (parent) != null) { bone.parent = FindBone (parent); } else if (name.StartsWith ("Left")) { bone.parent = FindBone ("Left " + parent); } else if (name.StartsWith ("Right")) { bone.parent = FindBone ("Right " + parent); } bone.parent.children.Add (bone); bones.Add (bone); } void BuildCapsules () { foreach (BoneInfo bone in bones) { if (bone.colliderType != typeof (CapsuleCollider)) continue; int direction; float distance; if (bone.children.Count == 1) { BoneInfo childBone = (BoneInfo)bone.children [0]; Vector3 endPoint = childBone.anchor.position; CalculateDirection (bone.anchor.InverseTransformPoint (endPoint), out direction, out distance); } else { Vector3 endPoint = (bone.anchor.position - bone.parent.anchor.position) + bone.anchor.position; CalculateDirection (bone.anchor.InverseTransformPoint (endPoint), out direction, out distance); if (bone.anchor.GetComponentsInChildren (typeof (Transform)).Length > 1) { Bounds bounds = new Bounds (); foreach (Transform child in bone.anchor.GetComponentsInChildren (typeof (Transform))) { bounds.Encapsulate (bone.anchor.InverseTransformPoint (child.position)); } if (distance > 0) distance = bounds.max [direction]; else distance = bounds.min [direction]; } } CapsuleCollider collider = bone.anchor.gameObject.AddComponent (); collider.direction = direction; Vector3 center = Vector3.zero; center [direction] = distance * 0.5F; collider.center = center; collider.height = Mathf.Abs (distance); collider.radius = Mathf.Abs (distance * bone.radiusScale); } } void BuildBodies () { foreach (BoneInfo bone in bones) { bone.anchor.gameObject.AddComponent (); if (setMassOnRagdoll) { bone.anchor.GetComponent ().mass = bone.density; } } } void BuildJoints () { foreach (BoneInfo bone in bones) { if (bone.parent == null) continue; CharacterJoint joint = bone.anchor.gameObject.AddComponent (); bone.joint = joint; // Setup connection and axis joint.axis = CalculateDirectionAxis (bone.anchor.InverseTransformDirection (bone.axis)); joint.swingAxis = CalculateDirectionAxis (bone.anchor.InverseTransformDirection (bone.normalAxis)); joint.anchor = Vector3.zero; joint.connectedBody = bone.parent.anchor.GetComponent (); joint.enablePreprocessing = false; // turn off to handle degenerated scenarios, like spawning inside geometry. // Setup limits SoftJointLimit limit = new SoftJointLimit (); limit.contactDistance = 0; // default to zero, which automatically sets contact distance. limit.limit = bone.minLimit; joint.lowTwistLimit = limit; limit.limit = bone.maxLimit; joint.highTwistLimit = limit; limit.limit = bone.swingLimit; joint.swing1Limit = limit; limit.limit = 0; joint.swing2Limit = limit; } } void CalculateMassRecurse (BoneInfo bone) { float mass = bone.anchor.GetComponent ().mass; foreach (BoneInfo child in bone.children) { CalculateMassRecurse (child); mass += child.summedMass; } bone.summedMass = mass; } void CalculateMass () { // Calculate allChildMass by summing all bodies CalculateMassRecurse (rootBone); if (setMassOnRagdoll) { // Rescale the mass so that the whole character weights totalMass float massScale = totalMass / rootBone.summedMass; foreach (BoneInfo bone in bones) { bone.anchor.GetComponent ().mass *= massScale; } } // Recalculate allChildMass by summing all bodies CalculateMassRecurse (rootBone); } static void CalculateDirection (Vector3 point, out int direction, out float distance) { // Calculate longest axis direction = 0; if (Mathf.Abs (point [1]) > Mathf.Abs (point [0])) direction = 1; if (Mathf.Abs (point [2]) > Mathf.Abs (point [direction])) direction = 2; distance = point [direction]; } static Vector3 CalculateDirectionAxis (Vector3 point) { int direction = 0; float distance; CalculateDirection (point, out direction, out distance); Vector3 axis = Vector3.zero; if (distance > 0) axis [direction] = 1.0F; else axis [direction] = -1.0F; return axis; } static int SmallestComponent (Vector3 point) { int direction = 0; if (Mathf.Abs (point [1]) < Mathf.Abs (point [0])) direction = 1; if (Mathf.Abs (point [2]) < Mathf.Abs (point [direction])) direction = 2; return direction; } static int LargestComponent (Vector3 point) { int direction = 0; if (Mathf.Abs (point [1]) > Mathf.Abs (point [0])) direction = 1; if (Mathf.Abs (point [2]) > Mathf.Abs (point [direction])) direction = 2; return direction; } static int SecondLargestComponent (Vector3 point) { int smallest = SmallestComponent (point); int largest = LargestComponent (point); if (smallest < largest) { int temp = largest; largest = smallest; smallest = temp; } if (smallest == 0 && largest == 1) return 2; else if (smallest == 0 && largest == 2) return 1; else return 0; } Bounds Clip (Bounds bounds, Transform relativeTo, Transform clipTransform, bool below) { int axis = LargestComponent (bounds.size); if (Vector3.Dot (worldUp, relativeTo.TransformPoint (bounds.max)) > Vector3.Dot (worldUp, relativeTo.TransformPoint (bounds.min)) == below) { Vector3 min = bounds.min; min [axis] = relativeTo.InverseTransformPoint (clipTransform.position) [axis]; bounds.min = min; } else { Vector3 max = bounds.max; max [axis] = relativeTo.InverseTransformPoint (clipTransform.position) [axis]; bounds.max = max; } return bounds; } Bounds GetSpineAndChestBounds (Transform relativeTo) { // Pelvis bounds Bounds bounds = new Bounds (); if (leftHips != null) { bounds.Encapsulate (relativeTo.InverseTransformPoint (leftHips.position)); } if (rightHips != null) { bounds.Encapsulate (relativeTo.InverseTransformPoint (rightHips.position)); } if (leftArm != null) { bounds.Encapsulate (relativeTo.InverseTransformPoint (leftArm.position)); } if (rightArm != null) { bounds.Encapsulate (relativeTo.InverseTransformPoint (rightArm.position)); } Vector3 size = bounds.size; size [SmallestComponent (bounds.size)] = size [LargestComponent (bounds.size)] / 2.0F; bounds.size = size; return bounds; } void AddSpineAndChestColliders () { // Middle spine and pelvis if (middleSpine != null && pelvis != null) { Bounds bounds; BoxCollider box; // Middle spine bounds bounds = Clip (GetSpineAndChestBounds (pelvis), pelvis, middleSpine, false); box = pelvis.gameObject.AddComponent (); box.center = bounds.center; box.size = bounds.size; bounds = Clip (GetSpineAndChestBounds (middleSpine), middleSpine, middleSpine, true); box = middleSpine.gameObject.AddComponent (); box.center = bounds.center; box.size = bounds.size; } else { // Only pelvis Bounds bounds = new Bounds (); if (leftHips != null) { bounds.Encapsulate (pelvis.InverseTransformPoint (leftHips.position)); } if (rightHips != null) { bounds.Encapsulate (pelvis.InverseTransformPoint (rightHips.position)); } if (leftArm != null) { bounds.Encapsulate (pelvis.InverseTransformPoint (leftArm.position)); } if (rightArm != null) { bounds.Encapsulate (pelvis.InverseTransformPoint (rightArm.position)); } Vector3 size = bounds.size; size [SmallestComponent (bounds.size)] = size [LargestComponent (bounds.size)] / 2.0F; BoxCollider box = pelvis.gameObject.AddComponent (); box.center = bounds.center; box.size = size; } } void AddHeadCollider () { if (head.GetComponent ()) { Destroy (head.GetComponent ()); } float radius = 0; if (leftArm != null && rightArm != null) { radius = GKC_Utils.distance (leftArm.transform.position, rightArm.transform.position); } else { radius = 1; } radius /= 4; SphereCollider sphere = head.gameObject.AddComponent (); sphere.radius = radius; Vector3 center = Vector3.zero; int direction; float distance; CalculateDirection (head.InverseTransformPoint (pelvis.position), out direction, out distance); if (distance > 0) center [direction] = -radius; else center [direction] = radius; sphere.center = center; } public bool checkAllBonesFound () { bool checkResult = false; if ((head != null) && (pelvis != null) && (middleSpine != null)) { if (ignoreEmptyLimbFieldsEnabled) { checkResult = true; } else { checkResult = ((leftHips != null) && (leftKnee != null) && (rightHips != null) && (rightKnee != null) && (leftArm != null) && (leftElbow != null) && (rightArm != null) && (rightElbow != null)); } } return checkResult; } public void buildRagdoll () { if (!checkAllBonesFound ()) { print ("WARNING: not all bones necessary for the ragdoll of the new player has been found, " + "assign them manually on the top part to make sure all of them are configured correctly\n"); return; } OnWizardCreate (); updateRagdollActivatorParts (true); updateComponent (); } public void removeRagdoll () { if (bones.Count > 0) { for (int i = 0; i < bones.Count; i++) { if (bones [i].anchor != null) { Component [] components = bones [i].anchor.GetComponents (typeof (Joint)); foreach (Joint child in components) { DestroyImmediate (child); } components = bones [i].anchor.GetComponents (typeof (Rigidbody)); foreach (Rigidbody child in components) { DestroyImmediate (child); } components = bones [i].anchor.GetComponents (typeof (Collider)); foreach (Collider child in components) { DestroyImmediate (child); } } } bones.Clear (); } if (characterBody != null) { Component [] components = characterBody.GetComponentsInChildren (typeof (Joint)); foreach (Joint child in components) { DestroyImmediate (child); } components = characterBody.GetComponentsInChildren (typeof (Rigidbody)); foreach (Rigidbody child in components) { DestroyImmediate (child); } components = characterBody.GetComponentsInChildren (typeof (Collider)); foreach (Collider child in components) { DestroyImmediate (child); } } updateComponent (); } void updateComponent () { GKC_Utils.updateComponent (this); } public void updateRagdollActivatorParts (bool addingRagdollOnEditorTime) { if (mainRagdollActivator != null) { List bonesList = new List (); List rigidbodyList = new List (); List colliderList = new List (); for (int i = 0; i < bones.Count; i++) { bonesList.Add (bones [i].anchor); Rigidbody currentRigidbody = bones [i].anchor.GetComponent (); if (currentRigidbody != null) { rigidbodyList.Add (currentRigidbody); } Collider currentCollider = bones [i].anchor.GetComponent (); if (currentRigidbody != null) { colliderList.Add (currentCollider); } } mainRagdollActivator.addNewExtraRagdollInfo (characterBody, ragdollName, bonesList, rigidbodyList, colliderList, pelvis, rotationYOffset, addingRagdollOnEditorTime); if (addingRagdollOnEditorTime) { print ("Ragdoll Activator components assigned and updated"); } } } public void updateRagdollActivatorPatsIngame () { updateRagdollActivatorParts (false); } public void enableRagdollElements () { setActiveRagdollElements (true); } public void disableRagdollElements () { setActiveRagdollElements (false); } public void setActiveRagdollElements (bool state) { for (int i = 0; i < bones.Count; i++) { //enable or disalbe colliders in the ragdoll if (bones [i].anchor != null) { Collider currentCollider = bones [i].anchor.GetComponent (); if (currentCollider != null) { currentCollider.enabled = state; } Rigidbody currentRigidbody = bones [i].anchor.GetComponent (); if (currentRigidbody != null) { currentRigidbody.isKinematic = !state; } } } } public void activateRagdollState () { if (ragdollActivated) { return; } int newLayerIndex = LayerMask.NameToLayer ("Ignore Raycast"); for (int i = 0; i < bones.Count; i++) { //enable or disalbe colliders in the ragdoll if (bones [i].anchor != null) { Collider currentCollider = bones [i].anchor.GetComponent (); if (currentCollider != null) { currentCollider.enabled = true; if (useListToIgnoreOnRagdollActivated) { for (int j = 0; j < colliderListToIgnoreOnRagdollActivated.Count; j++) { if (colliderListToIgnoreOnRagdollActivated [j] != null) { Physics.IgnoreCollision (colliderListToIgnoreOnRagdollActivated [j], currentCollider); } } } } Rigidbody currentRigidbody = bones [i].anchor.GetComponent (); if (currentRigidbody != null) { currentRigidbody.gameObject.layer = newLayerIndex; currentRigidbody.isKinematic = false; } } } if (characterBody != null) { characterBody.transform.SetParent (null); } ragdollActivated = true; } public void activateEventToSendBonesList () { List bonesList = new List (); for (int i = 0; i < bones.Count; i++) { bonesList.Add (bones [i].anchor); } eventToSentBonesList.Invoke (bonesList); } public void getRagdollElementsOnBoneListManually () { bones.Clear (); Component [] components = characterBody.GetComponentsInChildren (typeof (Rigidbody)); int currentNumberOfBone = 0; foreach (Rigidbody child in components) { currentNumberOfBone++; BoneInfo newBone = new BoneInfo (); newBone.name = "Bone " + currentNumberOfBone; newBone.anchor = child.transform; newBone.joint = child.gameObject.GetComponent (); bones.Add (newBone); } updateRagdollActivatorParts (true); } [System.Serializable] public class BoneInfo { public string name; public Transform anchor; public CharacterJoint joint; public BoneInfo parent; public float minLimit; public float maxLimit; public float swingLimit; public Vector3 axis; public Vector3 normalAxis; public float radiusScale; public Type colliderType; public ArrayList children = new ArrayList (); public float density; public float summedMass; // The mass of this and all children bodies } }