One of the biggest issues developers had and still have with
XNA 4.0 is that it no longer supports a 3D model which contains multiple
animations for .fbx files. One approach is to export each animation into a
separate FBX file, and then use a custom processor to merge them. You can find a great example of this approach on Shawn Hargreaves Blog here. An open source implementation of this can also be found here. To explain
this better, lets say you have 5 characters in your game for example. Each of those characters has 15 animations. So for all five characters, you would have to export
each of their animations into separate individual files. This can be a pain and cause hassles
for developers; especially if your game has many characters.
It was because of
this flaw in XNA 4.0 that many developers found themselves leaving the
framework entirely. Some had no problems with their .fbx files using Blender as
their modeling and animation program of choice. Many abandoned .fbx altogether and
switched to DirectX files. Although .X rather than .FBX is a decent alternative, its a completely different format that may or may not be supported by your 3D software which posed some problems for developers using XNA 4.0. This flaw in XNA 4.0 hindered my progress for quite
some time. I simply didn't want to give up on creating a multi-take animation
importer. It took a long time but I was able to fix this and get multiple animations
working from a single .fbx file after modifying the XNAnimation Library to work in XNA 4.0. I
have posted the source code below for everyone. I hope others will find this useful.
How to extend the XNAnimation library for XNA 4.0 & MonoGame
In the XNAnimation Pipeline, right-click References and select Add Reference. Under the .NET tab found at the top, locate and add the following reference to the project: Microsoft.XNA.Framework.Content.Pipeline.FBXImporter. In the XNAnimation Pipeline, right-click the project and select Add; then select New Item. Create a new class called SkinnedModelImporter.cs and add the following source code below.
Source Code Below:
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;
// The type to import.
using TImport = Microsoft.Xna.Framework.Content.Pipeline.Graphics.NodeContent;
// Change the namespace to suit your project
namespace XNAnimationPipeline.Pipeline
{
[ContentImporter(".fbx", DisplayName = "Multi-take FBX Importer", DefaultProcessor = "")]
public class SkinnedModelImporter : FbxImporter
{
private List<string> _animfiles;
private List<string> _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<string>();
// 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<string, AnimationContent> 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<string> 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<string> 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<string> completefile = new List<string>();
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);
}
}
}
}
In the XNAnimation Pipeline, open up SkinnedModelImporter.cs and replace it with the following source code below.
Source Code Below:
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;
// The type to import.
using TImport = Microsoft.Xna.Framework.Content.Pipeline.Graphics.NodeContent;
// Change the namespace to suit your project
namespace SkinnedModelPipeline
{
[ContentImporter(".fbx", DisplayName = "Multi-take FBX Importer", DefaultProcessor = "")]
public class SkinnedModelImporter : FbxImporter
{
private List<string> _animfiles;
private List<string> _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<string>();
// 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<string, AnimationContent> anim in animnode.Animations)
{
if (!masternode.Animations.ContainsKey(anim.Key))
{
masternode.Animations.Add(anim.Key, anim.Value);
}
else
{
//overwrite the animation that was stored inside the
//master file because it is of the wrong length (except the first animation).
masternode.Animations[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<string> 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<string> 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<string> completefile = new List<string>();
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);
}
}
}
}
No comments:
Post a Comment