But I'm not here to tantalize or be faux subversive in a rather clumsily veiled attempt to get people to ask me for more information. Rather, I think i'm going to use this space to share random tips I come across during my game-ish interaction prototype development experience. Probably some Unity randomness, tons of Maya randomness, and who knows, maybe some of that Python stuff i seem to love so much.
Alright then, as the title states, let's dive right in. One of the really interesting things I came across in the Unity reference manual:
"The most convenient way for animators to work is to have a single model containing all animations."
Hmm...my 10+ years supporting animators in various degrees tells me otherwise, so of course I setup my character with individual animation files. No referencing or anything pretty like that, this is a pretty quick n dirty project, so no infrastructure or pipe really. We'll make that up next project, but that's for another post. Anyway...
If you're familiar with Unity, you may be aware that when you import an animation, it comes in as a Read-Only asset. In short, it means you can't add animation events or otherwise edit the animation in Unity. After a few hours, well ok, maybe only one, I found the accepted workaround, which is to duplicate the animation clip and thereby creating a new animation clip that's writeable. I quickly found out that many of the explanations i found online where contingent on creating animation the Unity approved way, i.e. one long animation file. That gives you an asset with a bunch of animation clips as children, like so:
With this layout, it's pretty easy to break the Read-Only state, you simply select the clip and Edit > Duplicate or Ctrl+D and you're good to go. The toolflow issues arise when you want to make an animation clip Read-Only and the animation is stored in a separate file. In that case, you get an asset that looks something like this:
In this case, you have actually have a bunch of individual assets that Unity collates into a single-ish asset. The question now is how do you duplicate one of these to break the Read-Only state? Well, selecting the actual asset and duplicating doesn't do it, it just creates another Read-Only animation entry. We can attempt to duplicate the entry in the Inspector:
...But that just gives us the same end result:( So what's a bear to do?? Good question, the secret actually lies in the animation asset's hierarchy. If we expand one of the animation assets, we get all sorts of fun things:
If we look down near the end of the animation asset's children, we find what we seek, the actual animation clip itself. THAT'S what we want to duplicate, so we select that and Edit > Duplicate or Ctrl+D. Now we have a discreet instance of the animation clip:
Now we just have to drag this instance into the appropriate slot in the character's Animation block in the Inspector, and we're good to go:
Now we can add Animation Events to our heart's content. Keep in mind we lose all this data when we reimport, so it's probably a good idea to come up with some sort of automated process for this. Good case for some sort of metadata system...Maybe we'll talk about that at some point, I do need to learn Unity Editor Scripting, but honestly, I have no idea when I'm going to get back to building big systems again. So anyway, that's what I got for now, like i said, it's a pretty specific case, but I imagine i can't be the only person who's ever run into this. Hopefully this helps.
Hope someone found this useful, next up, some thoughts on SoftKinetic and the iisu SDK...Stay Tuned!
this guy by far has the BEST unity coding tutorials I've found. Quick effective and to the point. http://www.youtube.com/user/BurgZergArcade
ReplyDeleteOh no kidding, i had just started going through those tutorials about a month ago, definitely impressed.
ReplyDeleteMy own preference is to use an AssetPostprocessor (my favorite class!) to do this (note: requires pro, but could be easily reorganized as a static function accessible from a menu item for Unity basic). AssetPostprocessor basically gets executed whenever an asset of the specified type is imported, so you can use this to make sure that all of the curve data for transforms are always in sync with the source asset, and your additional curves or AnimationEvents in the duplicate won't be overwritten. A basic implementation could look something like:
ReplyDeleteusing UnityEditor;
using UnityEngine;
using System.Collections;
///
/// Duplicate incoming animations.
///
public class DuplicateIncomingAnimations : AssetPostprocessor
{
///
/// The name of the sidecar folder to contain duplicate animations
///
private static readonly string animationSidecarFolderName = "Animations";
///
/// Raise the postprocess model event.
///
void OnPostprocessModel(GameObject go)
{
// create sidecar folder if needed
string containingDir = AssetDatabaseUtilities.GetDirectoryName(assetPath);
string sidecarDir = System.IO.Path.Combine(
new System.IO.DirectoryInfo(containingDir).FullName,
animationSidecarFolderName
);
if (!System.IO.Directory.Exists(sidecarDir))
AssetDatabase.CreateFolder(containingDir, animationSidecarFolderName);
sidecarDir = System.IO.Path.Combine(
new System.IO.DirectoryInfo(containingDir).Name,
animationSidecarFolderName
);
// check all the assets being imported for an animation clip
Object[] assets = AssetDatabase.LoadAllAssetsAtPath(assetPath);
foreach (Object asset in assets)
{
// skip non animation clip
if (asset.GetType() != typeof(AnimationClip)) continue;
AnimationClip source = (AnimationClip) asset;
// find a dupe, create one if it doesn't exist
string dupePath = System.IO.Path.Combine(sidecarDir, source.name)+".anim";
AnimationClip dupe = AssetDatabase.LoadAssetAtPath(dupePath, typeof(AnimationClip)) as AnimationClip;
if (dupe == null)
{
AssetDatabase.CreateAsset(EditorAnimationUtilities.DuplicateClip(source, ""), dupePath);
AssetDatabase.ImportAsset(dupePath, ImportAssetOptions.ForceUpdate);
dupe = AssetDatabase.LoadAssetAtPath(dupePath, typeof(AnimationClip)) as AnimationClip;
}
// update the dupe
AnimationClipCurveData[] sourceData = AnimationUtility.GetAllCurves(source);
foreach (AnimationClipCurveData datum in sourceData)
{
dupe.SetCurve(datum.path, datum.type, datum.propertyName, datum.curve);
}
dupe.localBounds = source.localBounds;
dupe.wrapMode = source.wrapMode;
}
}
}
Ah woops I should note AssetDatabaseUtilities is a custom class of mine in this example, but should be obvious what it is doing. ;)
ReplyDeleteWow, thanks for this! Yeah, the more i read about AssetPostProcessor, the more i realize he and i need to become really good friends. Hopefully I'll have my Pro license in the next week and I can start exploring a bit. Does this preserve animation events too?
ReplyDeleteYeah basically the duplicate will preserve any data that is not overwritten when the source file data are updated and copied over. Right now, the only AnimationCurves that will come from a source file are transform (local x/y/z position, local x/y/z/w rotation, local x/y/z scale). So, you can use this to maintain AnimationEvents as well as extra curves you happen to add to the clip that are specific to the prefab (e.g., if you animate material settings, a particle effect, or any other exposed property).
ReplyDelete