A custom con-tent pipeline processor will read the bulk data from your media files, process it, andthen store it in intermediate form.. If any changes are detected, when you build your g
Trang 1DrawFonts("DPad.Up: released", ++line);
if (zunePad.DPad.Down == ButtonState.Pressed) // Down
DrawFonts("DPad.Down: pressed", ++line);
else
DrawFonts("DPad.Down: released", ++line);
if (zunePad.DPad.Left == ButtonState.Pressed) // Left
DrawFonts("DPad.Left: pressed", ++line);
else
DrawFonts("DPad.Left: released", ++line);
if (zunePad.DPad.Right == ButtonState.Pressed) // Right
DrawFonts("DPad.Right: pressed", ++line);
else
DrawFonts("DPad.Right: released", ++line);
// A - press center of Zune pad
if (zunePad.Buttons.A == ButtonState.Pressed) // A
DrawFonts("A: pressed", ++line);
else
DrawFonts("A: released", ++line);
// B - press top right button
if (zunePad.Buttons.B == ButtonState.Pressed) // B
DrawFonts("B: pressed", ++line);
else
DrawFonts("B: released", ++line);
// running finger on Zune pad
float X = zunePad.ThumbSticks.Left.X; // thumbstick X
float Y = zunePad.ThumbSticks.Left.Y; // thumbstick Y
DrawFonts("ThumbSticks.Left.X", ++line);
DrawFonts("= " + X.ToString(), ++line);
DrawFonts("ThumbSticks.Left.Y", ++line);
DrawFonts("= " + Y.ToString(), ++line);
// show user how to exit game – back button is top left button
++line; // Back button is already used to exit in the template
DrawFonts("Press Back button", ++line);
DrawFonts("to exit.", ++line);
}
With everything in place you can now trigger the code to display the input devicestatus from Draw()
ShowInputDeviceStatus();
Trang 2When you run your code, you will see the press and release states of your controls
as well as the X and Y position of your finger on the Zune pad You can see from this
tiny example that Zune input handling is simple yet flexible enough to allow players
a full range of control over their game play
After enabling keyboard, mouse, game pad, and Zune input, you literally will
have placed control of your game engine in the hands of your players Your world is
now their oyster
To get the most from this chapter, try out these chapter review exercises
1. Try the step-by-step examples provided in this chapter, if you have not
already done so
2. If you run the solution from Exercise 1, when you left-click the mouse, the
word “Pressed” appears in the window Track the mouse state so you can
toggle between displaying pressed and released states in the game window
(A similar check exists that enables you to toggle between On and Off
states when pressing the letter T.)
3. In the “Collision Detection Using Lines and Spheres” solution from
Chapter 18, make your game pad rumble every time the camera collides
with something
Trang 4CHAPTER 24
Content Pipeline Processors
Trang 5Using predefined content types in XNA allows for easy deployment on your PC orXbox 360 For example, the XNA Framework offers built-in methods for loadingand accessingTexture2D, XACT(audio),XML,Effect(shaders), AutodeskFBX(model), andX(model) objects This chapter shows how to extend the content pipe-line to load files not supported out of the box by the XNA Framework
Aside from allowing you to load any graphics or data file on the PC and Xbox 360,custom content processors can also enable faster game startup times A custom con-tent pipeline processor will read the bulk data from your media files, process it, andthen store it in intermediate form This compiled binary intermediate data is stored in
an xnb file
The content processor also tracks changes to your media and to the cessing code itself If any changes are detected, when you build your game, the con-tent processor reloads the bulk data from your media files and recompiles it.Otherwise, if no changes are detected, the compiled data is read from the xnb filethat stored it Being able to read preprocessed data can be a big timesaver when largecompressed media files are loaded at game launch
content-pro-For the Quake II model loader (used in Chapter 26), setting up the model for XNA
deployment requires loading the bulk data, organizing the faces in the polygonshapes from the indexed information stored in the file, and generating the normalvectors to enable lighting This processing time can add unwanted delays to yourgame launch However, if you use a custom content processor to decompress and or-ganize your md2 data in an intermediate format, your game will not read from the
*.md2 file again Instead, the game will read the intermediate data from your piled xnb file during any consecutive run The initial data processing is only per-formed when either the original media file changes or the processor code is modified
com-In short, you will notice an improvement to your load times when using the contentprocessor
The content processor loads your external media and locates existing processor ponents All custom processors must derive from the ContentProcessor baseclass in a manner similar to the following:
com-public class MyContentProcessor : ContentProcessor<Tinput,Toutput>
{}
TinputandToutputare the user-defined input and output classes you create toinput your bulk data and output your compiled data in the required format
Trang 6TheContentImporterclass is defined with anImportmethod to read
unpro-cessed data from your original media file The class declaration is preceded by the
ContentImporterattribute to list the file extension(s) associated with this loader
and the processor used to convert it to a compiled format Additional extensions,
separated by commas, can be added to the string
[ContentImporter(string fileExt, DefaultProcessor = string processorName)]
public class MyContentImporter : ContentImporter<TerrainContent>{
public override MyCustomContent Import(String filename,
ContentImporterContext context){}
}
Inside the Import method, the file is opened and can be read with a
Sys-tem.IO.Filemethod or through theMemoryStreamand BinaryReader
ob-jects Using these objects, you can read text and binary formats
For this example, the System.IO.File ReadAllBytes()method reads in
the bytes from the raw image However, if you were reading text input, this could be
read with theFileobject’sReadAllText()method You can also load your data
withMemoryStreamandBinaryReaderobjects to read data in specific chunks to
handle integers, floats, vectors, and many other data types
After the data has been read, it is structured according to your own custom
data-storage class You define how you want the data organized and how you want it
exported to the xnb file
ContentTypeWriter
TheContentTypeWriterclass assists in writing your intermediary data as binary
output to the xnb file Output is written with theWrite()method override The
GetRuntimeType()method returns the custom data type of the processed
con-tent TheGetRuntimeReader()reader method returns the intermediate format
reader’s location:
[ContentTypeWriter]
public class MyContentWriter : ContentTypeWriter<MyCustomContent>{
protected override void Write(ContentWriter wr,
Trang 7public class MyReader : ContentTypeReader<MyCustomContent>{
protected override MyCustomContent Read(ContentReader input,
MyCustomContent existingInstance){} }
When theRead()method override is finished importing your managed data, itreturns this data in the format you defined in your storage class This data is thenmade available to your game project
This example demonstrates how to create a custom content processor that loads aheight map from a raw image This content processor converts the height data togenerate position and normal vectors The vertices created from this newly generateddata are used in Chapter 25 to build a rolling landscape To keep the content proces-sor demonstration in this chapter focused, the terrain is not fully implemented How-
Trang 8ever, the height data associated with the current camera position is updated as it
moves through the world and this height information is printed in the window
XNA does not provide a code library for loading raw images, so you need an
al-ternate way to load them You can get away withBinaryReadermethods to load
them on Windows On the Xbox 360, theBinaryReadermethods will find your
.raw files if you place your media resources in the debug folder when deploying your
solution However, to handle these files more gracefully, you should create a custom
processor to load them through the content pipeline
Load performance is another reason to use the content processor to load your
ter-rain data The raw image stores an array of bytes When it is used as a height map,
each pixel stores height information between 0 and 255 The pixels from this
rectan-gular raw image are mapped to the rectanrectan-gular ground in your world To
superim-pose each pixel over the corresponding section of ground, you will need to calculate
the position vector associated with each pixel Also, to enable lighting, you will need
to calculate the normal vector associated with each pixel in the raw file
This example begins with the “Directional Lighting Example” from Chapter 22
This project can be found in the Solutions folder on this book’s website
Building a Custom Content Processor in Windows
In order to compile the content processor into a DLL that can be used either on
Win-dows or the Xbox 360, you must add a separate Content Pipeline Extension Library
project to your solution from the Solution Explorer To add it, right-click the
solu-tion name and choose Add | New Project When prompted in the Add New Project
di-alog, select Content Pipeline Extension Library The Content Pipeline Extension
project is used because it already has the proper assembly references and does not
contain the Content subproject For this example, name your content pipeline
pro-ject as TerrainPipeline
Once your new library project has been added, you will be able to see it as a
sepa-rate project in the Solution Explorer Rename the cs code file that is genesepa-rated to
TerrainContent.cs Then replace the code in this file with the following shell to
im-plement your own content processor:
Trang 9Your custom data class is designed by you to store your data in the format you quire For this example, the user-defined classTerrainContentstores bulk heightdata from the raw file It then uses this data to generate position and normal vectorsalong with the terrain dimensions and stores these new values at the class level.TheTerrainContentclass is referenced throughout your content processor togenerate and access your height map data, so it must be made public Also, to ensurethat the height map is mapped properly to the rectangular world, the number of rowsand columns, the world dimensions, and the cell height and width are also made pub-lic This terrain-defining code belongs in the TerrainPipeline namespace ofyour TerrainContent.cs file:
re-public class TerrainContent{
public byte[] height;
public Vector3[] position;
public Vector3[] normal;
public float cellWidth, cellHeight;
// hard coded values to match height map pixel and world dimensions public int NUM_ROWS = 257;
public int NUM_COLS = 257;
public float worldWidth = 16.0f;
public float worldHeight = 16.0f;
public float heightScale = 0.0104f;
// constructor for raw data - used during bulk data import
public TerrainContent(byte[] bytes){
// sets height and width of cells made from pixels in raw file
public void setCellDimensions(){
cellWidth = 2.0f*worldWidth/(NUM_COLS - 1);
cellHeight = 2.0f*worldHeight/(NUM_ROWS - 1);
}
// generate X, Y, and Z position data where Y is the height.
private void generatePositions(){
position = new Vector3[NUM_ROWS*NUM_COLS];
Trang 10for (int row = 0; row < NUM_ROWS; row++){
for (int col = 0; col < NUM_COLS; col++){
float X = -worldWidth + col*cellWidth;
float Y = height[row*NUM_COLS + col]*heightScale;
float Z = -worldHeight + row*cellHeight;
position[col + row*NUM_COLS] = new Vector3(X, Y, Z);
}
}
}
// generate normal vector for each cell in height map
private void generateNormals(){
Vector3 tail, right, down, cross;
normal = new Vector3[NUM_ROWS*NUM_COLS];
// normal is cross product of two vectors joined at tail
for (int row=0; row<NUM_ROWS - 1; row++){
for (int col = 0; col < NUM_COLS - 1; col++){
tail = position[col + row*NUM_COLS];
right = position[col + 1 + row*NUM_COLS] - tail;
down = position[col + (row + 1)*(NUM_COLS)] - tail;
cross = Vector3.Cross(down, right);
With theTerrainContentclass in place to store the terrain data, a derived
in-stance of theContentProcessorclass is needed as a processor interface for the
Trang 11Extending the ContentImporter class enables the overridden Import()method to read your data from the original media file TheContentImporterat-tribute precedes theContentImporterclass definition to list the file extensionsthat can use this importer.
For this example, theReadAllBytes()method reads in the bytes from the rawimage Of course, you can use other methods to read your data:
// stores information about importer, file extension, and caching
[ContentImporter(".raw", DefaultProcessor = "TerrainProcessor")]
// ContentImporter reads original data from original media file
public class TerrainPipeline : ContentImporter<TerrainContent>{
// reads original data from binary or text based files
public override TerrainContent Import(String filename,
ContentImporterContext context){
byte[] bytes = File.ReadAllBytes(filename);
TerrainContent terrain = new TerrainContent(bytes);
return terrain; // returns compiled data object }
}
Once the data is read, it is passed to your custom data class This data initializes
a custom data object that organizes the data as you need it The data object is thenreturned to your processor so it can be written in a compiled binary format to an.xnb file
Adding the extendedContentTypeWriterclass to yourTerrainPipelinenamespace allows you to output your compiled binary custom data to an xnb file.TheWrite()method receives your integer, float, and vector data and then writes it
in binary format to the file When you write your data, you have to write it in the quence you want to retrieve it The writer/reader combination uses a “first in firstout” sequence for your data storage and access
se-AGetRuntimeType() method is included in theContentTypeWriterclass
to return the custom data type of the processed content to be loaded at run time AGetRunTimeReader()method is also added to return the intermediate contentreader’s location in the solution:
// write compiled data to *.xnb file
[ContentTypeWriter]
public class TerrWriter : ContentTypeWriter<TerrainContent>{
protected override void Write(ContentWriter cw,
TerrainContent terrain){
cw.Write(terrain.NUM_ROWS);
Trang 12for (int row = 0; row < terrain.NUM_ROWS; row++){
for (int col = 0; col < terrain.NUM_COLS; col++){
// Sets the CLR data type to be loaded at runtime.
public override string GetRuntimeType(TargetPlatform targetPlatform){
return "TerrainRuntime.Terrain, TerrainRuntime,
Version=1.0.0.0, Culture=neutral";
}
// Tells the content pipeline about reader used to load xnb data
public override string GetRuntimeReader(TargetPlatform targetPlatform){
return "TerrainRuntime.TerrainReader, TerrainRuntime,
Version=1.0.0.0, Culture=neutral";
}
}
At run time, the Content reader reads the compiled data from the xnb file A
sepa-rate project is used for this reader To create it, right-click the solution, choose Add |
New Project, and then choose Content Pipeline Extension Library In the Add New
Project dialog, assign it the name TerrainRuntime to match the value given in the
GetRuntimeReader()method from inside the content processor For readability,
rename the project code file that is generated to TerrainReader.cs
The ContentReader is derived from the BinaryReader class and exposes
similar methods for retrieving data in the segments you need Once the data is read,
an object of your custom data class is initialized This custom data object is then
made available to your XNA game project as soon as the data is loaded:
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
Trang 13namespace TerrainRuntime{
public class Terrain{
// these variables store values that are accessible in game class public byte[] height;
public Vector3[] position;
public Vector3[] normal;
public int NUM_ROWS, NUM_COLS;
public float worldWidth, worldHeight, heightScale;
public float cellWidth, cellHeight;
normal = new Vector3[NUM_ROWS*NUM_COLS];
// read in position and normal data to generate height map for (int row = 0; row < NUM_ROWS; row++){
for (int col = 0; col < NUM_COLS; col++){
position[col + row*NUM_COLS] = cr.ReadVector3(); normal[col + row*NUM_COLS] = cr.ReadVector3(); }
} } }
// loads terrain from an XNB file.
public class TerrainReader : ContentTypeReader<Terrain>{
protected override Terrain Read(ContentReader input,
Terrain existingInstance){
return new Terrain(input);
} }
}
Trang 14The game project must reference the TerrainRuntime project To reference this
as-sembly, right-click the game project’s References folder in the Solution Explorer and
choose Add Reference In the Add Reference dialog, select the TerrainRuntime
pro-ject from the Propro-jects tab and click OK You will now see this TerrainRuntime
refer-ence listed in your game project (see Figure 24-1) The game project’s Content project
needs to reference the TerrainPipeline to load the raw content To add it, right-click
the References node under the Content folder and choose Add Reference Then in the
Add Reference dialog, select the TerrainPipeline project from the Projects tab (see
Figure 24-1)
Wherever you want to use your custom data type, the new namespace for your
runtime content must be included in your original game project:
Trang 15Next, your heightMap.raw file must be referenced in the Images folder for yourgame project You can get this file from the Images directory on this book’s website.Once the heightMap.raw file is referenced, you can set its properties to use yourcustom content processor to load it First, you need to build your TerrainPipeline andTerrainRuntime projects to see their references when setting up the heightMap.rawfile You can build each project by right-clicking each project name in the SolutionExplorer and choosing Build Then, to assign the custom content processor to readthe raw file, right-click heightMap in the Solution Explorer and select Properties.Under the Build Action property drop-down, select Compile The Content Importerattribute should be set toTerrainPipeline, and the Content Processor attributeshould be set toTerrainProcessor Figure 24-2 shows the content pipeline prop-erty settings for the heightMap.raw file.
In your game project you need an instance of the terrain object at the class level:Terrain terrain;
F I G U R E 2 4 - 2
Media file references the custom content importer and processor.