Ragdoll
From Unify Community Wiki
Author: Unity Tech
Description
This wizard makes ragdoll character creation a snap!
Usage
This wizard is included in Unity and accessible from Game Object -> Create Other -> Ragdoll menu item.
C# - RagdollBuilder.cs
using UnityEngine; using UnityEditor; using System.Collections; using System; class RagdollBuilder : ScriptableWizard { public Transform root; public Transform leftHips; public Transform leftKnee; public Transform leftFoot; public Transform rightHips; public Transform rightKnee; public Transform rightFoot; public Transform leftArm; public Transform leftElbow; public Transform rightArm; public Transform rightElbow; public Transform middleSpine; public Transform head; public float totalMass = 20; public float strength = 0.0F; Vector3 right = Vector3.right; Vector3 up = Vector3.up; Vector3 forward = Vector3.forward; Vector3 worldRight = Vector3.right; Vector3 worldUp = Vector3.up; Vector3 worldForward = Vector3.forward; public bool flipForward = false; 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 } ArrayList bones; BoneInfo rootBone; string CheckConsistency () { PrepareBones(); Hashtable map = new Hashtable (); foreach (BoneInfo bone in bones) { if (bone.anchor) { 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 OnDrawGizmos () { if (root) { Gizmos.color = Color.red; Gizmos.DrawRay (root.position, root.TransformDirection(right)); Gizmos.color = Color.green; Gizmos.DrawRay (root.position, root.TransformDirection(up)); Gizmos.color = Color.blue; Gizmos.DrawRay (root.position, root.TransformDirection(forward)); } } [MenuItem ("GameObject/Create Other/Ragdoll")] static void CreateWizard () { ScriptableWizard.DisplayWizard("Create Ragdoll", typeof (RagdollBuilder)); } 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 && root != null) up = CalculateDirectionAxis(root.InverseTransformPoint(head.position)); if (rightElbow != null && root != null) { Vector3 removed, temp; DecomposeVector(out temp, out removed, root.InverseTransformPoint(rightElbow.position), up); right = CalculateDirectionAxis(removed); } forward = Vector3.Cross(right, up); if (flipForward) forward = -forward; } void OnWizardUpdate () { errorString = CheckConsistency (); CalculateAxes(); if (errorString.Length != 0) { helpString = "Drag all bones from the hierarchy into their slots.\nMake sure your character is in T-Stand.\n"; } else { helpString = "Make sure your character is in T-Stand.\nMake sure the blue axis faces in the same direction the chracter is looking.\nUse flipForward to flip the direction"; } isValid = errorString.Length == 0; } void PrepareBones () { if (root) { worldRight = root.TransformDirection(right); worldUp = root.TransformDirection(up); worldForward = root.TransformDirection(forward); } bones = new ArrayList(); rootBone = new BoneInfo (); rootBone.name = "Root"; rootBone.anchor = root; rootBone.parent = null; rootBone.density = 2.5F; bones.Add (rootBone); AddMirroredJoint ("Hips", leftHips, rightHips, "Root", 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); // AddMirroredJoint ("Hips", leftHips, rightHips, "Root", worldRight, worldForward, -0, -70, 30, typeof(CapsuleCollider), 0.3F, 1.5F); // AddMirroredJoint ("Knee", leftKnee, rightKnee, "Hips", worldRight, worldForward, -0, -50, 0, typeof(CapsuleCollider), .25F, 1.5F); AddJoint ("Middle Spine", middleSpine, "Root", 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 () { Cleanup(); BuildCapsules(); AddBreastColliders(); AddHeadCollider(); BuildBodies (); BuildJoints (); CalculateMass(); CalculateSpringDampers(); } 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) { AddJoint ("Left " + name, leftAnchor, parent, worldTwistAxis, worldSwingAxis, minLimit, maxLimit, swingLimit, colliderType, radiusScale, density); 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 = (CapsuleCollider)bone.anchor.gameObject.AddComponent ("CapsuleCollider"); 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 Cleanup () { foreach (BoneInfo bone in bones) { if (!bone.anchor) continue; Component[] joints = bone.anchor.GetComponentsInChildren(typeof(Joint)); foreach (Joint joint in joints) DestroyImmediate(joint); Component[] bodies = bone.anchor.GetComponentsInChildren(typeof(Rigidbody)); foreach (Rigidbody body in bodies) DestroyImmediate(body); Component[] colliders = bone.anchor.GetComponentsInChildren(typeof(Collider)); foreach (Collider collider in colliders) DestroyImmediate(collider); } } void BuildBodies () { foreach (BoneInfo bone in bones) { bone.anchor.gameObject.AddComponent("Rigidbody"); // bone.anchor.rigidbody.SetDensity (bone.density); bone.anchor.rigidbody.mass = bone.density; } } void BuildJoints () { foreach (BoneInfo bone in bones) { if (bone.parent == null) continue; CharacterJoint joint = (CharacterJoint)bone.anchor.gameObject.AddComponent ("CharacterJoint"); 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.rigidbody; // Setup limits SoftJointLimit limit = new SoftJointLimit (); 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.rigidbody.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); // Rescale the mass so that the whole character weights totalMass float massScale = totalMass / rootBone.summedMass; foreach (BoneInfo bone in bones) bone.anchor.rigidbody.mass *= massScale; // Recalculate allChildMass by summing all bodies CalculateMassRecurse(rootBone); } ///@todo: This should take into account the inertia tensor. JointDrive CalculateSpringDamper (float frequency, float damping, float mass) { JointDrive drive = new JointDrive(); drive.positionSpring = 9 * frequency * frequency * mass; drive.positionDamper = 4.5F * frequency * damping * mass; return drive; } void CalculateSpringDampers () { // Calculate the rotation drive based on the strength and how much mass the character needs to pull around. foreach (BoneInfo bone in bones) { if (bone.joint) bone.joint.rotationDrive = CalculateSpringDamper (strength / 100.0F, 1, bone.summedMass); } } /* void AddJoint (string name, Complexity complexity, Transform anchor, Transform connectTo, Vector3 worldTwistAxis, Vector3 worldSwingAxis, float minLimit, float maxLimit, float swingLimit, float mass) { if (!connectTo.rigidbody) connectTo.gameObject.AddComponent("Rigidbody"); CharacterJoint joint = (CharacterJoint)anchor.gameObject.AddComponent ("CharacterJoint"); joint.axis = CalculateDirectionAxis (anchor.InverseTransformDirection(worldTwistAxis)); joint.swingAxis = CalculateDirectionAxis (anchor.InverseTransformDirection(worldSwingAxis)); joint.anchor = Vector3.zero; joint.connectedBody = connectTo.rigidbody; SoftJointLimit limit = new SoftJointLimit (); limit.limit = minLimit; joint.lowTwistLimit = limit; limit.limit = maxLimit; joint.highTwistLimit = limit; limit.limit = swingLimit; joint.swing1Limit = limit; limit.limit = 0; joint.swing2Limit = limit; JointDrive drive = new JointDrive (); drive.spring = 0.2F; drive.damper = .1F; drive.force = 10.0F; joint.rotationDrive = drive; connectTo.rigidbody.mass = 2; anchor.rigidbody.mass = 2; } /* void BuildCapsule (BoneInfo bone) { CapsuleCollider collider = (CapsuleCollider)bone.body.gameObject.AddComponent ("CapsuleCollider"); Bounds bounds; if (Editor.CalculateSkinnedAABB (bone.body, bone.body, out bounds)) { int direction; float distance; CalculateDirection (bounds.max, out direction, out distance); collider.direction = direction; collider.height = distance; collider.radius = SecondLargestComponent (); } else { } if (bone.children.Count == 1) { } } */ /* void AddCapsule (Transform anchor, Transform parent, Transform next, float directionScale, float radiusScale) { if (anchor.collider) Destroy (anchor.collider); Vector3 endPoint; if (next) endPoint = next.position; else endPoint = directionScale * (anchor.position - parent.position) + anchor.position; int direction; float distance; CalculateDirection (anchor.InverseTransformPoint(endPoint), out direction, out distance); distance = distance / anchor.lossyScale[direction]; CapsuleCollider collider = (CapsuleCollider)anchor.gameObject.AddComponent ("CapsuleCollider"); 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 * radiusScale); }*/ 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 GetBreastBounds (Transform relativeTo) { // Root bounds Bounds bounds = new Bounds (); bounds.Encapsulate (relativeTo.InverseTransformPoint (leftHips.position)); bounds.Encapsulate (relativeTo.InverseTransformPoint (rightHips.position)); bounds.Encapsulate (relativeTo.InverseTransformPoint (leftArm.position)); 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 AddBreastColliders () { // Middle spine and root if (middleSpine != null && root != null) { Bounds bounds; BoxCollider box; // Middle spine bounds bounds = Clip (GetBreastBounds (root), root, middleSpine, false); box = (BoxCollider)root.gameObject.AddComponent("BoxCollider"); box.center = bounds.center; box.size = bounds.size; bounds = Clip (GetBreastBounds (middleSpine), middleSpine, middleSpine, true); box = (BoxCollider)middleSpine.gameObject.AddComponent("BoxCollider"); box.center = bounds.center; box.size = bounds.size; } // Only root else { Bounds bounds = new Bounds (); bounds.Encapsulate (root.InverseTransformPoint (leftHips.position)); bounds.Encapsulate (root.InverseTransformPoint (rightHips.position)); bounds.Encapsulate (root.InverseTransformPoint (leftArm.position)); bounds.Encapsulate (root.InverseTransformPoint (rightArm.position)); Vector3 size = bounds.size; size[SmallestComponent (bounds.size)] = size[LargestComponent (bounds.size)] / 2.0F; BoxCollider box = (BoxCollider)root.gameObject.AddComponent("BoxCollider"); box.center = bounds.center; box.size = size; } } void AddHeadCollider () { if (head.collider) Destroy (head.collider); float radius = Vector3.Distance(leftArm.transform.position, rightArm.transform.position); radius /= 4; SphereCollider sphere = (SphereCollider)head.gameObject.AddComponent ("SphereCollider"); sphere.radius = radius; Vector3 center = Vector3.zero; int direction; float distance; CalculateDirection (head.InverseTransformPoint(root.position), out direction, out distance); if (distance > 0) center[direction] = -radius; else center[direction] = radius; sphere.center = center; } }