12/22/07

Vacation

Taking a break for a week or so, going to fly down south, hang out with my in-laws. Speaking of airports & in-laws, here's a good book to read when you're bored:

"The Design of Everyday Things" is probably the best non-programming book a programmer can read.

As programmers it's our job is to construct systems that make life easier (or just more fun) for someone else. When a user launches our program for the first time, they do so without any knowledge of how it works, or any knowledge of how we expected them to use it. "The Design of Everyday Things" focuses on that moment of initial user interaction, exploring the reasons for a users "success" or "failure" with systems, both foreign and familiar. It mainly boils down to the user interface, and that's what this book is about - building user interfaces that make sense.

Using familiar things like telephones and refrigerators, the author demonstrates how common things, even simple things we have a lot of experience with, can be rendered confusing by an interface that does not convey proper usage to the user. Ever been stopped short by pushing a door that must be pulled? Sometimes stupid mistakes are just stupid mistakes, but sometimes they're actually encouraged by devices that don't naturally convey their proper use.

It's an interesting book that contains lots of useful concepts for anyone who designs anything for other people to use.

12/15/07

2D camera progress

I got to play with this some more tonight.


direct link

Tonight, my goal was to simply make sure I could occlude tiles correctly using the technique illustrated in the 2D camera article.

A 200 x 200 tile grid of tiles is defined, each tile is 64x64 pixels. A 320x240 camera is affixed to the player's ship (you can see it's white outline in the video), and as the ship moves around, the camera position is transformed, a bounding box is created around the transformed camera, this bounding box is "snapped" to the grid, and a subset of the grid is readily obtained for drawing. I ended up using even less / more simplistic math than I had assumed it would require, you don't really have to use mod arithmetic to do this, you can just mix & match Math.Clamp, .Floor & .Ceiling and pretty much get your array indexes straight away (there will be a fully verbose tutorial on all this when it's done, skip the details for now).

Now that the occlusion works, hopefully I'll get a chance to finish this tomorrow by adding the necessary transforms to make it behave like a camera (ship stays in the center of the screen, game world scrolls, that sort of thing). I think I'll need to use a viewport object to physically clip the excess, although I'm not sure yet. Lots of fun stuff to learn still.

12/14/07

VB.NET XNA Project Template updated for Game Studio 2.0

The VB.NET XNA Project Template for Visual Studio has been updated to include the latest version of the VBContentManager.

If you have previously placed the VB.NET XNA Project Template into your Visual Studio templates folder, after installing Game Studio 2.0 you should overwrite the older template .zip file with the newer version now available.

VB.NET XNA Content Pipeline for Game Studio 2.0

[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 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"
 .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

12/12/07

2D Camera

This is the idea:



Create a "bounding box" (orange) around the camera (blue), snap that bounding box to the grid on all sides (moving outwards, in all directions from the bounding box, until you hit a grid line - green). Then just divide the corners of the green square by tilesize, you should end up with actual grid array element positions to fetch tiles with.

There's a little slop when the camera is rotated (you'll pick up extra tiles you don't really need to draw & update) but, on the other hand, you don't have to scan around or perform any intersect tests, so hopefully the net result is reasonably quick.

If this doesn't work I'm screwed, because I can't think of any other way to do it! Now I just have to write it...

Update: I got about half way through it this evening and figured I'd run it just to see what happens. Here's some "unexpected results" for you:


direct link

Yea it's not quite done yet :)

12/11/07

A thruster component

[GS 2.0 Beta]

One of the things I want to feature in Star Maze are ship upgrades, allowing you to attach different thrusters & weapons to your ship.

Given the parent/child system, we know that we can "attach" one sprite to another, like what we did in Tutorial 7. And since our game objects are nothing more than "sprites with a brain", we can actually attach one game object to another. So after incorporating the psuedo-physics parameters & processing to the sprite class, I created a game component meant soley to be a child of another; a thruster module which can be attached to anything to make it move. This is the base thruster class from which you derive to create a custom thruster object:

Public Interface IThruster
 Sub StopThrusterAnim()
 Sub StartThrusterAnim()
End Interface

Public MustInherit Class BaseThruster
 Inherits Sprite
 Implements IThruster

 Protected sngForce As Single
 Protected enumKey As Keys
 Protected bolIsOn As Boolean
 Protected enumType As ThrusterType

 Public MustOverride Sub StopThrusterAnim() Implements IThruster.StopThrusterAnim
 Public MustOverride Sub StartThrusterAnim() Implements IThruster.StartThrusterAnim

 Public Property Force() As Single
 Get
 Return Me.sngForce
 End Get
 Set(ByVal value As Single)
 Me.sngForce = value
 End Set
 End Property

 Enum ThrusterType
 Linear
 Rotational
 End Enum

 Public Overrides Sub Update()

 Dim quant As Quaternion
 Dim direction As Vector2

 If Me.Game.KeyboardState.IsKeyDown(Me.enumKey) Then

 If Not Me.bolIsOn Then
 Me.StartThrusterAnim()
 Me.bolIsOn = True
 End If

 Select Case Me.enumType

 Case ThrusterType.Linear

 Me.WorldMatrix.Decompose(Nothing, quant, Nothing)
 Vector2.Transform(New Vector2(Math.Sin(CDbl(Me.sngRotation)), -Math.Cos(CDbl(Me.sngRotation))), Matrix.CreateFromQuaternion(quant), direction)

 Me.Parent.ApplyForce((direction * Me.sngForce) * (Me.Game.GameTime.ElapsedGameTime.TotalMilliseconds / 1000))

 Case ThrusterType.Rotational

 Me.Parent.ApplyRotationalForce(Me.sngForce * (Me.Game.GameTime.ElapsedGameTime.TotalMilliseconds / 1000))

 End Select

 ElseIf Me.bolIsOn Then

 Me.StopThrusterAnim()
 Me.bolIsOn = False

 End If

 MyBase.Update()

 End Sub

 Public Sub New(ByVal parent As Ship, ByVal type As ThrusterType, ByVal key As Keys, ByVal name As String, ByVal force As SingleByVal containerSize As Vector2)
 MyBase.New(Program.StarMaze, parent, name, TrueTrue, containerSize)
 Me.sngForce = force
 Me.enumKey = key
 Me.enumType = type
 End Sub

 Public Sub New(ByVal parent As Ship, ByVal type As ThrusterType, ByVal key As Keys, ByVal name As String, ByVal force As SingleByVal sheet As String, ByVal subtexture As String)
 MyBase.New(Program.StarMaze, parent, name, TrueTrue, sheet, subtexture)
 Me.sngForce = force
 Me.enumKey = key
 Me.enumType = type
 End Sub

End Class


With the base thruster class you can create a rotational thruster for spinning around or a linear thruster for vector movement. A keyboard key parameter will be monitored by the thruster to turn itself on and off, and there's a parameter to hold how much force the thruster applies to it's parent (whatever it's attached to).

When it Updates, it calls the ApplyForce or ApplyRotationalForce methods of it's parent, and the system moves accordingly.

A simple interface defines two routines, StopThrusterAnim & StartThrusterAnim, which provides the implementor with a place & time to perform graphical feedback as it relates to the thruster entering an On or Off state.

Using this base thruster class I created 4 different game components; custom thrusters which I can attach to the player's ship. Two different aft thrusters (linear), a port thruster (clockwise rotational), and a starboard thruster (counterclockwise rotational).

The player's ship object can now be defined very simply, by creating instances of these thrusters and attaching whichever ones we want accordingly:

Public Class Ship
 Inherits Sprite

 Protected aftThruster, portThruster, starboardThruster As BaseThruster

 Public Sub New()

 MyBase.New(Program.StarMaze, Nothing"playership"TrueTrue"starmaze""playership")

 Me.Origin = New Vector2(54, 82)

 Me.sngMass = 1
 Me.sngFriction = 25

 Me.aftThruster = New AftThruster2(Me)
 Me.portThruster = New PortThruster1(Me)
 Me.starboardThruster = New StarboardThruster1(Me)

 Me.AddChild(Me.aftThruster)
 Me.AddChild(Me.portThruster)
 Me.AddChild(Me.starboardThruster)

 End Sub

End Class

Here's a quick lo-fi vid of this in action:


(direct link to youtube)

In the video I start out using one of two aft thruster classes which derive from base thruster (the green one, it produces 500 pixels per second (pps) of linear force). When the app stops and I go into the IDE, I swap out the green 500pps thruster for a different thruster class, a red one that produces 250 pps of linear force. You my also be able to see the tiny little port and starboard thrusters applying the counter- and clockwise rotational forces as well.

As you can see from the Ship class code above, the ship itself has no inherit way to move itself; it's just a hull that doesn't handle keyboard input or anything of the sort. To make it move, we simply attach thruster components. The neat part about this system is that you can attach any thruster you want to any game object you want. You can put thrusters on the player's ship, on enemy AI ships, you can stick one on a bowling ball if you want. The thruster component is a somewhat generic way to apply force, to any game object.

My laptop was lagging for unrelated reasons when I recorded this, so the fps is kinda low, but in an otherwise normal environment the code runs quite nicely.

12/9/07

Fun with (very basic) physics

[GS 2.0 Beta]

So in developing a new system of sprite movement, I decided to try a little bit of really basic physics. Adding a few simple members to the sprite class to store values relating to velocity, momentum & friction:

 Protected objVelocity As Vector2
 Protected sngFriction, sngMass As Single

and then, adding method called ApplyForce:

 Public Sub ApplyForce(ByVal force As Vector2)

 If Me.sngMass > 0 Then
 Me.objVelocity += (force / Me.sngMass)
 Else
 Me.objVelocity += force
 End If

 End Sub

and finally, processing this information during update:

 Public Sub UpdatePhysics()

 Dim time As Single
 Dim negX, negY As Boolean

 'Our unit of measure is pixles per second
 time = Me.Game.GameTime.ElapsedGameTime.TotalMilliseconds / 1000

 'If there is any velocity...

 If Me.objVelocity <> Vector2.Zero Then

 'before we apply friction, let's remember whether
 'or not we started out with positive or negative
 'velocity value, for both x and y, by setting
 '"wasNegativeX/Y" booleans

 negX = Me.objVelocity.X < 0
 negY = Me.objVelocity.Y < 0

 'simulate some psuedo-friction by normalizing the
 'velocity vector and multiplying it by the friction
 'value.  This gives us an amount to "push" the object
 'in the exact opposite direction.  Multiplied by
 'time to do just enough for this frame only.  Subtract
 'the result from the object's velocity, to slow it down.

 Me.objVelocity -= (Vector2.Normalize(Me.objVelocity) * Me.sngFriction) * time

 'Because friction is simulated by pushing the object in
 'the opposite direction, towards the end when the object
 'is almost stopped we don't want the psuedo-friction to
 'start pushing the object backwards; at most, friction
 'stops something, but it shouldn't ever move it.  So using
 'or cached "whether or not it was negative" booleans,
 'check to see if the polarity of our X/Y velocity changed
 'due to friction.  If it did, snap the velocity of that
 'value to 0.

 If (negX And Me.objVelocity.X > 0) Or (Not negX And Me.objVelocity.X < 0) Then
 Me.objVelocity.X = 0
 End If

 If (negY And Me.objVelocity.Y > 0) Or (Not negY And Me.objVelocity.Y < 0) Then
 Me.objVelocity.Y = 0
 End If

 'increment the object's position by the amount of
 'velocity appropriate for this frame by multiplying
 'by the time value.

 Me.Position += (Me.objVelocity * time)

 End If

 End Sub

I haven't really discussed my existing system for movement on the blog yet; we'll get to that eventually, but I was amused by this velocity/friction/mass approach and it's probably more relevant to the needs of gaming anyway.

This is far from an actual simulator, I'm only using mass to resist initial force and not really applying it to friction but that's ok. This is supposed to be simple, and the results aren't too shabby.



In the video, just the new "physics" values introduced are being used to move the spaceship around. While the player holds down the thrust key, the current orientation of the ship * some thrust value determines a parameter for ApplyForce. Using a PlayerShip game object (as described in previous tutorials, it just inherits from sprite), I created a ThrusterOn method. As long as the user holds the truster key down, I call PlayerShip.ThrusterOn

 Public Sub ThrusterOn()

 Dim time As Single

 time = Me.Game.GameTime.ElapsedGameTime.TotalMilliseconds / 1000

 Me.ApplyForce((Vector2.Normalize(New Vector2(Math.Sin(CDbl(Me.sngRotation)), -Math.Cos(CDbl(Me.sngRotation)))) * 250) * time)

 End Sub


The ship has a little mass to it, so it resists force somewhat (this gives you a way to tweak acceleration curve vs. thruster power), and the ship's psuedo-friction value constantly pulls it's velocity toward zero. This example was built using my "real" 2D engine (which is a little further along than what the blog has covered so far, i.e. there's keyboard handling & debugging & screen management, etc.), but the movement you see is achieved through the simple additions described above.

12/8/07

Velocity, distance and time

The first animation system I created, which I currently use, was designed with UI components in mind. Things like buttons, windows, forms & controls, stuff like that. The current animation system I have for this is very good at moving something from one predefined point to another: from A to B. Or from rotation value X to rotation value Y. But always from one finite value to another finite value.

I'll keep this around, because it's useful, but games are not so rigidly constructed as UIs. I'm forming the opinion that it's better to not rely on knowledge of predefined points when moving game objects, but rather rely on movement values such as velocity and direction, and just react accordingly to those values over time. So in consuming yet more R&D time before I put any more tutorials out, I'm going to construct such a thing. I have a few ideas I want to play with which may prove very useful.

I'm also working on a grid object which can be incorporated into the parent/child system so that at any point, a grid w/ a camera can be arbitrarily put anywhere & globally transformed by it's parent.

12/3/07

Ramble

I've been surprised by how much GPU power it takes to run a pixel shader over a scene. The 2-pass Gaussian blur I've been testing with pushes my laptop's GPU pretty hard (a Mobility FireGL V5200; not exactly an uber GPU). Initially I attributed the performance I was seeing to my code being inefficient, but after comparing performance with the post processing samples on the Creator's Club site (which I presume to be well formed) I don't think I'm doing anything "wrong" - I'm just a little bit brainwashed by the performance of modern CPUs.

A modern CPU (like an Intel core duo 2.0Ghz processor) can perform mathematical operations so fast as to be indistinguishable from magic. But I was forgetting that the modern GPU hasn't had as much time as the CPU to mature & advance as a technology. GPUs are fast, but they still have plenty of room to grow. In thinking my code was inefficient, in truth I was overestimating the ability of my laptop's GPU.

So the shader business is basically done. I've got a system of renderTargets built into the engine to handle arbitrary scene processing at arbitrary points in the parent/child hierarchy, which accomplishes the goal. Now all I need to do is steal my wife's 8800GTX out of her gaming rig and cram it into my laptop with a vice. Yea, that should work.

My job has me somewhat diverted from this blog, although, the stuff I'm tasked with at work is vicariously related. In my current project, a "mostly complete" version of this 2D engine is used to create an XNA application. But I'm a "regular" programmer, not a "game developer", and so the goal of this 2D engine is to create a really user-friendly, neato touch-screen interface in an attempt to delude all who use it into believing their job is not quite as boring and mundane as it is. Which is not such a bad thing to do for people, really.

This app at work likes to get real chummy with a SQL database to conduct important workly business. About a 3rd of this chummy relationship is facilitated through the DataAdapter.Fill method, mainly because I have a small ADO engine with some built in concurrency checking features etc which I'm reusing in the XNA app. What's particularly problematic about using DataAdapter.Fill with an XNA application is the fact that DataAdapter.Fill is a synchronous method: this means DataAdapter.Fill "blocks the calling thread" while it's grabbing data out of SQL. I'm no game industry veteran by any means, but I can guess blocking the main thread of a game probably ranks in the top 3 "worst design features of all time". While it just so happens I can get away with this over the LAN, deploying this app over a VPN is obviously not going to work.

So currently I'm devising an async system for DataAdapter.Fill and just basically making all the changes I have to make to get this done, it's a little extensive because I'm revamping an entire remote access system, just a little. To be honest it's something I've always known I should do, I just never had a real critical reason to do it. Kind of a hassle but certainly for the best.

But this relates partly to my 2D engine / game programming hobby in that I'll end up with a model whereby the game loop submits requests for remote data, then continues to render normally while it polls for the result to complete. I would assume this sort of model will be handy for a networking layer to a multiplayer game, like for sending requests to a game server or peer client and awaiting the response. So, mundane as it is, this sort of thing should prove reasonably useful in the future.