Search Unity

How to move a skinned mesh renderer over to another model

Discussion in 'Scripting' started by Ragee, Jun 29, 2020.

  1. Ragee

    Ragee

    Joined:
    Nov 24, 2012
    Posts:
    15
    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 https://forum.unity.com/threads/prefab-breaks-on-mesh-update.282184/

    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:
    Code (CSharp):
    1. //Copyright(c) 2016 Tim McDaniel (TrickyHandz on forum.unity3d.com)
    2. // Updated by Piotr Kosek 2019 (shelim on forum.unity3d.com)
    3. //
    4. // Adapted from code provided by Alima Studios on forum.unity.com
    5. // http://forum.unity3d.com/threads/prefab-breaks-on-mesh-update.282184/#post-2661445
    6. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
    7. // software and associated documentation files (the "Software"), to deal in the Software without
    8. // restriction, including without limitation the rights to use, copy, modify, merge, publish,
    9. // distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
    10. // Software is furnished to do so, subject to
    11. // the following conditions:
    12. // The above copyright notice and this permission notice shall be included in all copies or
    13. // substantial portions of the Software.
    14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    15. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    16. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    17. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    18. // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
    19. // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    20. // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    21. // SOFTWARE.
    22. using UnityEngine;
    23. using UnityEditor;
    24. public class UpdateSkinnedMeshWindow : EditorWindow
    25. {
    26.     [MenuItem("Window/Update Skinned Mesh Bones")]
    27.     public static void OpenWindow()
    28.     {
    29.         var window = GetWindow<UpdateSkinnedMeshWindow>();
    30.         window.titleContent = new GUIContent("Skin Updater");
    31.     }
    32.     private SkinnedMeshRenderer targetSkin;
    33.     private Transform rootBone;
    34.     private void OnGUI()
    35.     {
    36.         targetSkin = EditorGUILayout.ObjectField("Target", targetSkin, typeof(SkinnedMeshRenderer), true) as SkinnedMeshRenderer;
    37.         rootBone = EditorGUILayout.ObjectField("RootBone", rootBone, typeof(Transform), true) as Transform;
    38.         GUI.enabled = (targetSkin != null && rootBone != null);
    39.         if (GUILayout.Button("Update Skinned Mesh Renderer"))
    40.         {
    41.             Transform[] newBones = new Transform[targetSkin.bones.Length];
    42.             for (int i = 0; i < targetSkin.bones.Length; i++)
    43.             {
    44.                 foreach (var newBone in rootBone.GetComponentsInChildren<Transform>())
    45.                 {
    46.                     if (newBone.name == targetSkin.bones[i].name)
    47.                     {
    48.                         newBones[i] = newBone;
    49.                         continue;
    50.                     }
    51.                 }
    52.             }
    53.             targetSkin.bones = newBones;
    54.         }
    55.     }
    56. }
     
  2. z_orochii

    z_orochii

    Joined:
    Mar 8, 2013
    Posts:
    20
    Thanks, it worked flawlessly for me!

    Just for reference, using version 2019.4.
     
  3. z_orochii

    z_orochii

    Joined:
    Mar 8, 2013
    Posts:
    20
    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.

    Code (CSharp):
    1. //Copyright(c) 2016 Tim McDaniel (TrickyHandz on forum.unity3d.com)
    2. // Updated by Piotr Kosek 2019 (shelim on forum.unity3d.com)
    3. // Updated by Orochii Zouveleki 2020: Added a couple extra debug info.
    4. //
    5. // Adapted from code provided by Alima Studios on forum.unity.com
    6. // http://forum.unity3d.com/threads/prefab-breaks-on-mesh-update.282184/#post-2661445
    7. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
    8. // software and associated documentation files (the "Software"), to deal in the Software without
    9. // restriction, including without limitation the rights to use, copy, modify, merge, publish,
    10. // distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
    11. // Software is furnished to do so, subject to
    12. // the following conditions:
    13. // The above copyright notice and this permission notice shall be included in all copies or
    14. // substantial portions of the Software.
    15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    16. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    17. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    18. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    19. // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
    20. // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    21. // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    22. // SOFTWARE.
    23. using UnityEngine;
    24. using UnityEditor;
    25. public class UpdateSkinnedMeshWindow : EditorWindow {
    26.     [MenuItem("Window/Update Skinned Mesh Bones")]
    27.     public static void OpenWindow()
    28.     {
    29.         var window = GetWindow<UpdateSkinnedMeshWindow>();
    30.         window.titleContent = new GUIContent("Skin Updater");
    31.     }
    32.     private GUIContent statusContent = new GUIContent("Waiting...");
    33.     private SkinnedMeshRenderer targetSkin;
    34.     private Transform rootBone;
    35.     private bool includeInactive;
    36.     private string statusText = "Waiting...";
    37.     private void OnGUI()
    38.     {
    39.         targetSkin = EditorGUILayout.ObjectField("Target", targetSkin, typeof(SkinnedMeshRenderer), true) as SkinnedMeshRenderer;
    40.         rootBone = EditorGUILayout.ObjectField("RootBone", rootBone, typeof(Transform), true) as Transform;
    41.         includeInactive = EditorGUILayout.Toggle("Include Inactive", includeInactive);
    42.         bool enabled = (targetSkin != null && rootBone != null);
    43.         if (!enabled) {
    44.             statusText = "Add a target SkinnedMeshRenderer and a root bone to process.";
    45.         }
    46.         GUI.enabled = enabled;
    47.         if (GUILayout.Button("Update Skinned Mesh Renderer"))
    48.         {
    49.             statusText = "== Processing bones... ==";
    50.             // Look for root bone
    51.             string rootName = "";
    52.             if (targetSkin.rootBone != null) rootName = targetSkin.rootBone.name;
    53.             Transform newRoot = null;
    54.             // Reassign new bones
    55.             Transform[] newBones = new Transform[targetSkin.bones.Length];
    56.             Transform[] existingBones = rootBone.GetComponentsInChildren<Transform>(includeInactive);
    57.             int missingBones = 0;
    58.             for (int i = 0; i < targetSkin.bones.Length; i++)
    59.             {
    60.                 if (targetSkin.bones[i] == null) {
    61.                     statusText += System.Environment.NewLine + "WARN: Do not delete the old bones before the skinned mesh is processed!";
    62.                     missingBones++;
    63.                     continue;
    64.                 }
    65.                 string boneName = targetSkin.bones[i].name;
    66.                 bool found = false;
    67.                 foreach (var newBone in existingBones)
    68.                 {
    69.                     if (newBone.name == rootName) newRoot = newBone;
    70.                     if (newBone.name == boneName)
    71.                     {
    72.                         statusText += System.Environment.NewLine + "· " + newBone.name + " found!";
    73.                         newBones[i] = newBone;
    74.                         found = true;
    75.                     }
    76.                 }
    77.                 if (!found) {
    78.                     statusText += System.Environment.NewLine + "· " + boneName + " missing!";
    79.                     missingBones++;
    80.                 }
    81.             }
    82.             targetSkin.bones = newBones;
    83.             statusText += System.Environment.NewLine + "Done! Missing bones: " + missingBones;
    84.             if (newRoot != null) {
    85.                 statusText += System.Environment.NewLine + "· Setting " + rootName + " as root bone.";
    86.                 targetSkin.rootBone = newRoot;
    87.             }
    88.         }
    89.         // Draw status because yeh why not?
    90.         statusContent.text = statusText;
    91.         EditorStyles.label.wordWrap = true;
    92.         GUILayout.Label(statusContent);
    93.     }
    94. }
    Cheers!
     
    timmccune, Tioon, Alscenic and 10 others like this.
  4. chaoticmass

    chaoticmass

    Joined:
    Nov 25, 2018
    Posts:
    7
    So how do I use this script?
     
  5. chaoticmass

    chaoticmass

    Joined:
    Nov 25, 2018
    Posts:
    7
    nevermind, I figured it out. Add the script then find a new UpdateSkinnedMeshWindow in the window drop down. Thanks for the handy script!
     
    IgorAherneBusiness likes this.
  6. local306

    local306

    Joined:
    Feb 28, 2016
    Posts:
    155
    @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:

    1. How easily could this be converted to a run-time script? i.e.) to be used as character clothing swap functionality
    2. 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?
     
    Raisito likes this.
  7. z_orochii

    z_orochii

    Joined:
    Mar 8, 2013
    Posts:
    20
    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.
     
  8. Vladislav6

    Vladislav6

    Joined:
    Sep 11, 2017
    Posts:
    1
    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 ;*
     
    BuzzKirill, tok23, bugbeeb and 2 others like this.
  9. AWildCoconut

    AWildCoconut

    Joined:
    Aug 8, 2020
    Posts:
    6
    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!
     
  10. mcbauer

    mcbauer

    Joined:
    Oct 10, 2015
    Posts:
    524
    I found @z_orochii approach very helpful, thank you
     
    timmccune likes this.
  11. Kalen1111

    Kalen1111

    Joined:
    Jun 2, 2021
    Posts:
    1
    Hey, our team has slightly optimized the code above
    Code (CSharp):
    1. public class TransferMesh : MonoBehaviour
    2. {
    3.     [SerializeField] private SkinnedMeshRenderer[] targetSkin;
    4.     [SerializeField] private Transform rootBone;
    5.  
    6.     private void Awake()
    7.     {
    8.         if (!rootBone)
    9.         {
    10.             rootBone = FindObjectOfType<RootBone>().transform;
    11.         }
    12.  
    13.         Dictionary<string, Transform> boneDictionary = new Dictionary<string, Transform>();
    14.         Transform[] rootBoneChildren = rootBone.GetComponentsInChildren<Transform>();
    15.         foreach (Transform child in rootBoneChildren)
    16.         {
    17.             boneDictionary[child.name] = child;
    18.         }
    19.  
    20.         for (int j = 0; j < targetSkin.Length; j++)
    21.         {
    22.             Transform[] newBones = new Transform[targetSkin[j].bones.Length];
    23.             for (int i = 0; i < targetSkin[j].bones.Length; i++)
    24.             {
    25.                 if (boneDictionary.TryGetValue(targetSkin[j].bones[i].name, out Transform newBone))
    26.                 {
    27.                     newBones[i] = newBone;
    28.                 }
    29.             }
    30.             targetSkin[j].bones = newBones;
    31.         }
    32.     }
    33. }
     
    grzeszygmunt1 and Hoijima like this.
  12. grzeszygmunt1

    grzeszygmunt1

    Joined:
    Jul 15, 2023
    Posts:
    3
    RootBone Class is to complicated brother

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

    grzeszygmunt1

    Joined:
    Jul 15, 2023
    Posts:
    3
    thank you brother this solution deservers a special yt video
     
  14. grzeszygmunt1

    grzeszygmunt1

    Joined:
    Jul 15, 2023
    Posts:
    3
    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<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 and add Cloth component"))
    {
    Transform[] newBones = new Transform[targetSkin.bones.Length];

    Cloth _cloth = targetSkin.gameObject.AddComponent<Cloth>();

    _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<Transform>())
    {
    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;
    }
    }
     
  15. BuzzKirill

    BuzzKirill

    Joined:
    Nov 14, 2017
    Posts:
    48
    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
     
  16. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,011
    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.
     
    timmccune, halley and BuzzKirill like this.