11/20/07

VB.NET XNA Content Pipeline for Game Studio 2.0 (beta)

[GS 2.0 Beta]

Moving forward, the tutorials on this website will be referencing XNA 2.0.

Here is an updated version of the VBContentManager class for use with XNA 2.0

  • The class itself now inherits from the XNA 2.0 ContentManager

  • The LoadPipeline() method has been replaced by the CompileContent & LoadAllContent methods. Call these methods as appropriate where LoadPipeline was used before.

  • You can now specify a platform (Windows or Xbox360), although it's not exactly known whether or not you can run VB.NET on an Xbox360. This is there just in case/for the future. The target platform is Windows by default

  • You can now specify whether or not the MSBuild window is visible, having the content compile in "stealth mode" (as a C# project does)

  • The content folder is no longer supplied relative to the bin\Debug folder. You now supply it relative to the Project node in which the folder resides. For example, if you made a folder called "My Content" in your game's project in the Solution Explorer, you would simply supply "My Content" as the path to your content (rather than "../../My Content" like before)

  • The 'fake' project file built for MSBuild is now a .contentproj file, rather than an actual .csproj file. Not that the extension matters much, this was simply for the sake of emulating the file created in XNA 2.0 (where, in C#, content is now a sub-project of the game and has it's own project file - the VBContentManager simply simulates this file). As an aside, the project file being generated is more well formed than before, so MSBuild should no longer throw warning messages as it compiles.

  • In the previous version of the VBContentManager, choosing not to compile the content would result in a runtime error. You no longer have to compile the content; if you know it's already compiled (like say for instance, right before you are going to distribute the application to someone's PC) you can omit the call to CompileContent in your code and load the already-compiled content (skips the call to MSBuild completely).
Here is the code for the class:

Public Class VBContentManager
 Inherits ContentManager

 Public Fonts As Hashtable(Of SpriteFont)
 Public Textures As Hashtable(Of Texture2D)
 Public SubTextures As Hashtable(Of Hashtable(Of Rectangle))
 Public Sounds As Hashtable(Of SoundBank)
 Public Effects As Hashtable(Of Effect)

 Private strContentprojFile, strProjectFolder, strExecutingFolder, strContentFolder As String
 Private objContent As List(Of ContentFile)
 Private intDupeSound, intDupeTexture, intDupeFont, intDupeEffect As Integer

 Public Enum Platform
 Windows
 XBox360
 End Enum

 Public Enum ContentTypes
 Textures = 0
 Fonts = 1
 Sounds = 2
 Effects = 3
 End Enum

 Private Enum ImporterName
 TextureImporter = 0
 FontDescriptionImporter = 1
 XactImporter = 2
 EffectImporter = 3
 End Enum

 Private Enum ProcessorName
 TextureProcessor = 0
 FontDescriptionProcessor = 1
 XactProcessor = 2
 EffectProcessor = 3
 End Enum

 Public Structure ContentFile

 Public ContentType As ContentTypes
 Public File As System.IO.FileInfo
 Public Name As String

 Public Sub New(ByVal ContentType As ContentTypes, ByVal ContentFile As System.IO.FileInfo, ByVal Name As String)
 Me.ContentType = ContentType
 Me.File = ContentFile
 Me.Name = Name
 End Sub

 End Structure

 ''' <summary>
 ''' Creates a new ContentManager which has been extended with functionality to support a Visual Basic (or other non-C#) project
 ''' </summary>
 ''' <param name="ServiceProvider">Your game has a .Services property; send that in here.</param>
 ''' <param name="ContentFolder">Your content folder (a relative path, from your game's Project node as it appears in the Solution Explorer)</param>
 ''' <remarks></remarks>
 Public Sub New(ByVal ServiceProvider As IServiceProvider, ByVal ContentFolder As String)

 MyBase.New(ServiceProvider)

 With System.Reflection.Assembly.GetEntryAssembly.Location
 Me.strExecutingFolder = .Substring(0, .LastIndexOf("\"))
 End With

 With Me.strExecutingFolder
 With .Substring(0, .LastIndexOf("\") - 1)
 Me.strProjectFolder = .Substring(0, .LastIndexOf("\"))
 End With
 End With

 Me.strContentFolder = Me.strProjectFolder & "\" & ContentFolder

 Me.Fonts = New Hashtable(Of SpriteFont)
 Me.Textures = New Hashtable(Of Texture2D)
 Me.SubTextures = New Hashtable(Of Hashtable(Of Rectangle))
 Me.Sounds = New Hashtable(Of SoundBank)
 Me.Effects = New Hashtable(Of Effect)

 End Sub

 ''' <summary>
 ''' Attempts to load all assets which have been compiled by the Content Pipeline. Assets are placed into the respective Hash table (Fonts, Textures, et al)
 ''' </summary>
 ''' <remarks></remarks>
 Public Sub LoadAllContent()

 Dim objCompiledContent As New List(Of IO.FileInfo)
 Dim strUnknownFiles As String = ""

 For Each file As IO.FileInfo In New IO.DirectoryInfo(Me.strExecutingFolder).GetFiles("*.xnb", IO.SearchOption.AllDirectories)

 Try

 'Font?

 With file.FullName.ToLower
 Me.Fonts.Add(file.Name.Replace(file.Extension, ""), Me.Load(Of SpriteFont)(.Replace(Me.strExecutingFolder & "\""").Replace(file.Extension, "")))
 End With

 Catch notAFont As Exception

 Try

 'Texture2D?

 With file.FullName
 Me.Textures.Add(file.Name.Replace(file.Extension, ""), Me.Load(Of Texture2D)(.Replace(Me.strExecutingFolder & "\""").Replace(file.Extension, "")))
 End With

 Catch notATexture2D As Exception

 Try

 'Sound?

 With file.FullName.ToLower
 Me.Sounds.Add(file.Name.Replace(file.Extension, ""), Me.Load(Of SoundBank)(.Replace(Me.strExecutingFolder & "\""").Replace(file.Extension, "")))
 End With

 Catch notASoundBank As Exception

 Try

 'Effect?

 With file.FullName.ToLower
 Me.Effects.Add(file.Name.Replace(file.Extension, ""), Me.Load(Of Effect)(.Replace(Me.strExecutingFolder & "\""").Replace(file.Extension, "")))
 End With

 Catch unknown As Exception

 strUnknownFiles &= vbCrLf & file.FullName

 End Try

 End Try

 End Try

 End Try

 Next

 If strUnknownFiles <> "" Then

 MsgBox("The VBContentManager was unable to determine the correct loader for:" & vbCrLf & _
 strUnknownFiles & vbCrLf & vbCrLf & _
 "Unless loaded by something else, the above content won't be available at runtime." & vbCrLf & vbCrLf & _
 "This application may become unstable as a result.")

 End If

 End Sub

 ''' <summary>
 ''' Compiles the VB Content Pipeline
 ''' </summary>
 ''' <param name="stealth">Hide/Show the MSBuild window during the compile process</param>
 ''' <param name="platform">If there's a way the Xbox360 will work with VB, you could pass in the Xbox parameter as a target platform to MSBuild.  But it's Windows by default.</param>
 ''' <remarks></remarks>
 Public Sub CompileContent(ByVal stealth As BooleanOptional ByVal platform As Platform = VBContentManager.Platform.Windows)

 Dim objProcess As Process
 Dim objPSI As New Diagnostics.ProcessStartInfo

 Me.objContent = New List(Of ContentFile)

 Me.HarvestContent()

 If Me.objContent.Count > 0 Then

 Me.BuildContentProj(platform)

 Try 'to write the content project file to disk

 Using TempStreamWriter As System.IO.StreamWriter = New System.IO.StreamWriter(Me.strProjectFolder & "\Content.contentproj"False)

 With TempStreamWriter

 .Write(Me.strContentprojFile)
 .Close()

 End With

 End Using

 Try

 With objPSI
 .FileName = Environment.GetEnvironmentVariable("Windir") & "\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe"
 .Arguments = """" & Me.strProjectFolder & "\Content.contentproj""" & _
 " /verbosity:normal" & _
 " /l:FileLogger,Microsoft.Build.Engine;logfile=""" & Me.strProjectFolder & "\Content.log"""
 If stealth Then
 .Arguments &= " /noconsolelogger"
 End If
 If stealth Then
 .WindowStyle = ProcessWindowStyle.Hidden
 End If
 End With

 'give the .contentproj file to MSBuild

 objProcess = System.Diagnostics.Process.Start(objPSI)

 While Not objProcess.HasExited
 Threading.Thread.Sleep(100)
 End While

 Catch ex As Exception

 MsgBox("When building the pipeline, trying to run MSBuild.exe resulted in: " & ex.Message)

 End Try

 Catch ex As Exception

 MsgBox("When building the pipeline, trying to create the Content.contentproj file """ & Me.strContentFolder & "\Content.contentproj"" resulted in: " & ex.Message)

 End Try

 End If

 End Sub

 Private Sub HarvestContent()

 Dim objRoot As System.IO.DirectoryInfo = New System.IO.DirectoryInfo(Me.strContentFolder)

 For Each sound As System.IO.FileInfo In objRoot.GetFiles("*.xab", IO.SearchOption.AllDirectories)
 If Me.objContent.Contains(New ContentFile(ContentTypes.Sounds, sound, sound.Name.Replace(sound.Extension, ""))) Then
 Me.intDupeSound += 1
 Me.objContent.Add(New ContentFile(ContentTypes.Sounds, sound, sound.Name.Replace(sound.Extension, "") & Me.intDupeSound.ToString))
 Else
 Me.objContent.Add(New ContentFile(ContentTypes.Sounds, sound, sound.Name.Replace(sound.Extension, "")))
 End If
 Next

 For Each texture As System.IO.FileInfo In objRoot.GetFiles("*.png", IO.SearchOption.AllDirectories)
 If Me.objContent.Contains(New ContentFile(ContentTypes.Textures, texture, texture.Name.Replace(texture.Extension, ""))) Then
 Me.intDupeTexture += 1
 Me.objContent.Add(New ContentFile(ContentTypes.Textures, texture, texture.Name.Replace(texture.Extension, "") & Me.intDupeTexture.ToString))
 Else
 Me.objContent.Add(New ContentFile(ContentTypes.Textures, texture, texture.Name.Replace(texture.Extension, "")))
 End If
 Next

 For Each texture As System.IO.FileInfo In objRoot.GetFiles("*.jpg", IO.SearchOption.AllDirectories)
 If Me.objContent.Contains(New ContentFile(ContentTypes.Textures, texture, texture.Name.Replace(texture.Extension, ""))) Then
 Me.intDupeTexture += 1
 Me.objContent.Add(New ContentFile(ContentTypes.Textures, texture, texture.Name.Replace(texture.Extension, "") & Me.intDupeTexture.ToString))
 Else
 Me.objContent.Add(New ContentFile(ContentTypes.Textures, texture, texture.Name.Replace(texture.Extension, "")))
 End If
 Next

 For Each texture As System.IO.FileInfo In objRoot.GetFiles("*.gif", IO.SearchOption.AllDirectories)
 If Me.objContent.Contains(New ContentFile(ContentTypes.Textures, texture, texture.Name.Replace(texture.Extension, ""))) Then
 Me.intDupeTexture += 1
 Me.objContent.Add(New ContentFile(ContentTypes.Textures, texture, texture.Name.Replace(texture.Extension, "") & Me.intDupeTexture.ToString))
 Else
 Me.objContent.Add(New ContentFile(ContentTypes.Textures, texture, texture.Name.Replace(texture.Extension, "")))
 End If
 Next

 For Each font As System.IO.FileInfo In objRoot.GetFiles("*.spritefont", IO.SearchOption.AllDirectories)
 If Me.objContent.Contains(New ContentFile(ContentTypes.Fonts, font, font.Name.Replace(font.Extension, ""))) Then
 Me.intDupeFont += 1
 Me.objContent.Add(New ContentFile(ContentTypes.Fonts, font, font.Name.Replace(font.Extension, "") & Me.intDupeFont.ToString))
 Else
 Me.objContent.Add(New ContentFile(ContentTypes.Fonts, font, font.Name.Replace(font.Extension, "")))
 End If
 Next

 For Each effect As System.IO.FileInfo In objRoot.GetFiles("*.fx", IO.SearchOption.AllDirectories)
 If Me.objContent.Contains(New ContentFile(ContentTypes.Effects, effect, effect.Name.Replace(effect.Extension, ""))) Then
 Me.intDupeEffect += 1
 Me.objContent.Add(New ContentFile(ContentTypes.Effects, effect, effect.Name.Replace(effect.Extension, "") & Me.intDupeEffect.ToString))
 Else
 Me.objContent.Add(New ContentFile(ContentTypes.Effects, effect, effect.Name.Replace(effect.Extension, "")))
 End If
 Next

 End Sub

 Private Sub BuildContentProj(ByVal platform As Platform)

 Dim strPlatform As String

 Select Case platform
 Case VBContentManager.Platform.XBox360
 strPlatform = "Xbox 360"
 Case Else
 strPlatform = "Windows"
 End Select

 Me.strContentprojFile = _
 "<Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">" & vbCrLf & _
 vbTab & "<Import Project=""$(MSBuildExtensionsPath)\Microsoft\XNA Game Studio\v2.0\Microsoft.Xna.GameStudio.ContentPipeline.targets"" />" & vbCrLf & _
 vbTab & "<PropertyGroup>" & vbCrLf & _
 vbTab & vbTab & "<OutputType>Library</OutputType>" & vbCrLf & _
 vbTab & vbTab & "<XnaFrameworkVersion>v2.0</XnaFrameworkVersion>" & vbCrLf & _
 vbTab & vbTab & "<ProjectDir>" & Me.strProjectFolder & "\</ProjectDir>" & vbCrLf & _
 vbTab & vbTab & "<XNAContentPipelineTargetPlatform>" & strPlatform & "</XNAContentPipelineTargetPlatform>" & vbCrLf & _
 vbTab & vbTab & "<OutputPath>" & Me.strExecutingFolder & "\</OutputPath>" & vbCrLf & _
 vbTab & "</PropertyGroup>" & vbCrLf & _
 vbTab & "<ItemGroup>" & vbCrLf & _
 vbTab & vbTab & "<Reference Include=""Microsoft.Xna.Framework.Content.Pipeline.EffectImporter, Version=2.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d, processorArchitecture=MSIL"" />" & vbCrLf & _
 vbTab & vbTab & "<Reference Include=""Microsoft.Xna.Framework.Content.Pipeline.FBXImporter, Version=2.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d, processorArchitecture=MSIL"" />" & vbCrLf & _
 vbTab & vbTab & "<Reference Include=""Microsoft.Xna.Framework.Content.Pipeline.TextureImporter, Version=2.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d, processorArchitecture=MSIL"" />" & vbCrLf & _
 vbTab & vbTab & "<Reference Include=""Microsoft.Xna.Framework.Content.Pipeline.XImporter, Version=2.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d, processorArchitecture=MSIL"" />" & vbCrLf & _
 vbTab & "</ItemGroup>"

 For Each content As ContentFile In Me.objContent

 Me.strContentprojFile &= vbCrLf & _
 vbTab & "<ItemGroup>" & vbCrLf & _
 vbTab & vbTab & "<Compile Include=""" & content.File.FullName & """>" & vbCrLf & _
 vbTab & vbTab & vbTab & "<Name>" & content.Name & "</Name>" & vbCrLf & _
 vbTab & vbTab & vbTab & "<Importer>" & CType(content.ContentType, ImporterName).ToString & "</Importer>" & vbCrLf & _
 vbTab & vbTab & vbTab & "<Processor>" & CType(content.ContentType, ProcessorName).ToString & "</Processor>" & vbCrLf & _
 vbTab & vbTab & "</Compile>" & vbCrLf & _
 vbTab & "</ItemGroup>"

 Next

 Me.strContentprojFile &= vbCrLf & "</Project>"

 End Sub

End Class


To see how a Game can utilize the VBContentManager class, see the previous tutorial on the subject, and then read the change log at the top of this post.

Some slight changes can be made to the MyBaseGame class as well. Now that a ContentManager is built into v2.0 of the XNA Game class, you can overload the Content property to return your VBContentManager.

0 comments: