February 2008 - Posts

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: , , ,