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.

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

     
    Aldeminor, Zaddo, HeriHH and 3 others like this.
  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
     
  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:
    66
    @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.