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");
}
}
}
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
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
}
}
@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:
Add the CharacterPoser component to your character
Set the Parent transform
Open the unity Animation (Window > Animation > Animation)
Select an Animation and a key frame to the Pose you want
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();
}
}
}
}