In this article i will try to create a ragdoll in Unity 2019
which blends into a Get Up animation.
Basic workflow for this type of system is usually spherically lerping
all the body parts between the animation and the last ragdoll
orientation.
Let's start with setting up the scene;
First make sure you have a fully working rigged character
and set up the ragdolls for it. Also make sure you have some
animations for idle movement and getting up both from face and
back.
Now let's create a script called 'RagdollController' and attachit to the root of our character(the one that has the animator component).First things first, we need to be able to reach our body part's rigidbody objects and for that we will use 'GetComponentsInChildren<>()'. Create an array of Rigidbody called 'bodyparts' and use the expression above to automatically fill the array with your body parts(Make sure it is public so you can see which element assigned to which body part). Also we need the animator component.
<code>
public Rigidbody[] bodyparts;
Animator animator;
void Start()
{
bodyparts = GetComponentsInChildren<Rigidbody>();
animator = GetComponent<Animator>();
}
<code>
We need to turn on and off the kinematics of these bodies otherwise during transitions we will have problems and for we will create a method which is quite simple.
<code>
void kinematicToggle(Rigidbody rigidbody,bool value)
{
rigidbody.isKinematic = value;
}
<code>
Now we will create Ragdolled property which will do the necessary work and complete the transition. We want this property to do different work depending on its earlier state. For example if we want to make it false, then we need to turn on the kinematics and vice versa. We also need to store the orientations of body parts so we will include that to the 'Ragdolled' too.
And here is how 'Ragdolled' property should look at this point;
<code>
protected bool ragdolled;
public bool Ragdolled
{
get
{
return ragdolled;
}
set
{
if(value)
{
animator.enabled = false;
for (int i = 0; i < bodyparts.Length; i++)
{
kinematicToggle(bodyparts[i], false);
}
}
else
{
for (int i = 0; i < bodyparts.Length; i++)
{
storedRot[i] = bodyparts[i].transform.localRotation;
}
for (int i = 0; i < bodyparts.Length; i++)
{
kinematicToggle(bodyparts[i], true);
}
animator.enabled = true;
}
ragdolled = value;
}
}
<code>
We will toggle Ragdolled on and off with space key so add this to 'Update()'.
<code>
if (Input.GetKeyDown(KeyCode.Space))
Ragdolled = !Ragdolled;
<code>
At this point we should have an instant transition between ragdoll and animation. To fix that we will continue with the critical part, which is blending. For this we will need 3 states; blend, animation and ragdoll(we will use enum structures to mimic states).
<code>
enum characterStates
{
animation,ragdoll,blend
};
public class RagdollController : MonoBehaviour
{
characterStates myState;
}
<code>
We need to create a 'blend' float which will be the lerping factor and we will slowly increase it with each frame to get a smooth transition.
<code>
public bool Ragdolled
{
get
{
return ragdolled;
}
set
{
if(value)
{
animator.enabled = false;
for (int i = 0; i < bodyparts.Length; i++)
{
kinematicToggle(bodyparts[i], false);
}
myState = characterStates.ragdoll;
}
else
{
for (int i = 0; i < bodyparts.Length; i++)
{
storedRot[i] = bodyparts[i].transform.localRotation;
}
for (int i = 0; i < bodyparts.Length; i++)
{
kinematicToggle(bodyparts[i], true);
}
animator.enabled = true;
blend = 0;
myState = characterStates.blend;
}
ragdolled = value;
}
}
<code>
Now let's set the blending state up. We need to do this operation in the 'LateUpdate()' because otherwise the animation system will update the rotation after we do the blending and it will make our work completely worthless.
<code>
void LateUpdate()
{
if (myState == characterStates.blend)
{
for (int i = 0; i < bodyparts.Length; i++)
{
bodyparts[i].transform.localRotation = Quaternion.Slerp(storedRot[i], bodyparts[i].transform.localRotation, blend);
}
blend += Time.deltaTime * 2;
if (blend > 0.9f)
{
myState = characterStates.animation;
}
}
}
<code>
At this point we should be able to see some promising results.
We need to change our animation to Get Up animation, this can be done by using Play() method of Animator class.
I want to talk a particular problem when i did all this. If we use Play method in our code, somehow character gets an offset in XZ plane. I don't know why this occures and i couldn't find any way around it.
Here is the final Ragdolled;
<code>
protected bool ragdolled=false;
public bool Ragdolled
{
get
{
return ragdolled;
}
set
{
if(value)
{
animator.enabled = false;
for (int i = 0; i < bodyparts.Length; i++)
{
kinematicToggle(bodyparts[i], false);
}
myState = characterStates.ragdoll;
}
else
{
for (int i = 0; i < bodyparts.Length; i++)
{
storedRot[i] = bodyparts[i].transform.localRotation;
}
for (int i = 0; i < bodyparts.Length; i++)
{
kinematicToggle(bodyparts[i], true);
}
animator.enabled = true;
blend = 0;
myState = characterStates.blend;
animator.Play("Get Up From Face");
}
ragdolled = value;
}
<code>
Now we need to figure out a way to determine if character is facing the ground or sky. It's fine though, we have math to help us. We will use dot product to figure that out.
<code>
if( Vector3.Dot(bodyparts[1].transform.up,Vector3.up)>0)
{
animator.Play("Get Up From Back");
}
else
{
animator.Play("Get Up From Face");
}
<code>
Replacing this with the Play() line should do the trick.
I will try to update this thread as well as trying to figure out a new way to fix the offset problem in XZ and rotating the parent according to hip.
Feel free to cantact me for any questions: goktugsaray1@gmail.com
Vay be sağ ol asın
ReplyDelete