XNAnimation dlls/ Multiple animations bug

Apr 15, 2011 at 8:26 PM

I'm currently working on a project using XNAnimation with XNA game studio 3.1 and Visual C# 2008 for Xbox 360.

I have encountered a bug after Deploy Succeeded

 Cannot find ContentTypeReader XNAnimation.Pipeline.SkinnedModelReader, XNAnimation, Version=0.7.0.0, Culture=neutral, PublicKeyToken=121fbd51268a0405.

I've looked around many forum posts and found a few solutions like building XNAnimation with the modified private static readonly string xboxPublicKeyTokens = "0c21691816f8c6d0"; line, but I have not been able to make it work properly.

It seems a little strange that I have not been able to find a working XNAnimationPipeline.dll for Xbox360 and I would like to know if someone can point me to one or provide one for me. 

Apr 16, 2011 at 7:36 PM

I'm not sure about the specific error, but you should double check using 3.1, I don't think it's going to be supported for indie games anymore if that's what you're going for.  The newer versions of xnanimation are using 4.0 thanks to r2 anyway.

And the way I understand it there is no pipeline dll for the xbox because wherever it's going to be deployed the content is always first compiled in windows.  Just add that reference under the windows project and it'll automatically be used even if your startup project is set to the xbox version.

Apr 18, 2011 at 6:36 PM
Edited Apr 19, 2011 at 2:16 PM

Ok, i'm currently switching my project to XNA 4.0. 

So I added the XNAnimation projects (dev) to my project and it works on Windows and Xbox360. However, only one animation is available for StartClip and the .fbx clearly has more than one. Multiple animations worked with the same model on 3.1. Anyone knows why?

If I try to play an animation that used to work on 3.1, I get a KeyNotFoundException was unhandled : The given key was not present in the dictionary. in XNAnimation.ReadOnlyDictionary.

I'm using this line  to play the animation : 

m_animationController.StartClip(m_model.AnimationClips["walk"]); Which works on 3.1

 

This one works on 4.0 and 3.1

m_animationController.StartClip(m_model.AnimationClips["hit"]);

 

I have also tried with the sample model (PlayerMarine.fbx) and get the same error, only the Idle animation seems to be available, the dictionary only contains one Item (Idle).

Apr 30, 2011 at 4:28 PM
Edited Apr 30, 2011 at 4:29 PM

InXNA 4.0 the standard fbx importer can only import one animation. If there are multible animations in the file, it will pick the first one.

Solution: extend the fbxImporter class and make an own content importer.

I found one a while ago, but forgot where so I can not take credit nor correctly give credit for following code (works for fbx files created with blender) :

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Design;
using AnimationChannel = Microsoft.Xna.Framework.Content.Pipeline.Graphics.AnimationChannel;
// TODO: replace this with the type you want to import.
using TImport = Microsoft.Xna.Framework.Content.Pipeline.Graphics.NodeContent;

namespace XNAnimationPipeline.Pipeline
{
    [ContentImporter(".fbx", DisplayName = "Multi-take FBX Importer", DefaultProcessor = "Model - XNAnimation")]
    public class SkinnedModelImporter : FbxImporter
    {
        private List _animfiles;
        private List _fbxheader;
        private TImport _master;
        private ContentImporterContext _context;

        public override TImport Import(string filename, ContentImporterContext context)
        {
            _context = context;

            // _animfiles will contain list of new temp anim files.
            _animfiles = new List();

            // Decouple header and animation data.
            ExtractAnimations(filename);

            // Process master file (this will also process the first animation)
            _master = base.Import(filename, context);

            // Process the remaining animations.
            foreach (string file in _animfiles)
            {
                TImport anim = base.Import(file, context);

                // Append animation to master NodeContent.
                AppendAnimation(_master, anim);
            }

            // Delete the temporary animation files.
            DeleteTempFiles();

            return _master;
        }

        private void AppendAnimation(NodeContent masternode, NodeContent animnode)
        {
            foreach (KeyValuePair anim in animnode.Animations)
            {
                masternode.Animations.Add(anim.Key, anim.Value);
            }

            //foreach (NodeContent child in animnode.Children) {
            //    if (child != null) {
            //        AppendAnimation(child);
            //    }
            //}

            for (int i = 0; i < masternode.Children.Count; i++)
            {
                if (animnode.Children[i] != null)
                {
                    AppendAnimation(masternode.Children[i], animnode.Children[i]);
                }
            }
        }

        private void ExtractAnimations(string filename)
        {
            List masterFile = File.ReadAllLines(filename).ToList();
            string path = Path.GetDirectoryName(filename);
            int open_idx = 0,
                length,
                num_open = -1,
                filenum = 0;
            bool foundTake = false;

            int idx = masterFile.IndexOf("Takes:  {") + 1;
            _fbxheader = masterFile.Take(idx).ToList();
            List anims = masterFile.Skip(idx).ToList();

            // Extract each animation and create a temporary anim file.
            for (int i = 0; i < anims.Count; i++)
            {
                if (anims[i].Contains("Take: "))
                {
                    open_idx = i;
                    num_open = 0;
                    foundTake = true;
                }

                if (anims[i].Contains("{") &&
                    foundTake)
                {
                    num_open++;
                }

                if (anims[i].Contains("}") &&
                    foundTake)
                {
                    num_open--;
                }

                if (num_open == 0 &&
                    foundTake)
                {
                    // Skip first animation since this is processed in the master
                    // fbx file.
                    if (filenum > 0)
                    {
                        length = i - open_idx + 1;

                        // Create temp file from header + anim data.
                        CreateTempFile(Path.Combine(path, "tmp.anim." + filenum + ".fbx"),
                                       anims.Skip(open_idx).Take(length).ToArray());
                    }
                    filenum++;
                    foundTake = false;
                }
            }
        }

        private void CreateTempFile(string filename, string[] data)
        {
            List completefile = new List();
            completefile.AddRange(_fbxheader);
            completefile.AddRange(data);

            try
            {
                // Write data to new temp file.
                File.WriteAllLines(filename, completefile.ToArray());

                // Store temp file name for processing.
                _animfiles.Add(filename);
            }
            catch
            {
                // Error while creating temp file.
                _context.Logger.LogWarning(null, null, "Error creating temp file: {0}", filename);
            }
        }

        private void DeleteTempFiles()
        {
            foreach (string file in _animfiles)
            {
                File.Delete(file);
            }
        }
    }
}

This fixed it for me, but still left me with the problem, that models without textures will cause an exception (see a few posts earlier.. no answer yet..)