- Home /
Shared skeleton and animation state
Suppose I have an animated human mesh and, as one example, a large pair of gloves that I would like to have rendering (around the actual hands) at certain times. The gloves have already been skinned to the same skeletal structure that the human model uses.
Ultimately my goal is to have the gloves mesh animate along with the human model by virtue of the fact that its mesh renderer is using the human model's bones (which are animated by the human object's animation component).
My first stab so far has been essentially the following code:
public class Equipmentizer : MonoBehaviour { public GameObject target;
void Start () { SkinnedMeshRenderer targetRenderer = target.GetComponent<SkinnedMeshRenderer>(); Dictionary<string,Transform> boneMap = new Dictionary<string,Transform>(); foreach( Transform bone in targetRenderer.bones ) boneMap[bone.gameObject.name] = bone;
SkinnedMeshRenderer myRenderer = gameObject.GetComponent<SkinnedMeshRenderer>();
for( int i = 0; i < myRenderer.bones.Length; ++i )
{
GameObject bone = myRenderer.bones[i].gameObject;
if( !boneMap.TryGetValue(bone.name, out myRenderer.bones[i]) )
{
Debug.Log("Unable to map bone \"" + bone.name + "\" to target skeleton.");
break;
}
}
}
Alas the gloves still animate separately, clearly still using their own bones. After some additional testing, it seems that any assignments to elements of the mesh renderer's bones array do not actually happen. No errors though.
Is there a way to make this work, or perhaps a better approach to solving this problem? I do want to avoid saving all interchangeable skinned components out into one big asset and then hiding or showing individual meshes - this has been the only alternative suggestion I've encountered so far.
Answer by pixels · Jan 24, 2011 at 08:26 PM
I've resolved this issue, so in the event that someone might find it useful in the future, I'll provide an answer here. The code I posted in the question is mostly correct; however, it seems that assigning to individual elements of the SkinnedMeshRenderer's bones array will always silently fail (more accurately, it probably succeeds at modifying the wrong data).
The solution of course was to create a new array of bones and assign it to the whole property at once. The final code looks like this:
public class Equipmentizer : MonoBehaviour { public GameObject target;
// Use this for initialization void Start () { SkinnedMeshRenderer targetRenderer = target.GetComponent<SkinnedMeshRenderer>(); Dictionary<string,Transform> boneMap = new Dictionary<string,Transform>(); foreach( Transform bone in targetRenderer.bones ) boneMap[bone.gameObject.name] = bone;
SkinnedMeshRenderer myRenderer = gameObject.GetComponent<SkinnedMeshRenderer>();
Transform[] newBones = new Transform[myRenderer.bones.Length];
for( int i = 0; i < myRenderer.bones.Length; ++i )
{
GameObject bone = myRenderer.bones[i].gameObject;
if( !boneMap.TryGetValue(bone.name, out newBones[i]) )
{
Debug.Log("Unable to map bone \"" + bone.name + "\" to target skeleton.");
break;
}
}
myRenderer.bones = newBones;
}}
And I now have multiple meshes smoothly animating with one shared skeleton!
Also note that because the attached object doesn't use its own skeleton now, there's no sense in keeping its Animation component around.
thanks for this. while I cant copy your code, i will use this info to solve a problem I'm having!
Thanks for that. I've been scratching my head all afternoon trying to work out why poking values into the bones array didn't work.
After searching for a solution for months, I can confirm this also works for me. Thanks!
Be aware that ALL bones need to have at least one weighted vertex, otherwise they won't get exported. It's sufficient to give a bone one vertex with, say, a 0.001 weight.
Which is not really practicable if you have many different skinned meshes that are only affected by a few bones.
Answer by TMPxyz · Dec 16, 2012 at 03:32 PM
Indeed, `Transform[] xxx = SkinnedMeshRenderer.bones` creates a clone of the Transform[], so without `SkinnedMeshRenderer.bones = yyy` nothing will take effect;
void Start () {
SkinnedMeshRenderer targetRenderer = m_attached.GetComponent<SkinnedMeshRenderer>();
Dictionary<string, Transform> boneMap = new Dictionary<string, Transform>();
foreach( Transform bone in targetRenderer.bones )
{
boneMap[bone.name] = bone;
}
SkinnedMeshRenderer thisRenderer = GetComponent<SkinnedMeshRenderer>();
Transform[] boneArray = thisRenderer.bones;
for(int idx = 0; idx < boneArray.Length; ++idx )
{
string boneName = boneArray[idx].name;
if( false == boneMap.TryGetValue(boneName, out boneArray[idx]) )
{
Debug.LogError("failed to get bone: " + boneName);
Debug.Break();
}
}
thisRenderer.bones = boneArray; //take effect
}
This works great for me, but what is the additional step that I need to take to separate a new skinned mesh from the original?
Currently, when I destroy or hide the game object containing the original skinned mesh renderer it causes the new smr to stop animating - Its still getting data from that mesh.
Answer by neoRiley · Jan 09, 2020 at 12:48 AM
Great example and thanks for the help!
I made one adjustment - in my situation, the 2nd GameObject needed bones from throughout the hierarchy so I added a method that farms all bones.
using System.Collections.Generic;
using UnityEngine;
public class Equipmentizer : MonoBehaviour
{
public GameObject target;
void Start ()
{
Dictionary<string,Transform> boneMap = new Dictionary<string,Transform>();
GetAllSkinnedMeshRenderers(ref boneMap);
SkinnedMeshRenderer myRenderer = GetComponent<SkinnedMeshRenderer>();
Transform[] newBones = new Transform[myRenderer.bones.Length];
for(int i = 0; i < myRenderer.bones.Length; ++i )
{
GameObject bone = myRenderer.bones[i].gameObject;
if( !boneMap.TryGetValue(bone.name, out newBones[i]) )
{
Debug.Log("Unable to map bone \"" + bone.name + "\" to target skeleton.");
}
}
myRenderer.bones = newBones;
}
void GetAllSkinnedMeshRenderers(ref Dictionary<string, Transform> map)
{
SkinnedMeshRenderer[] renderers = target.GetComponentsInChildren<SkinnedMeshRenderer>();
Dictionary<string, Transform> boneMap = new Dictionary<string, Transform>();
foreach (SkinnedMeshRenderer smr in renderers)
{
foreach (Transform bone in smr.bones)
{
if(!boneMap.ContainsKey(bone.gameObject.name)) boneMap[bone.gameObject.name] = bone;
}
}
map = boneMap;
}
}
works great, thanks again
THANK YOU, this solved my issue of the bounding box forever expanding and staying stuck at the origin and stretching the mesh infinitely
Answer by alwynd · Aug 30, 2013 at 02:39 PM
Dudes, I can honestly just say thank you, I have a dude, with lots of stuff, armor, weapons, clothes, all seperate fbx's, but skinned against the same skeleton, and tons of animations working with Mecanim, idle, normal and combat animations, thank you, I was almost ready to friggin give up, couldn't find a good solution for this for days, your script works perfectly, and I just didn't know how to do this, I know he needed to be reparented to a different skeleton, but when you do this in the editor, it doesn't work, because the minute the animator starts doing other animations, those meshes, even though they have been set to different root bones, still do their own thing. Thank you!
Answer by dreamstate · Aug 01, 2012 at 11:48 PM
So I would really like to do the same thing you are talking about here but I have been messing with your script and my models for hours now and I don't get it...what should the "target" variable be? Should it be the item I am instantiating (skinned armor piece in this case)? OR should it be the main character body (and it has the full skeleton, they are both skinned to the same rig in maya exported as fbx)......any info would help at this point...or if u would be so kind as to make a small downloadable unity project that we can open and see how it is arranged Im sure many many people would find this useful...probably others have but I am somewhat new to coding so I guess that is why this isn't making sense.
Thank you very much for the code though ....cause Im sure it works, just me not getting it
Your answer
Follow this Question
Related Questions
The name 'Joystick' does not denote a valid type ('not found') 2 Answers
Get same Skeletal Rig movement from 3dsmax to Unity 1 Answer
animation.Play (MissingComponentException: There is no 'Animation' attached to.. 2 Answers
Loop Animation In Script? 2 Answers
Play animations to different objects 2 Answers