The architecture and flow of content processing
Content processing is a special module in Windows Phone 7 Game Programming. It provides the flexibility for different types of game content including images, effects, and models. Besides this, the most powerful aspect of content processing is the extension to customize your game with different types of contents. In a commercial game, the customized game contents are especially relevant. Mastering the technique of dealing with any kind of game content is the key ability to master in your Windows Phone 7 Game Programming life. In this section, you will learn the basics of XNA Content Processing and, for example, we will process the text as game contents.
As a key part of the XNA game framework, the content processing pipeline transforms the original art or information file to a format that can be read and used in the Windows Phone 7 XNA framework. Actually, you don’t need to rewrite the importer for some common game content file formats, such as .FBX, .PNG, .fx, and so on. The XNA team has done all the hard work for you, saving you time on learning and researching these formats. Usually, all the built-in content processing components are enough for you to write games. However, if you want to use a new file format that XNA does not support, the best method is to develop your own importer and processor to get the file data. Before doing the job, it is better to have a thorough understanding of how the Window Phone 7 XNA Content Pipeline works, which will help you to fix some weird and unexpected content processing bugs. Believe me, it is true!
The content processing pipeline generally has the following two phases:
- The reading phase: In this phase, the asset file is read from the local disk, the file is then processed, and the process data is stored in a binary form as exported file (.xnb).
- The loading phase: In this phase, the exported data is loaded from the .xnb binary file into the XNA game application memory. This loading operation is completed manually by the user.
By putting the importing and processing operations in the compile phase, the game contents loading time is reduced. When the actual game reads the exported file at runtime, it only needs to get the processed data from the binary file. Another convenience is that the exported file could be used on different platforms, such as Windows, Xbox, and Windows Phone 7.
The following diagram shows the main content processing flow in XNA:
The importer converts the game assets into objects that the standard content processor can recognize, or other specific customized processor could deal with. An importer typically converts the content into managed objects and includes strong typing for such assets as meshes, vertices, and materials. In addition, a custom importer may produce custom objects for a particular custom content processor to consume.
The job of the content processor is to transform the imported game assets and compile them into a managed code object that can be loaded and used by XNA games on Windows, Xbox, or Windows Phone. The managed code object is the .xnb file. Different processors deal with different game asset types.
If the imported file format is recognized by the Windows Phone 7 XNA built-in importers, the corresponding processor will be assigned to it automatically. Otherwise, if the content is not recognized properly, you should manually set the specific importer and processor in the file property panel (don’t worry about how to do this just now, as you will learn this later in the chapter). After that, when you build your game, the assigned importer and processor for each asset will build the recognized assets to binary XNB files which can be loaded at runtime.
The loading phase
In the reading phase, the game contents are serialized in a binary file, they are ready for use in your game, and then in the loading phase, you can load the processed file in your game by the content loader. When the game needs the game assets, it must call the ContentManager.Load() method to invoke the content loader, specifying the expected object type. The content loader then locates and loads the asset data from the .xnb file into the game memory.
Content Document Object Model
When importing the game asset to your Windows Phone 7 XNA game project, the XNA framework will transform the data to a built-in type system named Content Document Object Model (DOM) which represents the set of classes for standard content processing. Additionally, if the importer and processor are customized, the importer will return ContentItem with the information you specified, or a predefined data type. The following diagram is the main route of the model content processing pipeline specified by Microsoft:
This is how it works:
- The first step is to import the original model game asset into the game project, where
each corresponding content importer converts the input model data into an XNA
Document Object Model (DOM) format. - The output of the model importers is a root NodeContent object, which describes a graphics type that has its own coordinate system and can have children. The classes: BoneContent, MeshContent, GeometryContent, VertexContent, and IndexCollection are all inherited from the NodeContent class. As a result, the root NodeContent object output from a model importer might have some NodeContent, MeshContent, and BoneContent children. Moreover, the NodeContent.Children property represents the hierarchy of the model.
- After the models have been imported, they can be processed by their corresponding model processors, or also by your processor. This separation allows you to define two importers for importing X and FBX files, but use the same model processor to process their output. ModelProcessor receives the root NodeContent object as a parameter, generated by the model importer, and returns a ModelContent object.
- The exported ModelContent model data has a Tag property which is an object, containing vertex and bone data, but no animation data.
- At the end of the reading phase, this processed data needs to be stored in an XNB binary file. To be able to store the ModelContent object in an XNB file, ModelContent and each object inside it must have its own ContentTypeWriter. ContentTypeWriter defines how the data of each object is written into the binary XNB file.
- During the reading phase, at runtime, ContentManager reads the binary XNB file and uses the correct ContentTypeReader for each object it finds in the XNB file.
As the XNA Content Pipeline does not have full support for models with skeletal animation, you need to extend the content pipeline, adding support for skeletal animation.
Creating a custom importer and processor for your text
Most of the time, the standard content importers and processors are enough for Windows Phone Game Development. However, sometimes you need to process a different type of file that the XNA framework does not support. At present, you will need, a custom importer and processor. In this recipe, you will learn how to write your own importer and processor for text in Windows Phone 7 XNA.
Getting ready
In order to read the specific game asset, first you should develop a corresponding importer and processor. Then, write the processed data to an XNB file with the override Write() method of ContentTypeWriter class in the content pipeline project. After that, override the Read() method of ContentTypeWriter class in a separate class in the main game project. In this example, you will write a text importer and read the data from the compiled text in an XNB file into your game.
How to do it…
Carry out the following steps to create a custom importer and processor:
- Create a Windows Phone Game project named TextContentReader and a Content Pipeline Extension Library project named TextImporterProcessor with a clicking sequence: File | New | Project | XNA Game Studio 4.0 | Content Pipeline Extension Library. When the content pipeline project is created, we add five new files into it: Text.cs, TextImporter.cs, TextOutput.cs, TextProcessor.cs, and TextWriter.cs.
- In this step, we define the TextInput class and the TextOutput class. The TextInput class in the TextInput.cs file looks as follows:
[code]
// TextInput class for importer and processor
public class TextInput
{
// Text string
string text;
// Constructor
public TextInput(string text)
{
this.text = text;
}
// Text property
public string Text
{
get
{
return text;
}
}
}
[/code] - The other class TextOutput in the TextOutput.cs file is:
[code]
// TextOutput class for processor and ContentTypeWriter and
// ContentTypeReader
public class TextOutput
{
// Text byte array
byte[] textOutput;
public TextOutput(byte[] textOutput)
{
this.textOutput = textOutput;
}
// Compiled text property
public byte[] CompiledText
{
get
{
return textOutput;
}
}
}
[/code] - Create the TextImporter class in the TextImporter.cs file as follows:
[code]
// Text importer
[ContentImporter(“.txt”, DefaultProcessor = “TextImporter”,
DisplayName = “TextImporter”)]
public class TextImporter : ContentImporter<TextInput>
{
// Override the Import() method
public override TextInput Import
(string filename, ContentImporterContext context)
{
// Read the text data from .txt file
string text = System.IO.File.ReadAllText(filename);
// Return a new TextInput object
return new TextInput(text);
}
}
[/code] - Create a TextProcessor class that uses TextInput from TextImporter as an input and outputs a TextOutput object. Define the class in the TextProcessor. cs file as follows:
[code]
// Text content processor
[ContentProcessor(DisplayName = “TextProcessor”)]
public class TextProcessor :
ContentProcessor<TextInput, TextOutput>
{
// Override the Process() method
public override TextOutput Process
(TextInput input, ContentProcessorContext context)
{
// Return the TextOutput object
return new
TextOutput(Encoding.UTF8.GetBytes(input.Text));
}
}
[/code] - The last step in the loading phase is to save the TextOutput data into an XNB file. In this step, we will create the TextWriter class in TextWriter.cs to define the behavior of how to write the TextOutput data into an XNB fie. The implementation of TextWriter is as follows:
[code]
// TextWriter for writing the text information into XNB
// file
[ContentTypeWriter]
public class TextWriter : ContentTypeWriter<TextOutput>
{
// Override the Write() method
protected override void Write
(ContentWriter output, TextOutput value)
{
// Write the text length information to XNB file
output.Write(value.CompiledText.Length);
// Write the text string into XNB file
output.Write(value.CompiledText);
}
public override string GetRuntimeType
(TargetPlatform targetPlatform)
{
// Get the run time type of TextOutput
return typeof(TextOutput).AssemblyQualifiedName;
}
public override string GetRuntimeReader
(TargetPlatform targetPlatform)
{
// Get the TextOutput assembly information
return “TextContentReader.TextReader,
TextContentReader,” +
” Version=1.0.0.0, Culture=neutral”;
}
}
[/code] - In the loading phase, the first step is to define the output type of ContentTypeReader. For TextReader, the output type is the TextOutput class that came with it in the TextImporterProcessor project. Now we add TextOutput.cs and TextReader.cs into the main game project TextContentReader. For TextOutput.cs, we should change the namespace from TextImporterProcessor to TextContentReader, and the rest remains the same. Then, we define the TextReader class in TextReader.cs as follows:
[code]
public class TextReader : ContentTypeReader<TextOutput>
{
// Read the TextOutput data from xnb file
protected override TextOutput Read
(ContentReader input, TextOutput existingInstance)
{
// Read the text length
int size = input.ReadInt32();
// Read the text content
byte[] bytes = input.ReadBytes(size);
// Generate a TextOutput object with the already
// read text.
return new TextOutput(bytes);
}
}
[/code] - Load the text of the XNB file into the main game and render the content on the screen. We add gameSprite.spritefont into the content project and insert the TextImporterProcessor DLL into the content project reference. In the content project, right-click on the TextContent.txt file and choose the Properties from the pop-up menu. We then choose the text corresponding to the importer and processor in the property panel, as shown in the following screenshot:
- Now, insert the following lines into the TextContentReader class field, as follows:
[code]
SpriteFont font;
string str;
[/code] - Then, add the following code into the LoadContent() method as follows:
[code]
font = Content.Load<SpriteFont>(“gameFont”);
TextOutput text = Content.Load<TextOutput>(“TextContent”);
str = System.Text.Encoding.UTF8.GetString(
text.CompiledText, 0, text.CompiledText.Length);
[/code] - The last part is to render the text content on the screen, so add the following code in the Draw() method as follows:
[code]
spriteBatch.Begin();
spriteBatch.DrawString(font, str, new Vector2(0, 0), Color.
White);
spriteBatch.End();
[/code] - Now build and run the Windows Phone 7 XNA game, making sure that the Copy to Output Directory in the Property panel of the TextContent.txt text file in the content project has been set to Copy always. The application will run as shown in the following screenshot:
How it works…
In step 2, the TextInput class will be used to load the text data from TextImporter and send the data to TextProcessor as the input.
In step 3, the TextOutput class stores the text information compiled by TextProcessor as the output text data. All the string data is stored in TextOutput as byte array.
In step 4, before the TextImporter class, we add a ContentImporter attribute to provide some properties for the importer. The first parameter defines the file extension that the importer could recognize when the game asset is imported; the DefaultProcessor argument instructs the XNA system which processor will be the default processor corresponding to the importer. The last argument DisplayName specifies the display name when you have chosen the importer for your asset. The following is the ContentImporter attribute that TextImporter must inherit from the ContentImporter class and override the Import() method. The importer reads a text file containing characters and generates the original file to TextInput object. From XNA SDK:
When the game is built, the ContentImporter.Import function is called once for each XNA content item in the current project.
When invoked against an input file in the appropriate format, a custom importer is expected to parse the file and produce as output one or more content objects of appropriate types. Since an importer’s output is passed directly to a Content Pipeline processor, each type that an importer generates must have at least one processor available that can accept it as input.
In step 5, for the TextProcessor class, we declare another class attribute named ContentProcessor. DisplayName shows the display name when it is chosen for a specific game asset. As the Process() method is the default method in ContentProcessor, as a subclass, TextProcessor should override the Process() method to handle the TextInput data from TextImporter. Here, we use the Encoding. UTF8.GetBytes() to transform the text string into a byte array and generate the TextOutput object. The generated TextOutput object is the intermediate data for ContentTypeWriter to write to an XNB file.
In step 6, the TypeWriter class is derived from the ContentTypeWriter class and overrides the Write(), GetRuntimeType(), and GetRuntimeReader() methods. The Write() method performs the key operation of writing the text information into an XNB file. Notice that the text information writing order must comply with the data when read by TextReader at runtime. The GetRuntimeType() method identifies the type of an object your game should load from the XNB file written by the writer object. In this instance, the XNB file contains the binary array from your custom TextOutput type. The GetRuntimeReader() method specifies what reader should be invoked to load the XNB file in your game. It returns the namespace and name of the reader class, followed by the name of the assembly in which that class is physically located. In general, the assembly string of ContentTypeReader should be:
[code]
GameNamespace.ContentReaderClassName, ContentReaderClassName,
Version=x.x.x.x, Culture=neutral
[/code]
In step 7, TextReader is the subclass of ContentTypeReader and overrides the Read() method to read the XNB file generated in the reading phase. The reading order is the same as the data writing order. Essentially, what we are going to do here is load all the serialized bits of the XNB file. Finally, the method returns a new TextOutput object to the ContentManager.Load() method.
Processing XML files
In game development, the XML file often plays the role of describing the whole game world, configuring the basic game settings or defining the object properties in the game scenario. For processing an XML file, the .NET framework supports several methods. In Windows Phone 7 XNA programming, if XML is a part of the game content, you can process the file in a way to suit your requirements. At present, the technique of writing the XML file importer and processor will help you. In the following recipe, you will learn this useful technique, and it is easy to extend for your own game.
How to do it…
The following steps will lead you to create the content importer and process for XML files:
- Create a Windows Phone Game project named XMLReader. Change Game1.cs to XMLReaderGame.cs. Then, add a Content Pipeline Extension Library project in the solution named XMLImporterProcessor. Add Person.cs, PersonWriter.cs, PersonXMLInput.cs, XMLImporter.cs, and XMLProcessor.cs files into the project. In the content project, add the PersonInfo.xml file. The XML file looks as follows:
[code]
<?xml version=”1.0″ encoding=”utf-8″ ?>
<Person>
<Name>Steven</Name>
<Age>24</Age>
<Gender>Male</Gender>
</Person>
[/code] - Create the PersonXMLInput class in PersonXMLInput.cs as follows:
[code]
[XmlRoot(“Person”)]
public class PersonXMLInput
{
[XmlElement(“Name”)]
public string Name;
[XmlElement(“Age”)]
public int Age;
[XmlElement(“Gender”)]
public string Gender;
}
[/code] - Then, build the Person class in Person.cs as follows:
[code]
public class Person
{
public string Name;
public int Age;
public string Gender;
public Person(string name, int age, string gender)
{
this.Name = name;
this.Age = age;
this.Gender = gender;
}
}
[/code] - Create the XMLImporter class in XMLImporter.cs as follows:
[code]
[ContentImporter(“.xml”, DisplayName=”XMLImporter”,
DefaultProcessor=”XMLProcessor”)]
public class XMLImporter : ContentImporter<PersonXMLInput>
{
public override PersonXMLInput Import
(string filename, ContentImporterContext context)
{
PersonXMLInput personXMLInput = new PersonXMLInput();
// Create an XML reader for XML file
using (System.Xml.XmlReader reader =
System.Xml.XmlReader.Create(filename))
{
// Create an XMLSerializer for the AnimationSet
XmlSerializer serializer = new
XmlSerializer(typeof(PersonXMLInput));
// Deserialize the PersonXMLInput from the
// XmlReader to the PersonXMLInput object
personXMLInput =
(PersonXMLInput)serializer.Deserialize(reader);
}
return personXMLInput;
}
}
[/code] - Implement XMLProcessor in XMLProcessor.cs as follows:
[code]
[ContentProcessor(DisplayName=”XMLProcessor”)]
public class XMLProcessor :
ContentProcessor<PersonXMLInput, Person>
{
public override Person Process(PersonXMLInput input,
ContentProcessorContext context)
{
return new Person(input.Name, input.Age, input.Gender);
}
}
[/code] - Define the PersonWriter class for writing the importer XML person information into an XNB file:
[code]
[ContentTypeWriter]
class PersonWriter : ContentTypeWriter<Person>
{
// Override the Write() method
protected override void Write(ContentWriter output,
Person value)
{
output.Write(value.Name);
output.Write(value.Age);
output.Write(value.Gender);
}
public override string GetRuntimeType
(TargetPlatform targetPlatform)
{
// Get the run time type of TextOutput
return typeof(Person).AssemblyQualifiedName;
}
public override string GetRuntimeReader
(TargetPlatform targetPlatform)
{
// Get the PersonReader assembly information
return “XMLReader.PersonReader, XMLReader,” +
” Version=1.0.0.0, Culture=neutral”;
}
}
[/code] - Load the person information from the XNB file. We should add PersonReader.cs and Person.cs in the main game project. The only difference between Person.cs here and in the XMLImporterProcessor project is that the namespace here is XMLReader. The Person class will be the storage for the person information from the XNB file when loaded by the content manager. The PersonReader should be:
[code]
public class PersonReader : ContentTypeReader<Person>
{
protected override Person Read
(ContentReader input, Person existingInstance)
{
return new Person(input.ReadString(),
input.ReadInt32(), input.ReadString());
}
}
[/code] - Read the XML file in the game. First, we add the following lines into the class field:
[code]
SpriteFont font;
Person person;
string textPersonInfo;
[/code] - Then, load the PersonInfo.xml in LoadContent() as follows::
[code]
person = Content.Load<Person>(“PersonInfo”);
font = Content.Load<SpriteFont>(“gameFont”);
textPersonInfo = “Name: ” + person.Name + “n” + “Age: ” +
person.Age + “n” + person.Gender;
[/code] - Draw the person information on the screen.
[code]
spriteBatch.Begin();
spriteBatch.DrawString(font, textPersonInfo,
new Vector2(0, 0), Color.White);
spriteBatch.End();
[/code] - Now build and run the application, and the person information should appear as shown in the following screenshot:
How it works…
In step 2, the PersonXMLInput class defines how the person information maps to the PersonXMLInput from XML description file. XmlRoot(“Person”) stands for the root name <Person> in the XML file, the other three XmlElement attributes represent the XML element: <Name>, <Age>, and <Gender>.
In step 3, the Person class is the output type of XMLProcessor and the input type of XMLReader.
In step 4, we create an XMLReader object to read the content of the XML file. The following XMLSerializer object is used to read the PersonXMLInput information from the XML file. The new personXMLInput object will be passed to XMLProcessor as the input.
In step 5, the processor code is simple; it extracts information from the PersonXMLInput object input to generate a new Person class object as the output.
In step 6, the Person object is the input of PersonWriter. The ContentWriter writes the Name, Age, and Gender into the XNB file, one by one. The GetRuntimeType() method identifies the type of Person your game should load from the XNB file written by the PersonWriter object. The GetRuntimeReader() method specifies the XMLReader which will be invoked to load the XNB file in your game.
In step 7, the PersonReader reads the person information from the XNB file written by PersonWriter and builds a new Person object.
Manipulating the extracted information from an image in the content pipeline
Images are irreplaceable in games. They could be the sprites, the game world in 2D or the game user interface. In the 3D world, usually, images represent the appearance of 3D models. In the Windows Phone 7 XNA framework, the default texture or model importers and processors have already dealt with the images for you. Sometimes, you may want to handle the image information as per your needs when loading them from an XNB file. In this recipe, you will learn how to get and operate the image data in the XNA content pipeline.
How to do it…
Carry out the following steps:
- Create a Windows Phone Game project named ImageProcessorGame and change Game1.cs to ImageProcessorGame.cs. Add the BackgroundMaze.png (shown in the following screenshot) into the content project. Then, add a Content Pipeline Extension Library into the game solution named ImageProcessor, and then add ImageProcessor.cs into the project:
- Create the ImageProcessor class in the ImageProcessor content library project as follows:
[code]
[ContentProcessor(DisplayName = “Image Processor”)]
public class ImageProcessor : TextureProcessor
{
public override TextureContent Process
(TextureContent input, ContentProcessorContext context)
{
// Random object to generate random color
Random random = new Random();
Color color = Color.White;
// PixelBitmapContent maintain pixel value in a 2D
//array
PixelBitmapContent<Color> image = null;
// TextureContent object get the image content
TextureContent texContent = base.Process(input,
context);
// Convert the texture content into PixelBitmapContent
texContent.ConvertBitmapType(typeof
(PixelBitmapContent<Color>));
// Get the pixel color from the image content
for (int face = 0; face < texContent.Faces.Count;
face++)
{
MipmapChain mipChain = texContent.Faces[face];
for (int mipLevel = 0;
mipLevel < mipChain.Count; mipLevel++)
{
image = (PixelBitmapContent<Color>)
input.Faces[face][mipLevel];
}
}
int RowSpan = 0;
// Generate the random color strip line by line
for (int i = 0; i < image.Height; i++)
{
if (RowSpan++ < 20)
{
for (int j = 0; j < image.Width; j++)
{
// If the pixel color is black,
// replace it with a random color
if (image.GetPixel(j, i) == Color.Black)
{
image.SetPixel(j, i, color);
}
}
}
else
{
// Begin a new line and generate another random
//color
RowSpan = 0;
color.R = (Byte)random.Next(0, 255);
color.G = (Byte)random.Next(0, 255);
color.B = (Byte)random.Next(0, 255);
}
}
return texContent;
}
}
[/code] - Now, build the ImageProcessor project and you will get the ImageProcessor. dll library file. Add the ImageProcessor.dll into the reference list of the content project. Right-click on the BackgroundMaze.png to change the corresponding processor, as shown in the following screenshot:
- In this step, we will draw the processed image on the screen. We add the following code as the field of the ImageProcessorGame class:
[code]
// The Texture2D object
Texture2D image;
[/code] - Then, load the image in LoadContent() by using the following line:
[code]
image = Content.Load<Texture2D>(“BackgroundMaze”);
[/code] - Draw the processed image on screen, paste the code in the Draw() method:
[code]
spriteBatch.Begin();
spriteBatch.Draw(image, new Vector2(0,0), Color.White);
spriteBatch.End();
[/code] - Now build and run the application. It runs as shown in the following screenshot:
How it works…
In step 2, the ImageProcessor inherits from TextureProcessor for dealing with the image information imported by the built-in importer which supports .bmp, .dds, .dib, .hdr, .jpg, .pfm, .png, .ppm, and .tga image formats. If the importer does not support the image format, such as .gif, then you should implement the corresponding one.
As the input of ImageProcessor, TextureContent contains basic methods to manipulate the image content. The random and color objects will be used to generate random colors. Image is an object of PixelBitmapContent<Color> and the PixelBitmapContent is a subclass of BitmapContent. The BitmapContent class represents a single two-dimensional image with a fixed size and fixed format. Various pixel encodings are supported by subclasses of the bitmap type. Higher-level constructs, such as textures, that may contain multiple mipmap images or cube map faces are stored as collections of multiple bitmaps. The image object declares the type to which the texContent will be converted. The following methods base.Process() and TexContent.ConvertBitmapType change the texContent to a designated format. Here we change the contents to colors. By default, the texContent will be converted to TextureContent2D, but could also be TextureContent3D. This means that the image can have multiple faces and mipmaps. For a simple image, it has only one face and one mipmap. Our job here is to iterate the mipmaps in every face in an image. After the first for loop, you will get the image color set. The second for loop is easy; we go through all of the colors pixel by pixel in the image. If the pixel color is black, we use the image.SetPixel() method to set the color to a randomly generated color.
Extracting BoundingSphere and BoundingBox information from models
Extracting the BoundingSphere and BoundingBox information often helps you in detecting collisions in a 3D game. The BoundingSphere and BoundingBox come from the model vertices. For BoundingSphere, you should know the center and radius, whereas for BoundingBox, you need the min and max points. In this recipe, you will learn how to build and get the BoundingSphere and BoundingBox from 3D models produced by 3D modeling tool such as 3DS MAX or Maya. The default format in XNA is FBX, a compatible format shared between most 3D modeling tools in the content processing phase for Windows Phone 7 XNA game.
Getting ready
BoundingBox is actually specified by eight points, whereas BoundingSphere consists of a center point and the radius. BoundingBox is axis aligned, where each face is perpendicular to the axis.
The reason to use BoundingBox is for performance and ease. BoundingBox has the advantage that it fits the non-rotated rectangular objects very well. Mostly, game objects are static, so there is no need to perform accurate collision detection. Even if you want to make a more precise collision detection, BoundingBox can still help you to eliminate the non-collided object to gain better performance. The disadvantage of BoundingBox is that when it is applied to an object that is not axis aligned and rotating, you have to recreate the BoundingBox. This means performing the rotation matrix multiplication of every BoundingBox point, and this will slow down the game running speed.
As compared to BoundingBox, BoundingSphere needs less information: only the center point vector and the radius are required. If the object encompassed by BoundingBox is rotated, you need to recreate the BoundingBox. When the game objects are bounded by BoundingSphere, any collision detection between them will be fast. You only need to compute the distance of the center points of the two objects. As a suggestion, the BoundingSphere could be the first choice when you want to perform the basic collision detection. The disadvantage of BoundingSphere is that if the object is a long box, there will be a log space which will be wasted.
How to do it…
The following steps show you the best practice approach to extracting the BoundingBox and BoundingSphere from 3D models, which will help you to perform the collision detection in the real game:
- Create a Windows Phone Game project named ObjectBoundings. Change Game1. cs to ObjectBoundingsGame.cs. Add a Content Pipeline Extension Library named ModelBoundsProcessor and insert a new processor definition file named ModelBoundingBoxProcessor.cs. Then, in the content project, we add a box. fbx model file.
- Define ModelBoundingBoxProcessor. We add the following lines into the class field:
[code]
// The collection for MeshBoundingSpheres
Dictionary<string, BoundingBox> MeshBoundingBoxes = new
Dictionary<string, BoundingBox>();
// The collection for MeshBoundingBoxes
Dictionary<string, BoundingSphere> MeshBoundingSpheres = new
Dictionary<string, BoundingSphere>();
// The dictionary stores the BoundingSphere and BoundingBox
// information of a model
Dictionary<string, object> bounds = new Dictionary<string,
object>();
[/code] - Next, we implement the Process() method for ModelBoundingBoxProcessor as follows:
[code]
public override ModelContent Process(NodeContent input,
ContentProcessorContext context)
{
// Get the children NodeContents
NodeContentCollection nodeContentCollection =
input.Children;
// If the input NodeContent does not have children mesh,
// the input is the NodeContent for processing
if (input.Children.Count == 0)
{
// Cast the input to MeshContent
MeshContent meshContent = (MeshContent)input;
// Get the points of the mesh
PositionCollection vertices = new PositionCollection();
// Get the world transformation of current meshcontent
Matrix absoluteTransform =
meshContent.AbsoluteTransform;
// Translate the points’ positions from object
//coordinates to world coordinates
foreach (Vector3 vec in meshContent.Positions)
{
Vector3 vector =
Vector3.Transform(vec, absoluteTransform);
// Add the transformed vector to the vertice list
vertices.Add(vector);
}
// Generate the BoundingBox of the mesh from the
// transformed vertice list
BoundingBox boundingbox =
BoundingBox.CreateFromPoints(vertices);
MeshBoundingBoxes.Add(input.Name, boundingbox);
// Generate the BoundingSpere of the mesh from the
// transformed vertice list
BoundingSphere boundingSphere =
BoundingSphere.CreateFromPoints(vertices);
MeshBoundingSpheres.Add(input.Name, boundingSphere);
}
else
{
// If the root NodeContent has children, process them
ParseChildren(nodeContentCollection);
}
// Deal with model in default
ModelContent modelContent = base.Process(input, context);
// Add the output BoundingBoxes and BoundingSpere to the
//bounds
bounds.Add(“BoundingBoxes”, MeshBoundingBoxes);
bounds.Add(“BoundingSpheres”, MeshBoundingSpheres);
// Assign the bounds to Tap for using in the game.
modelContent.Tag = bounds;
return modelContent;
}
[/code] - Read BoundingSphere and BoundingSphere information of model in the main game project ObjectBoundings. Insert the following code as the class fields:
[code]
Model modelBox;
Dictionary<string, object> bounds;
Dictionary<string, BoundingSphere> boundingSpheres;
Dictionary<string, BoundingBox> boundingBoxes;
[/code] - Then, add the following code into the LoadContent() method:
[code]
modelBox = Content.Load<Model>(“box”);
// Read the the bounds dictionary object from Tag property
bounds = (Dictionary<string, object>)modelBox.Tag;
// Get the BoundingSphere dictionary
boundingSpheres = (Dictionary<string,
BoundingSphere>)bounds[“BoundingSpheres”];
// Get the BoundingBox dictionary
boundingBoxes = (Dictionary<string,
BoundingBox>)bounds[“BoundingBoxes”];
[/code]
How it works…
In step 2, MeshBoundingBoxes is the dictionary that holds the BoundingBox of every mesh of a model by name. MeshBoundingSpheres stores the BoundingSphere of every mesh of model by name. bounds is the final dictionary where both BoundingSphere and BoundingBox collections are saved.
In step 3, the input is a NodeContent object that represents the root of the model tree. The first thing is to get the children NodeContents of input to hold the submeshes. The following condition judgment checks the count of children NodeObjects of input. If the input does have children, we will deal with the input itself. Understand this condition as we transform the input to MeshContent, which represents the model mesh. The following vertex is a PositionCollection object that stores the collection of Vector3. Then, we use MeshContent.AbsoluteTransform to get the world transformation matrix of model. After that, we iterate every point in the mesh and transform them from object coordinate to world coordinate. Finally, we use the BoundingBox.CreateFromPoints() and BoundingSphere.CreateFromPoints() methods to generate the BoundingBox and BoundingSphere of the model. Otherwise, if the model has more than one submesh, we use ParseChildren() to work on. The only difference is that we should traverse all the submeshes, and the ParseChildren () method should be:
[code]
private void ParseChildren(NodeContentCollection
nodeContentCollection)
{
//Iterate every NodeContent in the children NodeContent collection
foreach (NodeContent nodeContent in nodeContentCollection)
{
// Cast the input to MeshContent
MeshContent meshContent = (MeshContent)nodeContent;
// Get the points of the mesh
PositionCollection vertices = new PositionCollection();
// Get the world transformation of current meshcontent
Matrix absoluteTransform = meshContent.AbsoluteTransform;
// Translate the points’ positions from object
// coordinates to world coordinates
foreach (Vector3 vec in meshContent.Positions)
{
Vector3 vector = Vector3.Transform(vec,
absoluteTransform);
// Add the transformed vector to the vertice list
vertices.Add(vector);
}
// Generate the BoundingBox of the mesh from the
// transformed vertice list
BoundingBox boundingbox = BoundingBox.
CreateFromPoints(vertices);
MeshBoundingBoxes.Add(nodeContent.Name, boundingbox);
// Generate the BoundingSpere of the mesh from the
// transformed vertice list
BoundingSphere boundingSphere =
BoundingSphere.CreateFromPoints(vertices);
MeshBoundingSpheres.Add(nodeContent.Name, boundingSphere);
}
}
[/code]
Now you have learned how to get the BoundingSphere and BoundingBox information from a model, you can use this information in your game for efficient collision detection.