Page 1 of 1

Custom Content Importer in XNA How to write a Conetnt Pipeline Extension Library in XNA 3.1

#1 ikofthetoids  Icon User is offline

  • New D.I.C Head
  • member icon

Reputation: 3
  • View blog
  • Posts: 39
  • Joined: 18-December 08

Posted 28 February 2010 - 07:17 PM

Tired of having to save/load all your non-supported game assets through XMLSerializer? Well you're in luck! This tutorial will show you how to write a custom Content Pipeline Extension Library in XNA 3.1. It is aimed more at what I like to call "comfortable programmers", meaning you may not be a professional, but you can understand most of what is thrown at you. Enjoy :)

Part 1: Adding an Extension Library to our Project

First off, Open up whatever project you want to add the importer to. This should be any XNA 3.1 game running on any platform (windows, xbox, or zune). Now, find the Solution node in your Solution Explorer; this should be the highest node in the hierarchy, and consist of "Solution '[Name of Project]' (# project(s))'. Right click this node and go down to Add->New Project to add a new project to your solution. In the dialog that pops up, choose "Content Pipeline Extension Library (3.1)" under the "XNA Game Studio 3.1" node. Name it whatever you want and create it. In the new project that was just created, there should be a class called "ContentProcessor1.cs". We don't need this, so just delete it. Now, we need to add an importer to this project. Right click on the project node of your newly created content extension library (this should be whatever you named it), and go down to Add->New Item. In this dialog box, select "Content Importer", and name this whatever you want. I suggest name it something like "MyContentImporter", or just plain old "Importer" so it's easily identifiable.

Let's review: You added a Content Pipeline Extension Library to your solution and deleted the default processor. Then, you created a Content Importer in this project. Great. Movin on...

Part 2: The Datatype

Now, you need to determine what kind of data you want to load. This could be anything -- from built-in datatypes like integers and strings, to custom structures and classes. Just because this tutorial is titled "Custom Content Importer in XNA", we're going to go the custom route, and design a struct to hold the data we load. Here, I'm going to define a struct called "GenericStruct", which will hold a string, an integer, and a boolean value:

public struct GenericStruct
{
    public string Name;
    public int NumberOfEyes;
    public bool IsHuman;
}


Put this struct anywhere in your Content Pipeline Extension Library. It is important that you define this struct in your extension library and not in your game because you can just make a reference to it later. For now, just make sure it is in the namespace of your extension library. Now, we need to specify that our importer should load this datatype. In the Content Importer class that you created in the previous step, there should be a statement at the top that says this:

using TImport = System.String;


Here, change "System.String" to "[Extension Library Namespace].GenericStruct", so that the importer knows to load files and convert them to our structure.

Review: We defined a structure to hold our data and put it in our Content Pipeline Extension Library project. Then, we changed the Import type in our content importer to our structure.

Part 3: Get a file

Next, you're going need a file to actually load into your game. I personally find designing a file layout is the funnest part of creating new asset types. For this tutorial, I'm going to be loading files with extension ".dat" -- data files. In order to specify that our importer should handle all files with extension *.dat, we're going to change this statement in our Content Importer:

[ContentImporter(".abc", DisplayName = "ABC Importer", DefaultProcessor = "AbcProcessor")]


to this:

[ContentImporter(".dat", DisplayName = "DAT Importer", DefaultProcessor = "DataProcessor")]


NOTE: The latter two values (DisplayName and DefaultProcessor) don't really matter. All that's important is that you put your file extension in the first value.

Now we have our importer set up to import a file "[FileName].dat" and save it to our struct. So... what now? Well, we have to get a file to actually import. Our struct contains a string, an int, and a bool, so we need a file that contains a string, an int, and a bool that we can load into it. I find that, while it may be tedious to just do this for one file, writing a separate program to generate a file is the best way to do this. So, we're going to put this project on hold for a little while.

Part 4: Write a File Generator

Open up a new instance of Visual C# and create a Console Application (separate from your game). Call it "File Generator", or whatever other name you see fit. Write something similar to this:

using System;
using System.Collections.Generic;
using System.IO;

namespace File_Generator
{
    class Program
    {
        static void Main(string[] args)
        {
            string Name = "Tim";
            int NumberOfEyes = 2;
            bool IsHuman = true;

            Export("C:\\Users\\[User Name]\\Desktop\\File.dat", Name, NumberOfEyes, IsHuman);
        }
        static void Export(string Path, string Name, int NumberOfEyes, bool IsHuman)
        {
            Stream stream;
            BinaryWriter writer;

            stream = File.Open(Path, FileMode.Create);
            writer = new BinaryWriter(stream);

            writer.Write(Name.Length);
            writer.Write(Name);
            writer.Write(NumberOfEyes);
            writer.Write(IsHuman);

            writer.Close();
        }
    }
}


Make sure to change the "[User Name]" part of the path to whatever Name your account is on. In fact, you can just change the path to wherever you want the new file to be. Now you have a file generator that will generate a data file ("File.dat") that contains all necessary information. This file layout is like this:

Integer (32): An integer specifying how long the proceeding string is. In this case it's 3
String (?): A string representing the "Name" property. In this case it's "Tim"
Integer (32): An integer representing the "NumberOfEyes" property. In this case it's 2
Boolean (1): A boolean value representing the "IsHuman" property. In this case it's "true"

Now, you have the data file. If you want you can generate more files with different parameters (just remember to change the path to something else that you want just write over pre-existing files). After you've generated enough files, open up your game project again and add them to the Content folder.

Review: We created a simple console application that writes binary files containing data we can load into our game. Using this console application, we created a file and added it to our game's content folder. Now, we can load it.

Part 5: Writing the Importer

As of now, Our Importer doesn't do anything. Well that's about to change! that there is a function:

public override TImport Import(string filename, ContentImporterContext context)
{
    // TODO: read the specified file into an instance of the imported type.
    throw new NotImplementedException();
}


This function takes two arguments, a string value ("filename") that holds the path to the file the game is requesting to load, and a ContentImporterContext class. All we're concerned with is the first one. Our file type is saved in binary format, so we're going to use a BinaryReader (similar to the BinaryWriter we used in our file generation program) to read from the file. make sure to include "using System.IO;" at the top of the class or you won't be able to make a reference to BinaryReader. Now, declare a stream and a reader, and open the file path. Your code should look like this:

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;

// TODO: replace this with the type you want to import.
using TImport = [Extension Library Namespace].GenericStruct;

namespace ContentPipelineExtension1
{
    /// <summary>
    /// This class will be instantiated by the XNA Framework Content Pipeline
    /// to import a file from disk into the specified type, TImport.
    /// 
    /// This should be part of a Content Pipeline Extension Library project.
    /// 
    /// TODO: change the ContentImporter attribute to specify the correct file
    /// extension, display name, and default processor for this importer.
    /// </summary>
    [ContentImporter(".dat", DisplayName = "DAT Importer", DefaultProcessor = "DataProcessor")]
    public class ContentImporter1 : ContentImporter<TImport>
    {
        public override TImport Import(string filename, ContentImporterContext context)
        {
            Stream stream;
            BinaryReader reader;

            stream = File.Open(filename, FileMode.Open);
            reader = new BinaryReader(stream);
        }
    }
}


Now we have our code all set up to read from the file. Before we can do that though, let's think about what our algorithm for importing the file is going to be. We know the file starts out with an integer, then it has a string, then another integer, and finally a boolean value. So in order to read the file, we're going to have to do something like this:

int Length = reader.ReadInt32();
char[] RawString = reader.ReadChars(Length);
string Name = new string(RawString);
int NumberOfEyes = reader.ReadInt32();
bool IsHuman = reader.ReadBoolean();


This will load a bunch of variables for us. Now all we have to do is convert these variables into our struct:

GenericStruct r;
r = new GenericStruct();

r.Name = Name;
r.NumberOfEyes = NumberOfEyes;
r.IsHuman = IsHuman;


Our final result:

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;

// TODO: replace this with the type you want to import.
using TImport = [Extension Library Namespace].GenericStruct;

namespace ContentPipelineExtension1
{
    /// <summary>
    /// This class will be instantiated by the XNA Framework Content Pipeline
    /// to import a file from disk into the specified type, TImport.
    /// 
    /// This should be part of a Content Pipeline Extension Library project.
    /// 
    /// TODO: change the ContentImporter attribute to specify the correct file
    /// extension, display name, and default processor for this importer.
    /// </summary>
    [ContentImporter(".dat", DisplayName = "DAT Importer", DefaultProcessor = "DataProcessor")]
    public class ContentImporter1 : ContentImporter<TImport>
    {
        public override TImport Import(string filename, ContentImporterContext context)
        {
            Stream stream;
            BinaryReader reader;

            stream = File.Open(filename, FileMode.Open);
            reader = new BinaryReader(stream);

            int Length = reader.ReadInt32();
            char[] RawString = reader.ReadChars(Length);
            string Name = new string(RawString);
            int NumberOfEyes = reader.ReadInt32();
            bool IsHuman = reader.ReadBoolean();

            GenericStruct r;
            r = new GenericStruct();

            r.Name = Name;
            r.NumberOfEyes = NumberOfEyes;
            r.IsHuman = IsHuman;

            return r;
        }
    }
}


And there you have it. Your importer is complete! But, you aren't done yet. We still have to get all our references in order.

Review: You declared and initialized a BinaryReader in our Importer to read from the "filename" argument. This BinaryReader you set up to read from the binary file we created, and return our structure containing the data from the file.

Part 6: Wrapping it all Up

In the Solution Explorer, right click on the "Content" node in our game project and go down to "Add Reference...". In this dialog, select the "Projects" tab and pick your Content Pipeline Extension Library. Now right click on your game project node (not the Content node of your game project, but the game project node itself), and add a reference to you extension library to this as well. Now, at the top of you game's code add "using [Extension Library Namespace];" (of course, as with all other instances in this tutorial in which I said "[Extension Library Namespace]", replace this with the actual namespace you used for you extension library).

Now You are done! All you have to do now is load your file.

Part 7: Load the File

all you have to do to load your file is add this line of code to your LoadContent() method:

G
enericStruct MyGenericStruct;
MyGenericStruct = Content.Load<GenericStruct>("File"); // Note: as with other types, you don't say "File.dat". Just "File"


Happy Loading! :w00t:

Is This A Good Question/Topic? 2
  • +

Replies To: Custom Content Importer in XNA

#2 Guest_Jonathan*


Reputation:

Posted 23 August 2010 - 03:15 PM

What is this? A half baked tutorial on writing a content importer without any information on how to write the data out to the .xnb file, or how to read it back in using the reader in the game project. Useless!
Was This Post Helpful? -3

#3 freakteam  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 18-November 10

Posted 18 November 2010 - 03:42 AM

Thanks, very usefull!
Was This Post Helpful? 0
  • +
  • -

#4 matric  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 02-January 11

Posted 02 January 2011 - 07:10 AM

Thanx alot!
This one was really good written and usefull.

Keep it up :)
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1