using System; using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine; namespace Invector.vCharacterController { class vRagdollBuilder : ScriptableWizard { public enum vRagdollAvatarType { Human, Generic } public vRagdollAvatarType avatarType = vRagdollAvatarType.Human; [Header("--- Animator of cursorObject Character ---")] public Animator animator; [Header("---Generic Character template---")] public vRagdollGenericTemplate genericTemplate = null; [Header("--- Bones ---")] 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; [Header("--- Properties ---")] public bool enableProjection = true; public bool proportionalMass = true; [Header("Total Mass will be ignored and set to 1 if Proportional Mass is true")] 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 ""; } [MenuItem("Invector/Basic Locomotion/Components/Ragdoll")] static void CreateWizard() { ScriptableWizard.DisplayWizard("Create Ragdoll", typeof(vRagdollBuilder)); //ScriptableWizard.DisplayWizard("Create Ragdoll", typeof (RagdollBuilder),"Create","Load Bones"); } 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 Update() { 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 (Selection.activeGameObject != null && Selection.activeGameObject.transform.GetComponent() != null) { animator = Selection.activeGameObject.transform.GetComponent(); animator.Rebind(); } if (animator != null && avatarType == vRagdollAvatarType.Human) { GetHumanBones(); } else { GetGenericBones(); } 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 GetHumanBones() { try { root = animator.GetBoneTransform(HumanBodyBones.Hips); leftHips = animator.GetBoneTransform(HumanBodyBones.LeftUpperLeg); leftKnee = animator.GetBoneTransform(HumanBodyBones.LeftLowerLeg); leftFoot = animator.GetBoneTransform(HumanBodyBones.LeftFoot); rightHips = animator.GetBoneTransform(HumanBodyBones.RightUpperLeg); rightKnee = animator.GetBoneTransform(HumanBodyBones.RightLowerLeg); rightFoot = animator.GetBoneTransform(HumanBodyBones.RightFoot); leftArm = animator.GetBoneTransform(HumanBodyBones.LeftUpperArm); leftElbow = animator.GetBoneTransform(HumanBodyBones.LeftLowerArm); rightArm = animator.GetBoneTransform(HumanBodyBones.RightUpperArm); rightElbow = animator.GetBoneTransform(HumanBodyBones.RightLowerArm); middleSpine = animator.GetBoneTransform(HumanBodyBones.Chest); head = animator.GetBoneTransform(HumanBodyBones.Head); EditorUtility.SetDirty(this); } catch { } } void GetGenericBones() { if (!genericTemplate && Selection.activeObject) { return; } try { var rootTransform = Selection.activeTransform; root = genericTemplate.GetRoot(rootTransform); leftHips = genericTemplate.GetLeftHips(rootTransform); leftKnee = genericTemplate.GetLeftKnee(rootTransform); leftFoot = genericTemplate.GetLeftFoot(rootTransform); rightHips = genericTemplate.GetRightHips(rootTransform); rightKnee = genericTemplate.GetRightKnee(rootTransform); rightFoot = genericTemplate.GetRightFoot(rootTransform); leftArm = genericTemplate.GetLeftArm(rootTransform); leftElbow = genericTemplate.GetLeftElbow(rootTransform); rightArm = genericTemplate.GetRightArm(rootTransform); rightElbow = genericTemplate.GetRightElbow(rootTransform); middleSpine = genericTemplate.GetMiddleSpine(rootTransform); head = genericTemplate.GetHead(rootTransform); EditorUtility.SetDirty(this); } catch { } } void OnWizardCreate() { //if(Selection.activeGameObject!=null) var hasRagdoll = Selection.activeGameObject.GetComponent(); if (!hasRagdoll) { Selection.activeGameObject.AddComponent(); } Selection.activeGameObject.GetComponent().collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative; Cleanup(); BuildCapsules(); AddBreastColliders(); 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) { 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 = 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 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) { if (collider.transform != leftFoot.transform && collider.transform != rightFoot) { DestroyImmediate(collider); } } } } void BuildBodies() { foreach (BoneInfo bone in bones) { bone.anchor.gameObject.AddComponent(); var hasDamageReceiver = bone.anchor.gameObject.GetComponent(); if (!hasDamageReceiver) { bone.anchor.gameObject.AddComponent(); } bone.anchor.GetComponent().mass = bone.density; bone.anchor.GetComponent().collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative; // set layer "BodyPart" to each bone bone.anchor.gameObject.layer = LayerMask.NameToLayer("BodyPart"); } } 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(); // 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; joint.enableProjection = enableProjection; } } 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); // Rescale the mass so that the whole character weights totalMass float massScale = totalMass / rootBone.summedMass; foreach (BoneInfo bone in bones) { if (proportionalMass) { bone.anchor.GetComponent().mass = 10; } else { 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 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 = root.gameObject.AddComponent(); box.center = bounds.center; box.size = bounds.size; bounds = Clip(GetBreastBounds(middleSpine), middleSpine, middleSpine, true); box = middleSpine.gameObject.AddComponent(); 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 = root.gameObject.AddComponent(); box.center = bounds.center; box.size = size; } } void AddHeadCollider() { if (head.GetComponent()) { Destroy(head.GetComponent()); } float radius = Vector3.Distance(root.InverseTransformPoint(rightArm.transform.position), root.InverseTransformPoint(leftArm.transform.position)); radius /= 4; SphereCollider sphere = head.gameObject.AddComponent(); 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; } } }