It's All About the Cameras

Organization of the Core

The structure of the Nimble2D library is based on the idea of a central "Core" object that contains references to all of the major component objects and gives a coordinated means for all objects to be able to work off the same hub. The organization structure looks like this:
  • Core - The main object and "central hub"
    • Log - A log file writing class
    • Trig - An instance of the "QuickTrig" class I've written about previously
    • R - A central instance of a random number generator
    • GameClock - The GameClock, based on the class I've written about previously
    • GameSpace - a list of GameSpace objects used to organize game objects
      • LayerList - a list of layers within the GameSpace, which control the order that game objects are drawn in and which cameras can "see" those objects
        • GameObjects - a list of objects on the Layer within the GameSpace
    • MainScreen - The access point into how things are drawn to the screen
      • TargetPictureBox - The picture box being drawn to
      • CameraList - A list of Camera Objects (these are the key to a lot of things)
    • Input - The NimbleInput class
      • RawKey - an array of the low-level key up/down values
      • KeyState - an abstracted value for each key to allow for testing of down, double-down (like double-clicking for the mouse), first time down and for "clearing" keys (reporting it being "up" until the user releases and re-presses the key)
      • Mouse - organization around mouse information, such as location on the screen, location within the current GameSpace, and left/mid/right button states
      • Action - a layer abstracted above key and mouse states to define a game action and the input states that will trigger it
        • Trigger List - a list of defined input states that will trigger the action
    • Sound - A class to control music and sound effects (not yet built)
The main way to use the library will generally go as follows: 
  1. Create an instance of the "Core" object
  2. Setup GameSpaces & game objects as needed
  3. Define Input Actions as needed
  4. Launch the MainScreen
  5. Set Cameras on GameSpaces
  6. Loop:
    1. Core.UpdateAll
    2. AI & Physics
    3. Core.DrawAll

Why is it "All About the Cameras"?

So, how does everything come back to the Camera objects?  The Cameras are what put a frame around the things being seen on screen.  This frame is what the Input class will use to translate screen coordinates into GameSpace coordinates for the mouse.  It will also be involved in the Sound class to determine stereo panning for dynamic sound location.  And it will cooperate with GameSpace Layers to determine what is actually drawn to the screen and in what order.

It does this by acting as a miniature backbuffer ... everything "in the Camera" is drawn to the Camera's buffer, and then all of the active Cameras' buffers are drawn to the screen.  The properties that are important to translating between GameSpace and Screen space are:

  • Height & Width -- The size of the Camera's buffer that will be drawn on the screen ... if the Camera is locked to the full screen, these will be the same as the MainScreen's Height and Width
  • ScreenX & ScreenY -- The upper-left corner of where the Camera's buffer will be drawn onto the screen
  • CurrentX & CurrentY -- The current position of the Camera's center point within the GameSpace

Translating "Screen Space" to "GameSpace"

Consider this sort of setup ...Example Nimble2D Camera Setup
  •  The main screen is 400 x 300 and 3 cameras are used:
    • Camera 0 - the "score board" area of the screen is drawing just layer 0 from GameSpace 0 and is not used to translate the mouse position
    • Camera 1 - the "Main UI" area of the screen is drawing just layer 1 from GameSpace 0 and is not used to translate the mouse position
    • Camera 2 - the "Game Play" area; it is locked to the full size of the main screen and *is* used to translate the mouse position

As the mouse is moved around the screen, its current screen position is stored within the Core.Input.Mouse structure.  Those values alone might work just fine in a lot of circumstances.  In our example, a user's interaction within the "Main UI" area of the screen would likely just use the mouse's screen coordinates to figure out if the user is over a button.

However, in a moving environment -- such as most video games use -- where the game play may be scrolling around the screen, it is much better to have some way to tell where in the GameSpace the mouse pointer is currently pointing.

To accomplish this, the NimbleInput class steps through each of the active Camera objects that have their "UseForMouse" property set to True.  If the Mouse's screen location is within the area of the screen that the Camera is drawing to, then it uses the Camera's current GameSpace location, its size, and the position it draws to the screen to calculate the Mouse's GameSpace X and Y values.

The code that does the work looks something like this:

Mouse.GameSpaceX = Camera.CurrentX - Camera.Width / 2 + (Mouse.ScreenX - Camera.ScreenX)

Mouse.GameSpaceY = Camera.CurrentY - Camera.Height / 2 + (Mouse.ScreenY - Camera.ScreenY) 

And with that, Nimble2D will provide easy-to-access information on where in your game world the mouse is currently located.

Posted by MattWorden | with no comments

Another Look at the Pixel-Plotting Tests

Thanks to codeimp's comment on my previous post, I went back to my pixel-plotting test app to add-in a LockBits method to see how it would stand up to the pure GDI+ methods.

As it turns out, I found that a poorly-placed "End If" was causing the code to not draw 100,000 pixels per test ... but only 10,000 per test.  Once I corrected that, I noticed that there was some time gaps starting to show between the methods.  So, I jacked up the test amount to 1 million pixels per test and I added-in the LockBits approach as an 8th way to go about things.

 On my ancient game dev laptop (1GHz P3), I got the following results (in seconds):

1 - DrawRectangle: 52.10
2 - FillRectangle: 42.90
3 - DrawEllipse: 59.34
4 - FillEllipse: 52.03
5 - DrawLine: 48.83
6 - DrawImage: 119.28
7 - DrawString: 79.32
8 - LockBits: 24.07

The relative speed of the LockBits is impressive.  However, I was surprised that the DrawImage approach was the slowest way to go.  And, FillRectangle looks to be the best of the standard GDI+ routines.

Now, keep in mind that these times are for 1 million plots, pausing every 1000 to invalidate the target PicBox and run a DoEvents to update the screen.  In a normal game situation, you're likely to only need to draw a few thousand specks (at most) and you won't be pausing in the middle of it to make sure the screen updates.

There were a couple other asthetic things that I noticed as well. The Rect methods are incapable of drawing a true single pixel ... their definition forces both methods to draw a 2x2 square instead.  Both Ellipse methods and the DrawLine method make 2-pixel long lines.  This leaves the last 3 methods for being the only ones to give you true single pixels per plot.

LockBits comes out looking pretty good.  The negative is that it takes a special bulk routine to make it efficient ... can't have each individual dot work as its own object that draws itself when requested.  The GDI+ routines do allow for individual drawing at the expense of speed (and some asthetics).

Posted by MattWorden | with no comments
Filed under: ,

Interesting Pixel-Plotting Efficiency Test Results

I recently ran a little experiment to see which would be the fastest way to plot a bunch of single pixels using the methods available in the System.Drawing namespace.  It turns out, for those who haven't looked, that there is no set-pixel type method because everything is built to work with various unit scales -- some of which have no use for single-pixel work.

I tested DrawRectangle, FillRectangle, DrawEllipse, FillEllipse, DrawLine, DrawImage (using a 1x1 rectangle from a memory bitmap that had been pre-built to have a bunch of randomly colored pixels), and even DrawString (at the right point size, the period is essentially a single pixel).  I ran each one through 100,000 pixels and stopwatch timed each one ... turns out that they were all within a couple tenths of a second of one another (less than 1% difference), except for DrawString -- that one was a couple seconds slower across 100,000 dots than the others.

So, I guess that means it really doesn't matter how you go about doing it ... just pick the method that fits the best into the code and gives the best on screen results.

(btw, I am still working on the "It's All About the Cameras" post that I promised last time ...)

Posted by MattWorden | 2 comment(s)
Filed under: ,

NimbleInput Overview

One of the first things I like to think through is how user input will be provided in a game, and how I want to manage that within my game code.

I've become fond of working at the higher abstraction level of "user actions" within the game code itself.  I'd rather write my code to respond to a "main weapons fire" action than tie it directly to a certain key or mouse button press.  This allows more flexibility in user control setup, and sometimes allows a block of response code to be written before the user control details are nailed down.

However, you can't get actions without working through how a system will read and report the lower level input.  With this GDI-based library, I expect to be making smaller windowed games that use keyboard and mouse input.  No need at this time to get gamepad/joystick support in place ... but I'll leave open some hooks to add it later, if needed.

Keyboard
Each update cycle, Nimble will grab the current state of all the keys on the keyboard and will do some calculations to handle situations such as "double hits" and "first time down".  There will be some methods to test the current state of each key.  But, primarily, these states will feed into the action system.

So far, I have not been able to find any built-in methods within the .Net framework to allow me to check the current state of any key on the keyboard.  (If anyone knows how to do this in standard .Net, please point me in the right direction.)  So, I will be using InterOp to tap into the same Windows API I used in the VB6 days to get the key states.

Mouse
Happily, .Net does have nice real-time mouse-handling methods available, including one to get the mouse position relative to the upper-left of any form or control you supply to it.  And, the key state method mentioned above will also get the status of the mouse buttons.  So, mouse buttons will be handled as if they were just a few more keyboard keys.

Once the X & Y of the mouse can be determined for the main screen object -- a picturebox control, in Nimble2D's case -- it then has to be translated to the corresponding position within the GameSpace being shown.  So, the NimbleInput.Mouse object will have ScreenX, ScreenY, GameSpace, GameSpaceX, and GameSpaceY properties available (among others).

The ability to translation to GameSpace coordinates will rely on how GameSpaces, Layers, and Cameras are setup and used within Nimble2D.  This means that I need to flesh-out the basic functions of those objects before I can implement the mouse-related input items.  And, that sets up the next post nicely.  Watch for "It's All About the Cameras" to explain how that piece all works.

Actions
As I mentioned at the start of this post, "user actions" are higher abstraction of the actual input being received.  The game's code will be able to define typical actions needed in the game -- "run right", "jump", "fire main", "fire secondary", etc. -- and then ask NimbleInput if that action is currently "on".

Low-level input states are assigned to an action, which is what NimbleInput actually checks for to report whether an action is on or not.  This will allow for flexibility in assigning user controls at run-time, and for allowing multiple keys to trigger the same action.  For example, a game that needs a simple 4-key control method ("up", "down", "left", "right") could allow for both the traditional arrow keys, plus the W-A-S-D keys to provide that control ... or even to allow the user to select his/her own keys controls for each action needed.

Taking that example to a finer detail level ... The game code could define an action of "Up" and assign both the "Up Arrow" and "W" keys to that action.  During the game loop, a request for the status of the "Up" action would have NimbleInput checking the states of both the assigned keys.  If either key is currently down, then a "TRUE" value would be passed back to indicate that the action is currently on.

There will be different action assignment methods available to cover situations such as single key presses, double key presses ("double clicks"), absolute mouse locations and relative mouse locations.  The mouse location assignments should make things easier for handling mouse-over of menu items or mouse-steering.

General Methods
A few basic general methods will also be provided, such as clearing all keys (makes them all read as "up" until they are pressed again later) and checking for "any key pressed".

More Details Coming
As I mentioned in the "mouse" section above, some of the basic graphical objects and features need to be put in place before we can work out the mouse location.  So, once the GameSpaces, Layers, Main Screen, and Cameras are worked out, I'll come back with a more detailed look (including code) at NimbleInput.

 

Posted by MattWorden | with no comments

Using the clsGameClock Class

The previous post introduced the Game Clock class that I'll be using with the Nimble libraries and walked through a few concepts that were considered in putting the class together.  I mentioned in that post that I wanted the system to be flexible enough to fit the style of the programmer using it -- allowing everything from lock-frame timing to simple delta-time to handling split update/present cycles.

Simple Delta-Time - The simplest way to use the Game Clock is simple delta-time, with a single update and present on each game loop.  It is as simple as this:

'Declarations
Private GameClock as New clsGameClock()
'----------

'In Game Loop ...
GameClock.Update

UpdateGameObjects()

DrawAllGameObjects()

Inside your "UpdateGameObjects", you would use GameClock.DeltaTime and GameClock.DeltaTime_Raw as the multiplier for any physics math.  During "DrawAllGameObjects", you can use GameClock.CPS to show the cycles-per-second rate of the game.  Finally, GameClock.SpeedFactor can be adjusted to speed up and slow down the on-screen action.

Lock-Frame - It's quite easy to switch to a lock-frame approach.  Just adjust the GameClock.MinimumDelta value to fit the number of frames to be targetted each second:

'In Setup
'    To Target 50 frames-per-second
GameClock.MinimumDelta = 1/50

Of course, when using lock-frame, you may not want to use the .DeltaTime values ... just update the same amount each loop.  But then you lose the added benefit of having an automated SpeedFactor available if you have a need to add slow-down effects.

Splitting Update/Present - And that brings us to a more complex idea ... splitting your game logic update cycles from your presentation frames.  The concept is to cycle through your game logic as many times as possible while still leaving enough time to draw everything once within a target frame rate.  This done by getting an estimate of how long it is taking to draw everything and how much time it takes to get through a single game logic update cycle.  There is a check against the target total time wanted to take for a complete loop ... and if there is enough time left to work in another game logic cycle then we do.

In both cases, we base our estimates on the previous loop ... and, as a failsafe, we give ourselves a way to trigger the system to pair a single logic loop with the next draw loop.  This single-cycle trigger would be used in a case where we've drastically changed the number of objects that need to be updated and/or drawn.  Maybe we've switched from a menu to the start of the next level.  Or, maybe there was an explosion and we've just popped 100 new objects onto the screen.

Let's look at the code, and then walk through things ...

'Declarations
Private TargetLoopTime As Double
Private LastDrawStart As Double, LastLoopStart As Double
Private SingleCycleOnce As Boolean
'----------

'In Setup
'   To Target drawing a new frame 40 times per second
TargetLoopTime = 1/40

'Set the system to only do a single update loop the first time
SingleCycleOnce = True
'----------

'In Game Loop ...

' === Game Logic Update Loops
Do

   'Update Clock
   myGameClock.Update()

   'Record update loop's start time
   LastLoopStart = myGameClock.GameTime

   UpdateGameObjects()

Loop Until SingleCycleOnce Or _
   (((myGameClock.GameTime_Live - LastDrawStart) + _
      (myGameClock.GameTime_Live - LastLoopStart)) >= myTargetLoopTime)

'Reset Single Cycle Once flag
SingleCycleOnce = False


' === Draw to Game Screen

'Wait if we are drawing too early in the total cycle
Do
   'Nothing
Loop Until myGameClock.GameTime_Live - LastDrawStart >= myTargetLoopTime

'Capture GameTime before Draw Starts
LastDrawStart = GameClock.GameTime_Live

DrawAllGameObjects()

'Let GameClock know about screen refresh
GameClock.FrameDrawn()

We first pick a target for how long it should take before drawing the next frame.  As an example, to refresh the game screen 40 times per second, we set our TargetLoopTime to 1/40.  With that timeframe set, we have our guide in which to fit our multiple logic loops plus a single draw loop.

Then we start our game logic loops.  The game clock is updated to get the latest delta and to keep count of the cycles, and we record the game time at the start of our logic updates.  We allow all of the objects to update themselves and we check if we have enough time to make another loop.  Also, if we've had a reason to drop out of the logic looping early, it will do that as well.

The SingleCycleOnce flag allows the program code to catch situations where the logic and/or draw cycles will take longer than they have been.  Perhaps a new wave of enemies has just hit the gamespace, or a large particle effect, etc.  In that case, we simply need to set SingleCycleOnce to TRUE to get the update/draw loop to run as quickly as possible and re-evaluate the time needed to get everything done.

Once the updates are done, the game time that the Draw cycle starts is captured to be used during the next set of logic loops.  Finally, we let the GameClock know that a frame has been drawn in order to let it track FPS separately from CPS.

You'll notice that this system is very conservative -- only gaining extra logic loops when it is fairly sure to have enough time to get them in.  The reasoning is that having a steady FPS is very important for a nice player experience ... it's better to sacrifice one extra logic loop than to have it drop to a slower framerate.  At its slowest, the system ensures 1 logic cycle and 1 draw cycle -- the same as running on simple delta time.

I'll be working a system similar to this into Nimble2D's Core class.  It will be initially setup to default to a straight, simple delta-time system with a single logic cycle followed by a single draw cycle -- CPU will be the same as FPU.  However, with a few simple changes, the game can be running on a split cycle system with measurements for each cycle rate.

 

Tick-Tock Game Clock

There are some things needed in a game that are independent of the graphics system.  One of them is the system used to keep track of time passing as the game runs.  I prefer to work with a delta-time based system.  This means that we figure out how much time has passed since the last loop and use that -- whatever it may be -- to handle our logic, physics calcs, collision detection, etc.  The other traditional way of doing things is to lock on to a consistent loop rate and then you can assume things will be happen in the same amounts each loop.

It's also a good idea to separate game logic (object movement & changes, AI, physics, etc.) from user presentation (graphics and sound).  To give a realistic experience, you only need to update the presentation about 30 frames per second.  (However, updating more often can give a more intense experience.)  Game logic can usually benefit from looping as quickly as possible -- small time deltas allow for easier and more responsive collision checks, physics and AI.  The approach would then be to cycle through the game logic as many times as possible while still leaving enough time to draw everything at the wanted presentation rate.

I would like Nimble2D to provide ways to take any of these approaches.  And this all starts with how the game clock system works.  Let's walk through a few concepts to get to our complete clsGameClock class to see what's going on there.

Concept 1 - DeltaTime - VB.Net provides a very nice Stopwatch object that works very similarly to a handheld stopwatch.  It can be started, stopped, reset, and asked for the current elapsed time.  If a high precision timer is available through the OS, it will use that.  The elapsed time can be reported in milliseconds, seconds, and several other deliminations.  The general idea with a game clock is to get a Stopwatch running, and then check how much time has elapsed on each game logic loop.  The difference from one loop to the next will be your "delta time".  It is used with your game objects' velocity, acceleration, etc., to update their positions.

For example, if an object is moving at 300 pixels per second and your DeltaTime is captured in seconds, then in a single game loop the object will move a distance of 300 * DeltaTime.

Our DeltaTime-finding code would look like this:

'Declarations ...
Private SW As Stopwatch
Private myLastTime As Double, myDeltaTime as Double
'----------

'In the Constructor ...
SW = New Stopwatch()
SW.Start()
myLastTime = 0.0F
myDeltaTime = 0.0F
'----------

'In the Update method ...
myDeltaTime = SW.Elapsed.TotalSeconds - myLastTime
myLastTime = SW.Elapsed.TotalSeconds

Concept 2 - Careful with Data Types - In the previous post, I mentioned wanting to avoid data type conversions whenever possible.  The Stopwatch uses a Double time to give the best possible precision to the elapsed time when working in seconds.  However, GDI generally likes to work with Single data types.  So, once we capture our DeltaTime, we will convert it to a Single data type and provide that for anything that needs a DeltaTime value.  This will mean that object variables for velocities, accelerations, etc., should be held as Singles and multiplied by the Single DeltaTime to get a Single result (such as a position) which can be used by GDI without having to do extra data type conversions along the way.  In other words, we're purposely doing 1 data conversion per loop to avoid multiple conversions per object per loop.

This adds the following things to our code:

'Declaration ...
Private sngDelta As Single
'----------

'In the Update method, after the value for myDeltaTime is found
sngDelta = CSng(myDeltaTime)

Concept 3 - Minimum DeltaTime - There are a lot of reasons to have an assumed "smallest possible delta time".  For true DeltaTime users, having a minimum DeltaTime will avoid problems on very, very fast machines providing extremely small DeltaTime values, which may play havoc with phyics math.  For those who like locked frame rates, you can use a larger minimum DeltaTime (such as 1/40 for 40 frames-per-second) to cause the gameclock to be the governor of the frame timing.  While waiting for the minimum DeltaTime, we should allow the application to DoEvents so that form events can be responded to.

To make this happen, the following code elements are added:

'Declaration ...
Private myMinDelta As Double
'----------

'In the Constructor ...
'     A Minimum DeltaTime can be passed as a parameter
Public Sub New(Optional ByVal MinimumDelta As Double = 0.0001F)
     myMinDelta = MinimumDelta
'----------

'In the Update method, replace what we've already done with ...
Do
     Application.DoEvents()
     myDeltaTime = SW.Elapsed.TotalSeconds - myLastTime
Loop Until myDeltaTime >= myMinDelta

myLastTime = SW.Elapsed.TotalSeconds
sngDelta = CSng(myDeltaTime)

Concept 4 - Speed Factor - To allow for special effects along the lines of slowing everything down ("bullet time") or speeding everything up at the same time, we can include a "speed factor" to be applied to the DeltaTime.  Normally, this will just be at a value of 1.0f.  However, for example, it could be adjusted to 0.5f to slow things down to half-speed, or to 2.0f for double-time.  If our DeltaTime is going to be adjusted by this SpeedFactor value, then we should also provide a "raw" version of DeltaTime for those things that should be moving at normal speed at all times (like camera movement ... or a main character during a "timewarp" effect).

It only take a little extra code to make this work:

'Declarations ...
Private mySpeedFactor As Double
Private sngDeltaRaw As Double
'----------

'In the Update method, change the DeltaTime setting to ...
sngDelta = CSng(myDeltaTime * mySpeedFactor)
sngDeltaRaw = CSng(myDeltaTime)

Concept 5 - CPS - The game clock is a great place to put cycles-per-second tracking code.  This is a pretty typical measurement used by game programmers to monitor just how fast things are working.  (Normally, this is referred to as "frames per second", but since we might be tracking game logic cycles separate from frame drawing cycles, I'm giving this the more generic "cycles-per-second" label.)  This can be done by simply counting the number of times the Update method is called during the past second, then storing that value for reporting.

The code looks like this:

'Declarations
Private CycleTime As Double, CycleCount As Integer, CurCPS As Integer
'----------

'In the Update method, the following code is added at the end ...
CycleTime = CycleTime + myDeltaTime
CycleCount = CycleCount + 1

If CycleTime > 1.0F Then
     CurCPS = CycleCount
     CycleCount = 0
     CycleTime = CycleTime - 1.0F
End If

This wraps up the major concepts.  Add in some Properties for exposing information that will be useful to the rest of the game, and some code for pausing, restarting, and resetting ... and you'll get the entire clsGameClock class.  Get the full VB file here: LINK

This doesn't yet address how to go about splitting your game logic loops from your drawing loops.  That will be covered in the next post about how to make use of the clsGameclock class.

Making GDI+ Nimble

One thing that the GDI+ (System.Drawing namespace in VB.Net) system is *not* is overly quick.  It does a great job of certain things required by a more general, modern OS-based drawing system -- fonts, alpha, lines & shapes, etc.  But it isn't game-graphics quick.

Mainly, it uses the CPU to do its work (as opposed to the graphics card's GPU, like DirectX and OpenGL use) ... and the CPU is already busy trying to take care of overthing else going on (memory management, math calculations, input/output routing, etc.).  So, there are some things that we'll want to keep in mind when working in the GDI for game graphics.

Read Up:  First, read up on how the GDI works.  Here's a great site for higher-level overviews and some lower-level tips and tricks: http://www.bobpowell.net/gdiplus_faq.htm ... Also, Microsoft's very own official doco page is here: http://msdn.microsoft.com/en-us/library/ms533798.aspx.  These in-depth resources will be useful when you're trying to squeeze the last couple performance drops out of the GDI.

Keep Things Compact:  One of the best things for speeding up the GDI is keeping things as small as possible while still being able to deliver the game experience you're looking for.

Play Area:  The size of the "play area" (the on-screen window used to show what's going on in the game) will have an overall effect on speed.  The bigger it is, the longer it takes to clear, the more surface area available to draw game objects too, and the longer it takes to draw the backbuffer to the screen object.  While not impossible to go 800x600 (a pretty traditional size for retro-style games) on your play area, it will simply mean that the CPU requirement for your end-users will need to be bumped up.  Doing "mini-games" at 500x500 or 640x480 (another traditional size) is very doable.

Game Object Sizes:  Similar to the impact of size on the play area, the size of your actual in-screen game object graphics will matter.  Drawing screen-sized backgrounds will have a greater impact than clearing the game screen to a solid color and drawing just a few smaller items in as the background.  Keeping items that appear in number (particles, explosions, bullets, HUD and UI items, etc.) smaller is even more important.

Game Object Counts:  Limiting the number of game objects being drawn on screen in any particular loop is an area of important balance.  You want to provide the player with a certain type of game experience.  Sometimes, this requires a lot of stuff happening on-screen at the same time.  But the "feel" of the game will be diminished if it becomes chunky and slow while all of those things are happening.  So, balancing the two (and keeping an eye on the CPU horsepower requirements) will be a good challenge for a game programmer using GDI.  One quick solution is to have a (moveable) cap on particles and other things that appear in bulk but are mainly for eye candy.  When we get into putting some particles on the screen, I'll show a "particle manager" class that handles the limiting by ditching older particles for newer ones when its at the limit.  We'll also work through how to auto-test and auto-adjust these systems to be able to give those with better computers a "thicker" experience and still allow those with slower machines to play the game in an enjoyable manner.

Testing for Gaphics Efficiency:  In a lot of cases, there may be 2 or more ways to accomplish a similar effect.  If it seems that it might be a routine that is called often enough, it would be best to find the fastest way to deliver what you're after.  What's the fastest way to draw a 2x2 solid colored square?  DrawRectangle?  FillRectangle?  DrawLine?  DrawImage (with a 2x2 bitmap)?  To be honest, I'm not really sure ... but I plan to test these things as I come across them.  I'll do some searching for other's advice and I'll run some tests similar to what Almar Joling used to do on his VBfibre site (http://www.persistentrealities.com/vbfibre).  Once I have my answer, I'll code that as my go-to routine for that effect.

Coding for Efficiency Everywhere Else:  There are typical programming things that can be done to speed up the rest of your code, freeing up extra cycles for graphics drawing (and making things smoother overall).  Avoiding type conversions or heavy calcs inside loops, etc.  These are the typical good coding practices that we should be doing anyway ... but now we have an extra reason.  (Full disclosure:  I am *not* well brushed up on good coding practices in general ... so, feel free to help me out and point our areas where I can improve ... seriously!)

Beware the Garbage Collector:  Garbage Collectors are nice, helpful and good things.  But you just don't want them to come calling mid-game-loop ... they seem to want to show up when your right in a very intense moment in the game.  When they do their thing, they steal CPU cycles away from your game and usually cause it to give a big hitch.  This can really mess with your physics and logic calcs and generally makes the game look chunk and slow, if only for a fraction of a second.  To avoid this, use a create-hold-dispose approach for objects, where the drawing objects it needs (brushes, pens, bitmaps, paths, etc.) are created when the object is first instantiated.  They're held for the life of the object, and then finally properly disposed of when the object is being destroyed.  It's also important that you are careful of when you create and destroy your game objects.  Use "manager" classes to pre-create the number of game objects you will likely need during the intense part of the game ... then release them during "between level" and other slower times in the game.

The (lack of) speed of the GDI+ system was raised in a comment on my previous post ... and it's a valid concern.  But there are advantages to using GDI+ -- mainly having to do with compatibility and ease of development and deployment.  If a fun game can be made made quickly and easily distributed to work on a lot of machines, that's a good thing.  The trick is keeping a realistic viewpoint -- we won't be creating monster games ... but it should be the perfect fit for small games.

Posted by MattWorden | 2 comment(s)
Filed under: ,

Done Pondering, Shifting Gears

As my last post detailed (a couple of months ago), I came to a crossroads and have figured out a direction to go.  The games I hope to make in the near future are not very complex and do not require amazing graphics capabilities.  So, I will take this blog down a more generic path in 2D game programming.

Although I plan to use VB.Net and the .Net framework's built-in 2D Graphics capabilities, I hope to keep things at a more drawing-system-agnostic level.  This can't be done completely, because your choice drawing system determines a lot of the detailed options available for drawing things ... and that will determine how to track and organize some data.

My rate of posting will not be very fast either.  If I can average a post or two per month, then I'll be a happy guy.  If I get some games completed, I'll be even happier! ;-)

So, keep an eye out and see what I have to offer here.

Thanks for reading!

Posted by MattWorden | 2 comment(s)
Filed under: ,

Pondering Pause

So, it's been a few months since I posted here.  But, it's not like I've been slacking.  I've even fleshed out some of the start-up sequence code for Nimble (I ended up changing one of my dev machines, and that caused me to need to do some extra caps checks and respond properly to what was found).  And then there is work and a half-dozen other "free time" projects (won't go into a list here).

 But, now we've come to a point where I need to stop and ponder a few things.  First, SlimDX has their June 2008 release out, which changes a few things ... and I think it would be best to bring Nimble up to the latest-and-greatest.  Second, I've been thinking through the games I'd like to get to making.  The responses I've gotten from the ARA Tech Demo (plus some of my own re-tinkering) have made that fade from my mind as my "next big project".  I find myself sliding back to my strategy/board gaming roots.  And those sorts of games wouldn't really require a full Nimble2D-type engine to get to coding them ... in fact, they could probably be done using straight Graphics.Drawing calls.  Finally, I've found myself paying some attention to some wise advice I received from one of the folks hanging out at the Indiegamers Forums -- find/build an engine to use quickly, so that games can get made.

And now I'm off on a week's vacation ... and I plan to do some pondering.  Advice and comments are always welcome! :-)

Posted by MattWorden | with no comments

My "QuickTrig" Helper Class

I've uploaded a ZIP file that contains the source code for the "QuickTrig" helper class that I've mentioned a couple of times now.  You can get it by clicking this link.

For those curious, looking through that should help explain how I can handle my angles primarily in degrees (as mentioned in my previous post).

Feel free to give it a look ... and use it if it helps you out.  Performance-wise, I've found the COS and SIN look-up functions to take half the time of the comparable Math.Cos and Math.Sin traditional functions.  While the speed increase is nice, being able to just work in degrees with the rest of my code is the biggest help given by this class.

-Matt

Posted by MattWorden | with no comments
Filed under:

Units of Measure

I've been working through some auto-moving sprite functionality, and have wandered a bit further down that road than I originally expected to at this point ... so now I need to back up a bit to get back on the main route of progress.  Here is my list of current things to do:

  • Finish fleshing-out my auto-move sprite class (going to set it up to work like a cross between a simple-but-somewhat-intelligent game object and a particle)
  • Crop things down into a "base" sprite class ... and change the auto-move sprite to inherit from the base
  • Build the primative (circle, rectangle, triangle, line) sprites from the base sprite
  • Finish the initial camera functionality (sprite-following, defined bounds, and defined viewports -- leading to multiple cameras in a GameSpace and "split screens" ... btw: thanks once again to the XNA Machine Blog who is taking a slightly different approach to similar terrain -- and that's helping me see things in new ways as well)

Once I get that far, I should be ready to compile a quick demo on how the camera and sprite basics work.

But I wanted to be sure to touch upon a couple units-of-measure that I will be using throughout the library ... and since I don't have something productive to show from my code at this point, I figured now might be as good a point as any. ;-)

Angles In Degrees -- I prefer my angles to be measured in degrees.  Something about my old-fashioned American math training just won't let go of me ... so, I like my circles to start at 0 degrees at top, increase in a clockwise fashion -- 90 degrees is to the right, 180 degrees is down -- until you end up at the top again at 360 degrees.  So, if you see a property or function that is returning an angle (names like "Heading", "Facing", "Aiming", "GetAngle", etc.), it will be in degrees.  However, for properties, I will likely have an equivalently named property with a "_InRadians" suffix (such as "Heading_InRadians") to give something to work with if someone wants to use Windows functions that prefer radians.  The "QuickTrig" helper functions in Nimble2D operate in degrees -- so, making use of those is very easy with this degrees-friendly approach.

Time in Seconds -- I've found it traditional for game programmers to deal with time in milliseconds, for a number of different reasons.  However, I've found over time that I always need to convert those into true seconds to do things related to time-based programming (X = X + VX * (DeltaMS / 1000), etc.).  So, with this library, I've decided to always deal in actual seconds ... usually of type Double.

Space Measurements in "GameSpace Pixels" -- Position and spacial measurements will essentially still be in pixels, with the normal caviats thrown in when dealing with current 3D programming: It's a pixel if everything is at a 1.0 scale.  And everything will be relative to each other within the GameSpace ... so, basically, the camera's position will determine what will be showing up on screen.  Having a position of (800, -16372) may be in the middle of the screen, if the camera is moved into the right place ... and something at (805, -16372) will be 5 pixels to the right of the first object, assuming a scale of 1.0.  When I combine these two things, I like to think of these units as "GameSpace Pixels", as it reminds me of the relativity between all things within the GameSpace.

Rates are "per Second" -- Any rate-style measurement -- "Linear Speed", "Acceleration", "Rotational Speed", "Alpha Fade Rate", etc. -- will be in a "per second" format.  Linear Speed, for example, is in "GameSpace Pixels per Second" ... Acceleration is "GameSpace Pixels per Second per Second" ... Rotational Speed is "Degrees per Second" ... Alpha Fade Rate is in "Values per Second" (where a "value" is each integer in the 0-to-255 range).

"ColorOnly" versus "Color" -- There are times when I like to separate the Alpha channel out by itself from the rest of what would normally be an ARGB-style System.Drawing.Color.  Also, there are cases (such as background color) where the Alpha channel just doesn't matter.  As an example of the former, sprites will have a "SpriteAlpha" property that can hold a value of 0 (completely transparent) to 255 (completely opaque) and a separate "SpriteColorOnly" that, while still being of type System.Drawing.Color, will only make use of the Red, Green, and Blue channels to color-shade the sprite.  If something contains just the word "Color" (and not the "Only"), then all 4 channels will be used for whatever purpose that property or function is up to.

Those are the main ones that I've run into so far ... I'm sure there will be more, so I reserve the right to re-visit this topic (and maybe even change what's been stated here). ;-)

-Matt

Doing 2D in SlimDX

Now that I've had a couple of weeks to play around with 2D programming in Direct3D9 via SlimDX, here are a few things that I've found.

After creating the Direct3D Device (as explained in the "Graphics Enumeration and Launching the Game Form" post), I create a Direct3D Sprite object (which I will be calling a "Sprite Batch" object):

varSB = New Direct3D9.Sprite(varD3D_Device)

This will be passed through the main Nimble2D object to each of the 2D objects that will be drawing themselves to the screen.

The magical math-thingy that makes the whole thing work is how Matrices and Matrix Transformations are used to determine where and how 2D sprites are drawn.  As mentioned earlier, all objects within a gamespace (including cameras) will have a location (simply a "Current X" and "Current Y") within "game space", which essentially is a way to measure their positions relative to each other.  They will also have a "Facing" angle and a "Scale".  These are the main pieces used within SlimDX's nifty Transformation2D function to get the matrix needed to draw things how they should appear on screen.

First, the camera:

varActualMatrix = SlimDX.Matrix.Transformation2D(New SlimDX.Vector2(varWidth / 2, varHeight / 2), 0, New SlimDX.Vector2(varScale), New SlimDX.Vector2(varWidth / 2, varHeight / 2), varN2D.QuickTrig.DegreesToWinRadians(varFacing), New SlimDX.Vector2(varCurX, varCurY))

varInverseMatrix = SlimDX.Matrix.Invert(varActualMatrix)

The parameters of that function are as follows:

  • scalingCenter - A Vector2 around which to scale things (set to the middle of the screen)
  • scalingRotation - A Single that indicates rotating the X/Y scaling away from the identity angle
  • scaling - A Vector2 that gives the actual X/Y scaling (1.0 = original size)
  • rotationCenter - A Vector2 around which to rotate things
  • rotation - The angle to rotate (I like to work with angles in degrees ... DirectX and Windows trig functions prefer radians ... so I have a translator function)
  • translation - A Vector2 to relocate everything in X/Y space

The first line sets the camera's actual matrix ... the second line uses that to create the inverse of it, which will be passed to the sprites within the gamespace to relocate them in relation to the camera.

 So, the sprites go like so:

Me.varMyTransform = SlimDX.Matrix.Transformation2D(New SlimDX.Vector2(varCurX, varCurY), 0, New SlimDX.Vector2(varSourceScale.X * varScale, varSourceScale.Y * varScale), New SlimDX.Vector2(varCurX, varCurY), varN2D.QuickTrig.DegreesToWinRadians(varFacing), New SlimDX.Vector2(0))

(For my sprites, I have a "SourceScale" that indicates if the initial source image is always to be stretched.)

To draw things, the following steps are taken:

  1. Begin Scene & Clear to Background color (Direct3D Device)
  2. Cycle through all layers ...
    1. Begin Drawing Sprites (Sprite Batch)
    2. Cycle through all sprites ...
      1. Transform the Sprite Batch Matrix
      2. Draw the sprite
    3. End Drawing Sprites
  3. End Scene
  4. Present to Screen

In this system, these steps are scattered through several objects, but put together, they look something like this:

     varD3D_Device.BeginScene()

     varD3D_Device.Clear(ClearFlags.Target, varCurGameSpace.BackgroundColorOnly, 1, 0)

     varN2D.SpriteBatch.Begin(SpriteFlags.AlphaBlend Or SpriteFlags.DoNotSaveState)

     varN2D.SpriteBatch.Transform = SlimDX.Matrix.Multiply(varMyTransform, LayerTransform)

     varN2D.SpriteBatch.Draw(varSourceTexture, varSourceRect, New SlimDX.Vector3(varOrigin.X, varOrigin.Y, 0), New SlimDX.Vector3(varCurX, varCurY, 0), Color.FromArgb(varAlpha, varColorOnly))

     varN2D.SpriteBatch.End()

     varD3D_Device.EndScene()

     varD3D_Device.Present()

I have other code bits in between things here and there to handle stuff like allowing for additive blending effects, full-screen fade in/out, writing "screen info" (screen size, camera position, FPS) to the screen, etc.

What I'm working through now is the sprite-based primary shapes (filled rectangles, circles, and triangles, lines, empty rectangles, filled rectangles with borders, and empty polygons using lines).  I hope to give some details about the lookup-table trig system being used at some point as well.

-Matt

Posted by MattWorden | 2 comment(s)
Filed under: , , ,

A GDI-based Screenshot Method

I was struggling with trying to make an easy screenshot method using SlimDX.  Looking at MDX samples, the standard method was to grab the Backbuffer into a surface (Device.GetBackbuffer), then use the SurfaceLoader.Save helper method to save that surface to a file.  I have not been able to find that SurfaceLoader.Save method anywhere within SlimDX.  (I have added it to the SlimDX "issues" list as an enhancement request.)

In the meantime, I was looking for a way to make it work.  An internet acquaintence of mine (and a hyper-good coder also working on a SlimDX-based project), CodeImp, suggested that I lock the surface and transfer the data to a GDI bitmap and use the Bitmap's ability to save-to-file.  I also wondered about using the SlimDX's Texture.FromStream method to create a texture from the surface, then do a Texture.SaveToFile ... but none of these worked.  For some reason, once I locked the rectangle on the surface, that LockedRect's Stream wasn't able to get its own length -- so it kept throwing errors.

 So, my next attempt was to use the surface's GetDC to see if I could create a GDI Graphics device from that DC and do something with it that way.  That's where the "looky what I found" happened ... GDI Graphics devices have a method called "CopyFromScreen", which does exactly what it says.  This made me drop any SlimDX-related code and go pure GDI, with great results.

Here are the steps:

  1. Determine your Top, Left, Width, and Height to capture ... if in full screen, these are 0,0,ScreenWidth,ScreenHeight ... if in windowed, these are the Top, Left, Width, Height of your Game Form
  2. Create a new Bitmap using Width and Height
  3. Create a Graphics Device from the new Bitmap
  4. Do Graphics.CopyFromScreen
  5. Do Bitmap.Save
  6. Dispose the Graphics Device and Bitmap

So, it's not quite the 3-step process available with MDX, but this pure-GDI process should work no matter what your drawing system is.  Here's the actual VB.Net code:

        tempBMP = New Bitmap(tempWidth, tempHeight)
        G = System.Drawing.Graphics.FromImage(tempBMP)

        G.CopyFromScreen(tempLeft, tempTop, 0, 0, New Size(tempWidth, tempHeight))

        tempFileName = "Screenshot_" & Date.Now.ToString("yyyyMMdd_hhmmss") & ".png"

        tempBMP.Save(tempFileName, System.Drawing.Imaging.ImageFormat.Png)

        G.Dispose()
        tempBMP.Dispose()

This routine names the file using the current date and time (down to the second) and saves it as a PNG.  It seems to work fine in both windowed and fullscreen mode.

-Matt

Posted by MattWorden | with no comments
Filed under: ,

Laying Out the Graphics Approach

I won't be able to do much more coding until the weekend.  So, I figured I should give a quick overview of how I'm going to approach the main graphics drawing routine.  I'll start high-level and work downward, then spiral back upward, and then back down a little bit before popping up into the atmosphere to wrap everything together.

It would be interesting to me to be able to approach displaying a game as if it were a live-action TV show.  You would have different sets (I'm going to call them "Game Spaces") for different sort of things.  Think about a normal local newscast -- you have the "main anchor desk" set, the "weather map" set, the "sports report" set, the "investigative reporter" set, etc.  For a game, this might translate into things like a main menu, a configuration menu, a high scores list, the pre-level briefing, the main gameplay space, the post-level results, and a game over screen.  Each set would have 1 or more cameras trained on it, and you would play the director ... "switch to set 1, camera 1" ... "zoom camera 2" ... "switch to camera 2" ... "prepare camera 1 on set 2" ... "move things around on set 2" ... "switch to set 2, camera 1" ... etc.

Now, when programming 2D games, I've always logically grouped my graphics into layers.  There's usually some sort of background layer which consists of your ground-level surface or a starfield or something else that is simply going to be behind everything else.  It may scroll around, but it will otherwise not really interact with anything else in the game and it's just there for your eyes and brain to have a backdrop on which to make sense of the rest of the game.  Then there is usually one or more layers of game objects (these are usually called "Sprites", and I don't plan to stray from that norm).  These layers might consist of a map layer and a layer for ships and weapons, etc.  Basically, I try to group the sprites that may interact with each other (and probably won't overlap in 2D space) into the same layer.

Let's use Gem Raider as a quick example.  Look at this screenshot (click it for a big version):

Example Screenshot of Gem Raider

In this game, I had 6 layers (starting back to front) ...

  1. The grid-like backdrop surface
  2. The "under the walls" explosion layer
  3. The "walls" layer for the main static map structures
  4. The "moving objects" layer for the player ship, the gems, and all of the weapons
  5. The "above everything" explosions layer
  6. The UI layer to show the score and the quick key listing

Each layer becomes, essentially, a collection of sprites.  Especially if you treat your primatives (drawn circles, rectangles, lines) and text as sprites, which I plan to do.  Sprites move around freely in game space.  Layers are drawn from back to front, and the current camera determines what part of that game space actually makes it to be displayed on the player's screen.

So, why put things in layers?  Why not just assign each sprite a "z" (or depth) value and allow DirectX to sort the order to draw things in?  Because I would like the programmer to easily be able to apply logic to all sprites in a layer at the same time.  Also, it will make for a logical grouping within which to breakup update and draw processes, if the game programmer wishes.  While Nimble2D will be designed to very easily draw everything on screen, it will still allow the programmer some capability to mix in some of their own drawing code if they wish (perhaps to render a true 3D object in amongst the layers somewhere).

Just as sprites will be able to move around, rotate and scale, cameras will be able to move around, rotate and scale/zoom ... and this will be reflected in how the sprites are drawn to the screen.  However, there will also be some "over layers" available within a game space that will not be impacted by the camera.  These over layers will be used to render UI components and messages onto the screen (after the camera shot is drawn) and always have them show up in the same screen location, no matter what is going on with the camera.

For most of what I'll be doing with Nimble2D to start, I'm just going to work with a single camera per game space, which will be drawing directly to the backbuffer.  However, I will also be keeping in mind that this system will eventually allow for multiple cameras in a game space, which will draw to their own render targets and those resulting graphics will be assembled onto the screen by the game space ... this will make it easy to do split screens for player-versus-player type games or games which might need to track multiple locations in the game space at the same time.

At the highest level -- the Nimble2D class itself -- each game space that is created will be tracked by the system.  A call to Nimble2D.UpdateAll will push update calls down through the camera, layers and sprites of the current game space (and every game space that is setup to do "background updates", which I will get into at a different time).  Drawing everything in the current game space to the screen will be done with Nimble2D.DrawAll, and switching between screens will be as easy as setting a different game space as Nimble2D.CurrentGameSpace.  As mentioned, there will also be finer-grain controls available to allow the programmer to update and draw just the pieces as is needed.  This also sets a nice framework for easily allowing full-screen fade-in and fade-out and other fun stuff.

For now, let's put it all together with this graphic of a single game space:

Nimble2D Game Space Visualization

It shows multiple layers resulting in a collection of sprites within the game space.  A camera presents a portion of the game space to the screen.  And over layers are added to give the player a single frame of the game.

2D game programmers will need to shift their thinking a bit from managing things within the scope of "screen space" to managing things within "game space" and grouping those things into layers ... and to how they wish to position the camera to show the game to the player.  (This also presents a programming challenge for the NimbleInput class -- giving mouse coordinates in both screen and game space ... but that's exactly where a library like this is supposed to make things easy on the game programmer, right?)

There is a lot of class skeletons and methods to get into place just to draw some simple sprites on the screen.  But once those are in place, the actual game code writing should become much easier, and we can then start fleshing things out to add value quickly.

-Matt

Posted by MattWorden | with no comments

Graphics Enumeration and Launching the Game Form

When it comes time to write a new game, I'd like to be able to tell my graphics library the various screen styles that the game wants to make available to the player and then have the library figure out which ones are supported by the player's computer.  This will be how Nimble2D does things ... receive a list of requested screen styles, look at each available adapter for support for the requested style, and provide back a list of available screen styles.

 Here's how the graphics enumeration testing app starts things off:

        Nimble2D = New clsNimble2D()

        'At least 1 video adapter is needed to continue ...
        If Nimble2D.Adapters_Count < 1 Then
            Application.Exit()
            Exit Sub
        End If


        '--- Change this list to reflect requested screen styles ---
        Nimble2D.AddRequestedScreenStyle(800, 600, False)
        Nimble2D.AddRequestedScreenStyle(800, 600, True, clsNimble2D.enumColorDepth.CD_32bit)
        Nimble2D.AddRequestedScreenStyle(800, 600, True, clsNimble2D.enumColorDepth.CD_16bit)
        Nimble2D.AddRequestedScreenStyle(900, 400, False)
        Nimble2D.AddRequestedScreenStyle(1024, 768, True, clsNimble2D.enumColorDepth.CD_32bit)
        Nimble2D.AddRequestedScreenStyle(1024, 768, True, clsNimble2D.enumColorDepth.CD_16bit)
        '---

Then the start-up form cycles through the available styles for the selected adapter:

        'Load Screen Styles List Box
        If modGame.Nimble2D.AvailableScreenStyles_Count(Me.cboAdapters.SelectedIndex) > 0 Then

            For j As Integer = 0 To modGame.Nimble2D.AvailableScreenStyles_Count(Me.cboAdapters.SelectedIndex) - 1
                Me.lstScreenStyle.Items.Add(modGame.Nimble2D.AvailableScreenStyleName(Me.cboAdapters.SelectedIndex, j))

            Next

            Me.lstScreenStyle.SelectedIndex = 0

        End If

That gets us this far:  Nimble2D Graphic Enumeration Startup Form

Once the player selects the adapter and screen style and clicks "Launch", the game's code creates an instance of a game form and asks Nimble2D to launch it on the requested adapter in the requested screen style:

        GameForm = New frmGame()

        Nimble2D.LaunchGameScreen(GameForm, AdapterIndex, ScreenStyleIndex)

Which results in this:   Nimble2D Graphics Enumeration Launched Game Form

 So ... how's it done?  Well, SlimDX (which is what Nimble2D uses to access DirectX) makes a number of enumeration functions and properties available.  The available adapters are grabbed when Nimble2D is first constructed, using this code:

        Direct3D.Initialize()

        Me.varAdapters_Count = Direct3D.AdapterCount()

        For j As Integer = 0 To Me.varAdapters_Count - 1
            Me.varAdapter(j) = Direct3D.GetAdapterIdentifier(j).Description
        Next

The handling of requested screen styles is a bit more complicated.  So, instead of giving code here, I'll walk through the steps.

  1. The supplied parameters for screen style are:  Width, Height, FullScreen, and ColorDepth
  2. If FullScreen = False, then the ColorDepth is assumed to be whatever the primary adapter is currently set to, and the screen style will be considered available as long as the Width and Height are smaller than the current screen size
  3. If FullScreen = True, then each adapter is run through the various formats for the given ColorDepth in preference order.  As soon as a supported format in the given Width and Height is found, then it is marked as available for that adapter.  The first-found supported format is noted to be used later if that screen style is selected for the launch.

When the game requests for the game form to be launched, Nimble2D resizes the supplied game form to the width and height of the selected screen style, shows the form, and creates the Direct3D Device:

        GameForm.Size = New Size(varSelectedScreenStyle.Width, varSelectedScreenStyle.Height)
        GameForm.Show()

        Dim presentparams As New PresentParameters

        presentparams.BackBufferWidth = varSelectedScreenStyle.Width
        presentparams.BackBufferHeight = varSelectedScreenStyle.Height

        If varSelectedScreenStyle.FullScreen Then
            presentparams.BackBufferFormat = varSelectedScreenStyle.BackBufferFormat
            presentparams.Windowed = False
        Else
            presentparams.Windowed = True
        End If

        presentparams.DeviceWindowHandle = GameForm.Handle

        varD3D_Device = New Device(AdapterIndex, DeviceType.Hardware, GameForm.Handle, CreateFlags.HardwareVertexProcessing, presentparams)

The next thing to tackle is the main skeleton structure that will be used to get graphics on the screen ... Game Spaces (and their cameras), Layers, and Sprites.

-Matt

p.s. When requesting a screen style that is full screen, only 16- and 32-bit color depths are allowed.  The various formats (5 to 6 in each color depth) are checked in order of "preference".  If someone reading this has strong knowledge about graphics formats and in what order preference should be given, please post a comment or send me an e-mail ... I have some questions for you. ;-)

Posted by MattWorden | 1 comment(s)
Filed under:
More Posts Next page »