Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Showcase Copy character position or pose from animation keyframe clip

Discussion in 'Animation' started by marck_ozz, Nov 12, 2020.

  1. marck_ozz

    marck_ozz

    Joined:
    Nov 30, 2018
    Posts:
    107
    Something that nobody asked for (I think)... but could be usefull.

    In my scene I use the "death_animation" in a lot of my characters just to give a "feeling of mess" but this has a cost in performance so I decided to copy this " death pose" or "death Position" in a different prefab of my characters to use them without animation, animator or any script. But copy one by one of all the rigged components of different characters is a lot of work so I made this scripsts that works in the editor not in play mode

    Copy Transfor Script:
    This is the main script that make the work of copy all the position from one character to another.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class copyTransform : MonoBehaviour
    6. {
    7.     public Transform obj01;
    8.     public Transform obj02;
    9.     Transform[] transforms01;
    10.     Transform[] transforms02;
    11.     int transLength01;
    12.     int transLength02;
    13.  
    14.     private TransformData transformDataOfObj01; //TransformData is a class that store and transfer the transform from
    15.     private TransformData transformDataOfObj02; // one object to another
    16.  
    17.     public void transformThis()
    18.     {
    19.         transforms01 = obj01.GetComponentsInChildren<Transform>();
    20.         transforms02 = obj02.GetComponentsInChildren<Transform>();
    21.  
    22.         transLength01 = transforms01.Length;
    23.         transLength02 = transforms02.Length;
    24.  
    25.         if(transLength01 == transLength02) //MUST BE the same character with the same rigged structure
    26.         {
    27.          
    28.             for (int i = 0; i <= transLength01; i++)
    29.             {
    30.                 transformDataOfObj01 = new TransformData(transforms01[i].transform);
    31.                 transformDataOfObj01.ApplyTo(transforms02[i].transform);
    32.             }
    33.         }
    34.         else
    35.         {
    36.             Debug.Log("Objects does not have the same childrens Length");
    37.         }
    38.  
    39.  
    40.     }
    41. }
    TransformData script is a class that store and transfer the transform from one object to another, this
    script was made by user "derHugo" in this StackOverflow post: https://stackoverflow.com/questions/56794066/copying-all-the-transform-values-of-a-gameobject

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. [SerializeField]
    6. public class TransformData
    7. {
    8.     public Vector3 LocalPosition = Vector3.zero;
    9.     public Vector3 LocalEulerRotation = Vector3.zero;
    10.     public Vector3 LocalScale = Vector3.one;
    11.  
    12.     // Unity requires a default constructor for serialization
    13.     public TransformData() { }
    14.  
    15.     public TransformData(Transform transform)
    16.     {
    17.         LocalPosition = transform.localPosition;
    18.         LocalEulerRotation = transform.localEulerAngles;
    19.         LocalScale = transform.localScale;
    20.     }
    21.  
    22.     public void ApplyTo(Transform transform)
    23.     {
    24.         transform.localPosition = LocalPosition;
    25.         transform.localEulerAngles = LocalEulerRotation;
    26.         transform.localScale = LocalScale;
    27.     }
    28. }
    finally add a custom Inspector button in the main CopyTransform script.This script MUST BE in a folder named "Editor" under the Asset folder. Find more info in here: https://learn.unity.com/tutorial/editor-scripting#5c7f8528edbc2a002053b5f8

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEditor;
    4.  
    5. [CustomEditor(typeof(copyTransform))]
    6. public class CopyTransBuilderEditor : Editor
    7. {
    8.     public override void OnInspectorGUI()
    9.     {
    10.         DrawDefaultInspector();
    11.  
    12.         copyTransform myScript = (copyTransform)target;
    13.         if (GUILayout.Button("Copy Transform"))
    14.         {
    15.             myScript.transformThis();
    16.         }
    17.     }
    18. }
    19.  
    Last, a video of how it works. I hope you find this usefull

     
  2. HeriHH

    HeriHH

    Joined:
    Jun 5, 2020
    Posts:
    1
    Perfect!
    I was just searching around for this functionality and found this post. The scripts work perfectly in 2021 LTS.

    Muchas gracias Marck_ozz..
     
    marck_ozz likes this.
  3. PrisVas

    PrisVas

    Joined:
    May 23, 2013
    Posts:
    8
    Thank you very much!
    It works great. But I did some change, so, it's not necessary to duplicate the object and add the script. Here is my version:


    Code (CSharp):
    1.  
    2. using UnityEditor;
    3. using UnityEngine;
    4. public class CopyTransform : MonoBehaviour
    5. {
    6.     [MenuItem("GameObject/SetPose", false, -10)] // now it show on right click on the object
    7.     public static void SetPose()
    8.     {
    9.         TransformData transformDataOfObj01; //TransformData is a class that store and transfer the transform from
    10.         TransformData transformDataOfObj02; // one object to another
    11.        
    12.         Transform obj01 = Selection.activeTransform; // get the clicked object
    13.         Transform obj02 = Instantiate(obj01, obj01.position, obj01.rotation); //duplicate it
    14.        
    15.         Transform[] transforms01 = obj01.GetComponentsInChildren<Transform>();
    16.         Transform[] transforms02 = obj02.GetComponentsInChildren<Transform>();
    17.        
    18.         int transLength01 = transforms01.Length;
    19.         int transLength02 = transforms02.Length;
    20.  
    21.         if (transLength01 != transLength02) return; //MUST BE the same character with the same rigged structure
    22.        
    23.         for (int i = 0; i <= transLength01-1; i++)
    24.         {
    25.             transformDataOfObj01 = new TransformData(transforms01[i].transform);
    26.             transformDataOfObj01.ApplyTo(transforms02[i].transform);
    27.         }
    28.            
    29.         DestroyImmediate(obj02.GetComponent<Animator>()); //destroy the animator of the copy
    30.     }
    31. }
    32.  
    upload_2022-7-28_14-7-28.png
     
    faltoid likes this.
  4. SpaceDragonMap

    SpaceDragonMap

    Joined:
    Jun 4, 2020
    Posts:
    5
    Hi PrisVas,

    Thank you for sharing the new code. Do we still need the original code files from marck_ozz? Or we just need the codes you posted?

    Thanks again,
     
  5. Zaddo

    Zaddo

    Joined:
    May 19, 2012
    Posts:
    78
    @marck_ozz Thanks. This helped a lot. I used your code as a foundation for another use case. I have a LOD on my character components, and I turn off animation when far away, but the characters default to a T-Pose, which doesn't look good at a distance.

    These scripts will set the default character pose at run time.

    How to Use:
    1. Add the CharacterPoser component to your character
    2. Set the Parent transform
    3. Open the unity Animation (Window > Animation > Animation)
    4. Select an Animation and a key frame to the Pose you want
    5. Click the SavePose button on CharacterPoser

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6.  
    7. namespace Character.Utility
    8. {
    9.     public class CharacterPoser : MonoBehaviour
    10.     {
    11.  
    12.        [SerializeField] public Transform parent;
    13.  
    14.        [SerializeField] public TransformData[] data;
    15.  
    16.         // Start is called before the first frame update
    17.         void Start()
    18.         {
    19.             if (parent != null && data != null)
    20.                 foreach (var d in data) d.Apply();
    21.         }
    22.  
    23.  
    24.         [Serializable]
    25.         public class TransformData
    26.         {
    27.             public Transform jointTransform;
    28.             public Vector3 LocalPosition = Vector3.zero;
    29.             public Vector3 LocalEulerRotation = Vector3.zero;
    30.             public Vector3 LocalScale = Vector3.one;
    31.  
    32.             // Unity requires a default constructor for serialization
    33.             public TransformData() { }
    34.  
    35.             public TransformData(Transform transform)
    36.             {
    37.                 jointTransform = transform;
    38.                 LocalPosition = transform.localPosition;
    39.                 LocalEulerRotation = transform.localEulerAngles;
    40.                 LocalScale = transform.localScale;
    41.             }
    42.  
    43.             public void Apply()
    44.             {
    45.                 jointTransform.localPosition = LocalPosition;
    46.                 jointTransform.localEulerAngles = LocalEulerRotation;
    47.                 jointTransform.localScale = LocalScale;
    48.             }
    49.         }
    50.     }
    51. }
    52.  
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEditor;
    4. using UnityEngine;
    5.  
    6. namespace Character.Utility
    7. {
    8.  
    9.  
    10.  
    11.     [CustomEditor(typeof(CharacterPoser))]
    12.     public class CharacterPoserEditor : Editor
    13.     {
    14.  
    15.         public override void OnInspectorGUI()
    16.         {
    17.             CharacterPoser poser = (CharacterPoser)target;
    18.  
    19.             serializedObject.Update();
    20.  
    21.  
    22.             // Check if any control changed between here and EndChangeCheck
    23.             EditorGUI.BeginChangeCheck();
    24.  
    25.             base.DrawDefaultInspector();
    26.  
    27.             EditorGUILayout.BeginVertical();
    28.             string poseData = "";
    29.             if (poser.data == null)
    30.                 poseData = "No pose data";
    31.             else
    32.                 poseData = $"Pose Saved. has {poser.data.Length} transforms";
    33.             EditorGUILayout.LabelField(poseData);
    34.             EditorGUILayout.EndVertical();
    35.  
    36.             if (GUILayout.Button("SavePose", GUILayout.Width(150), GUILayout.Height(20)))
    37.             {
    38.                 SavePose(poser);
    39.                 Debug.Log($"Pose Saved for {poser.data.Length} transforms");
    40.             }
    41.  
    42.             if (GUILayout.Button("TestPose", GUILayout.Width(150), GUILayout.Height(20)))
    43.             {
    44.                 TestPose(poser);
    45.                 Debug.Log($"Character Pose set");
    46.             }
    47.  
    48.             // If any control changed, then apply changes
    49.             if (EditorGUI.EndChangeCheck())
    50.             {
    51.                 serializedObject.ApplyModifiedProperties();
    52.             }
    53.  
    54.         }
    55.  
    56.         private void SavePose(CharacterPoser poser)
    57.         {
    58.             if (poser.parent == null)
    59.             {
    60.                 Debug.LogError($"Transform parent not set");
    61.             }
    62.             else
    63.             {
    64.                 List<CharacterPoser.TransformData> data = new List<CharacterPoser.TransformData>();
    65.  
    66.                 var transforms = poser.parent.GetComponentsInChildren<Transform>();
    67.  
    68.                 for (int i = 0; i < transforms.Length; i++)
    69.                 {
    70.                     CharacterPoser.TransformData tdata = new CharacterPoser.TransformData(transforms[i]);
    71.                     data.Add(tdata);
    72.                 }
    73.  
    74.                 poser.data = data.ToArray();
    75.  
    76.                 GUI.changed = true;
    77.                 EditorUtility.SetDirty(target);
    78.             }
    79.         }
    80.  
    81.         private void TestPose(CharacterPoser poser)
    82.         {
    83.             foreach (var p in poser.data)
    84.             {
    85.                 p.Apply();
    86.             }
    87.         }
    88.  
    89.     }
    90.  
    91. }
     
    GBudee likes this.