|
|
The logistics of how content gets built in the VB template will change when GS 3.0 is released. Rather than hard-code all of the content building specifics into the VBContentManager class, this functionality has been moved into a stand-alone application, which will act as a bridge between a non-Game Studio project and the Content Pipeline. This application is called the XNA Content Assistant, and is a Windows form application with a graphical interface. However, it also supports being called from the command line in an invisible mode.
I'm currently putting the XNA Content Assistant through beta, naturally I want to debug such an important component thoroughly. It is functional at this time, and so here's a preview of what it looks like, and what it does:

You start out by opening your game's Visual Studio or Visual Express project file. (when running from a command line, you would supply this as an argument)

Based upon the location and contents of the specified project file, the XNA Content Assistant will automatically determine where your content folder is (even if you did not name it "content"). It will then automatically determine which XNA framework version your game is programmed against. The XNA Content Assistant understands how to build content for Game Studio Express (1.0 Refresh) projects, Game Studio 2.0, and Game Studio 3.0 projects created in either VB or C# in Visual Studio 2005, 2008, and the Express editions.
On the GUI, we can see the Assistant has successfully harvested content from my "Timeclock" application, and I am proceeding to select a platform (this would be the 2nd and final parameter you would need to supply via command line)

And lastly when we execute the build process, we can see that MSBuild's output is integrated nicely into the Assistant's UI, to help troubleshoot build issues such as missing fonts, etc. It also exports a log and a copy of the content project submited to MSBuild in your project's folder for reference.

As future XNA versions are released, XNA Content Assistant will be updated and your existing template can simply continue to utilize the updated Assistant, rather than requiring code changes to the template itself (i.e., the VBContentManager class, which will be largely obviated by the Assistant).
Here's an example of how you can teach the VBContentManager to load other content types.
This sample code below shows you what to add (in bold) in order to load .fbx models. Please note that to actually display a 3D model, you'll have to write your own 3D rendering code - 3D is out of scope of my 2D engine blog. This just demonstrates how you can teach the VBContentManager to load other content types, the example just happens to use .fbx models.
Public Class VBContentManager Inherits ContentManager
'Add a Hashtable(Of Model) called Modles, to 'give your models a place to arrive in memory...
Public Models As Hashtable(Of Model)
'Add "intDupeModel" variable to this series... 'This allows the VBContentManager to append a 'unique number to any duplicate filenames it 'may encounter when loading models from disk
Private intDupeSound, intDupeTexture, intDupeFont, intDupeEffect, intDupeModel As Integer
'Add "Models" to the ContentTypes enumeration, 'and make it the next available value
Public Enum ContentTypes Textures = 0 Fonts = 1 Sounds = 2 Effects = 3 Models = 4 End Enum
'Add "FbxImporter" to the ImporterName enumeration 'Make sure it has the same value as Models in the 'ContentTypes enumeration...
Private Enum ImporterName TextureImporter = 0 FontDescriptionImporter = 1 XactImporter = 2 EffectImporter = 3 FbxImporter = 4 End Enum
'Add "ModelProcessor" to the ProcessorName enumeration 'Make sure it has the same value as Models in the 'ContentTypes enumeration.
Private Enum ProcessorName TextureProcessor = 0 FontDescriptionProcessor = 1 XactProcessor = 2 EffectProcessor = 3 ModelProcessor = 4 End Enum
'In Public Sub New, add code to instantiate the Models hashtable 'you added above
Public Sub New(ByVal ServiceProvider As IServiceProvider, ByVal ContentFolder As String)
'(existing code omitted)
'Add code to instantiate the models hashtable
Me.Models = New Hashtable(Of Model)
End Sub
'In LoadAllContent, add a Try block for Model importing in the 'Catch block for Effects, and move the unknown file handler 'to the catch block for attempted Model load
Public Sub LoadAllContent()
'(existing code omitted)
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
'Instead of giving up at this point, catch this as notAnEffect, 'and add 1 more check to see if the file is a model...
Catch notAnEffect As Exception
Try
'Model?
With file.FullName.ToLower Me.Models.Add(file.Name.Replace(file.Extension, ""), Me.Load(Of Model)(.Replace(Me.strExecutingFolder & "\", "").Replace(file.Extension, ""))) End With
'If that doesn't work, then I've run out of things 'to try, so...
Catch unknown As Exception strUnknownFiles &= vbCrLf & file.FullName End Try
End Try
'(existing code omitted)
End Sub
'In HarvestContent, add code to search for .fbx files. Also, 'I'm going to add code to look for .tga texture files as well 'in case I need those too.
Private Sub HarvestContent()
'(existing code omitted)
'Add code to look for .tga files...
For Each texture As System.IO.FileInfo In objRoot.GetFiles("*.tga", 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
'Add code to look for .fbx files... these are similar 'code blocks, just change the ContentType enumeration and the 'extension we want to look for from all the existing ones in 'this sub
For Each model As System.IO.FileInfo In objRoot.GetFiles("*.fbx", IO.SearchOption.AllDirectories) If Me.objContent.Contains(New ContentFile(ContentTypes.Models, model, model.Name.Replace(model.Extension, ""))) Then Me.intDupeModel += 1 Me.objContent.Add(New ContentFile(ContentTypes.Models, model, model.Name.Replace(model.Extension, "") & Me.intDupeModel.ToString)) Else Me.objContent.Add(New ContentFile(ContentTypes.Models, model, model.Name.Replace(model.Extension, ""))) End If Next
End Sub
The VBContentManager code should now be able to import .fbx files. Create a folder called Models in your Content folder in the solution explorer, and put .fbx files in there:  Here I copied all the models (and textures) from the SpaceWar example, and they now compile & load into the Models hashtable of the VBContentManager.
[GS 2.0]
Game Studio 2.0 beta has ended and Game Studio 2.0 has been officially released. There are breaking changes in the ContentPipeline.targets file, so I've updated the VBContentManager class to generate a Content.contentproj file that works within the new parameters. The usage of the class remains the same. - Works with Game Studio 2.0
- Now outputs content to the appropriate Visual Studio bin location (i.e., bin\Debug or bin\Release, whichever you've set Visual Studio to)
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
'Is this a sheet w/ a rectangle map? If so, load the rectangle map
If IO.File.Exists(file.FullName.Replace(file.Extension, ".map")) Then Me.LoadMap(file.Name.Replace(file.Extension, ""), file.FullName.Replace(file.Extension, ".map")) End If
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
Private Sub LoadMap(ByVal strTextureNameName As String, ByVal strMapFile As String)
Dim objReader As IO.StreamReader Dim strMapData As String Dim strLine() As String Dim strItem() As String
objReader = New IO.StreamReader(strMapFile)
Me.SubTextures.Add(strTextureNameName, New Hashtable(Of Rectangle))
strMapData = objReader.ReadToEnd
objReader.Close()
objReader.Dispose()
strLine = strMapData.Split(vbCrLf.ToCharArray, System.StringSplitOptions.RemoveEmptyEntries)
For Each strMapItem As String In strLine
strItem = strMapItem.Split(",".ToCharArray, System.StringSplitOptions.RemoveEmptyEntries)
Me.SubTextures(strTextureNameName).Add(strItem(0), New Rectangle(CInt(strItem(1)), CInt(strItem(2)), CInt(strItem(3)), CInt(strItem(4))))
Next
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 Boolean, Optional 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" .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.strProjectFolder & "\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 & "<ParentOutputDir>" & Me.strExecutingFolder & "</ParentOutputDir>" & vbCrLf & _ vbTab & vbTab & "<ContentRootDirectory>\</ContentRootDirectory>" & vbCrLf & _ vbTab & vbTab & "<XNAContentPipelineTargetPlatform>" & strPlatform & "</XNAContentPipelineTargetPlatform>" & vbCrLf & _ vbTab & vbTab & "<OutputPath>" & Me.strExecutingFolder & "\</OutputPath>" & vbCrLf & _ vbTab & vbTab & "<OutputDirectory>" & Me.strExecutingFolder & "\</OutputDirectory>" & 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>"
If content.ContentType = ContentTypes.Textures Then
'If this texture is a sheet w/ a map, copy it's map out in the executable area 'along side the existing or eventual compiled texture
If IO.File.Exists(content.File.FullName.Replace(content.File.Extension, ".map")) Then
Try IO.File.Copy(content.File.FullName.Replace(content.File.Extension, ".map"), _ Me.strExecutingFolder & content.File.FullName.Replace(Me.strProjectFolder, "").Replace(content.File.Extension, ".map"), True) Catch ex As Exception 'I'm going to be optimistic and assume that if I can't copy 'it's because the file is already there, and life is just fine End Try
End If
End If
Next
Me.strContentprojFile &= vbCrLf & vbTab & "<!-- To modify your build process, add your task inside one of the targets below and uncomment it. " & vbCrLf & _ vbTab & " Other similar extension points exist, see Microsoft.Common.targets." & vbCrLf & _ vbTab & "<Target Name=""BeforeBuild"">" & vbCrLf & _ vbTab & "</Target>" & vbCrLf & _ vbTab & "<Target Name=""AfterBuild"">" & vbCrLf & _ vbTab & "</Target>" & vbCrLf & _ vbTab & "-->" & vbCrLf & _ "</Project>"
End Sub
End Class
[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 Boolean, Optional 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.
[GSE 1.0 Refresh]
Here's the link to Tutorial 4: Starting an engine on Vimeo.
This tutorial is a little bit longer and a little bit more involved than the past tutorials. We'll start down the road to separating game logic from game logistics by creating a new project called XNA 2D Engine and moving the "MyGame" class into it. We'll turn the MyGame into a base class to be used with the 2D engine.
We'll introduce the VBContentManager class, my implementation of a ContentManager extended with our "VB Content Pipeline hack" code (there's lots more on that, below). We'll then go on to making a primitive sprite in Photoshop and drawing it to the screen with a SpriteBatch object.
Here is some companion text to go with the video, that describes in detail some code I'll be importing but not spending a lot of time describing in the video:
In a standard C# XNA project, the default game class is created for you with a GraphicsDeviceManager and a ContentManager both instantiated and ready to go. As we saw in Tutorial 3, it's fairly easily make our own class that inherits from the base XNA Game class, create our own GraphicsDeviceManager and quickly reproduce what the C# template generates automatically. What tutorial 3 did not touch on was the ContentManager, because for us VB'ers, the ContentManager is really the point where the lack of VB support in XNA requires us to do a little bit of legwork, so I held off on the subject until now. In C#, the ContentManager class loads cross-platform file types (like .xnb files) that MSBuild creates automatically from things like .png or .jpg files that get included in the C# project. The primary issue when using a ContentManager with VB is getting these .xnb files created from our .png or .jpg files, because there is no automatic conversion process built into the IDE. My approach to solving this problem was to borrow largely what's been documented by Alan Phipps, and from that, create my own ContentManager class called the VBContentManager class. Here's is the VBContentManager class, in it's entirety: 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)
Private strFakeCSharpProject As String Private strContentRootFolder As String Private strPipelineRootFolder As String Private objContent As List(Of ContentFile) Private intDupeSound, intDupeTexture, intDupeFont As Integer
Public Enum ContentTypes Textures = 0 Fonts = 1 Sounds = 2 End Enum
Private Enum ImporterName TextureImporter = 0 FontDescriptionImporter = 1 XactImporter = 2 End Enum
Private Enum ProcessorName SpriteTextureProcessor = 0 FontDescriptionProcessor = 1 XactProcessor = 2 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="ContentRootFolder">The root of your content folder, relative to your projects bin\Debug folder.</param> ''' <remarks></remarks> Public Sub New(ByVal ServiceProvider As IServiceProvider, ByVal ContentRootFolder As String)
MyBase.New(ServiceProvider)
Me.strContentRootFolder = ContentRootFolder Me.strPipelineRootFolder = ContentRootFolder.Replace("../", "")
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)
End Sub
''' <summary> ''' Load all assets that have been compiled by a VBContentManager. ''' </summary> ''' <param name="Compile">Whether or not to have MSBuild compile your game content before attempting to load it</param> ''' <remarks></remarks> Public Sub LoadPipeline(ByVal Compile As Boolean)
If Compile Then Me.CompileContent() End If
For Each obj As ContentFile In Me.objContent
Try
Select Case obj.ContentType
'The pipeline will be compiled into either debug or release, depending upon 'which option is chosen in the IDE. We don't want to use the My namespace 'though, because it does not work with the xbox 360.
Case ContentTypes.Fonts
With obj.File.FullName.ToLower Me.Fonts.Add(obj.Name, Me.Load(Of SpriteFont)(.Substring(.LastIndexOf("\" & Me.strPipelineRootFolder.ToLower) + 1).Replace(obj.File.Extension.ToLower, ""))) End |