MOVING MESHES TO NEW ARMATURES
Using Unity Version 2019.4
A customer recently asked me if it was possible to swap hairstyles between my characters. The answer? Yes, but it can be complicated.
Hi there! I have a problem I’m hoping you can help me with. We’ve purchased your Sci-Fi Scarlett Riley character and I’m wondering if it’s possible to use the hair from one with the others? Also, is there a way to use Sci-Fi Scarlett’s accessories (belt, backpack) only when we want to? Should we just hide them and make turn them on when needed? We’re hoping to create more armors for her like helmets, weapons, etc. How do we make the pieces bend with the character animations only when they are equipped?
This is a common question with a complicated answer.
Of course, there are hundreds of ways to achieve this. If I reiterate the question above, it seems simple enough: We want to add additional meshes to an existing armature. Hundreds of games do this. It must be simple, right?
Most people initially approach this in one of two ways:
1. Export the rigged character with the mesh parts. As the accessories are changed, added, or removed, a script will turn their respective mesh renderers on or off. Why it is not ideal: This can mean exporting a rigged character with a ridiculous number of meshes. Additionally, characters that need to share the same accessories need each part exported with their rig, which means duplicating each accessory/hairstyle mesh for each and every character. That could be…a lot.
2. Just parent the mesh parts to the existing character rig. This is the primary way to add attachments like weapons or shields to characters. A script will either parent a mesh from the scene to an existing target within the character rig or instantiate a prefab to the existing target within the character rig.
Why it is not ideal: As stated above, this works well for attachments like weapons. But the reason this will not often work for other parts is because it ignores bone weighting completely. In other words, the entire weapon is turned into a child of the target and will move as the target moves. However, if you try this with, say, parenting a belt to a hip or spine bone, it will not be weighted to the armature and the vertices in the weight will stay rigid as the character moves, like this:
Following that, our question is not only how to parent additional parts to the armature, but also how to make sure it’s weights respond properly to the bones of that armature.
This post will use my Scarlett Riley character as an example, but you could definitely adapt the code for your own characters too! Scarlett is a great example to work with because there are multiple rigs, hairstyles and accessories. You can purchase Scarlett to follow along if you want :)
When you open the Prefab folder in the Scarlett Riley package, you'll see five folders for the different clothing options. Inside each folder are three FBX files -- one for each hairstyle.
Start by choosing any of the prefab files and drag it into the Scene hierarchy. This will be the main armature (the one you are going to move parts to). You'll notice that all of the prefabs contain all of the accessories. Let's get rid of those so our character isn't wearing any of them.
Right click on the character in the hierarchy window and select Unpack Prefab Completely. Now delete the utility belt, backpack and holster. You're left with just the eyes, hair and body.
Now take a look at the Mesh folder. It's set up the same way as the Prefab folder. Select one of the mesh FBX files and click the arrow to expand it. (You can use any of the Mesh files for the accessories since they have all been exported wearing all of the accessories).
The three outlined parts are what we are going to need to reference. We are going to add these parts from this mesh file to the character in our Scene.
What this code will do:
This code will instantiate clones from an existing rig and bind them to a new rig. It works in the editor as well as runtime, but you’ll need to create an editor script in order to have it function that way.
What you need:
A rigged character
Mesh parts, already weighted to an existing armature with the same bones as the rigged character. NOTE: This code will not magically morph the mesh correctly to the character. It merely tells existing weights on a mesh to use the new armature.
A material to apply to the part you want to add This example uses parts with one material and applies it automatically. You will have to adapt the code to address items with multiple materials, or if you want to choose the material to add.
If you don't want to go through the whole article, you can skip to the end to grab the code.
Create a new C# script. I called mine “DressingRoom”. This doesn't have to live on the character, since it contains a public paramter to reference the rigs and parts.
Next, let’s declare our parameters. I wanted my code to do four things:
Instantiate a clone of the right part and bind it to the new armature,
Apply a material to the new part,
Record that the part had been added so I don't add the same part multiple times, and
Delete the part if needed.
Here are the parameters I used:
Let's break down what these parameters are for:
newArmature: The new armature that we want to move parts to.
rootBoneName: The root bone of the new armature that we want to move parts to.
partToAdd: This is the part we want to move to the new armature
partAdded: This is the original part we just added.
clonedObject: This is the clone of the part we just added.
materialForNewPart: The material we want to apply to the cloned part.
partsAdded: A list that tracks all of the "partsAdded" to prevent duplicates.
As I've said a few times, in order to move the new part to the existing armature, the weights need to already be present on both. There can be additional bones on either armature, but the ones for the new part need to exist in the new armature.
Our main function will rebind the new part to the new armature, but we also want to take care of some housekeeping before we do anything else. First, we should check if the character is already “wearing” the item. Second, we need to grab the part and make sure it's correctly referenced. We’ll flesh both of these out further in a minute. For now, create a public void Function called "Rebind".
If “alreadyWearing” is true, this code will check to see if you're character is already wearing the item in order to prevent duplicates. If it's false, it will get the part reference we want to add. We don't want the script to rebind the original mesh part -- just a clone, so a function will take care of that for us. Finally, it will make sure a new armature is assigned and locate the root bone that’s referenced.
The remaining lines in the function find the Skinned Mesh Renderer of the cloned part, and which bones it is currently weighted to. We’ll then ask the script to bind the mesh instead to the bones of the new rig, but using the weights it already has. So the weights for Spine1 in the old rig will now be bound to the Spine1 bone in the new rig, and so on.
There are four functions which work in tandem to instantiate the cloned part, apply the material, add the part to our list, and physically move the mesh to the new rig.
Recalling our "Rebind" function, we now need to create the bool function that will check if "alreadyWearing" is true. Here, we’ll reference our "partsAdded" list to see if the part has been added.
Lastly, we need to set up a way to delete the item. For this, you'll need two functions. The first gets a reference to the item in the list from the name of the part we want to remove. The second takes care of deleting the item. The reason this is a bit messy is because the object we want to remove can't be used as a direct reference. Remember, when we instantiated it, a clone was created, so the original reference is no longer tied to what we captured in our list. If we try to reference the original object, it will come back as not being worn, no matter how many times it's added. That's why we need a workaround to find the clone reference to delete.
If you want to be able to add multiple items, it's as simple as creating a function to overwrite the added item. I threw together something quickly which could be called easily from the UI for testing:
If you want to get a list of all the items currently being "worn", you can do it with this: