How to move a skinned mesh renderer over to another model

Hi,

I spent many hours trying figure out how to move a skinned mesh renderer over another model of the same configuration.

I finally found the solution in this forum Prefab Breaks on Mesh Update

As I struggled so much with the solution I thought I would post another thread with a more obvious subject so that it will be easier for others to find.

The latest code which worked flawlessly for me is:

//Copyright(c) 2016 Tim McDaniel (TrickyHandz on forum.unity3d.com)
// Updated by Piotr Kosek 2019 (shelim on forum.unity3d.com)
//
// Adapted from code provided by Alima Studios on forum.unity.com
// http://forum.unity3d.com/threads/prefab-breaks-on-mesh-update.282184/#post-2661445
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to
// the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using UnityEngine;
using UnityEditor;
public class UpdateSkinnedMeshWindow : EditorWindow
{
    [MenuItem("Window/Update Skinned Mesh Bones")]
    public static void OpenWindow()
    {
        var window = GetWindow<UpdateSkinnedMeshWindow>();
        window.titleContent = new GUIContent("Skin Updater");
    }
    private SkinnedMeshRenderer targetSkin;
    private Transform rootBone;
    private void OnGUI()
    {
        targetSkin = EditorGUILayout.ObjectField("Target", targetSkin, typeof(SkinnedMeshRenderer), true) as SkinnedMeshRenderer;
        rootBone = EditorGUILayout.ObjectField("RootBone", rootBone, typeof(Transform), true) as Transform;
        GUI.enabled = (targetSkin != null && rootBone != null);
        if (GUILayout.Button("Update Skinned Mesh Renderer"))
        {
            Transform[] newBones = new Transform[targetSkin.bones.Length];
            for (int i = 0; i < targetSkin.bones.Length; i++)
            {
                foreach (var newBone in rootBone.GetComponentsInChildren<Transform>())
                {
                    if (newBone.name == targetSkin.bones[i].name)
                    {
                        newBones[i] = newBone;
                        continue;
                    }
                }
            }
            targetSkin.bones = newBones;
        }
    }
}
21 Likes

Thanks, it worked flawlessly for me!

Just for reference, using version 2019.4.

I did a couple changes to the code, basically while doing some stuff with it I needed a bit more of debug info to fix my bones and stuff, hope it helps others too.

  • Support for disabled transforms.
  • Extra debug info: bones found, missing bone count, etc.
//Copyright(c) 2016 Tim McDaniel (TrickyHandz on forum.unity3d.com)
// Updated by Piotr Kosek 2019 (shelim on forum.unity3d.com)
// Updated by Orochii Zouveleki 2020: Added a couple extra debug info.
//
// Adapted from code provided by Alima Studios on forum.unity.com
// http://forum.unity3d.com/threads/prefab-breaks-on-mesh-update.282184/#post-2661445
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to
// the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using UnityEngine;
using UnityEditor;
public class UpdateSkinnedMeshWindow : EditorWindow {
    [MenuItem("Window/Update Skinned Mesh Bones")]
    public static void OpenWindow()
    {
        var window = GetWindow<UpdateSkinnedMeshWindow>();
        window.titleContent = new GUIContent("Skin Updater");
    }
    private GUIContent statusContent = new GUIContent("Waiting...");
    private SkinnedMeshRenderer targetSkin;
    private Transform rootBone;
    private bool includeInactive;
    private string statusText = "Waiting...";
    private void OnGUI()
    {
        targetSkin = EditorGUILayout.ObjectField("Target", targetSkin, typeof(SkinnedMeshRenderer), true) as SkinnedMeshRenderer;
        rootBone = EditorGUILayout.ObjectField("RootBone", rootBone, typeof(Transform), true) as Transform;
        includeInactive = EditorGUILayout.Toggle("Include Inactive", includeInactive);
        bool enabled = (targetSkin != null && rootBone != null);
        if (!enabled) {
            statusText = "Add a target SkinnedMeshRenderer and a root bone to process.";
        }
        GUI.enabled = enabled;
        if (GUILayout.Button("Update Skinned Mesh Renderer"))
        {
            statusText = "== Processing bones... ==";
            // Look for root bone
            string rootName = "";
            if (targetSkin.rootBone != null) rootName = targetSkin.rootBone.name;
            Transform newRoot = null;
            // Reassign new bones
            Transform[] newBones = new Transform[targetSkin.bones.Length];
            Transform[] existingBones = rootBone.GetComponentsInChildren<Transform>(includeInactive);
            int missingBones = 0;
            for (int i = 0; i < targetSkin.bones.Length; i++)
            {
                if (targetSkin.bones[i] == null) {
                    statusText += System.Environment.NewLine + "WARN: Do not delete the old bones before the skinned mesh is processed!";
                    missingBones++;
                    continue;
                }
                string boneName = targetSkin.bones[i].name;
                bool found = false;
                foreach (var newBone in existingBones)
                {
                    if (newBone.name == rootName) newRoot = newBone;
                    if (newBone.name == boneName)
                    {
                        statusText += System.Environment.NewLine + "· " + newBone.name + " found!";
                        newBones[i] = newBone;
                        found = true;
                    }
                }
                if (!found) {
                    statusText += System.Environment.NewLine + "· " + boneName + " missing!";
                    missingBones++;
                }
            }
            targetSkin.bones = newBones;
            statusText += System.Environment.NewLine + "Done! Missing bones: " + missingBones;
            if (newRoot != null) {
                statusText += System.Environment.NewLine + "· Setting " + rootName + " as root bone.";
                targetSkin.rootBone = newRoot;
            }
        }
        // Draw status because yeh why not?
        statusContent.text = statusText;
        EditorStyles.label.wordWrap = true;
        GUILayout.Label(statusContent);
    }
}

Cheers!

17 Likes

So how do I use this script?

nevermind, I figured it out. Add the script then find a new UpdateSkinnedMeshWindow in the window drop down. Thanks for the handy script!

1 Like

@Ragee @z_orochii this editor script works like a charm! Thank you so much for providing this and the edit.

This is sort of new territory for me though and I have a couple of questions:

  • How easily could this be converted to a run-time script? i.e.) to be used as character clothing swap functionality
  • What would be required to change this such that the target mesh is now parented to the root bone object to consolidate it a littler better?
1 Like

Sorry for the extra late reply, you probably figured out something by yourself at this point.

Techincally speaking you can do this same thing in runtime, only problem is that you’ll need to spawn the whole thing with an exact duplicate of the armature. I.e. spawn a whole fbx prefab, then run the code, then you’ll probably want to delete the old armature that came up with your skinned meshes.

The other way around this is that, since all we need from the original armature are the bone names, you can cache that as a list of strings, keeping the same order, store it somewhere, and then use that as reference to know what bones to assign and where, similar to what this code does. That way you can save up the hazzle of spawning the whole armature, and just spawn the necessary stuff.

Guys! You can simply duplicate original skinned mesh renderer object inside prefab, then you need to assign your new mesh to the mesh field of the duplicated object. And booom, now you have 2 skinned meshes on one armature ;*

5 Likes

oh my god if Vladislav6 didn’t save me here I would’ve been searching all day! You can indeed just switch the mesh of a skinned mesh renderer to change a meshes skeleton! I did this with my clothes system and it works just fine. I made the clothes in blender using the player skeleton as a reference and then I used this to make it use the players real skeleton. Works fine! Thanks Vladisla6!

I found @z_orochii approach very helpful, thank you

1 Like

Hey, our team has slightly optimized the code above

public class TransferMesh : MonoBehaviour
{
    [SerializeField] private SkinnedMeshRenderer[] targetSkin;
    [SerializeField] private Transform rootBone;

    private void Awake()
    {
        if (!rootBone)
        {
            rootBone = FindObjectOfType<RootBone>().transform;
        }

        Dictionary<string, Transform> boneDictionary = new Dictionary<string, Transform>();
        Transform[] rootBoneChildren = rootBone.GetComponentsInChildren<Transform>();
        foreach (Transform child in rootBoneChildren)
        {
            boneDictionary[child.name] = child;
        }

        for (int j = 0; j < targetSkin.Length; j++)
        {
            Transform[] newBones = new Transform[targetSkin[j].bones.Length];
            for (int i = 0; i < targetSkin[j].bones.Length; i++)
            {
                if (boneDictionary.TryGetValue(targetSkin[j].bones[i].name, out Transform newBone))
                {
                    newBones[i] = newBone;
                }
            }
            targetSkin[j].bones = newBones;
        }
    }
}
4 Likes

RootBone Class is to complicated brother

if someone want to use this optimized code just remove 10 line

thank you brother this solution deservers a special yt video

I add Cloth Class to save bone names bc when im making a prefab from this swapped object bone array was getting clear so i must save this names in custom class

using UnityEditor;
using UnityEngine;
public class UpdateSkinnedMeshWindow : EditorWindow
{
[MenuItem(“Window/Update Skinned Mesh Bones”)]
public static void OpenWindow()
{
var window = GetWindow();
window.titleContent = new GUIContent(“Skin Updater”);
}
private SkinnedMeshRenderer targetSkin;
private Transform rootBone;
private void OnGUI()
{
targetSkin = EditorGUILayout.ObjectField(“Target”, targetSkin, typeof(SkinnedMeshRenderer), true) as SkinnedMeshRenderer;
rootBone = EditorGUILayout.ObjectField(“RootBone”, rootBone, typeof(Transform), true) as Transform;
GUI.enabled = (targetSkin != null && rootBone != null);
if (GUILayout.Button(“Update Skinned Mesh Renderer and add Cloth component”))
{
Transform[ ] newBones = new Transform[targetSkin.bones.Length];

Cloth _cloth = targetSkin.gameObject.AddComponent();

_cloth.skinnedMeshRenderer = targetSkin;

string[ ] _boneNames = new string[targetSkin.bones.Length];

for (int i = 0; i < targetSkin.bones.Length; i++)
{
foreach (var newBone in rootBone.GetComponentsInChildren())
{
if (newBone.name == targetSkin.bones*.name)*
{
boneNames = newBone.name;
newBones = newBone;
continue;
}
}
}
_cloth.SetBoneNames(_boneNames);
targetSkin.bones = newBones;
}
}
}
public class Cloth : MonoBehaviour
{
[SerializeField] string[ ] boneNames;
public SkinnedMeshRenderer skinnedMeshRenderer;
public void SetBoneNames(string[ ] _boneNames)
{
boneNames = _boneNames;
}
public string[ ] GetBoneNames()
{
return boneNames;
}
}_

Can someone explain to a noob why one would use the script posted (and iterated on) above, if you can just do this? What’s the advantage of using the script? thank you

Because the most important thing about the SkinnedMeshRenderer is the bones array (which is not visible in the inspector and is created when the model is imported). This array contains the actual reference to the concrete gameobject / transform instances of each bone. When you duplicate a SkinnedMeshRenderer, you would also duplicate the actual bone gameobject hierarchy. So each SMR would have it’s own bone gameobjects. In order to “combine” two SMR on one bone hierarchy, they would need to use the same bones. Note that when you import two meshes which have the same skeleton, the bones array could still be in a different order. This order is mesh specific. The order of the bones in the SMR is directly related to the order of the skinning information in the mesh itself. Namely bindPoses and boneWeights.

So when you import several meshes with the same skeleton, the order of the bones is not guaranteed. That’s what the script does. It matches the bones based on the names. Though those scripts all use a quite naive approach to match the bones in an O(n²) manner. For meshes large number of bones this would not be a good implementation. You usually want to use a Dictionary<string, Transform> to look up the target bone instances when you iterate through the old bones.

Here’s an old UnityAnswers / Discussions thread. Unfortunately the code formatting got messed up during the migration, but this implementation uses a dictionary to re-map the bones.

3 Likes

I’d like to thank the people that made and then improved this script. You literally saved my hobby project. I have a bunch of clothing for my character, and I just couldnt get the animations to sync up, and moving the clothes to the character just didnt work…

This however fixed that issue, thank you. You saved me hours of learning more indeph 3d modelling (If I didnt just give up)

God damn~! You are my angel baby~! I must give you a huge thumb~!