Merging animation files

Merging animation files

  • Comments 28

Our skinned animation sample supports any number of animations, which are imported from the input BoneContent.Animations by the SkinnedModelProcessor, stored in the output SkinningData.AnimationClips dictionary, and can be switched or blended between in whatever way your runtime game code sees fit.

But if you try to create a model which contains multiple animations, you will discover that current Autodesk FBX exporters only support a single animation take per FBX file!

One solution is to concatenate all your animations into a single long one, then choose the appropriate time region when you play back the data. For instance, "run" might be located between 2 sec and 3.1 sec, while 3.1 sec to 5.4 sec is "jump". Kinda like the animation version of sprite sheets, this works ok, but it can be a pain keeping track of which data means what.

Another approach is to export each animation into a separate FBX file, then use a custom processor to merge them.

  • Add the following processor code to a Content Pipeline Extension project
  • Insert whatever other processor code you need (eg. the SkinnedModelProcessor from our skinning sample) at the TODO comment
  • Build the Content Pipeline Extension project
  • Add the Content Pipeline Extension project to Content \ References
  • Export each animation into a separate FBX file
  • Add just one of these FBX files (the master version, which will provide the model geometry) to your Content project
  • In the Visual Studio properties for this master FBX file, set the processor to MergeAnimationsProcessor
  • Click the plus icon to the left of the processor selection, which will expand the processor properties
  • In the MergeAnimations box, type semicolon separated names of the other animation files, eg. "run.fbx;jump.fbx;turn.fbx"
  • Build your content, and look in the output pane to confirm that it loaded and merged the expected animation data
  • Now your BoneContent.Animations can contain as many different animation clips as you like!

 

using System.Linq;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;

namespace MergeAnimationsPipeline
{
    [ContentProcessor]
    public class MergeAnimationsProcessor : ModelProcessor
    {
        public string MergeAnimations { get; set; }


        public override ModelContent Process(NodeContent input, ContentProcessorContext context)
        {
            if (!string.IsNullOrEmpty(MergeAnimations))
            {
                foreach (string mergeFile in MergeAnimations.Split(';')
                                                            .Select(s => s.Trim())
                                                            .Where(s => !string.IsNullOrEmpty(s)))
                {
                    MergeAnimation(input, context, mergeFile);
                }
            }

            // TODO: whatever other processing you need.
            // eg. if you use SkinnedModelProcessor from our sample, that code would go here.

            return base.Process(input, context);
        }


        void MergeAnimation(NodeContent input, ContentProcessorContext context, string mergeFile)
        {
            NodeContent mergeModel = context.BuildAndLoadAsset<NodeContent, NodeContent>(
                                                new ExternalReference<NodeContent>(mergeFile), null);

            BoneContent rootBone = MeshHelper.FindSkeleton(input);

            if (rootBone == null)
            {
                context.Logger.LogWarning(null, input.Identity, "Source model has no root bone.");
                return;
            }

            BoneContent mergeRoot = MeshHelper.FindSkeleton(mergeModel);

            if (mergeRoot == null)
            {
                context.Logger.LogWarning(null, input.Identity, "Merge model '{0}' has no root bone.", mergeFile);
                return;
            }

            foreach (string animationName in mergeRoot.Animations.Keys)
            {
                if (rootBone.Animations.ContainsKey(animationName))
                {
                    context.Logger.LogWarning(null, input.Identity,
                        "Cannot merge animation '{0}' from '{1}', because this animation already exists.",
                        animationName, mergeFile);
                        
                    continue;
                }

                context.Logger.LogImportantMessage("Merging animation '{0}' from '{1}'.", animationName, mergeFile);

                rootBone.Animations.Add(animationName, mergeRoot.Animations[animationName]);
            }
        }
    }
}
  • Or you could use blender, which exports multiple "Actions" just fine.

    The only caveat is that you need to hack the fbx exporter to generate transforms compatible with xna.(just do a google search).

  • The "export separate .fbx files, then merge" approach is an artist's worst nightmare.  I'm pretty shocked that you are advocating this, Mr. Hargreaves.  Furthermore, my comparisions between .fbx and .x have shown that typically .fbx files are nearly an order of magnitude larger in file size than a .x.

    The lack of definable clips and the file size were why on  we opted not to use .fbx on my XNA project, instead we went with .X using KW-Xport, which supports the ability to name the individual clips within a single file.

    The programmer on my project was able to throw together a utility to parse the .x file and add in the clip names based on a .txt I wrote that explicitly defined the frame ranges for each clip.  So now we don't even have to fool with inputting the clips into KW-X!  Again bypassing the .fbx/merge approach.

    The only problem we're having now with animation in XNA is this stupid "1-second bug," where clips of less than a second in length get some dead space added to them.  We've managed to solve this by stretching the clip to be longer than a second and then scaling the clip back to the correct size in code.  What framework designer didn't realize that most game animations are under a second? - I thought this was pretty ridiculous.

  • > The "export separate .fbx files, then merge" approach is an artist's worst nightmare. I'm pretty shocked that you are advocating this, Mr. Hargreaves.

    I think that's a little unfair :-)

    I'm glad you have found an exporter and workflow that meet your needs. That's great. But many people like FBX, and prefer working with separate files vs. time offsets. Which is also great: people should be free to do whatever makes them the most happy!

    I was just sharing some code that might be helpful for those people who happen to prefer the latter approach. I fail to see what is so shocking about that?

  • I've just come to respect your ideas and opinions on the technical aspects of game design, and your perspective from working in the industry - and that .fbx technique just seems like a hack that someone of your calibur would not use...

    But I guess if it works, and people like it - to each his own.

  • More MotoGP rendering techniques please :)

  • MayaTerror:

    > .fbx technique just seems like a hack that someone of your calibur would not use...

    I don't think its a hack. As Shawn mentions, it is conceptually similar to sprite sheets. I've worked with animators who like things all in one source file, and ones who like them to be separate files, because the latter solution allows the names of the animations to be easily viewable, you can see the sizes, time when they were last modified etc.

    The concept of merging many things into one is controversial, I'd agree, but not a hack, it is a a pipeline decision. You can apply it with audio samples too. Fewer files mean less seek time, so its good for loading speed. This makes it feel like a valid option, and I think it helps to have options?

  • This is excellent Shawn!

    I've been using all kinds of other methods for some time to achieve exactly this goal.

    I want to have the animations in different files, the artists wants that too!

    You can reuse animations on different models and you can easily find a specific animation. Also no hacks or overhead in maintaining text/xml files with animation config stuff.

    Nice, thanks!

  • I *knew* I'd be seeing this as a blog post Shawn! ;)

  • The *only* reason I used the word "hack" was because, as Shawn himself admits, the .fbx exporters only support a single clip per file, making .fbx "broken" in my opinion.  You're therefore having to put together some code to make it work, i.e. a hack.  I guess you could argue against that, but it's really inconvenient, you gotta admit.  Anyway I'm not trying to argue or be controversial, I agree it's good to have this kind of info availiable because for a long time on our project we've debated the merits of the "master animation file" vs. "single clip" approach, and the debate still seems to come up all the time.

    I think it's like some kind of weird twist of fate that Shawn always seems to be posting about whatever we're working on in XNA at the moment...

  • I want to try this out.

    We have had some problems with laying out all the animations in one .fbx file, then splitting them up during import using the XNA Animation Component library. The animation clips seem to bleed into each other when we do that. It is like the boundary frames for each clip that we supply to the processor are not respected. This error is both confusing and very annoying.

    Also, having a simple clip in each file seems to be the more natural way of working for my artist.

    If this method separates the clips cleanly, I will be happy.

  • I am a bit worried about the space this is going to take.

    The .fbx files are quite large, up to 10 MB, and multiplying them by number of animation clips will quickly use a lot of room on the source repository and make the project a bit unwieldy...

  • >>>I am a bit worried about the space this is going to take.

    The .fbx files are quite large, up to 10 MB, and multiplying them by number of animation clips will quickly use a lot of room on the source repository and make the project a bit unwieldy...

    <<<

    This seems like a valid concern to me. My main player + perhaps 10% of the final animations and less than full detail is about 5MB just for the mesh + anims. But even worse is that the intermediate file for this is 22Mb, I dread to think what it will be eventually. Luckily I dont need multiple files due to the fact I am using blender:-)

    Is there a way to try turning off intermediate file generation for selected fbx models? I seem to remember an attribute, but that was presumably just if you write the importer...

    David

  • My team and I had to deal with this crap when we were working on an independent study project in the spring. At the time, the programmer we had working on animation ended up having to go into Maya, do all those exports as ASCII FBXs, and then copy-paste the takes from each one together. Horribly inefficient, but we were lucky in only dealing with a few models. This probably would've been a little easier, and has the benefit of not requiring the FBX files in bloated ASCII format. Really, though, Autodesk just needs to fix their damn exporter.

  • The fbx exporter I use does export takes just fine from Blender, and I've been able to import several takes from one FBX file into XNA using the example skinning processor. I got this problem only today when converting my XNA 3.1 project to XNA 4.0. It seems the problem is in MeshHelper or perhaps even before the NodeContent reaches the processor. It would be nice if I didn't have to recreate my whole workflow and start splitting stuff in different files just because of this, as it worked fine in 3.1.

    Meh...

    /mike

  • I also have been using Blender as the modeling tool and export multiple clips. I have not upgraded to 4.0 yet. I currently use a custom processor to pre-build all content to xnb files, so that the game loads quickly. Will xnb files built with 3.1 load in 4.0?

Page 1 of 2 (28 items) 12
Leave a Comment
  • Please add 3 and 2 and type the answer here:
  • Post