- Home /
How to rebind animated variable?
Let's say I animate a variable of my script in Unity animation editor. Then if I rename that variable in my script - I loose all animation curves related to that variable. Is there a way to "rebind" those animation curves to renamed variable?
Thank you to those who contributed in the evolution towards this final and handy script. I had this exact issue and this saved me hours and hours of work.
Answer by Paulius-Liekis · Apr 18, 2012 at 09:12 AM
The only way is to write a script which finds that specific curve and adds a new curve with new variable name.
Are you aware of a source for said script and/or are you willing to share, perchance? Thanks!
@Datael: no, I don't have that script. But it's possible by writing editor script. You need to use AnimationUtils which gives you access to individual curves of AnimationClip.
I actually wrote my own script doing what I needed for me a few hours after I replied. Thanks for the information!
Just in case anyone else wants it, it's a really simple window, it's probably buggy etc, so use with caution, but it could be a decent base for a more real tool to do this:
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
public class AnimationTargetRenamer : EditorWindow {
[$$anonymous$$enuItem("Window/Animation Target Renamer")]
public static void OpenWindow() {
GetWindow<AnimationTargetRenamer>("Animation Target Renamer");
}
void Update() {
Repaint();
}
void OnGUI() {
GameObject go = Selection.activeGameObject;
if (go != null && go.animation != null) {
Animation ani = go.animation;
AnimationClipCurveData[] curveDatas = AnimationUtility.GetAllCurves(ani.clip, true);
List<string> targetObjectPaths = new List<string>();
foreach (var curveData in curveDatas) {
if (!targetObjectPaths.Contains(curveData.path)) {
targetObjectPaths.Add(curveData.path);
}
}
Dictionary<string, string> newNames = new Dictionary<string, string>();
foreach (var target in targetObjectPaths) {
string newName = GUILayout.TextField(target);
if (newName != target) {
newNames.Add(target, newName);
}
}
foreach (var pair in newNames) {
string oldName = pair.$$anonymous$$ey;
string newName = pair.Value;
foreach (var curveData in curveDatas) {
if (curveData.path == oldName) {
curveData.path = newName;
}
}
}
if (newNames.Count > 0) {
ani.clip.ClearCurves();
foreach (var curveData in curveDatas) {
ani.clip.SetCurve(curveData.path, curveData.type, curveData.propertyName, curveData.curve);
}
}
} else {
GUILayout.Label("Please select an object with an animation.");
}
}
}
Answer by cymatist · Oct 18, 2013 at 03:14 PM
Thank you! Here is a further development of your code, debugged, cleaned up, and this time for C#.
using UnityEngine;
using UnityEditor;
using System;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// Animation target renamer.
/// This script allows animation curves to be moved from one target to another.
///
/// Usage:
/// 1) Select the gameobject whose curves you wish to move.
/// 2) Ensure that the animation to be modified is the default animation on the game object.
/// 3) Open the Animation Target Renamer from the Window menu in the Unity UI.
/// 4) Change the names in the textboxes on the right side of the window to the names of the objects you wish to move the animations to.
/// NOTE: if the objects do not exist, the original objects will be duplicated and renamed.
/// 5) Select whether or not the old object should be deleted.
/// 6) Press Apply.
/// </summary>
public class AnimationTargetRenamer : EditorWindow
{
/// <summary>
/// The animation whose curves to change.
/// </summary>
private static Animation ani;
/// <summary>
/// The curve data for the animation.
/// </summary>
private static AnimationClipCurveData[] curveDatas;
/// <summary>
/// The new animation clip.
/// </summary>
private static AnimationClip newClip;
/// <summary>
/// Whether or not the curve data is currently initialized.
/// </summary>
private static bool initialized = false;
/// <summary>
/// Whether or not to delete the old names of objects.
/// </summary>
private static bool deleteOldObjects = false;
/// <summary>
/// The names of the original GameObjects.
/// </summary>
private static List<string> origObjectPaths;
/// <summary>
/// The names of the target GameObjects.
/// </summary>
private static List<string> targetObjectPaths;
[MenuItem("Window/Animation Target Renamer")]
public static void OpenWindow ()
{
AnimationTargetRenamer window = (AnimationTargetRenamer)GetWindow<AnimationTargetRenamer> ("Animation Target Renamer");
initialized = false;
}
void OnGUI ()
{
// find the game object we're working on.
GameObject go = Selection.activeGameObject;
if (!initialized) {
// If the object isn't set or doesn't have an animation,
/// we can't initialize the cuve data, so do nothing.
if (go != null && go.animation != null) {
// if we haven't looked at the curve data, do so now,
// and initialize the list of original object paths.
ani = go.animation;
curveDatas = AnimationUtility.GetAllCurves (ani.clip, true);
origObjectPaths = new List<string> ();
foreach (AnimationClipCurveData curveData in curveDatas) {
if (!origObjectPaths.Contains (curveData.path)) {
origObjectPaths.Add (curveData.path);
}
}
initialized = true;
}
}
if (go != null && go.animation != null) {
// if we got here, we have all the data we need to work with,
// so we should be able to build the UI.
targetObjectPaths = new List<string> ();
foreach (AnimationClipCurveData curveData in curveDatas) {
if (!targetObjectPaths.Contains (curveData.path)) {
// if we haven't already added a target, add it to the list.
targetObjectPaths.Add (curveData.path);
}
}
// build the list of textboxes for renaming.
Dictionary<string, string> newNames = new Dictionary<string, string> ();
for (int t=0; t<targetObjectPaths.Count; t++) {
string newName = EditorGUILayout.TextField (origObjectPaths [t], targetObjectPaths [t]);
if (newName != targetObjectPaths [t]) {
newNames.Add (targetObjectPaths [t], newName);
}
}
// set the curve data to the new values.
foreach (KeyValuePair<string,string> pair in newNames) {
string oldName = pair.Key;
string newName = pair.Value;
foreach (var curveData in curveDatas) {
if (curveData.path == oldName) {
curveData.path = newName;
}
}
}
// display the check box with the option to delete old objects.
deleteOldObjects = EditorGUILayout.Toggle("Delete old objects", deleteOldObjects);
} else {
GUILayout.Label ("Please select an object with an animation.");
initialized = false;
}
if (GUILayout.Button ("Apply")) {
// get the actual gameobjects we're working on.
List<GameObject> originalObjects = new List<GameObject>();
List<GameObject> targetObjects = new List<GameObject>();
for(int i =0; i < origObjectPaths.Count; i++) {
originalObjects.Add(GameObject.Find(origObjectPaths[i]));
GameObject target = GameObject.Find(targetObjectPaths[i]);
if(target == null)
{
// If the target object doesn't exist, duplicate the source object,
// and rename it.
target = GameObject.Instantiate(originalObjects[i]) as GameObject;
target.name = targetObjectPaths[i];
target.transform.parent = originalObjects[i].transform.parent;
}
targetObjects.Add(target);
}
// set up the curves based on the new names.
ani.clip.ClearCurves ();
foreach (var curveData in curveDatas) {
ani.clip.SetCurve (curveData.path, curveData.type, curveData.propertyName, curveData.curve);
}
origObjectPaths.Clear ();
foreach (AnimationClipCurveData curveData in curveDatas) {
if (!origObjectPaths.Contains (curveData.path)) {
origObjectPaths.Add (curveData.path);
}
}
// if necessary, delete the old objects.
if(deleteOldObjects) {
for(int i = 0; i < originalObjects.Count; i++) {
if(originalObjects[i] != targetObjects[i]) {
DestroyImmediate(originalObjects[i]);
}
}
}
}
}
}
Any chance there is a way to do this with the Animator component?
Answer by secondbreakfast · Sep 12, 2014 at 10:39 PM
I took the script that cymatist wrote, cleaned it up and made it work directly on an Animation Clip (and it doesn't do the game object modifications as such). The result is that you can edit Animation Clips for both Animation and Animators.
using UnityEngine;
using UnityEditor;
using System;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// Animation clip target renamer.
/// This script allows animation curves to be moved from one target to another.
///
/// Usage:
/// 1) Open the Animation Clip Target Renamer from the Window menu in the Unity UI.
/// 2) Select the animation clip whose curves you wish to move.
/// 3) Change the names in the textboxes on the right side of the window to the names of the objects you wish to move the animations to.
/// 4) Press Apply.
/// </summary>
public class AnimationClipTargetRenamer : EditorWindow
{
public AnimationClip selectedClip;
/// <summary>
/// The curve data for the animation.
/// </summary>
private AnimationClipCurveData[] curveDatas;
/// <summary>
/// The names of the original GameObjects.
/// </summary>
private List<string> origObjectPaths;
/// <summary>
/// The names of the target GameObjects.
/// </summary>
private List<string> targetObjectPaths;
private bool initialized;
[MenuItem("Window/Animation Clip Target Renamer")]
public static void OpenWindow ()
{
AnimationClipTargetRenamer renamer = GetWindow<AnimationClipTargetRenamer> ("Animation Clip Target Renamer");
renamer.Clear();
}
private void Initialize() {
curveDatas = AnimationUtility.GetAllCurves(selectedClip, true);
origObjectPaths = new List<string>();
targetObjectPaths = new List<string>();
foreach (AnimationClipCurveData curveData in curveDatas) {
if (curveData.path != "" && !origObjectPaths.Contains(curveData.path)) {
origObjectPaths.Add(curveData.path);
targetObjectPaths.Add(curveData.path);
}
}
initialized = true;
}
private void Clear() {
curveDatas = null;
origObjectPaths = null;
targetObjectPaths = null;
initialized = false;
}
private void RenameTargets() {
// set the curve data to the new values.
for (int i=0; i < targetObjectPaths.Count; i++) {
string oldName = origObjectPaths[i];
string newName = targetObjectPaths[i];
if (oldName != newName) {
foreach (var curveData in curveDatas) {
if (curveData.path == oldName) {
curveData.path = newName;
}
}
}
}
// set up the curves based on the new names.
selectedClip.ClearCurves();
foreach (var curveData in curveDatas) {
selectedClip.SetCurve(curveData.path, curveData.type, curveData.propertyName, curveData.curve);
}
Clear();
Initialize();
}
void OnGUIShowTargetsList() {
// if we got here, we have all the data we need to work with,
// so we should be able to build the UI.
// build the list of textboxes for renaming.
if (targetObjectPaths != null) {
EditorGUILayout.Space();
EditorGUIUtility.labelWidth = 250;
for (int i=0; i < targetObjectPaths.Count; i++) {
string newName = EditorGUILayout.TextField(origObjectPaths[i], targetObjectPaths[i]);
if (targetObjectPaths[i] != newName) {
targetObjectPaths[i] = newName;
}
}
}
}
void OnGUI() {
AnimationClip previous = selectedClip;
selectedClip = EditorGUILayout.ObjectField("Animation Clip", selectedClip, typeof(AnimationClip), true) as AnimationClip;
if (selectedClip != previous) {
Clear();
}
if (selectedClip != null) {
if (!initialized) {
Initialize();
}
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Refresh")) {
Clear();
Initialize();
}
EditorGUILayout.EndHorizontal();
OnGUIShowTargetsList();
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Apply")) {
RenameTargets();
}
EditorGUILayout.EndHorizontal();
}
}
}
Dear @secondbreakfast,
You have saved my life from eternal damnation. I owe you my soul. You will forever be remembered.
Yours truly, brain56
P.S. Is your username supposed to be an LOTR reference?
I'm glad it helped. It really should be built into Unity to reattach broken links. Yes it is a reference from LOTR, how observant of you.
Hi there, thanks for the script, however, I can't find any text boxes on the right side , I assign the animation hit refresh & apply but nothing happens, any ideas on what is the issue ?
Answer by joao-cabral · Apr 08, 2017 at 11:03 PM
Thank you so much CrazyPanda.
I changed your script so that it allows for multi-clip editing. I also added the feature of removing from the start of the path:
using UnityEngine;
using UnityEditor;
using System;
using System.Collections;
using System.Collections.Generic;
public class AnimationPathRenamer : EditorWindow
{
private string prependPath;
private int removeLeft = 0;
private Vector2 scrollPosition = new Vector2();
public List<AnimationClip> selectedClips = new List<AnimationClip>();
public List<List<RemapperCurveData>> CurveDatasList = new List<List<RemapperCurveData>>();
public AnimationClip selectedClip;
public List<RemapperCurveData> CurveDatas;
private bool initialized;
[MenuItem("Window/Animation Clip Target Renamer")]
public static void OpenWindow()
{
AnimationPathRenamer renamer = GetWindow<AnimationPathRenamer>("Animation Clip Target Renamer");
renamer.Clear();
}
private void Initialize()
{
foreach (AnimationClip clip in selectedClips)
{
CurveDatas = new List<RemapperCurveData>();
var curveBindings = AnimationUtility.GetCurveBindings(clip);
foreach (EditorCurveBinding curveBinding in curveBindings)
{
RemapperCurveData cd = new RemapperCurveData();
cd.Binding = curveBinding;
cd.OldPath = curveBinding.path + "";
cd.NewPath = curveBinding.path + "";
cd.Curve = new AnimationCurve(AnimationUtility.GetEditorCurve(clip, curveBinding).keys);
CurveDatas.Add(cd);
}
CurveDatasList.Add(CurveDatas);
}
initialized = true;
}
private void Clear()
{
foreach (List<RemapperCurveData> oneCurveData in CurveDatasList)
{
oneCurveData.Clear();
}
CurveDatasList.Clear();
CurveDatas = null;
initialized = false;
}
void OnGUIShowTargetsList()
{
if (CurveDatasList.Count == 0)
Initialize();
foreach (List<RemapperCurveData> oneCurveData in CurveDatasList)
{
List<string> unique = new List<string>();
List<RemapperCurveData> uniqueCurveDatas = new List<RemapperCurveData>();
foreach (RemapperCurveData remap in oneCurveData)
{
if (!unique.Contains(remap.Binding.path))
{
unique.Add(remap.Binding.path);
uniqueCurveDatas.Add(remap);
}
}
if (uniqueCurveDatas != null && uniqueCurveDatas.Count > 0)
{
EditorGUILayout.Space();
EditorGUIUtility.labelWidth = 250;
for (int i = 0; i < uniqueCurveDatas.Count; i++)
{
string newName = EditorGUILayout.TextField(uniqueCurveDatas[i].OldPath, uniqueCurveDatas[i].NewPath);
if (uniqueCurveDatas[i].OldPath != newName)
{
var j = i;
oneCurveData.ForEach(x =>
{
if (x.OldPath == uniqueCurveDatas[j].OldPath)
{
x.NewPath = newName;
}
});
}
}
}
}
}
private void RenameTargets()
{
for (int i = 0; i < selectedClips.Count; i++)
{
CurveDatasList[i].ForEach(x =>
{
if (x.Binding.path != "" && x.OldPath != x.NewPath)
{
x.Binding.path = x.NewPath;
x.OldPath = x.NewPath;
}
});
selectedClips[i].ClearCurves();
foreach (var curveData in CurveDatasList[i])
{
selectedClips[i].SetCurve(curveData.Binding.path, curveData.Binding.type, curveData.Binding.propertyName, curveData.Curve);
}
}
Clear();
Initialize();
}
public class RemapperCurveData
{
public EditorCurveBinding Binding;
public AnimationCurve Curve;
public string OldPath;
public string NewPath;
}
void Add()
{
foreach (List<RemapperCurveData> oneCurveData in CurveDatasList)
{
foreach (RemapperCurveData remCurveData in oneCurveData)
{
string newName = EditorGUILayout.TextField(remCurveData.OldPath, remCurveData.NewPath);
remCurveData.NewPath = prependPath + newName;
}
}
}
void Remove()
{
foreach (List<RemapperCurveData> oneCurveData in CurveDatasList)
{
foreach (RemapperCurveData remCurveData in oneCurveData)
{
string newName = remCurveData.NewPath.Substring(removeLeft);
remCurveData.NewPath = newName;
}
}
}
void OnGUI()
{
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition,
GUILayout.Width(position.width), GUILayout.Height(position.height)); // Scroll //
int beforeCount = selectedClips.Count;
DropGUIArea();
int afterCount = selectedClips.Count;
if (beforeCount != afterCount)
{
EditorGUILayout.EndScrollView();
return;
}
for (int i = 0; i < selectedClips.Count; i++)
{
GUILayout.BeginHorizontal();
AnimationClip previous = selectedClips[i];
selectedClips[i] = EditorGUILayout.ObjectField("Animation Clip", selectedClips[i], typeof(AnimationClip), true) as AnimationClip;
if (selectedClips[i] != previous)
{
Clear();
}
if (selectedClips[i] != null)
{
if (!initialized)
{
Initialize();
}
}
else
{
return;
}
if (GUILayout.Button("Remove", GUILayout.ExpandWidth(false)))
{
selectedClips.RemoveAt(i);
Initialize();
}
GUILayout.EndHorizontal();
}
EditorGUILayout.Space();
prependPath = EditorGUILayout.TextField("Prepend Path", prependPath);
if (GUILayout.Button("Add"))
{
Clear();
Initialize();
Add();
prependPath = "";
}
removeLeft = EditorGUILayout.IntField("Remove at Start", removeLeft);
if (GUILayout.Button("Remove"))
{
Clear();
Initialize();
Remove();
removeLeft = 0;
}
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Refresh"))
{
Clear();
Initialize();
}
EditorGUILayout.EndHorizontal();
OnGUIShowTargetsList();
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Apply"))
{
RenameTargets();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndScrollView();
}
void DropGUIArea()
{
/// Drag Here //
Event evt = Event.current;
EditorGUILayout.Space();
Rect drop_area = GUILayoutUtility.GetRect(0.0f, 20.0f, GUILayout.ExpandWidth(true));
drop_area.x += 15;
drop_area.width -= (15 + 18);
GUIStyle estilo = new GUIStyle(GUI.skin.box);
estilo.normal.textColor = Color.black;
GUI.Box(drop_area, "Drag Here", estilo);
EditorGUILayout.Space();
switch (evt.type)
{
case EventType.DragUpdated:
case EventType.DragPerform:
if (!drop_area.Contains(evt.mousePosition))
return;
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (evt.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
foreach (UnityEngine.Object dragged_object in DragAndDrop.objectReferences)
{
AnimationClip draggedAnimation = (AnimationClip)dragged_object;
selectedClips.Add(draggedAnimation);
}
Initialize();
}
break;
}
}
}
Answer by CrazyPanda · Feb 16, 2016 at 01:15 PM
Updated script using LINQ for Unity 5.3 using UnityEngine; using UnityEditor; using System.Collections.Generic; using System.Linq; using PrefabEvolution;
/// <summary>
/// Animation clip target remapper.
/// This script allows animation curves to be moved from one target to another.
///
/// Usage:
/// 1) Open the Animation Clip Target Renamer from the Window menu in the Unity UI.
/// 2) Select the animation clip whose curves you wish to move.
/// 3) Change the names in the textboxes on the right side of the window to the names of the objects you wish to move the animations to.
/// 4) Press Apply.
/// </summary>
public class AnimationClipTargetRemapper : EditorWindow
{
public AnimationClip selectedClip;
public List<RemapperCurveData> CurveDatas;
private bool initialized;
[MenuItem("Window/Animation Clip Target Renamer")]
public static void OpenWindow()
{
AnimationClipTargetRemapper renamer = GetWindow<AnimationClipTargetRemapper>("Animation Clip Target Renamer");
renamer.Clear();
}
private void Initialize()
{
CurveDatas = new List<RemapperCurveData>();
var curveBindings = AnimationUtility.GetCurveBindings(selectedClip);
foreach (EditorCurveBinding curveBinding in curveBindings)
{
RemapperCurveData cd = new RemapperCurveData();
cd.Binding = curveBinding;
cd.OldPath = curveBinding.path+"";
cd.NewPath = curveBinding.path + "";
cd.Curve = new AnimationCurve(AnimationUtility.GetEditorCurve(selectedClip, curveBinding).keys);
CurveDatas.Add(cd);
}
initialized = true;
}
private void Clear()
{
CurveDatas = null;
initialized = false;
}
void OnGUIShowTargetsList()
{
if (CurveDatas == null) Initialize();
var uniqueCurveDatas = CurveDatas.Where(x=>x.Binding.path!="").GroupBy(g => g.Binding.path).Select(g => g.First()).ToList();
if (uniqueCurveDatas != null && uniqueCurveDatas.Count > 0)
{
EditorGUILayout.Space();
EditorGUIUtility.labelWidth = 250;
for (int i = 0; i < uniqueCurveDatas.Count; i++)
{
string newName = EditorGUILayout.TextField(uniqueCurveDatas[i].OldPath, uniqueCurveDatas[i].NewPath);
if (uniqueCurveDatas[i].OldPath != newName)
{
var j = i;
CurveDatas.Foreach(x =>
{
if (x.OldPath == uniqueCurveDatas[j].OldPath)
{
x.NewPath = newName;
}
});
}
}
}
}
private void RenameTargets()
{
CurveDatas.Foreach(x =>
{
if (x.Binding.path != "" && x.OldPath != x.NewPath)
{
x.Binding.path = x.NewPath;
x.OldPath = x.NewPath;
}
});
selectedClip.ClearCurves();
foreach (var curveData in CurveDatas)
{
selectedClip.SetCurve(curveData.Binding.path, curveData.Binding.type, curveData.Binding.propertyName, curveData.Curve);
}
Clear();
Initialize();
}
void OnGUI()
{
AnimationClip previous = selectedClip;
selectedClip = EditorGUILayout.ObjectField("Animation Clip", selectedClip, typeof(AnimationClip), true) as AnimationClip;
if (selectedClip != previous)
{
Clear();
}
if (selectedClip != null)
{
if (!initialized)
{
Initialize();
}
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Refresh"))
{
Clear();
Initialize();
}
EditorGUILayout.EndHorizontal();
OnGUIShowTargetsList();
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Apply"))
{
RenameTargets();
}
EditorGUILayout.EndHorizontal();
}
}
public class RemapperCurveData
{
public EditorCurveBinding Binding;
public AnimationCurve Curve;
public string OldPath;
public string NewPath;
}
}
Thanks Panda! Your script helped me a lot. Didn't compile initially, had to get rid of using PrefabEvolution;
and change Foreach
to ForEach
. Then it compiled and worked. Also, when using it it seemed to have saved my changes to animations even when I did not hit Apply. It is a bit annoying when ctrl-z is not supported.
Your answer
Follow this Question
Related Questions
How to change a variable in the same script? 3 Answers
Is it possible to create a script dinamic like animation>size controling the Elements variables? 1 Answer
Putting animations variables and run? 1 Answer
How do i make a character accelerate and play an animation when the click left-shift 1 Answer