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/22/07 |
![]() | 12/15/07 |
2D camera progress
I got to play with this some more tonight.
Tonight, my goal was to simply make sure I could occlude tiles correctly using the technique illustrated in the 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.
Tonight, my goal was to simply make sure I could occlude tiles correctly using the technique illustrated in the 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 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 you should overwrite the older template .zip file with .
If you have previously placed the VB.NET XNA Project Template into your Visual Studio templates folder, after installing you should overwrite the older template .zip file with .
VB.NET XNA Content Pipeline for Game Studio 2.0
[GS 2.0]
Game Studio 2.0 beta has ended and . 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.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
Game Studio 2.0 beta has ended and . 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)
![]() | 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:
Yea it's not quite done yet :)

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:
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 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 . 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 Single , ByVal containerSize As Vector2)
MyBase .New(Program.StarMaze, parent, name, True , True , 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 Single , ByVal sheet As String, ByVal subtexture As String)
MyBase .New(Program.StarMaze, parent, name, True , True , 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" , True , True , "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:
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.
One of the things I want to feature in 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 . 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:
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:
Here's a quick lo-fi vid of this in action:
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.
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:
and then, adding method called ApplyForce:
and finally, processing this information during update:
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
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.
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 ; 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 . 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.
A modern CPU (like an Intel core duo 2.0Ghz processor) can perform mathematical operations so fast as to be . 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.
Subscribe to:
Posts (Atom)
