- Home /
How to add keyframes on imported, read-only, animation?
I have multiple animation imported from 3ds max 8.
they are several animation of the same object divided in multiple .max files (ex fire, walk etc) so I've ended up importing them with the @ workaround. Now I need to add an event on them (for ex in the fire animation I have to wake up a fire emitter right in the moment that the weapons fires) unfortunately all the animations are read-only, so no editing is possible.
I know that this question had been posted several times on this forum, but none of the previous answers seems to work for me.
I've try to copy the entire animation (ctrl+d), and the copy remains read-only
I've try the script for copy the animation keyframes to a new anim file (I'm unable to find that script now, but if someone need it I can search better..) but the problem was that this script copy only the animation, I have a whole object with multiple mesh and materials that was lost...
So please someone help me... If you need I can provide the .max file or the demo or the entire project as well.....I need to fix this thing very badly.
Thanks
Answer by Bunny83 · Nov 21, 2011 at 05:09 PM
You have to duplicate the animationclip-asset. Select the animation clip in your imported model and press ctrl+d. Now you should have a seperate AnimationClip file. This animation clip have to be added to an instance of your model. Just drag your model asset into the scene and replace the referenced AnimationClip in the animation array. After you added the AnimationClip to your model you can open the animation editor window and select your model in the hierarchy. Select the right animation on top (if there are multiple) and you can edit anything in this animation. All changes will be saved to the .anim file since that's the animation you're using now.
You can't replace the AnimationClip on the imported model itself but you can create a new prefab out of the instanciated model in your scene. This prefab will used the assigned AnimationClip.
I have created a special AssetPostProcessor which automatically extracts the .anim files and add them automatically to the model prefab since we have 90+ animations ;)
edit
I've created a small test scene with your model:
AnimationEventsTest.unitypackage
All I've done is:
- Draged the model into the scene
- Created a new prefab ("Unit_Prefab")
- Draged the model onto the prefab to store the model in the prefab
- Assigned the seperate AnimationClips("moverun1.anim", "idlefiring1.anim") to the animation array on the new prefab.
- Added my test script to provide a function for the AnimationEvent.
- Select the model in the scene and open the animation window.
- Added two AnimationEvents for the run animation.
Your idle_firing animation seems to be empty, but the run animation works. Oh and i cleaned up unnecessary/duplicated textures. All the animation-fbx files doesn't need to have a material / texture. Unity just need the AnimationClip. Actually after you duplicated the AnimationClip you could completely remove the animation-fbx files. Only when you want to update your animation you have to reimport the animation and duplicate the Clip again.
Because of that procedure we created a specialized AssetPostProcessor which does this steps automatically. If you need to do this 90+ times it's no fun ;)
Thank you for the answer, unfortunately I can't manage to solve the problem that way... I've duplicated the animation script and then added it to the new prefab instance, and this way the script become editable, by the way the animation are not in the animation script (nor in the original neither..), they are stored in several sub-object child of the $$anonymous$$aster object, so when I use the Animation tool to inspect the animation script file I found it empty, but if I navigate trough the sub object to the one named $$anonymous$$aster, I can see the animation...
I have put my animation here: http://www.federicolaggiard.altervista.org/forumExample/Animations.rar
Please try to edit them yourself if you have time (you can find the .max files, the fbm files and the materials also)
Thank you!
$$anonymous$$an you did the magic! That's work like a charm! thank you so much for your patience
You mentioned a postProcessor to do this, can you link to it?
Answer by WILEz1975 · Jun 26, 2017 at 01:37 PM
The clip duplication technique is very cumbersome. With dozens of clips and the need to edit them from the program (Maya, Max, Blender etc ...) becomes some problems...
The best solution is to insert the events in the animations at runtime, in the start function with this function:
void AddEvent(int Clip,float time, string functionName,float floatParameter)
{
anim = GetComponent<Animator>();
AnimationEvent animationEvent = new AnimationEvent();
animationEvent.functionName = functionName;
animationEvent.floatParameter = floatParameter;
animationEvent.time = time;
AnimationClip clip = anim.runtimeAnimatorController.animationClips[Clip];
clip.AddEvent(animationEvent);
}
For example:
Animator anim;
void Start () {
anim = GetComponent<Animator>();
AddEvent(1, 0.2f, "EmitProjectile", 0);
}
void AddEvent(int Clip, float time, string functionName, float floatParameter)
{
anim = GetComponent<Animator>();
AnimationEvent animationEvent = new AnimationEvent();
animationEvent.functionName = functionName;
animationEvent.floatParameter = floatParameter;
animationEvent.time = time;
AnimationClip clip = anim.runtimeAnimatorController.animationClips[Clip];
clip.AddEvent(animationEvent);
}
$$anonymous$$gest an edit to use the initializer with new AnimationEvent()
AnimationEvent animationEvent = new AnimationEvent
{
functionName = functionName,
floatParameter = floatParameter,
time = time
};
Uhm, why? The Object initializer syntax is just a "shorthand" for the exact same code that is in the answer. Actually your code has 6 lines of code while the original has only 4. So it's just a matter of subjective preferences.
Of course adding animation events procedurally at runtime does work, however note that if the animationclip is shared between multiple objects you only have to add the event once. If every object adds the event each of the N objects will receive the event N times. (at least that's what it used to be several years ago)
Thank you for this. Upvoted. $$anonymous$$ine is longer ;)
The pretty version is here: https://pastebin.com/f0ZneTtx . Sorry I couldn't be bothered to adjust the CODE tags forever. $$anonymous$$y code is overflowing no matter what and it just doesn't work.
bool AddEvent(string clipName, float time, string functionName) { return AddEvent
AnimationEvent animationEvent = new AnimationEvent();
animationEvent.functionName = functionName;
animationEvent.time = time;
if (!(parameter is Object && parameter==null))
switch (parameter)
{
case int p: animationEvent.intParameter=p; break;
case float p: animationEvent.floatParameter=p; break;
case string p: animationEvent.stringParameter=p; break;
case Object p: animationEvent.objectReferenceParameter=p; break;
default:
break;
}
animationClip.AddEvent(animationEvent);
return true;
}
Note 1: ani is the Animator, implied elsewhere in the Class
Note 2: Switch-Case for types only works on C# 7+. Other options I found are checking for type names, and using lambdas (which I didn't like).
Answer by lesiry · Jul 30, 2014 at 07:35 AM
using UnityEditor; using UnityEngine; using System.IO; using System.Collections;
public class MultipleCurvesTransferer { const string duplicatePostfix = "_copy"; const string animationFolder = "Animations";
static void CopyClip(string importedPath, string copyPath)
{
AnimationClip src = AssetDatabase.LoadAssetAtPath(importedPath, typeof(AnimationClip)) as AnimationClip;
AnimationClip newClip = new AnimationClip();
newClip.name = src.name + duplicatePostfix;
AssetDatabase.CreateAsset(newClip, copyPath);
AssetDatabase.Refresh();
}
[MenuItem("Assets/Transfer Multiple Clips Curves to Copy")]
static void CopyCurvesToDuplicate()
{
// Get selected AnimationClip
Object[] imported = Selection.GetFiltered(typeof(AnimationClip), SelectionMode.Unfiltered);
if (imported.Length == 0)
{
Debug.LogWarning("Either no objects were selected or the objects selected were not AnimationClips.");
return;
}
//If necessary, create the animations folder.
if (Directory.Exists("Assets/" + animationFolder) == false)
{
AssetDatabase.CreateFolder("Assets", animationFolder);
}
foreach (AnimationClip clip in imported)
{
string importedPath = AssetDatabase.GetAssetPath(clip);
Debug.Log("OriginalPath: " + importedPath);
//If the animation came from an FBX, then use the FBX name as a subfolder to contain the animations.
string copyPath;
if (importedPath.Contains(".fbx") || importedPath.Contains(".FBX"))
{
//With subfolder.
string folder = importedPath.Substring(importedPath.LastIndexOf("/") + 1, importedPath.LastIndexOf(".") - importedPath.LastIndexOf("/") - 1);
if (!Directory.Exists("Assets/Animations/" + folder))
{
AssetDatabase.CreateFolder("Assets/Animations", folder);
}
copyPath = "Assets/Animations/" + folder + "/" + clip.name + duplicatePostfix + ".anim";
}
else
{
//No Subfolder
copyPath = "Assets/Animations/" + clip.name + duplicatePostfix + ".anim";
}
Debug.Log("CopyPath: " + copyPath);
CopyClip(importedPath, copyPath);
AnimationClip copy = AssetDatabase.LoadAssetAtPath(copyPath, typeof(AnimationClip)) as AnimationClip;
if (copy == null)
{
Debug.Log("No copy found at " + copyPath);
return;
}
// Copy curves from imported to copy
AnimationClipCurveData[] curveDatas = AnimationUtility.GetAllCurves(clip, true);
for (int i = 0; i < curveDatas.Length; i++)
{
AnimationUtility.SetEditorCurve(
clip,
EditorCurveBinding.FloatCurve(curveDatas[i].path, curveDatas[i].type, curveDatas[i].propertyName),
curveDatas[i].curve
);
}
Debug.Log("Copying curves into " + copy.name + " is done");
}
}
}
Answer by Crook108 · May 29, 2017 at 08:31 PM
I remember figuring out a way to unlock the read-only status on anim files on a previous project but it escapes me now. The best alternative I can think of if you just want to add events to your animations you can do it in the animation import options.
Just double click your animation file and expand the Events menu. Sadly the precision isn't perfect, its kind of hard to pick an exact key frame. Once you add an event you can name a function it calls and specify and input argument (float, int or string). You will need to link it to an object with a public function with the same name you specified earlier. I will try and update this if I find a better solution.
This one is the true answer. Don't duplicate your animation clips thats just a poor workflow. Doing it via script might work, but not as visually simple. I$$anonymous$$O.
Just as @ Crook108 stated.
Create a script with a public void calling your function (Let's name it public void DOGBAR$$anonymous$$). In my case its a sound effect.
Locate the specific animation and find the "Events" drop down way at the bottom. Then add a "ADD EVENTS" key. Inside the FUNCTION box, type in DOGBAR$$anonymous$$. Do that for as many keyframes that need this function to be called.
$$anonymous$$ake sure the script with the DOGBAR$$anonymous$$ function, is on the same game object as the animator that needs to find the function.
Done. Then at run time whenever that animation is called, it'll auto look for the script containing that function.
note*
It's possible to get the precise keyframe, just scrub the GREY BOX beside the big black play button.. Not sure why Unity colored it that way.. but scrubbing the timeline lets u find the preceise keyframe to add the event keyframe
Answer by refardeon · Mar 25, 2021 at 02:58 AM
Just stumbled over this - there is another way of dealing with this issue: In the import settings, under the 'Animation' tab, scroll down to the 'Events' tab to edit events - see attached image, I'm using it to emit footstep events. You can even use the little preview window on the bottom to find the correct spot in the animation. Same for the 'Curves' etc. Once you're done, apply your changes and you're done.