BzRagdoll.cs
Original ragdoll script by BzKovSoftunknown
csharp
2 years ago
15 kB
7
Indexable
Never
using System; using UnityEngine; using System.Collections.Generic; using System.Collections; using System.Diagnostics; namespace BzKovSoft.RagdollTemplate.Scripts.Charachter { [RequireComponent(typeof(IBzRagdollCharacter))] public sealed class BzRagdoll : MonoBehaviour, IBzRagdoll { // animator parameters: //readonly int _animatorForward = Animator.StringToHash("Forward"); //readonly int _animatorTurn = Animator.StringToHash("Turn"); // animator animations: [SerializeField] string _animationGetUpFromBelly = "GetUp.GetUpFromBelly"; [SerializeField] string _animationGetUpFromBack = "GetUp.GetUpFromBack"; Animator _anim; IBzRagdollCharacter _bzRagdollCharacter; // parameters for control character moving while it is ragdolled private const float AirSpeed = 5f; // determines the max speed of the character while airborne // parameters needed to control ragdolling RagdollState _state = RagdollState.Animated; float _ragdollingEndTime; //A helper variable to store the time when we transitioned from ragdolled to blendToAnim state const float RagdollToMecanimBlendTime = 0.5f; //How long do we blend when transitioning from ragdolled to animated readonly List<RigidComponent> _rigids = new List<RigidComponent>(); readonly List<TransformComponent> _transforms = new List<TransformComponent>(); Transform _hipsTransform; Rigidbody _hipsTransformRigid; Vector3 _storedHipsPosition; Vector3 _storedHipsPositionPrivAnim; Vector3 _storedHipsPositionPrivBlend; #region implementation of IBzRagdoll public bool Raycast(Ray ray, out RaycastHit hit, float distance) { var hits = Physics.RaycastAll(ray, distance); for (int i = 0; i < hits.Length; ++i) { var h = hits[i]; if (h.transform != transform && h.transform.root == transform.root) { hit = h; return true; } } // if no hits found, return false hit = new RaycastHit(); return false; } public bool IsRagdolled { get { return _state == RagdollState.Ragdolled || _state == RagdollState.WaitStablePosition; } set { if (value) RagdollIn(); else RagdollOut(); } } public void AddExtraMove(Vector3 move) { if (IsRagdolled) { Vector3 airMove = new Vector3(move.x * AirSpeed, 0f, move.z * AirSpeed); foreach (var rigid in _rigids) rigid.RigidBody.AddForce(airMove / 100f, ForceMode.VelocityChange); } } #endregion void Start() { _anim = GetComponent<Animator>(); _hipsTransform = _anim.GetBoneTransform(HumanBodyBones.Hips); _hipsTransformRigid = _hipsTransform.GetComponent<Rigidbody>(); _bzRagdollCharacter = GetComponent<IBzRagdollCharacter>(); //Get all the rigid bodies that belong to the ragdoll Rigidbody[] rigidBodies = GetComponentsInChildren<Rigidbody>(); foreach (Rigidbody rigid in rigidBodies) { if (rigid.transform == transform) continue; RigidComponent rigidCompontnt = new RigidComponent(rigid); _rigids.Add(rigidCompontnt); } // disable ragdoll by default ActivateRagdollParts(false); //Find all the transforms in the character, assuming that this script is attached to the root //For each of the transforms, create a BodyPart instance and store the transform foreach (var t in GetComponentsInChildren<Transform>()) { var trComp = new TransformComponent(t); _transforms.Add(trComp); } } void FixedUpdate() { if (_state == RagdollState.WaitStablePosition && _hipsTransformRigid.velocity.magnitude < 0.1f) { GetUp(); } if (_state == RagdollState.Animated && _bzRagdollCharacter.CharacterVelocity.y < -10f) { // kill and resuscitate will force character to enter in Ragdoll RagdollIn(); RagdollOut(); } } void LateUpdate() { if (_state != RagdollState.BlendToAnim) return; float ragdollBlendAmount = 1f - Mathf.InverseLerp( _ragdollingEndTime, _ragdollingEndTime + RagdollToMecanimBlendTime, Time.time); // In LateUpdate(), Mecanim has already updated the body pose according to the animations. // To enable smooth transitioning from a ragdoll to animation, we lerp the position of the hips // and slerp all the rotations towards the ones stored when ending the ragdolling if (_storedHipsPositionPrivBlend != _hipsTransform.position) { _storedHipsPositionPrivAnim = _hipsTransform.position; } _storedHipsPositionPrivBlend = Vector3.Lerp(_storedHipsPositionPrivAnim, _storedHipsPosition, ragdollBlendAmount); _hipsTransform.position = _storedHipsPositionPrivBlend; foreach (TransformComponent trComp in _transforms) { //rotation is interpolated for all body parts if (trComp.PrivRotation != trComp.Transform.localRotation) { trComp.PrivRotation = Quaternion.Slerp(trComp.Transform.localRotation, trComp.StoredRotation, ragdollBlendAmount); trComp.Transform.localRotation = trComp.PrivRotation; } //position is interpolated for all body parts if (trComp.PrivPosition != trComp.Transform.localPosition) { trComp.PrivPosition = Vector3.Slerp(trComp.Transform.localPosition, trComp.StoredPosition, ragdollBlendAmount); trComp.Transform.localPosition = trComp.PrivPosition; } } //if the ragdoll blend amount has decreased to zero, move to animated state if (Mathf.Abs(ragdollBlendAmount) < Mathf.Epsilon) { _state = RagdollState.Animated; } } /// <summary> /// Prevents jittering (as a result of applying joint limits) of bone and smoothly translate rigid from animated mode to ragdoll /// </summary> /// <param name="rigid"></param> /// <returns></returns> static IEnumerator FixTransformAndEnableJoint(RigidComponent joint) { if (joint.Joint == null || !joint.Joint.autoConfigureConnectedAnchor) yield break; SoftJointLimit highTwistLimit = new SoftJointLimit(); SoftJointLimit lowTwistLimit = new SoftJointLimit(); SoftJointLimit swing1Limit = new SoftJointLimit(); SoftJointLimit swing2Limit = new SoftJointLimit(); SoftJointLimit curHighTwistLimit = highTwistLimit = joint.Joint.highTwistLimit; SoftJointLimit curLowTwistLimit = lowTwistLimit = joint.Joint.lowTwistLimit; SoftJointLimit curSwing1Limit = swing1Limit = joint.Joint.swing1Limit; SoftJointLimit curSwing2Limit = swing2Limit = joint.Joint.swing2Limit; float aTime = 0.3f; Vector3 startConPosition = joint.Joint.connectedBody.transform.InverseTransformVector(joint.Joint.transform.position - joint.Joint.connectedBody.transform.position); joint.Joint.autoConfigureConnectedAnchor = false; for (float t = 0.0f; t < 1.0f; t += Time.deltaTime / aTime) { Vector3 newConPosition = Vector3.Lerp(startConPosition, joint.ConnectedAnchorDefault, t); joint.Joint.connectedAnchor = newConPosition; curHighTwistLimit.limit = Mathf.Lerp(177, highTwistLimit.limit, t); curLowTwistLimit.limit = Mathf.Lerp(-177, lowTwistLimit.limit, t); curSwing1Limit.limit = Mathf.Lerp(177, swing1Limit.limit, t); curSwing2Limit.limit = Mathf.Lerp(177, swing2Limit.limit, t); joint.Joint.highTwistLimit = curHighTwistLimit; joint.Joint.lowTwistLimit = curLowTwistLimit; joint.Joint.swing1Limit = curSwing1Limit; joint.Joint.swing2Limit = curSwing2Limit; yield return null; } joint.Joint.connectedAnchor = joint.ConnectedAnchorDefault; yield return new WaitForFixedUpdate(); joint.Joint.autoConfigureConnectedAnchor = true; joint.Joint.highTwistLimit = highTwistLimit; joint.Joint.lowTwistLimit = lowTwistLimit; joint.Joint.swing1Limit = swing1Limit; joint.Joint.swing2Limit = swing2Limit; } /// <summary> /// Ragdoll character /// </summary> private void RagdollIn() { //Transition from animated to ragdolled ActivateRagdollParts(true); // allow the ragdoll RigidBodies to react to the environment _anim.enabled = false; // disable animation _state = RagdollState.Ragdolled; ApplyVelocity(_bzRagdollCharacter.CharacterVelocity); } /// <summary> /// Smoothly translate to animator's bone positions when character stops falling /// </summary> private void RagdollOut() { if (_state == RagdollState.Ragdolled) _state = RagdollState.WaitStablePosition; } private void GetUp() { //Transition from ragdolled to animated through the blendToAnim state _ragdollingEndTime = Time.time; //store the state change time //_anim.SetFloat(_animatorForward, 0f); //_anim.SetFloat(_animatorTurn, 0f); _anim.enabled = true; //enable animation _state = RagdollState.BlendToAnim; _storedHipsPositionPrivAnim = Vector3.zero; _storedHipsPositionPrivBlend = Vector3.zero; _storedHipsPosition = _hipsTransform.position; // get distanse to floor Vector3 shiftPos = _hipsTransform.position - transform.position; shiftPos.y = GetDistanceToFloor(shiftPos.y); // shift and rotate character node without children MoveNodeWithoutChildren(shiftPos); //Store the ragdolled position for blending foreach (TransformComponent trComp in _transforms) { trComp.StoredRotation = trComp.Transform.localRotation; trComp.PrivRotation = trComp.Transform.localRotation; trComp.StoredPosition = trComp.Transform.localPosition; trComp.PrivPosition = trComp.Transform.localPosition; } //Initiate the get up animation string getUpAnim = CheckIfLieOnBack() ? _animationGetUpFromBack : _animationGetUpFromBelly; _anim.Play(getUpAnim, 0, 0); // you have to set time to 0, or if your animation will interrupt, next time animation starts from previous position ActivateRagdollParts(false); // disable gravity on ragdollParts. } private float GetDistanceToFloor(float currentY) { RaycastHit[] hits = Physics.RaycastAll(new Ray(_hipsTransform.position, Vector3.down)); float distFromFloor = float.MinValue; foreach (RaycastHit hit in hits) if (!hit.transform.IsChildOf(transform)) distFromFloor = Mathf.Max(distFromFloor, hit.point.y); if (Mathf.Abs(distFromFloor - float.MinValue) > Mathf.Epsilon) currentY = distFromFloor - transform.position.y; return currentY; } private void MoveNodeWithoutChildren(Vector3 shiftPos) { Vector3 ragdollDirection = GetRagdollDirection(); // shift character node position without children _hipsTransform.position -= shiftPos; transform.position += shiftPos; // rotate character node without children Vector3 forward = transform.forward; transform.rotation = Quaternion.FromToRotation(forward, ragdollDirection) * transform.rotation; _hipsTransform.rotation = Quaternion.FromToRotation(ragdollDirection, forward) * _hipsTransform.rotation; } private bool CheckIfLieOnBack() { var left = _anim.GetBoneTransform(HumanBodyBones.LeftUpperLeg).position; var right = _anim.GetBoneTransform(HumanBodyBones.RightUpperLeg).position; var hipsPos = _hipsTransform.position; left -= hipsPos; left.y = 0f; right -= hipsPos; right.y = 0f; var q = Quaternion.FromToRotation(left, Vector3.right); var t = q * right; return t.z < 0f; } private Vector3 GetRagdollDirection() { Vector3 ragdolledFeetPosition = ( _anim.GetBoneTransform(HumanBodyBones.Hips).position);// + //_anim.GetBoneTransform(HumanBodyBones.RightToes).position) * 0.5f; Vector3 ragdolledHeadPosition = _anim.GetBoneTransform(HumanBodyBones.Head).position; Vector3 ragdollDirection = ragdolledFeetPosition - ragdolledHeadPosition; ragdollDirection.y = 0; ragdollDirection = ragdollDirection.normalized; if (CheckIfLieOnBack()) return ragdollDirection; else return -ragdollDirection; } /// <summary> /// Apply velocity 'predieVelocity' to to each rigid of character /// </summary> private void ApplyVelocity(Vector3 predieVelocity) { foreach (var rigid in _rigids) rigid.RigidBody.velocity = predieVelocity; } private void ActivateRagdollParts(bool activate) { _bzRagdollCharacter.CharacterEnable(!activate); //_hipsTransform.GetComponentInChildren<Collider>() foreach (var rigid in _rigids) { Collider partColider = rigid.RigidBody.GetComponent<Collider>(); // fix for RagdollHelper (bone collider - BoneHelper.cs) if (partColider == null) { const string colliderNodeSufix = "_ColliderRotator"; string childName = rigid.RigidBody.name + colliderNodeSufix; Transform transform = rigid.RigidBody.transform.Find(childName); partColider = transform.GetComponent<Collider>(); } partColider.isTrigger = !activate; if (activate) { rigid.RigidBody.isKinematic = false; StartCoroutine(FixTransformAndEnableJoint(rigid)); } else rigid.RigidBody.isKinematic = true; } //if (activate) // foreach (var joint in GetComponentsInChildren<CharacterJoint>()) // { // var jointTransform = joint.transform; // var pivot = joint.connectedBody.transform; // jointTransform.position = pivot.position; // jointTransform.Translate(joint.connectedAnchor, pivot); // } } //Declare a class that will hold useful information for each body part sealed class TransformComponent { public readonly Transform Transform; public Quaternion PrivRotation; public Quaternion StoredRotation; public Vector3 PrivPosition; public Vector3 StoredPosition; public TransformComponent(Transform t) { Transform = t; } } struct RigidComponent { public readonly Rigidbody RigidBody; public readonly CharacterJoint Joint; public readonly Vector3 ConnectedAnchorDefault; public RigidComponent(Rigidbody rigid) { RigidBody = rigid; Joint = rigid.GetComponent<CharacterJoint>(); if (Joint != null) ConnectedAnchorDefault = Joint.connectedAnchor; else ConnectedAnchorDefault = Vector3.zero; } } //Possible states of the ragdoll enum RagdollState { /// <summary> /// Mecanim is fully in control /// </summary> Animated, /// <summary> /// Mecanim turned off, but when stable position will be found, the transition to Animated will heppend /// </summary> WaitStablePosition, /// <summary> /// Mecanim turned off, physics controls the ragdoll /// </summary> Ragdolled, /// <summary> /// Mecanim in control, but LateUpdate() is used to partially blend in the last ragdolled pose /// </summary> BlendToAnim, } } }