using UnityEngine; using System.Collections; namespace RootMotion.Dynamics { /// /// Automatically generates a ragdoll for a Biped character. /// [HelpURL("https://www.youtube.com/watch?v=y-luLRVmL7E&index=1&list=PLVxSIA1OaTOuE2SB9NUbckQ9r2hTg4mvL")] [AddComponentMenu("Scripts/RootMotion.Dynamics/Ragdoll Manager/Biped Ragdoll Creator")] public class BipedRagdollCreator : RagdollCreator { // Open the User Manual URL [ContextMenu("User Manual")] void OpenUserManual() { Application.OpenURL("http://root-motion.com/puppetmasterdox/html/page1.html"); } // Open the Script Reference URL [ContextMenu("Scrpt Reference")] void OpenScriptReference() { Application.OpenURL("http://root-motion.com/puppetmasterdox/html/class_root_motion_1_1_dynamics_1_1_biped_ragdoll_creator.html#details"); } // Open a video tutorial about setting up the component [ContextMenu("TUTORIAL VIDEO")] void OpenTutorial() { Application.OpenURL("https://www.youtube.com/watch?v=y-luLRVmL7E&index=1&list=PLVxSIA1OaTOuE2SB9NUbckQ9r2hTg4mvL"); } public bool canBuild; public BipedRagdollReferences references; public Options options = Options.Default; [System.Serializable] public struct Options { //[SpaceAttribute(10)] public float weight; [HeaderAttribute("Optional Bones")] public bool spine; public bool chest; public bool hands; public bool feet; [HeaderAttribute("Joints")] public JointType joints; public float jointRange; [HeaderAttribute("Colliders")] public float colliderLengthOverlap; public ColliderType torsoColliders; public ColliderType headCollider; public ColliderType armColliders; public ColliderType handColliders; public ColliderType legColliders; public ColliderType footColliders; public bool fixFootColliderRotation; public static Options Default { get { Options o = new Options(); // General Settings o.weight = 75f; o.colliderLengthOverlap = 0.1f; o.jointRange = 1f; o.chest = true; o.headCollider = ColliderType.Capsule; o.armColliders = ColliderType.Capsule; o.hands = true; o.handColliders = ColliderType.Capsule; o.legColliders = ColliderType.Capsule; o.feet = true; o.fixFootColliderRotation = true; return o; } } } public static Options AutodetectOptions(BipedRagdollReferences r) { Options o = Options.Default; if (r.spine == null) o.spine = false; if (r.chest == null) o.chest = false; // If chest bone is too high if (o.chest && Vector3.Dot(r.root.up, r.chest.position - GetUpperArmCentroid(r)) > 0f) { o.chest = false; if (r.spine != null) o.spine = true; } return o; } public static void Create(BipedRagdollReferences r, Options options) { string msg = string.Empty; if (!r.IsValid(ref msg)) { Debug.LogWarning(msg); return; } // Clean up ClearAll(r.root); // Colliders CreateColliders(r, options); // Rigidbodies MassDistribution(r, options); // Joints CreateJoints(r, options); } private static void CreateColliders(BipedRagdollReferences r, Options options) { // Torso Vector3 upperArmToHeadCentroid = GetUpperArmToHeadCentroid(r); if (r.spine == null) options.spine = false; if (r.chest == null) options.chest = false; Vector3 shoulderDirection = r.rightUpperArm.position - r.leftUpperArm.position; float torsoWidth = shoulderDirection.magnitude; float torsoProportionAspect = 0.6f; // Hips Vector3 hipsStartPoint = r.hips.position; // Making sure the hip bone is not at the feet float toHead = Vector3.Distance(r.head.position, r.root.position); float toHips = Vector3.Distance(r.hips.position, r.root.position); if (toHips < toHead * 0.2f) { hipsStartPoint = Vector3.Lerp(r.leftUpperLeg.position, r.rightUpperLeg.position, 0.5f); } Vector3 lastEndPoint = options.spine ? r.spine.position : (options.chest ? r.chest.position : upperArmToHeadCentroid); hipsStartPoint += (hipsStartPoint - upperArmToHeadCentroid) * 0.1f; float hipsWidth = options.spine || options.chest ? torsoWidth * 0.8f : torsoWidth; CreateCollider(r.hips, hipsStartPoint, lastEndPoint, options.torsoColliders, options.colliderLengthOverlap, hipsWidth, torsoProportionAspect, shoulderDirection); // Spine if (options.spine) { Vector3 spineStartPoint = lastEndPoint; lastEndPoint = options.chest ? r.chest.position : upperArmToHeadCentroid; float spineWidth = options.chest ? torsoWidth * 0.75f : torsoWidth; CreateCollider(r.spine, spineStartPoint, lastEndPoint, options.torsoColliders, options.colliderLengthOverlap, spineWidth, torsoProportionAspect, shoulderDirection); } if (options.chest) { Vector3 chestStartPoint = lastEndPoint; lastEndPoint = upperArmToHeadCentroid; CreateCollider(r.chest, chestStartPoint, lastEndPoint, options.torsoColliders, options.colliderLengthOverlap, torsoWidth, torsoProportionAspect, shoulderDirection); } // Head Vector3 headStartPoint = lastEndPoint; Vector3 headEndPoint = headStartPoint + (headStartPoint - hipsStartPoint) * 0.45f; Vector3 axis = r.head.TransformVector(AxisTools.GetAxisVectorToDirection(r.head, headEndPoint - headStartPoint)); headEndPoint = headStartPoint + Vector3.Project(headEndPoint - headStartPoint, axis).normalized * (headEndPoint - headStartPoint).magnitude; CreateCollider(r.head, headStartPoint, headEndPoint, options.headCollider, options.colliderLengthOverlap, Vector3.Distance(headStartPoint, headEndPoint) * 0.8f); // Arms float armWidthAspect = 0.4f; float leftArmWidth = Vector3.Distance(r.leftUpperArm.position, r.leftLowerArm.position) * armWidthAspect; CreateCollider(r.leftUpperArm, r.leftUpperArm.position, r.leftLowerArm.position, options.armColliders, options.colliderLengthOverlap, leftArmWidth); CreateCollider(r.leftLowerArm, r.leftLowerArm.position, r.leftHand.position, options.armColliders, options.colliderLengthOverlap, leftArmWidth * 0.9f); float rightArmWidth = Vector3.Distance(r.rightUpperArm.position, r.rightLowerArm.position) * armWidthAspect; CreateCollider(r.rightUpperArm, r.rightUpperArm.position, r.rightLowerArm.position, options.armColliders, options.colliderLengthOverlap, rightArmWidth); CreateCollider(r.rightLowerArm, r.rightLowerArm.position, r.rightHand.position, options.armColliders, options.colliderLengthOverlap, rightArmWidth * 0.9f); // Legs float legWidthAspect = 0.3f; float leftLegWidth = Vector3.Distance(r.leftUpperLeg.position, r.leftLowerLeg.position) * legWidthAspect; CreateCollider(r.leftUpperLeg, r.leftUpperLeg.position, r.leftLowerLeg.position, options.legColliders, options.colliderLengthOverlap, leftLegWidth); CreateCollider(r.leftLowerLeg, r.leftLowerLeg.position, r.leftFoot.position, options.legColliders, options.colliderLengthOverlap, leftLegWidth * 0.9f); float rightLegWidth = Vector3.Distance(r.rightUpperLeg.position, r.rightLowerLeg.position) * legWidthAspect; CreateCollider(r.rightUpperLeg, r.rightUpperLeg.position, r.rightLowerLeg.position, options.legColliders, options.colliderLengthOverlap, rightLegWidth); CreateCollider(r.rightLowerLeg, r.rightLowerLeg.position, r.rightFoot.position, options.legColliders, options.colliderLengthOverlap, rightLegWidth * 0.9f); // Hands if (options.hands) { CreateHandCollider(r.leftHand, r.leftLowerArm, r.root, options); CreateHandCollider(r.rightHand, r.rightLowerArm, r.root, options); } // Feet if (options.feet) { CreateFootCollider(r.leftFoot, r.leftLowerLeg, r.leftUpperLeg, r.root, options); CreateFootCollider(r.rightFoot, r.rightLowerLeg, r.rightUpperLeg, r.root, options); } } private static Collider CopyCollider(Collider c, GameObject destination) { if (c is CapsuleCollider) { var newCapsule = CopyCapsuleCollider(c as CapsuleCollider, destination); return newCapsule as Collider; } else if (c is SphereCollider) { var newSphere = CopySphereCollider(c as SphereCollider, destination); return newSphere as Collider; } else if (c is BoxCollider) { var newBox = CopyBoxCollider(c as BoxCollider, destination); return newBox as Collider; } return null; } private static CapsuleCollider CopyCapsuleCollider(CapsuleCollider o, GameObject destination) { CapsuleCollider d = destination.GetComponent(); if (d == null) d = destination.AddComponent(); d.isTrigger = o.isTrigger; d.sharedMaterial = o.sharedMaterial; d.center = o.center; d.radius = o.radius; d.height = o.height; d.direction = o.direction; return d; } private static SphereCollider CopySphereCollider(SphereCollider o, GameObject destination) { SphereCollider d = destination.GetComponent(); if (d == null) d = destination.AddComponent(); d.isTrigger = o.isTrigger; d.sharedMaterial = o.sharedMaterial; d.center = o.center; d.radius = o.radius; return d; } private static BoxCollider CopyBoxCollider(BoxCollider o, GameObject destination) { BoxCollider d = destination.GetComponent(); if (d == null) d = destination.AddComponent(); d.isTrigger = o.isTrigger; d.sharedMaterial = o.sharedMaterial; d.center = o.center; d.size = o.size; return d; } private static void CreateHandCollider(Transform hand, Transform lowerArm, Transform root, Options options) { Vector3 axis = hand.TransformVector(AxisTools.GetAxisVectorToPoint(hand, GetChildCentroid(hand, lowerArm.position))); Vector3 endPoint = hand.position - (lowerArm.position - hand.position) * 0.75f; endPoint = hand.position + Vector3.Project(endPoint - hand.position, axis).normalized * (endPoint - hand.position).magnitude; CreateCollider(hand, hand.position, endPoint, options.handColliders, options.colliderLengthOverlap, Vector3.Distance(endPoint, hand.position) * 0.5f); } private static void CreateFootCollider(Transform foot, Transform lowerLeg, Transform upperLeg, Transform root, Options options) { float legHeight = (upperLeg.position - foot.position).magnitude; Vector3 axis = foot.TransformVector(AxisTools.GetAxisVectorToPoint(foot, GetChildCentroid(foot, foot.position + root.forward) + root.forward * legHeight * 0.2f)); Vector3 endPoint = foot.position + root.forward * legHeight * 0.25f; endPoint = foot.position + Vector3.Project(endPoint - foot.position, axis).normalized * (endPoint - foot.position).magnitude; float width = Vector3.Distance(endPoint, foot.position) * 0.5f; Vector3 startPoint = foot.position; bool footBelowRoot = Vector3.Dot(root.up, foot.position - root.position) < 0f; Vector3 heightOffset = footBelowRoot ? Vector3.zero : Vector3.Project((startPoint - root.up * width * 0.5f) - root.position, root.up); Vector3 direction = endPoint - startPoint; startPoint -= direction * 0.2f; if (options.fixFootColliderRotation) { Vector3 fAxis = AxisTools.GetAxisVectorToDirection(foot, root.forward); if (Vector3.Dot(foot.rotation * fAxis, root.forward) < 0f) fAxis = -fAxis; Vector3 normal = Vector3.up; Vector3 tangent = foot.rotation * fAxis; Vector3.OrthoNormalize(ref normal, ref tangent); Vector3 fallback = foot.position + tangent; Vector3 childCentroid = GetChildCentroidRecursive(foot, fallback); Vector3 toChildC = childCentroid - foot.position; var child = new GameObject("Foot Collider").transform; child.parent = foot; child.localPosition = Vector3.zero; child.localRotation = Quaternion.identity; var collider = CreateCollider(child, startPoint - heightOffset, endPoint - heightOffset, options.footColliders, options.colliderLengthOverlap, width, foot); child.rotation = Quaternion.FromToRotation(child.rotation * fAxis, childCentroid - child.position) * child.rotation; Orthogonize(child, root.forward, root.up); Orthogonize(child, root.right, root.up); if (childCentroid != fallback) { Vector3 center = Vector3.Lerp(foot.position, childCentroid, 0.5f); Vector3 colliderCenter = GetColliderCenter(collider); child.position += center - colliderCenter; float bottomY = GetColliderBottom(collider, root.up); child.position += Vector3.up * (root.position.y - bottomY); } } else { CreateCollider(foot, startPoint - heightOffset, endPoint - heightOffset, options.footColliders, options.colliderLengthOverlap, width); } } public static Collider FixFootCollider(Transform foot, Transform root) { Vector3 fAxis = AxisTools.GetAxisVectorToDirection(foot, root.forward); if (Vector3.Dot(foot.rotation * fAxis, root.forward) < 0f) fAxis = -fAxis; Vector3 normal = Vector3.up; Vector3 tangent = foot.rotation * fAxis; Vector3.OrthoNormalize(ref normal, ref tangent); Vector3 fallback = foot.position + tangent; Vector3 childCentroid = GetChildCentroidRecursive(foot, fallback); Vector3 toChildC = childCentroid - foot.position; var child = new GameObject("Foot Collider").transform; child.parent = foot; child.localPosition = Vector3.zero; child.localRotation = Quaternion.identity; var footCollider = foot.GetComponent(); var collider = CopyCollider(footCollider, child.gameObject); if (Application.isPlaying) { Destroy(footCollider); } else { DestroyImmediate(footCollider); } child.rotation = Quaternion.FromToRotation(child.rotation * fAxis, childCentroid - child.position) * child.rotation; Orthogonize(child, root.forward, root.up); Orthogonize(child, root.right, root.up); if (childCentroid != fallback) { Vector3 center = Vector3.Lerp(foot.position, childCentroid, 0.5f); Vector3 colliderCenter = GetColliderCenter(collider); child.position += center - colliderCenter; float bottomY = GetColliderBottom(collider, root.up); child.position += Vector3.up * (root.position.y - bottomY); } return collider; } private static Vector3 GetColliderCenter(Collider c) { if (c is BoxCollider) { return c.transform.TransformPoint((c as BoxCollider).center); } if (c is CapsuleCollider) { return c.transform.TransformPoint((c as CapsuleCollider).center); } return c.transform.position; } private static float GetColliderBottom(Collider c, Vector3 up) { var t = c.transform; if (c is BoxCollider) { var box = c as BoxCollider; Vector3 axis = AxisTools.GetAxisVectorToDirection(t, -up); if (Vector3.Dot(t.rotation * axis, -up) < 0f) axis = -axis; var scaled = Vector3.Scale(box.size, axis * 0.5f); return (t.TransformPoint(box.center) + t.rotation * scaled).y; } if (c is CapsuleCollider) { var capsule = c as CapsuleCollider; Vector3 axis = AxisTools.GetAxisVectorToDirection(t, -up); if (Vector3.Dot(t.rotation * axis, -up) < 0f) axis = -axis; var scaled = capsule.radius * axis * 0.5f; return (t.TransformPoint(capsule.center) + t.rotation * scaled).y; } return GetColliderCenter(c).y; } private static void Orthogonize(Transform t, Vector3 direction, Vector3 normal) { Vector3 axis = AxisTools.GetAxisVectorToDirection(t, direction); if (Vector3.Dot(t.rotation * axis, direction) < 0f) axis = -axis; Vector3 tangent = t.rotation * axis; Vector3.OrthoNormalize(ref normal, ref tangent); t.rotation = Quaternion.FromToRotation(t.rotation * axis, tangent) * t.rotation; } private static Vector3 GetChildCentroidRecursive(Transform t, Vector3 fallback) { var children = t.GetComponentsInChildren(); if (children.Length < 2) return fallback; Vector3 c = Vector3.zero; for (int i = 1; i < children.Length; i++) { c += children[i].position; } c /= children.Length - 1; return c; } private static Vector3 GetChildCentroid(Transform t, Vector3 fallback) { if (t.childCount == 0) return fallback; Vector3 c = Vector3.zero; for (int i = 0; i < t.childCount; i++) { c += t.GetChild(i).position; } c /= (float)t.childCount; return c; } private static void MassDistribution(BipedRagdollReferences r, Options o) { int torsoBones = 3; if (r.spine == null) { o.spine = false; torsoBones--; } if (r.chest == null) { o.chest = false; torsoBones--; } float torsoPrc = 0.508f / (float)torsoBones; float headPrc = 0.0732f; float upperArmPrc = 0.027f; float lowerArmPrc = 0.016f; float handPrc = 0.0066f; float upperLegPrc = 0.0988f; float lowerLegPrc = 0.0465f; float footPrc = 0.0145f; r.hips.GetComponent().mass = torsoPrc * o.weight; if (o.spine) r.spine.GetComponent().mass = torsoPrc * o.weight; if (o.chest) r.chest.GetComponent().mass = torsoPrc * o.weight; r.head.GetComponent().mass = headPrc * o.weight; r.leftUpperArm.GetComponent().mass = upperArmPrc * o.weight; r.rightUpperArm.GetComponent().mass = r.leftUpperArm.GetComponent().mass; r.leftLowerArm.GetComponent().mass = lowerArmPrc * o.weight; r.rightLowerArm.GetComponent().mass = r.leftLowerArm.GetComponent().mass; if (o.hands) { r.leftHand.GetComponent().mass = handPrc * o.weight; r.rightHand.GetComponent().mass = r.leftHand.GetComponent().mass; } r.leftUpperLeg.GetComponent().mass = upperLegPrc * o.weight; r.rightUpperLeg.GetComponent().mass = r.leftUpperLeg.GetComponent().mass; r.leftLowerLeg.GetComponent().mass = lowerLegPrc * o.weight; r.rightLowerLeg.GetComponent().mass = r.leftLowerLeg.GetComponent().mass; if (o.feet) { r.leftFoot.GetComponent().mass = footPrc * o.weight; r.rightFoot.GetComponent().mass = r.leftFoot.GetComponent().mass; } } private static void CreateJoints(BipedRagdollReferences r, Options o) { // Torso if (r.spine == null) o.spine = false; if (r.chest == null) o.chest = false; float spineMinSwing = -30f * o.jointRange; float spineMaxSwing = 10f * o.jointRange; float spineSwing2 = 25f * o.jointRange; float spineTwist = 25f * o.jointRange; CreateJoint(new CreateJointParams( r.hips.GetComponent(), null, (o.spine ? r.spine : (o.chest ? r.chest : r.head)), r.root.right, new CreateJointParams.Limits(0f, 0f, 0f, 0f), o.joints )); if (o.spine) { CreateJoint(new CreateJointParams( r.spine.GetComponent(), r.hips.GetComponent(), (o.chest ? r.chest : r.head), r.root.right, new CreateJointParams.Limits(spineMinSwing, spineMaxSwing, spineSwing2, spineTwist), o.joints )); } if (o.chest) { CreateJoint(new CreateJointParams( r.chest.GetComponent(), (o.spine ? r.spine.GetComponent() : r.hips.GetComponent()), r.head, r.root.right, new CreateJointParams.Limits(spineMinSwing, spineMaxSwing, spineSwing2, spineTwist), o.joints )); } // Head Transform lastTorsoBone = o.chest ? r.chest : (o.spine ? r.spine : r.hips); CreateJoint(new CreateJointParams( r.head.GetComponent(), lastTorsoBone.GetComponent(), null, r.root.right, new CreateJointParams.Limits(-30f, 30f, 30f, 85f), o.joints )); // Arms CreateJointParams.Limits upperArmLimits = new CreateJointParams.Limits(-35f * o.jointRange, 120f * o.jointRange, 85f * o.jointRange, 45 * o.jointRange); CreateJointParams.Limits lowerArmLimits = new CreateJointParams.Limits(0f, 140f * o.jointRange, 10f * o.jointRange, 45f * o.jointRange); CreateJointParams.Limits handLimits = new CreateJointParams.Limits(-50f * o.jointRange, 50f * o.jointRange, 50f * o.jointRange, 25f * o.jointRange); // Left Arm CreateLimbJoints( lastTorsoBone, r.leftUpperArm, r.leftLowerArm, r.leftHand, r.root, -r.root.right, o.joints, upperArmLimits, lowerArmLimits, handLimits); // Right Arm CreateLimbJoints( lastTorsoBone, r.rightUpperArm, r.rightLowerArm, r.rightHand, r.root, r.root.right, o.joints, upperArmLimits, lowerArmLimits, handLimits); // Legs CreateJointParams.Limits upperLegLimits = new CreateJointParams.Limits(-120f * o.jointRange, 35f * o.jointRange, 85f * o.jointRange, 45 * o.jointRange); CreateJointParams.Limits lowerLegLimits = new CreateJointParams.Limits(0f, 140f * o.jointRange, 10f * o.jointRange, 45f * o.jointRange); CreateJointParams.Limits footLimits = new CreateJointParams.Limits(-50f * o.jointRange, 50f * o.jointRange, 50f * o.jointRange, 25f * o.jointRange); // Left Leg CreateLimbJoints( r.hips, r.leftUpperLeg, r.leftLowerLeg, r.leftFoot, r.root, -r.root.up, o.joints, upperLegLimits, lowerLegLimits, footLimits); // Right Leg CreateLimbJoints( r.hips, r.rightUpperLeg, r.rightLowerLeg, r.rightFoot, r.root, -r.root.up, o.joints, upperLegLimits, lowerLegLimits, footLimits); } private static void CreateLimbJoints(Transform connectedBone, Transform bone1, Transform bone2, Transform bone3, Transform root, Vector3 defaultWorldDirection, JointType jointType, CreateJointParams.Limits limits1, CreateJointParams.Limits limits2, CreateJointParams.Limits limits3) { Quaternion bone1DefaultLocalRotation = bone1.localRotation; bone1.rotation = Quaternion.FromToRotation(bone1.rotation * (bone2.position - bone1.position), defaultWorldDirection) * bone1.rotation; Vector3 bone1Dir = (bone2.position - bone1.position).normalized; Vector3 bone2Dir = (bone3.position - bone2.position).normalized; Vector3 bendPlaneNormal = -Vector3.Cross(bone1Dir, bone2Dir); float bone2PoseAngleOffset = Vector3.Angle(bone1Dir, bone2Dir); bool isVertical = Mathf.Abs(Vector3.Dot(bone1Dir, root.up)) > 0.5f; float verticalAngleOffsetMlp = isVertical ? 100f : 1f; // Fixing Mixamo's inverted legs // Fixing straight limbs if (bone2PoseAngleOffset < 0.01f * verticalAngleOffsetMlp) { if (isVertical) bendPlaneNormal = Vector3.Dot(bone1Dir, root.up) > 0f ? root.right : -root.right; else bendPlaneNormal = Vector3.Dot(bone1Dir, root.right) > 0f ? root.up : -root.up; //Debug.LogWarning("Limb " + bone1.name + ", " + bone2.name + ", " + bone3.name + " appears to be completely stretched out, Ragdoll Creator can not know how to assign joint limits. Please rotate the elbow/knee bone slightly towards its natural bending direction."); } CreateJoint(new CreateJointParams( bone1.GetComponent(), connectedBone.GetComponent(), bone2, bendPlaneNormal, limits1, jointType )); CreateJoint(new CreateJointParams( bone2.GetComponent(), bone1.GetComponent(), bone3, bendPlaneNormal, new CreateJointParams.Limits(limits2.minSwing - bone2PoseAngleOffset, limits2.maxSwing - bone2PoseAngleOffset, limits2.swing2, limits2.twist), jointType )); if (bone3.GetComponent() != null) { CreateJoint(new CreateJointParams( bone3.GetComponent(), bone2.GetComponent(), null, bendPlaneNormal, limits3, jointType )); } bone1.localRotation = bone1DefaultLocalRotation; } public static void ClearBipedRagdoll(BipedRagdollReferences r) { var transforms = r.GetRagdollTransforms(); foreach (Transform t in transforms) ClearTransform(t); } public static bool IsClear(BipedRagdollReferences r) { var transforms = r.GetRagdollTransforms(); foreach (Transform t in transforms) { if (t.GetComponent() != null) return false; } return true; } private static Vector3 GetUpperArmToHeadCentroid(BipedRagdollReferences r) { return Vector3.Lerp(GetUpperArmCentroid(r), r.head.position, 0.5f); } private static Vector3 GetUpperArmCentroid(BipedRagdollReferences r) { return Vector3.Lerp(r.leftUpperArm.position, r.rightUpperArm.position, 0.5f); } } }