11/30/07

Star Maze

When I was a kid, my dad, in a uncharacteristic fit of runaway consumerism, came home one afternoon with an Apple IIe rig. Monochrome green monitor, dual disk drives and a really big molded plastic desk to house it all. Expensive, cutting-edge technology that no one in my family really knew what to do with - including dad. But it was pretty awesome anyway.

I wouldn't become interested in programming for another decade or more (long after the Apple IIe was gone), but I have a lot of good memories of the unique & quirky games I played back on the Apple. So a few years ago when I discovered the AppleWin emulator project and the impressive ftp.apple.asimov.net disk image repository, I was stoked.

The vast array of software available in the asimov repository can be a blessing and a curse; it's comprehensive, but it quickly turns into a proverbial haystack when you can't remember the name of that one game you played 25 years ago. Last night I was pretty thrilled to finally reunite a vague memory of "that one gem collecting space game" with Star Maze.


Star Maze running in AppleWin

Since I've been building this 2D engine in XNA, there's been something of a worker thread spawned in the back of my mind. A background process that examines the games I play through a programmer's prism. Last night as I drifted around Star Maze, shooting bad guys, collecting jewels and enjoying the nostalgia, it occurred to me that Star Maze is actually a well designed game. It's ancient, and a hassle to play via the emulator, but whereas a lot of these old games I've exhumed from asimov lately have primarily had only nostalgic value and not much else, Star Maze sticks out as a somewhat viable game. I think there's a spark of gameplay beating beneath it's crude surface, preserved all these years.

Once the engine is done, I think I'll give my old pal Star Maze a face-lift.

11/27/07

VB.NET XNA Windows Game Project Template

I've made a VB.NET XNA Windows Game project template for Visual Studio. It should work with Visual Studio 2005 & Visual Basic Express edition (and may work in others, but has not been tested) It requires you install Game Studio 2.0.

The project template comes in 2 flavors: With Examples, and Without.

"With Examples" gives you a Game class that contains code and comments that take you step by step through the LoadContent, Update and Draw routines to perform a simple 2D task using XNA and VB.NET.

"Without Examples" gives you the same thing, without the sample code.

Both include an initialized VBContentManager for you to study, hack apart, belittle, delete, or just use as-is.

How to install it

Just save either one of the following files into your Visual Studio 2005 templates folder

With Examples

or

Without Examples

This is a zip file - do you mean unzip it into the templates folder?

Nope, just save the zip file right into the templates folder. Visual Studio 2005 knows what to do with it.

"Templates folder"?

Unless you've told Visual Studio otherwise, it's located on your computer here:

My Documents\Visual Studio 2005\Templates\ProjectTemplates\Visual Basic

You can just tell your browser to save the file there.

When you start a new project, you should now see WindowsGame under "My Templates" for Visual Basic:



Using the template should create a project for you that looks like this:


This is the "Without Examples" version

The goal is for you to be able to start one of these projects anew just like you see above, hit F5, and have a working "game" you can start modifying & adding content to right away.

Is this actual support for VB.NET + XNA?

Not really, this is the same old hack stuff you've been reading on my blog or Alan Phipp's site or wherever, I've just packaged up my version of the hack for you into a Visual Studio template, so you can be up and running with it right away.

11/25/07

The five P's

Before I move on with more tutorials, I'm going to do a little R&D on a few logistical things that I want to improve and/or be sure will still work once we're further down the road.

I want to reduce the number of calls being made to SpriteBatch.Begin and SpriteBatch.End, so a maximum number of sprites are actually sent as a batch (rather than having hundreds of "batches" of 1 sprite each, which is not exactly the optimal way to use SpriteBatch). I have an idea for this but I still need to play with it this week.

What I've been playing with this weekend are ways to make certain shader effects (like blurs) happen much, much faster. This is reasonably out of scope of what the blog has covered so far, but seeing as how I want to tie shader effects into the engine later on without invoking a messy refactor, I'm going to get it all straightened out before I continue the tutorials.

I also want to study the idea of a particle engine that will integrate nicely into what's being built here.

So the next tutorial whenever it comes will probably be a quick tour through the changes in Game Studio / XNA 2.0 and then we'll roll on from there.

The five P's are Prior Planning Prevents Poor Projects. Good advice.

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.

Game Studio 2.0

Game Studio 2.0 (Beta) has been released. At first glance, it looks like the most significant change (in terms of our VB shenanigans here) is the ContentManager class, and some logistics regarding the Content Pipeline. The VBContentManager class may change to reflect this, as necessary. I'll be taking my first look at it later today.

A big feature now included is networking. I don't really know yet if the networking component of XNA is going to be the most useful when communicating with MS services like XBox Live (et al) or, if you're actually supposed to utilize XNA on the server side of your own client/server game. I would suspect you'd probably just end up rolling your own network code for the custom scenario, but, who knows! We'll cross that bridge when we come to it.

There are some other changes that don't apply to us when using VB (such as the C# Game class template created by the IDE when you start a new Game Project).

11/17/07

VB.NET XNA Tutorial 8: Text

[GSE 1.0 Refresh]

Text as sprites

In XNA, text gets drawn to the screen in much the same way everything else in our 2D engine is being drawn to the screen: by having SpriteBatch draw sprites.

In order to put "Hello world!" on the screen, we'll need 8 regular sprites that just happen to look like characters. Specifically, we'll need the following sprites that look like:
  • H
  • e
  • l (we can use this one 3 times)
  • o (can be used twice)
  • w
  • r
  • d
  • !
Once we've made those sprites (and loaded them into the game), the next step will be to meticulously draw each one, in the right order, in the right place with perfect spacing, so when we're done we end up with words (and don't forget to offset "world!" from "Hello" to simulate a space character between them).

Sound tedious? How about an RPG with ten thousand lines of dialog. Sound daunting? If so, good! You're ready to appreciate the fact XNA can do all of the aforementioned work for you. Not only will it line up the little sprites on the screen for you, it can even create all the sprites for you.

MSBuild & SpriteFont

All we've asked MSBuild to do for us so far is convert our texture content (.png or .jpg, etc.) into .xnb files. The .xnb file format is a "cross-platform" file that can be used on either Windows or the Xbox360. In light of the known compatibility issues with VB.NET and the .NET framework found on the XBox360, why are we bothering to use MSBuild to produce a "cross-platform" file, anyway?

If you're making an XNA app exclusively for Windows, ending up with "cross-platform" content is somewhat incidental (a side-effect of the process). On the other hand, a distinct advantage to using MSBuild and the ContentManager can be found in SpriteFonts.

A SpriteFont starts out life as a small XML file with a ".spritefont" extension that you place in your content folder. The same MSBuild process that takes our texutre images and creates .xnb files can also take a .spritefont file to create another .xnb file - one that contains the characters of a specific font, at a specific size, as described by the contents of the .spritefont XML file. Here's an example of a .spritefont XML file:

<?xml version="1.0" encoding="utf-8"?>
<!--
This file contains an xml description of a font, and will be read by the XNA
Framework Content Pipeline. Follow the comments to customize the appearance
of the font in your game, and to change the characters which are available to draw
with.
-->
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
<Asset Type="Graphics:FontDescription">

<!--
Modify this string to change the font that will be imported.
-->
<FontName>Arial</FontName>

<!--
Size is a float value, measured in points. Modify this value to change
the size of the font.
-->
<Size>14</Size>

<!--
Spacing is a float value, measured in pixels. Modify this value to change
the amount of spacing in between characters.
-->
<Spacing>2</Spacing>

<!--
Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
and "Bold, Italic", and are case sensitive.
-->
<Style>Regular</Style>

<!--
CharacterRegions control what letters are available in the font. Every
character from Start to End will be built and made available for drawing. The
default range is from 32, (ASCII space), to 126, ('~'), covering the basic Latin
character set. The characters are ordered according to the Unicode standard.
See the documentation for more information.
-->
<CharacterRegions>
<CharacterRegion>
<Start>&#32;</Start>
<End>&#126;</End>
</CharacterRegion>
</CharacterRegions>
</Asset>
</XnaContent>

The C# Express IDE has native support for creating new .spritefont files, you just right-click the project and add one like you were adding a class file. With VB.NET, it's pretty much just as easy. There's no magic here, it's just an XML file. You can bring in an existing .spritefont file from disk, or just make a new text document item in your project, paste in the above code, and rename the file so it has a .spritefont extension. If you place it somewhere in your content folder, the VBContentManager understands to harvest .spritefont files and send them along to MSBuild. Similar to Textures, SpriteFonts are processed by MSBuild and the result is stored onto the VBContentManager as a SpriteFont object in the Fonts hashtable.

Implementation with the VBContentManager

Ok, so how do we use this "SpriteFont object" to get text onto the screen? It's a lot like drawing a texture; the SpriteBatch has a Draw() method for drawing textures, and it has a DrawString() method for drawing text to the screen. The DrawString() method accepts your SpriteFont object as a parameter (as well the text to render using the font, where to render it at, and what color to make it)


 'Draws "Hello world!" to the screen, using whatever font definition is found in
 'the file "test.spritefont" from the content folder.  Draws the string at the
 'upper-left corner of the screen in yellow.

 Me.objSB.Begin()

 Me.objSB.DrawString(Me.objCM.Fonts("test"), "Hello world!", Vector2.Zero, Color.Yellow)

 Me.objSB.End()


Without too much fuss, you'll just get the result you expect.

SpriteFont objects feature the MeasureString() method, which you may be familiar with already as it appears elsewhere in .NET, outside of XNA. MeasureString() is just a way to get the width and height of any string (but in a particular font and size), which can be handy for string-size related math (like centering text on the screen, for example).

In the screenshot below, I've placed a sprite font file in my project's Content\Fonts folder called test.spritefont. Test.spritefont specifies Arial at a size of 30. In the main draw loop of the game, I retrieve the SpriteFont object from the game's VBContentManager.Fonts hashtable, and use it's MeasureString method (in conjunction with the BackBufferWidth and BackBufferHeight) to center the text on the screen when calling SpriteBatch.DrawString:





A few quick extras re: the creation of SpriteFont files:

Mono-spaced fonts are not supported by Game Studio Express 1.0 (refresh) (the current version when I wrote this article). Last I heard, mono-spaced font support should make it into Game Studio 2.0.

As an aside, it's probably a reasonable speculation to guess that, due to Game Studio 2.0's compatibility with Visual Studio, it may be possible to simply add a SpriteFont file directly to a VB.NET XNA project after GS 2.0 is released (rather than having to "make your own"). Either way, we would continue to rely on something like the VBContentManager to invoke the MSBuild process in order to get the .xnb file created for us.

And finally, if you are interested in going beyond using the plain old fonts installed on your system, read this. My VBContentManager doesn't support what's discussed there, but you could certainly perform a quick & dirty modification along the lines of "if you find a texture in the fonts folder..." sort of thing.

11/16/07

VB.NET XNA Tutorial 7: Sprites as Game Objects

[GSE 1.0 Refresh]

Taking a second look at what was designed in Tutorial 6, Tutorial 7 explores how the Sprite class can be more than just a texture on the screen. We'll create a Ship class that derives from Sprite, and end up with something that's more of a "game object" than a "sprite".

This tutorial is only about 14 minutes and is pretty easy stuff; the main focus is basically re-usability of code via object-oriented design. Creating reusable game objects is a concept that will lend itself to things like UI components and Screens and eventually game state management (in future tutorials).

For now, here's Tutorial 7: Sprites as Game Objects.

11/15/07

VB.NET XNA Tutorial 6: The PointAt method

[GSE 1.0 Refresh]

In the comments for tutorial 5.3, Matt asked a practical question about how to solve a real-world problem using all this matrix cascade stuff; if a ship sprite had a "child" gun turret, how could we get that gun turret to aim at some other sprite on the screen?

Here's a method written to accomplish this task, called PointAt. Accepting two sprites as arguments, it will rotate the first in order to point at the second. This will cut through all the matrix clutter by turning that clutter around and using it to our advantage.

 ''' <summary>
 ''' Point one sprite's origin at another sprite's origin
 ''' </summary>
 ''' <param name="thePointer">The sprite you want to rotate</param>
 ''' <param name="theTarget">The sprite you want thePointer to point at</param>
 ''' <remarks></remarks>
 Public Sub PointAt(ByVal thePointer As Sprite, ByVal theTarget As Sprite)

 Dim objTargetWorldPosition As Vector2

 If theTarget.Parent IsNot Nothing Then
 objTargetWorldPosition = Vector2.Transform(theTarget.Location, theTarget.Parent.DrawMatrix)
 Else
 objTargetWorldPosition = theTarget.Location
 End If

 thePointer.Rotation = 0

 With Vector2.Transform(objTargetWorldPosition, Matrix.Invert(thePointer.DrawMatrix)) - thePointer.Origin

 thePointer.Rotation = (CSng(Math.Atan2(.Y, .X)) + (Math.PI / 2))

 End With

 End Sub

It looks simple, doesn't it? There's some new stuff in there (Matrix.Invert?) so I went ahead and made a video demonstrating what this "simple" method does, adding some slight difficulty for the PointAt method to overcome. The PointAt method is nice because it doesn't really care what we throw at it. It should be able to point any sprite at any other sprite, no matter where either sprite is or how far down any given matrix cascade either sprite happens to be.

Here is the link to Tutorial 6: The PointAt method on Vimeo.

In the video, the steps shown in Photoshop for inverting the matrix are oversimplified a little bit; in actuality, inverting the DrawMatrix of the cannon sprite would also involve un-doing the rotation of the PlayersShip (which would require a lot more steps to simulate in PhotoShop) but the net result is the same.

11/14/07

VB.NET XNA Tutorial 5.3: The Parent/Child Matrix Cascade

[GSE 1.0 Refresh]

While tutorial 5.3 encodes (...it really takes forever), I'll write a little preface. Things get a little "weird" in tutorial 5.3, so hopefully I'll be making sense.

In the previous tutorial we saw how a sprite can use a matrix to position itself on the screen. We drew kermit to the screen by supplying his matrix to the Begin method of the SpriteBatch, and then supplied his origin to the Draw method. These two separate pieces of data (the matrix and the origin) passed to those two methods worked together to put kermit where we wanted on the screen, and make him rotate how we wanted about his origin.

In tutorial 5.3, we're going merge origin information into the matrix, so that we no longer have our matrix and origin supplied as separate data pieces when we draw; we'll be able to pass a single matrix to Begin without supplying an origin value to Draw.

By incorporating the origin information into the matrix, the matrix becomes a more comprehensive/accurate/complete description of how to place a sprite on the screen. In turn, we can use that matrix to transform other things in addition to the sprite itself (such as the sprite's children).

So we're going to create a simple Parent/Child system, whereby each sprite has a reference to a parent sprite as well as a list of children sprites. This will give us a way to organize groups of sprites together into logical objects, and at the same time it will give us a vehicle for the "matrix cascade". Here's what a "matrix cascade" is designed to accomplish:

Dinner on a Plate

Dinner on a Plate demonstrates the net result of what our engine can do after tutorial 5.3. I thought it might be a good idea to get a clear picture of our goal, without which tutorial 5.3 can be, unfortunately, "a long trip through abstract city".

As you can see in Dinner on a Plate, it's starting to become pretty easy to build a new application that, if nothing else, demonstrates how a mildly complicated positioning exercise can be greatly simplified. In the video, a cookie and a piece of pizza are "put onto a dinner plate", and when the plate is rotated, the pizza and cookie behave as if they are intuitively attached to the plate. There are no laws of physics at work here, the explanation is simply that, as children of the dinner plate, the cookie and the pizza are subject to the dinner plates matrix cascade. This means that every time the dinner plate "changes" (location, rotation, origin or (eventually) scale), the cookie and pizza slice are automatically incorporating the new dinner plate matrix into their own matrices when they draw.

The "exciting" details can be found in Tutorial 5.3 - The Parent/Child Matrix Cascade.

11/12/07

Next tutorials will appear this week

I don't have to work this week. Ahh... it's so nice.

So now I'm trying to figure out a performance issue since I've added shader effects to my 2D engine. Right now I can script a 2-pass Gaussian blur effect on a per-sprite basis, but this causes the framerate to drop from 300-400fps, down to 18fps when the effect is activated. This is not good :) Figuring it out has my curiosity occupied for the time being.

There will be new tutorial entries this week, continuing with building the sprite class and at some point, introducing the animation script engine.

11/5/07

VB.NET XNA Tutorial 5: Sprites

[GSE 1.0 Refresh]

Before we get into Screens, ScreenManagers and scripting interfaces, we have to create a Sprite class. It will be the base class that all of those things either inherit from, or are made to manipulate.

I've split the Sprite tutorial into a series of videos, since as it turns out there's a bit to cover. It's not too complicated, but since sprites are the heart & soul of a 2D game, making the Sprite class is a pretty important step.

In the 5.x series of videos below, we'll create a Sprite class from the ground up, and give it the ability to move around. Later on, we'll create Screen objects from this Sprite class, as well as hooking up a scripting interface that can move a Sprite or a Screen. For now, here are the Sprite class tutorials:
  • Tutorial 5.0: A Sprite class
    The basics; adding the Sprite class to the 2D Engine project. This one's pretty easy.

  • Tutorial 5.1: Implementing a transform matrix
    Incorporating a transform matrix into the Sprite class, and using it to put the sprite at various places on the screen. Doesn't go into much detail regarding matrices.

  • Tutorial 5.2: Coordinate spaces, Origins, and Matrices
    I take a crack at explaining how transformation matrices, screen coordinates, and sprite origin all come together for the 2D sprite class. Not necessarily specific to VB, I just think easy-to-follow, non-mathematical tutorials on this subject are in short supply so hopefully this one makes sense and helps someone out. And hopefully I'm not making too many errors...

  • Tutorial 5.3: Creating a parent/child system
    This link takes you to a subsequent post dedicated to the subject.
That's about all I can muster for the weekend. This sprite class gives us a very strong foundation upon which to grow and add some very cool features, so we're not done yet; more to come in the weeks ahead.

11/2/07

Working for the weekend

Well I'm trying to push a project to the "beta" milestone here at work, so I've been putting all my time into that. I really want to get to the next tutorial because I've got some neat stuff I want to demonstrate. I think I'm going to take some anti-burnout time away from work pretty soon as well, which will let me play with the XNA stuff. Never the less, I should have time this weekend to put together tutorial 5.

I have a 2D sprite scripting interface coming along that I'm just about ready to use in the next tutorail. The scripting engine is lacking a couple features, but it's got more than enough functionality to demonstrate it as a system that works, so we'll step through building it from the ground up and learn how we can control sprite movement over time. So far the scripting interface can perform movement along a linear vector, movement over a bezier curve, rotate, scale, change texture, change origin, change tint, and just recently I added the ability to repeat arbitrary subsets of script, allowing you to create animation loops from any of those features. Later on down the road, it will be reasonably trivial to add just about anything, even post-processing effects like bloom or Gaussian blur (something I have in late stages development at the moment, but it's not quite ready).

The scripting engine works in tandem with a parent/child sprite class that implements a "cascading matrix" scheme. That means if you have a screen with a bunch of sprites on it, the screen is the "parent" and the sprites are the "children". You can use the scripting engine to rotate just one individual child on the screen, or, you could rotate the entire screen which would effectively rotate all the sprites. Like rotating a dinner plate, all the stuff on the plate goes along for the ride. I'll show you how it all works in the next tutorial, it's not too complicated and it gives you enough flexibility to start doing meaningful stuff on the screen.