Who wants to play videogames? We are making one, come see here.
TOMB OF ROOMS

Chopping up rag dolls in Unity

Since we have bladed attacks in Tomb of Rooms, I've been wanting to implement bodies getting chopped up for a while.


I couldn't find any concrete information online explaining how to do it but I decided to attempt it anyway. Since things turned out rather well I thought I'd share my experiences with the process in case anyone else is looking to do the same thing.

There is an asset you can buy on the Unity store for this. We are a very small team on a super low budget though, and we don't need a big expensive solution. We need something super simple with minimal setup as adding creatures is a long process already. Also as it turns out implementing it isn’t that taxing and can be quite fun, I encourage you to try!

My initial plan was to keep it simple, altering GameObjects, Meshes and other data as little as possible to achieve the effect I wanted.

With that in mind this was my plan:
  1. Copy the entire rag doll object
  2. Determine which polygons are on which side of the cut
  3. Manipulate the triangle data for both to divide up the polygons
  4. Cap the severed ends
  5. Remove components attached to the now empty parts of the bodies

You can find my c# source code here if you want to try it out in the Unity editor. If you are interested in finding out a bit more of the theory behind it though keep reading and I'll go into more detail.


1. Copying the rag doll object

This was the easiest bit. You can pass any GameObject to:
GameObject.Instantiate()
And it will clone the entire thing for you. You do need to take a little care though as any scripts on the object aren’t copied down to the level of what state they are in, but created fresh, even running Start() as they fire up.


2. Deciding how to split the mesh

There are lots of ways you can do this. I decided to sever the rag doll according to the bone weightings. That way there is very little setup, and all the physics objects get to stay the same. The downside is that the only places you can cut are at the joints between weighted areas.
Unity stores the bones as an array of transforms, and the vertex weights as index numbers for that array. The first thing to do is to find the index numbers of the bones we are interested in:
List<int> boneNumbers = new List<int>();
foreach (Transform t in bone.GetComponentsInChildren<Transform>())
{
    for (int i = 0; i < mySkinnedMeshRenderer.bones.Length; i++)
    {
        if (mySkinnedMeshRenderer.bones[i] == t) { boneNumbers.Add(i); }
    }
}
Since the bones are represented in Unity by a hierarchy of GameObjects, a handy shortcut is to just grab all the transforms for the children of the part we are detaching, and then find their index numbers.

We also need the weighting information for each vertex so we know what bone its attached to, which we can get with:
BoneWeight[] weights = myMesh.boneWeights;
Now we can write a function to determine if a vertex is attached to any of those bones:
private static bool isPartOf(BoneWeight b, List<int> indices, float threshold)
{
    float weight = 0;
    foreach (int i in indices)
    {
        if (b.boneIndex0 == i) weight += b.weight0;
        if (b.boneIndex1 == i) weight += b.weight1;
        if (b.boneIndex2 == i) weight += b.weight2;
        if (b.boneIndex3 == i) weight += b.weight3;
    }
    return (weight > threshold);
}
The threshold determines at what proportion of the total weighting it matches our list. Changing this number will adjust the severing point up and down the joint.


3. Dividing up the polygons

The SkinnedMeshRenderer’s Mesh can be accessed with:
Mesh myMesh = mySkinnedMeshRenderer.sharedMesh;
And copied with calls like this:
Mesh outerMesh = Object.Instantiate(myMesh) as Mesh;
Mesh innerMesh = Object.Instantiate(myMesh) as Mesh;
Unfortunately if your Skinned mesh uses multiple materials (and it probably does), it is not really one single mesh, but multiple sub meshes stored inside the main Mesh. Since all we are interested in is the triangle data, we can iterate through the submeshes with:
for (int subMesh = 0; subMesh < myMesh.subMeshCount; subMesh++)
And then grab the data we need for each submesh with:
int[] tris = myMesh.GetTriangles(subMesh);
The triangle data is stored as an array of vertex ID numbers. The first three numbers in the array define the first triangle, the next three the second and so on.

Before we start going through the triangle data we are going to need two lists. One to remember the tris that end up on one side of the cut, and one for the tris that end up on the other side:
List<int> outerTris = new List<int>();
List<int> innerTris = new List<int>();
Now we can iterate through each triangle in the submesh, determining which side of the cut it falls on, and adding its three vertex numbers to the relevant list:
for (int t = 0; t < tris.Length; t += 3)
{
    bool bVert1 = isPartOf(weights[tris[t]], boneNumbers, threshold);
    bool bVert2 = isPartOf(weights[tris[t + 1]], boneNumbers, threshold);
    bool bVert3 = isPartOf(weights[tris[t + 2]], boneNumbers, threshold);

    if (bVert1 || bVert2 || bVert3)
    {
        innerTris.Add(tris[t]);
        innerTris.Add(tris[t + 1]);
        innerTris.Add(tris[t + 2]);
    }
    else
    {
        outerTris.Add(tris[t]);
        outerTris.Add(tris[t + 1]);
        outerTris.Add(tris[t + 2]);
    }
}
Now we can update the sub mesh of our two new meshes to contain the correct tris:
outerMesh.SetTriangles(outerTris.ToArray(), subMesh);
innerMesh.SetTriangles(innerTris.ToArray(), subMesh);
Once we have finished iterating through the sub meshes, we end up with two new mesh objects that can be assigned back to the skinned mesh renderers for both the original GameObject and its clone like this:
mySkinnedMeshRenderer.sharedMesh = outerMesh;



4. Capping the ends

To Cap the separated ends of the mesh we need to:
  1. Find the edges of the cut ends
  2. Fill them with a surface of polygons
  3. UV map those polygons and apply a Material to them
A way we can find the edges of the cut is that when we check each polygon to see which side of the cut it belongs, we can also count the number of vertices on each side of the cut. If this polygon is being cut away, and two of them are on one side and one on the other, we know that the two belonging to the same side are part of the edge of the cut. We can then store these edges in a list.

There is probably a more compact way to write it, but my code to do so looks like this:
if (bVert1 && !bVert2 && !bVert3) 
{ 
    edges.Add(tris[t + 1]); 
    edges.Add(tris[t + 2]); 
}
if (!bVert1 && bVert2 && !bVert3)
{ 
    edges.Add(tris[t + 2]); 
    edges.Add(tris[t + 0]); 
}
if (!bVert1 && !bVert2 && bVert3) 
{ 
    edges.Add(tris[t + 0]); 
    edges.Add(tris[t + 1]); 
}
Now we can take that list of edges, and use it to cap the end. There are lots of algorithms to do this, but the simplest is probably the fan. To create a fan of polys all you need to do is to pick one vertex on the edge, and connect each edge to that vertex with a polygon.

The downsides to doing it this way are that complicated irregular edges are going to look weird, but we don't necessarily need it to look super tidy and all our cuts should be simple rough circle shapes.
Similarly with UV mapping the cap, its not super important that it looks perfect in my case, so I just create a Quaternion by grabbing three vertices, and then map all the vertices on a 2D plane at that orientation so they all fit within the UV map.

To create the Mesh itself, I copy across most of the data from the original SkinnedMeshRenderer, but put in my own triangle and UV data. I then use Unity's inbuilt method for calculating normals for the new geometry like so:
Mesh m = new Mesh();
m.vertices = parent.vertices;
m.bindposes = parent.bindposes;
m.boneWeights = parent.boneWeights;
m.triangles = triangles;
m.uv = uvs;
m.RecalculateNormals();
Its not a perfect solution that will fit every type of skinned mesh and every type of cut, but for most typical cases it should be fine.


5. Cleaning up the GameObjects

Once that is all done, we need to delete all the physics related stuff from the parts of the body that don't exist on each GameObject any more.
if (part.GetComponent<CharacterJoint>()) 
    GameObject.Destroy(part.GetComponent<CharacterJoint>());
if (part.collider) GameObject.Destroy(part.collider);
if (part.rigidbody) GameObject.Destroy(part.rigidbody);
It would be nice to just iterate through all the components, but Unity throws errors if you try to remove a RigidBody before a Joint. If we have any particle effects or other stuff on the bone GameObjects we need to get rid of it here too, otherwise there will be two copies of them in the scene.

One last thing to be aware of is that even though we've cleaned up the two GameObjects, the empty parts need to become the child of our seperated body part, or they will stay in the same place as the limb flies off and the polygons still partially weighted to them will stretch
GameObject boneInClone = findGameObjectIn(bone.name, clonedObject);
boneInClone.transform.parent = clonedObject.transform.parent;
clonedObject.transform.parent = boneInClone.transform;
You can do this by finding the severed limb in the cloned version of the rag doll (I wrote a quick function to do this), taking it out of the clone, and then making the rest of the clone a child of that part.


Conclusion

Kapow! That should be enough to get you started working something like this into your own project. It should cover your standard single SkinnedMeshRenderer humanoid rag doll sort of stuff, but its going to need more work to handle more unusual layouts, such as multiple SkinnedMeshRenderers, or objects containing a mix of skinned and unskinned meshes.

I am always looking to learn and improve, so if you have any corrections, or suggestions for improvements to the code or this post I'd love to know about them!

If you find this useful it would also be awesome to hear about what you are working on, so don't be a stranger!

4 comments:

  1. Wow this was great info thanks, I couldn't have figured this out by myself.

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Great article!!!

    I'm pretty new to Unity (I've been programming for a logn time though) so sorry for the lame question.

    I'm confused why should we clone the rigidbody object. Can't we do all the chop actions on the original ragdol?
    We could make all rigidbodies kinematic for a short time (to avoid strange behaviours) -> do all the chopping -> continue. Im just thinking thinking out loud here.

    ReplyDelete