January 2008 - Posts

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:

Breaking Up the Solution, and How I Program

I expect, in the end, Nimble2D will be a rather full-featured library for making games -- graphics, particles, collisions, input, sound effects, music, etc.  But to get there, I will need to break things up into manageable chunks, building higher priority things first, then fleshing-in the more detailed things.  I expect there to be a core set of projects to handle graphics, input, and sound, with a few add-on projects to handle more specialized things such as asset handling, multi-language, tile maps, etc.

The expected core projects:

  • Nimble2D -- The Graphics System -- handling graphics capabilities enumeration & screen launching, game spaces, layers, cameras, sprites, trig, primatives, text, particles, and anything else that has to do with graphics.
  • NimbleLog -- Easy Log File Writing -- a lowly project, but quite useful ... I like to write log files -- not sure I have more to say on that topic. ;-p
  • NimbleInput -- The Input System -- handling keyboard, mouse, and game controller input, mouse-to-game-object relations, key-to-action mapping, and other things having to do with the player interacting with the game.
  • NimbleSound -- The SFX & Music System -- for SFX:  volume/pan control, sprite-position-based volume/pan, delayed/repeated firing; for music: music file categorizing, queueing & shuffling, fade-in/fade-out ... and other things sound-related.

Some probable add-on projects:

  • NimbleMap -- Tile Map handling -- what's a good 2D graphics library without a nice-and-easy way to work with tile maps?
  • NimbleAssets -- Asset Data handling (aka "Pack File handling") -- a library-style approach to getting game assets and data out of packed and encrypted data files.
  • NimbleLang -- Multi-Language handling -- a library-style approach to getting language-specific text phrases and making multi-language support easier.

I expect to work on them essentially in the order I listed them.  But, as I said, I will probably build working frames around the main features, move on to the next one, then come back to flesh something out in an earlier project as I find it needed.

This leads into how I work as a programmer.  I am quite new to .Net, although I'm not new to VB.  I broke my teeth on Commodore BASIC, Quick BASIC, and Pascal while I was in high school and early college.  Then, I wandered in the wilderness for a while after changing my major in middle and later college.  Once I was out in the working world, I became aware of VB3 and the VBA tucked inside MS Access 2.0.  I quickly progressed to VB6 (which I've done both business and game programming in) and I'm still working with Access/VBA.  So, it's only been a few months that I've seriously tried to work on VB.Net and C# projects, which means that I'm rather new to strong object-oriented approaches -- especially with games ... and I will likely be doing things "wrong" and in "non-traditional" ways.  If you see something completely out of the norm, or something that can be done much more effeciently in another way, please be sure to make a comment or send me a message about it.

I'm a very practical worker, and while I can discuss and design just on theory, I usually need a practical example to develop for.  In this case, the practical purpose for Nimble2D is to help me build my current game, "Air Ralley Ace" (ARA), which is an arcade airplane racing game involving scrolling landscapes, layered clouds, and lots of colored smoke.  As I make progress, I will very likely be using ARA examples to show off capabilities.

So ... first-things-first ... I need Nimble2D to be able to let the game know a bit about the player's graphics card -- texture size/shape support, supported full screen sizes and color depths -- and be able to launch the selected screen style.

-Matt

Posted by MattWorden | with no comments
Filed under: ,

A Nimble Launch

Nimble2D LogoOn this first post of the new blogspace for Nimble2D, I'd like to do two things:  (1) Give credit where it is due, and (2) give a brief overview of how I see this project at the start.

So, credit ... First, a big thank you to Chris Williams for setting up ilovevb.net (a perfectly logical place for a project like this to land), and for providing the space and resources for me to blog about the project.  Second, I have to give a lot of credit to Dave Munsie of JGOware for his now-extinct DXGame engine, which really opened my eyes to how powerful doing 2D-via-3D could be.  (Now go to his site and play Retroblast ... then read the rest of this.)  Third, my recent epiphany on how to approach things with this project came about by reading the xna machine blog, where the concepts of matrix math and how game objects relate to one another (and to the camera) really became clear.  If you want to see some very neat VB.Net + XNA work taking place, be sure to check out that blog.

And finally, my traditional shout-out to the crew at the Game Programming Wiki (gpwiki.org), who provide a nice combination of friendly interaction and good brains to be picked.

Now, the project ... as given in the brief description: Nimble2D is a 2D game development library for .Net, based on SlimDX and DirectX9. It will use Direct3D9's Sprite functionality to create a fast 2D environment (with real-time rotation, scaling and blending), mobile cameras, sprite-based text and primatives, and real-time sound effect panning and volume based on sprite locations.  But first, I'll explain how I got here ... and where I think I'll be going.

I came to the point of writing my own 2D library due to a number of reasons.  First, my trusty tools that I've been using (VB6 + DXGame) are a bit long in the tooth ... although I'm happy with what they allowed me to do with Gem Raider.  Still, there were things that I wanted to do differently and I wanted to have a reason to get to use the .Net programming languages.  I was about half-way done with my new game project when it struck me that I should probably find a new set of tools to make the game with ... it would give me a very practical example of a game that I'd want to make, and a reason to learn how to use those new tools.  So, I looked around a bit and didn't quite find what I was looking for.  XNA was a bit requirements-heavy for my taste (I don't actually have a computer in my house that can install Game Studio Express due to OS or graphics card requirements) ... SDLdotNet didn't have real-time rotation nor additive blending (DXGame had spoiled me) ... Torque Game Builder didn't seem to fit my personal style ... and BlitzMax would require learning a BASIC-like language that wouldn't be very useful in a work setting.  So, I settled on VB.Net as my language (although I'll probably play around with C# as well) and looked for a way to tap into DirectX.  That's when I found SlimDX ... and suddenly everything came together.  Now all I need is to put together a fast, flexible 2D system ...

And that leads to Nimble2D.  Why "nimble"?  Well, frankly, all of the other names that I came up with first were taken or were hard to pronounce.  It was while I was describing to my wife (bless her for standing there and listening) how I wanted the cameras to work that I first used the word "nimble" as a descriptive ... and then I paused and ran over to Google for "nimble" combined with "game engine" and for "Nimble2D" ... and then I had the name.

So, here are some of the main concepts ... Imagine having a "game space" that is a flat surface, like a table top, to work on.  All of your in-game objects (sprites) are put down onto the game space in layers.  A camera is then hovered above the layered game space and snaps a picture of the current frame.  That camera should be able to move to frame the game space in whatever way the programmer would like, including rotation and zooming.  Now imagine having multiple game spaces to organize different aspects of a game -- a "main menu" game space, a "high score" game space, a "world map" game space, etc. -- and being able to switch between these spaces the same way a TV show director switches between his various sets.  This is the approach being taken with Nimble2D.  It will be a sprite-based, camera-based, matrix-based, real-time 2D-via-3D system ... and the library should allow the game programmer to just work with the traditional 2D settings (X, Y, Facing Angle, etc.).

Along the way, I hope to pack in some tricks I've learned as standard goodies within the libraries.  So now all I need to is write some code ... ;-)

-Matt