Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice
  4. Dismiss Notice

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. }
     
    Alscenic, mr_syn, mcmorry and 8 others like this.
  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!
     
    Tioon, Alscenic, mr_syn and 9 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 ;*
     
    justdizzy and zafery 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:
    496
    I found @z_orochii approach very helpful, thank you
     
  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. }