.: The Content Pipeline
|
|
| |
So far in our game, we have loaded textures straight from the hard drive as they were needed during play, XNA, however, provides us with a means of loading textures at specific times during the game when the disk activity will not affect gameplay. These textures are stored in memory and and can be accessed at any time by many different objects simultaneously, this increases speed and gives us more control over how and when these textures ar used. The content pipeline works with all kinds of assets, not just textures, but we will deal with that in a later tutorial.
When using XNA in C#, the process of drawing a textures is as follows:
1 - A content manager object is created.
2 - A texture is added to the solution explorer, you then manually assign various properties to it that will be used during the conversion process.
3 - The texture is then assigned to a texture object using the content manager load sub.
|
|
Now, 1 and 3 we can do in VB.NET Express, but 2 is a problem because our solution explorer does not have the part necessary to change the texture properties. However, now we know what we have to do, we have to create a system that will assign these properties to our textures and convert them before our game runs, and this is how we are going to do it.
Download the source code, from the last tutorial, copy the code folder and rename the copy to contentpipeline, but don't start the project just yet, first start VB.NET Express and select a new Console Application, call the console app ContentPipeline. Once the new project is open, delete the file Module1.vb and add a new class called Asset. Then, add the following code to the asset file:
Imports System.Windows.Forms
Imports System.IO
Public Class Asset
#Region "Objects and Variables"
'The Array to hold all assets that are to be converted to xnb files
Public Shared AssetArray() As Asset = New Asset() {}
'Global Asset Varables - Output folder for xnb files
'If left as the default, all xnb files will be created in the same folder as its corresponding asset.
'If this is changed the asset's folder structure will be created in the VB project folder.
'So if an asset is located at Images\Folder\asset.jpg, then the corresponding xnb file will be located at
'AssetOutputFolder\Images\Folder\asset.xnb
Public Shared AssetOutputFolder As String = ".\"
' The static readonly path to the folder that contains the VB project file vbproj
Public Shared ReadOnly vbprojFolder As String = Microsoft.VisualBasic.Left(Application.StartupPath, Len(Application.StartupPath) - 42)
'Asset Specific variables, that differ for each asset
Public AssetPath As String = "" ' An asset's Path name relative to the program executable
Public AssetType As AssetEnum ' The type of asset
Public AssetName As String = "" ' The name of the asset without the file extension.
Public AssetFileName As String = "" ' The name of the asset with the file extension.
#End Region
'The list of possible assets
Public Enum AssetEnum As Integer
Texture_Sprite32bpp = 1
Texture_ModelDXTmipmapped = 2
Texture_Mipmapped = 3
End Enum
'Any Asset to be added to the content pipeline should be listed in this sub
Public Shared Sub AssetList()
End Sub
''' <summary>
''' Add an asset to the content pipeline.
''' </summary>
''' <param name="AssetPathName">The path to the asset file, relative to the VB project file.
''' A jpeg in the same folder as the project .vbproj file would be: "asset.jpg",
''' the same file inside a folder called images, where this folder is at the same level as the VB project file would be
''' "images\asset.jpg"</param>
''' <param name="AssetType1">The kind of asset to be added:
''' Texture_Mipmapped - Low Compression, Texture_Sprite32bpp - Medium Compression, Texture_ModelDXTmipmapped - High Compression
''' Texture_Sprite32bpp is the default.
''' Important Note: Many graphics cards do not support mipmapped textures that are not sized as a power of 2.
''' Also, For a texture to use DXT compression it's size must be dividable by 4.</param>
''' <returns>True if the asset was successfully added to the asset array, otherwise False. Will fail if an
''' asset with the same name already exists in the array.</returns>
Public Shared Function AddAssettoContentPipeline(ByVal AssetPathName As String, ByVal AssetType1 As AssetEnum)
' A couple of string to hold the file name with and without the file extension
Dim FileNameWithExt As String = Mid(AssetPathName, InStrRev(AssetPathName, Chr(92)) + 1)
Dim FileNameWithoutExt As String = Microsoft.VisualBasic.Left(FileNameWithExt, InStrRev(FileNameWithExt, Chr(46)) - 1)
'Loop through the asset array to see if a asset of the same name already exists
For Each Asset1 As Asset In AssetArray
If Asset1.AssetName = FileNameWithoutExt Then
' If an asset of the same name already exists in the asset array, the next one
' will not be added and the sub will exit
MsgBox("A duplicate entry for Asset name: " & Asset1.AssetName & " already exists in the asset array" & vbCrLf & _
"Please rename one of the assets.")
Return False
End If
Next
'Create a temporary asset and assign the required variables
Dim TempAsset As New Asset
TempAsset.AssetPath = AssetPathName
TempAsset.AssetName = FileNameWithoutExt
TempAsset.AssetType = AssetType1
TempAsset.AssetFileName = FileNameWithExt
'Increase the size of the asset array and add the new asset to it
ReDim Preserve AssetArray(UBound(AssetArray) + 1)
AssetArray(UBound(AssetArray)) = TempAsset
'release variables
TempAsset = Nothing
FileNameWithExt = Nothing
FileNameWithoutExt = Nothing
Return True
End Function
'Sub Main will convert any asset that ha sbeen added to the asset array into xnb files
'Ensure that you have have added all assets in the AssetList Sub below
Public Shared Sub Main()
'Add assets to asset array
Asset.AssetList()
'Make sure that the asset array is valid
If Asset.AssetArray IsNot Nothing Then
'Make sure that it has at least one entry
If UBound(Asset.AssetArray) > -1 Then
Try
'Try to create the C# project file tat MSBuild needs to create the asset binaries
Using sw As StreamWriter = New StreamWriter(Asset.vbprojFolder & "\Content_Temp.csproj", False)
sw.WriteLine("<Project DefaultTargets=" & Chr(34) & "Build" & Chr(34) & " xmlns=" & Chr(34) & "http://schemas.microsoft.com/developer/msbuild/2003" & Chr(34) & ">")
sw.WriteLine("<PropertyGroup>")
sw.WriteLine("<Configuration Condition=" & Chr(34) & " '$(Configuration)' == '' " & Chr(34) & ">Debug</Configuration>")
sw.WriteLine("<Platform Condition=" & Chr(34) & " '$(Platform)' == '' " & Chr(34) & ">x86</Platform>")
sw.WriteLine("<XnaFrameworkVersion>v2.0</XnaFrameworkVersion>")
sw.WriteLine("<XnaPlatform>Windows</XnaPlatform>")
sw.WriteLine("</PropertyGroup>")
sw.WriteLine("<PropertyGroup Condition=" & Chr(34) & " '$(Configuration)|$(Platform)' == 'Debug|x86' " & Chr(34) & ">")
sw.WriteLine("<OutputPath>" & Asset.AssetOutputFolder & "</OutputPath>")
sw.WriteLine("</PropertyGroup>")
sw.WriteLine("<ItemGroup> ")
sw.WriteLine("<Reference Include=" & Chr(34) & "Microsoft.Xna.Framework.Content.Pipeline.EffectImporter, Version=2.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d, processorArchitecture=MSIL" & Chr(34) & ">")
sw.WriteLine("<Private>False</Private>")
sw.WriteLine("</Reference>")
sw.WriteLine("<Reference Include=" & Chr(34) & "Microsoft.Xna.Framework.Content.Pipeline.FBXImporter, Version=2.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d, processorArchitecture=MSIL" & Chr(34) & ">")
sw.WriteLine("<Private>False</Private>")
sw.WriteLine("</Reference>")
sw.WriteLine("<Reference Include=" & Chr(34) & "Microsoft.Xna.Framework.Content.Pipeline.TextureImporter, Version=2.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d, processorArchitecture=MSIL" & Chr(34) & ">")
sw.WriteLine("<Private>False</Private>")
sw.WriteLine("</Reference>")
sw.WriteLine("<Reference Include=" & Chr(34) & "Microsoft.Xna.Framework.Content.Pipeline.XImporter, Version=2.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d, processorArchitecture=MSIL" & Chr(34) & ">")
sw.WriteLine("<Private>False</Private>")
sw.WriteLine("</Reference>")
sw.WriteLine("</ItemGroup>")
sw.WriteLine("<ItemGroup> ")
'Add data to the csproj files for each asset in the asset array
For Each TempAsset As Asset In Asset.AssetArray
'Sort assetpath so that it does not have a \ or / at the start
If Mid(TempAsset.AssetPath, 1, 1) = Chr(92) Or Mid(TempAsset.AssetPath, 1, 1) = Chr(47) Then
TempAsset.AssetPath = Mid(TempAsset.AssetPath, 2)
End If
sw.WriteLine("<Compile Include=" & Chr(34) & TempAsset.AssetPath & Chr(34) & ">")
sw.WriteLine("<XNAUseContentPipeline>true</XNAUseContentPipeline>")
sw.WriteLine("<Importer>TextureImporter</Importer>")
Select Case TempAsset.AssetType
Case Is = Asset.AssetEnum.Texture_Mipmapped
sw.WriteLine("<Processor>TextureProcessor</Processor>")
Case Is = Asset.AssetEnum.Texture_ModelDXTmipmapped
sw.WriteLine("<Processor>TextureProcessor</Processor>")
Case Is = Asset.AssetEnum.Texture_Sprite32bpp
sw.WriteLine("<Processor>TextureProcessor</Processor>")
End Select
sw.WriteLine("<Name>" & TempAsset.AssetName & "</Name>")
sw.WriteLine("</Compile>")
Next
sw.WriteLine("</ItemGroup>")
sw.WriteLine("<Import Project=" & Chr(34) & "$(MSBuildExtensionsPath)\Microsoft\XNA Game Studio\v2.0\Microsoft.Xna.GameStudio.ContentPipeline.targets" & Chr(34) & " />")
sw.WriteLine("</Project>")
sw.Close()
End Using
Catch ex As Exception
'If the file creation fails then show this message box and exit the sub
MsgBox("While creating Content_Temp.csproj, the following error was raised:" & vbCrLf & ex.Message)
Exit Sub
End Try
' Run MSBuild and create the asset binaries
System.Diagnostics.Process.Start(Environment.GetEnvironmentVariable("Windir") & _
"\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe", "/nologo " & Chr(34) & Asset.vbprojFolder & _
"\Content_Temp.csproj" & Chr(34) & " /l:FileLogger,Microsoft.Build.Engine;logfile=" & _
Chr(34) & Asset.vbprojFolder & "\AssetBuild.log" & Chr(34))
End If
End If
End Sub
End Class
The String vbprojFolder is the folder that holds the program executable minus two folder levels, basically the program project folder, the one that holds the .vbproj file. The enumeration AssetEnum holds the list of possible asset objects that the content pipeline can convert, so far it only holds textures but over time I will add functionality for all asset objects. The Sub AssetList is the important one, it uses the AddAssettoContentPipeline Sub to add textures to the asset array and hence they will be converted to xnb files. Any texture you want to use should be added to this sub. Public Shared Sub Main is the starting point for the program and it converts the textures to xnb files, in a process that is described as follows:
XNA Game Studio Express uses MSBuild.exe to create the xnb files. e will create a C# project file and add a section for each asset in the asset array, MSBuild reads this .csproj file and converts any textures mentioned in it. This C# project file will be located in the program folder and will be called Content_Temp.csproj, an MSBUild log file will also be created in this folder. Once the process is complete these files can be deleted.
Lastly, open the project properties and set the startup object either Asset or Sub Main, both seem to work, also add the System.Windows.Forms reference, and that's the content pipeline built, now we need to add it to our game.
So, take the entire content pipeline project folder that you just made and move it into the source code folder from the last tutorial as shown below:

Now, open the XNAGame project, not the content pipeline (console application), that you just made. Once open, go to File/Open Project and open the content pipeline (console application) project and add it to the solution:

Remember to select "Add to Solution" and not "Close Solution", once open your solution explorer should look like this:

As you can see "XNAGame" is bold, which means that it is the Startup Project, if this is not the case in your solution then right click on XNAGame and select "Set as Startup Solution". Your game is now ready to use the content pipeline, so pay attention while I explain the process.
In XNAGAme - Public Sub New, we will create the content manager object:
Public Sub New()
XNAGraphics = New GraphicsDeviceManager(Me)
XNAContentManager = New ContentManager(Services)
End Sub
In XNAGame - Module Main we have added the following declarations:
'The location of the project folder, which contains the .vbproj file
Public ReadOnly XNAGameProjectFolder As String = Mid(Mid(Application.StartupPath, 1, _
InStrRev(Application.StartupPath, "\") - 1), 1, InStrRev(Mid(Application.StartupPath, 1, _
InStrRev(Application.StartupPath, "\") - 1), "\"))
The LoadGraphicsContent Sub becomes:
'Load All Graphics Content XNB files
Protected Overrides Sub LoadGraphicsContent(ByVal LoadAllContent As Boolean)
If LoadAllContent = True Then
'All texture objects have there corresponding xnb file loaded here. XNAGameProjectFolder is
'basically application.startuppath minus two folder levels, which is the vb project folder.
'IMPORTANT: If you recieve a "file not found error", check that you have ran the contentpipeline, and
'also check that you have added each asset to the asset array using teh asset.assetlist sub.
ArrowTex = XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Images\Missilelauncher")
BackgroundTex = XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Images\BackgroundEarth")
BallLauncherTex = XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Images\BallLauncherEarth")
CongratTex = XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Images\Congrat")
GameOverTex = XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Images\GameOver")
LetsGoTex = XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Images\LetsGo")
Number1Tex = XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Images\Number1")
Number2Tex = XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Images\Number2")
Number3Tex = XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Images\Number3")
PauseTex = XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Images\Paused")
RoofTex = XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Images\RoofTexEarth")
PlayAreaTex = XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Images\WaitingToStart")
End If
End Sub
This sub loads an XNB file and assigns it to a Texture object. The XNB file must have been created before this sub runs.
In the graphics module the DefineObjectDetails sub becomes:
'Function to Define NonBall Details
Public Sub DefineObjectDetails()
MainSB = New SpriteBatch(XNAGraphics.GraphicsDevice)
BackgroundRect.Height = Game1.Window.ClientBounds.Height
BackgroundRect.Width = Game1.Window.ClientBounds.Width
PlayAreaRect.Height = 480
PlayAreaRect.Width = 379
PlayAreaRect.X = 212
PlayAreaRect.Y = 60
BallLauncherRect.Height = 107
BallLauncherRect.Width = 380
BallLauncherRect.X = 212
BallLauncherRect.Y = 433
LetsGoRect = New Rectangle(220, 225, 360, 150)
CountDownRect = New Rectangle(350, 225, 100, 150)
CongratRect = New Rectangle(220, 275, 360, 50)
RoofRect = New Rectangle(PlayAreaRect.X, PlayAreaRect.Y - 480, 380, 480)
GameOverRect = New Rectangle(275, 250, 250, 100)
PauseRect = New Rectangle(325, 275, 150, 50)
DefineColoursArrayList()
ThemeRect = New Rectangle(212, 62, 380, 456)
End Sub
As you can see all references to loading textures from a file has been removed. You should also change any other Texture2D.FromFile instance to its corresponding Asset.AddAssettoContentPipeline, as example below:
In the Graphics Module - CreateBall Sub:
TempBall.BallTex = Texture2D.FromFile(Device, "../../images/Ball" & BallColour & ".png", TempBall.BallTexCreationParams)
becomes
TempBall.BallTex = XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "\Images\Ball" & BallColour)
This means the texture is loaded from an XNB file not a texture file. If the XNB file has been previously loaded, then this texture is referenced and another copy of that texture is not loaded from file.
The only other change is in Content Pipeline - Asset. The AssetList Sub becomes:
Public Shared Sub AssetList()
Asset.AddAssettoContentPipeline("Images\Missilelauncher.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\380x480AlphaChannelOnly.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\BackgroundEarth.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\BackgroundMars.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\BackgroundSun.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\BackgroundUranus.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\BallBlack.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\BallBlue.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\BallGreen.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\BallLauncherEarth.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\BallLauncherMars.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\BallLauncherSun.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\BallLauncherUranus.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\BallRed.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\BallYellow.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\Congrat.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\Earth.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\GameOver.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\LetsGo.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\Mars.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\Number1.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\Number2.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\Number3.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\Paused.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\RoofTexEarth.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\RoofTexMars.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\RoofTexSun.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\RoofTexUranus.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\Sun.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\Uranus.png", Asset.AssetEnum.Texture_Sprite32bpp)
Asset.AddAssettoContentPipeline("Images\WaitingToStart.png", Asset.AssetEnum.Texture_Sprite32bpp)
End Sub
Here I added all our game textures to the asset array, so that when the Content Pipeline program runs, all textures will be converted. Note that MSBuild checks to see if the texture has changed since the XNB file was built, if it hasn't changed then the XNB file is NOT rebuilt, meaning, if you have a lot of assets, you don't have to rebuild them all every time you change one.
Now, to run the game, we must first run Content Pipeline and then run XNAGame.
So, right click on the ContentPipeline project name and select Debug - Start New Instance. The XNB file will be created and then you can run XNAGame as normal. You do not have to convert assets every time you want to run the game, only run the content pipeline when the XNB has not been created or when an asset has changed. As you play the game you will notice that it runs a lot faster, especially when creating particles.
So, to Summarize, when you want to use a texture in your game, do the following:
1 - Create the content manager object in Sub New
' When an instance of the XNAGame class is created using the New keyword, this Sub runs.
Public Sub New()
XNAGraphics = New GraphicsDeviceManager(Me)
XNAContentManager = New ContentManager(Services)
End Sub
2 - Add the texture to the asset list sub
'Any Asset to be added to the content pipeline should be listed in this sub
Public Shared Sub AssetList()
Asset.AddAssettoContentPipeline("Images\Missilelauncher.png", Asset.AssetEnum.Texture_Sprite32bpp)
End Sub
3 - Load the XNB file into the game
'Load All Graphics Content XNB files
Protected Overrides Sub LoadGraphicsContent(ByVal LoadAllContent As Boolean)
If LoadAllContent = True Then
ArrowTex = XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Images\Missilelauncher")
End If
End Sub
4 - Build the XNB files by running the Content Pipeline program:

and then:
5 - Run your game as normal.
That's it, now we have incorporated the content pipeline into our game. There are a couple of things to note, Once you have completed your game, you don't need to distribute the content pipeline executables with it, they can be safely deleted. The content pipeline is in fact a separate project, I tried to join them so that you wouldn't have to work on 2 projects all the time. I did try to make it all happen automatically when you hit the debug button, but it just wouldn't play nice, so you have to build the XNB files separately. The XNB files do not have to be loaded in the LoadGraphicsContent Sub, they can be loaded anywhere, I load them here for simplicity. Once you have XNB files you dont need to distribute the textures with your game.
Excellent, I will do one more tutorial about what to do with your game when its finished, then I'm going to have a week off, before I come back and begin the 3D tutorial section. So, when I've finished the last tutorial I will upload it to the tutorials page, so until then, Enjoy.
Web site contents © Copyright Alan Phipps 2006, All rights reserved.
Website templates |