Copy character position or pose from animation keyframe clip

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.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class copyTransform : MonoBehaviour
{
    public Transform obj01;
    public Transform obj02;
    Transform[] transforms01;
    Transform[] transforms02;
    int transLength01;
    int transLength02;

    private TransformData transformDataOfObj01; //TransformData is a class that store and transfer the transform from
    private TransformData transformDataOfObj02; // one object to another

    public void transformThis()
    {
        transforms01 = obj01.GetComponentsInChildren<Transform>();
        transforms02 = obj02.GetComponentsInChildren<Transform>();

        transLength01 = transforms01.Length;
        transLength02 = transforms02.Length;

        if(transLength01 == transLength02) //MUST BE the same character with the same rigged structure
        {
          
            for (int i = 0; i <= transLength01; i++)
            {
                transformDataOfObj01 = new TransformData(transforms01[i].transform);
                transformDataOfObj01.ApplyTo(transforms02[i].transform);
            }
        }
        else
        {
            Debug.Log("Objects does not have the same childrens Length");
        }


    }
}

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: c# - Copying all the transform values of a gameObject? - Stack Overflow

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[SerializeField]
public class TransformData
{
    public Vector3 LocalPosition = Vector3.zero;
    public Vector3 LocalEulerRotation = Vector3.zero;
    public Vector3 LocalScale = Vector3.one;

    // Unity requires a default constructor for serialization
    public TransformData() { }

    public TransformData(Transform transform)
    {
        LocalPosition = transform.localPosition;
        LocalEulerRotation = transform.localEulerAngles;
        LocalScale = transform.localScale;
    }

    public void ApplyTo(Transform transform)
    {
        transform.localPosition = LocalPosition;
        transform.localEulerAngles = LocalEulerRotation;
        transform.localScale = LocalScale;
    }
}

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: Editor Scripting - Unity Learn

using UnityEngine;
using System.Collections;
using UnityEditor;

[CustomEditor(typeof(copyTransform))]
public class CopyTransBuilderEditor : Editor
{
    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();

        copyTransform myScript = (copyTransform)target;
        if (GUILayout.Button("Copy Transform"))
        {
            myScript.transformThis();
        }
    }
}

Last, a video of how it works. I hope you find this usefull

9 Likes

Perfect!
I was just searching around for this functionality and found this post. The scripts work perfectly in 2021 LTS.

Muchas gracias Marck_ozz…

1 Like

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:

using UnityEditor;
using UnityEngine;
public class CopyTransform : MonoBehaviour
{
    [MenuItem("GameObject/SetPose", false, -10)] // now it show on right click on the object
    public static void SetPose()
    {
        TransformData transformDataOfObj01; //TransformData is a class that store and transfer the transform from
        TransformData transformDataOfObj02; // one object to another
       
        Transform obj01 = Selection.activeTransform; // get the clicked object
        Transform obj02 = Instantiate(obj01, obj01.position, obj01.rotation); //duplicate it
       
        Transform[] transforms01 = obj01.GetComponentsInChildren<Transform>();
        Transform[] transforms02 = obj02.GetComponentsInChildren<Transform>();
       
        int transLength01 = transforms01.Length;
        int transLength02 = transforms02.Length;

        if (transLength01 != transLength02) return; //MUST BE the same character with the same rigged structure
       
        for (int i = 0; i <= transLength01-1; i++)
        {
            transformDataOfObj01 = new TransformData(transforms01[i].transform);
            transformDataOfObj01.ApplyTo(transforms02[i].transform);
        }
           
        DestroyImmediate(obj02.GetComponent<Animator>()); //destroy the animator of the copy
    }
}

8319177--1091922--upload_2022-7-28_14-7-28.png

1 Like

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,

@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
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


namespace Character.Utility
{
    public class CharacterPoser : MonoBehaviour
    {

       [SerializeField] public Transform parent;

       [SerializeField] public TransformData[] data;

        // Start is called before the first frame update
        void Start()
        {
            if (parent != null && data != null)
                foreach (var d in data) d.Apply();
        }


        [Serializable]
        public class TransformData
        {
            public Transform jointTransform;
            public Vector3 LocalPosition = Vector3.zero;
            public Vector3 LocalEulerRotation = Vector3.zero;
            public Vector3 LocalScale = Vector3.one;

            // Unity requires a default constructor for serialization
            public TransformData() { }

            public TransformData(Transform transform)
            {
                jointTransform = transform;
                LocalPosition = transform.localPosition;
                LocalEulerRotation = transform.localEulerAngles;
                LocalScale = transform.localScale;
            }

            public void Apply()
            {
                jointTransform.localPosition = LocalPosition;
                jointTransform.localEulerAngles = LocalEulerRotation;
                jointTransform.localScale = LocalScale;
            }
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

namespace Character.Utility
{



    [CustomEditor(typeof(CharacterPoser))]
    public class CharacterPoserEditor : Editor
    {

        public override void OnInspectorGUI()
        {
            CharacterPoser poser = (CharacterPoser)target;

            serializedObject.Update();


            // Check if any control changed between here and EndChangeCheck
            EditorGUI.BeginChangeCheck();

            base.DrawDefaultInspector();

            EditorGUILayout.BeginVertical();
            string poseData = "";
            if (poser.data == null)
                poseData = "No pose data";
            else
                poseData = $"Pose Saved. has {poser.data.Length} transforms";
            EditorGUILayout.LabelField(poseData);
            EditorGUILayout.EndVertical();

            if (GUILayout.Button("SavePose", GUILayout.Width(150), GUILayout.Height(20)))
            {
                SavePose(poser);
                Debug.Log($"Pose Saved for {poser.data.Length} transforms");
            }

            if (GUILayout.Button("TestPose", GUILayout.Width(150), GUILayout.Height(20)))
            {
                TestPose(poser);
                Debug.Log($"Character Pose set");
            }

            // If any control changed, then apply changes
            if (EditorGUI.EndChangeCheck())
            {
                serializedObject.ApplyModifiedProperties();
            }

        }

        private void SavePose(CharacterPoser poser)
        {
            if (poser.parent == null)
            {
                Debug.LogError($"Transform parent not set");
            }
            else
            {
                List<CharacterPoser.TransformData> data = new List<CharacterPoser.TransformData>();

                var transforms = poser.parent.GetComponentsInChildren<Transform>();

                for (int i = 0; i < transforms.Length; i++)
                {
                    CharacterPoser.TransformData tdata = new CharacterPoser.TransformData(transforms[i]);
                    data.Add(tdata);
                }

                poser.data = data.ToArray();

                GUI.changed = true;
                EditorUtility.SetDirty(target);
            }
        }

        private void TestPose(CharacterPoser poser)
        {
            foreach (var p in poser.data)
            {
                p.Apply();
            }
        }

    }

}
1 Like