none
GDI+ Gaming API (GdiGaming): CTP 1

    General discussion

  • NOTICE:

    The GdiGaming API is now being supported as a codeplex project.  The CTP1 version in this post is now obsolete. For more information, see the new project at http://gdigaming.codeplex.com

    GdiGaming API
    Community Technology Preview 1

    CTP 1 Download

    GdiGaming API (by downloading this software, you agree to the terms of use below)

    Compatibility

    This version of the GdiGaming API is targeting the .Net Framework 4 Client Profile and is intended for use with Visual Studio 2010.

    Introduction

    The GdiGaming API is a small, simplified, yet complete game-engine framework for Windows Forms applications which uses GDI+ for rendering graphics, Windows Media for playing sound, and Control mouse and keyboard handlers for input.  The API provides a GameEngine component and RenderCanvas control which can be placed on a Form at design-time to setup a game-engine environment.  Individual projects then define classes for players, enemies, and other interactive game elements which each inherit from the API provided GdiGaming.GameObject base class.  Each custom class needs only to override appropriate methods on GameObject such as OnInput, OnUpdate, OnCollision, etc. in order to implement its game-logic.  Each GameObject instance is assigned to a GameScene instance, and the GameEngine automatically runs its currently assigned GameScene.  Basic asset-managment is also provided to automatically handle loading and reusing image and sound assets.

    Features

    • Simplistic Object-Oriented Design
    • Resource/Asset Management
    • Automatic Rendering of Sprites & Backgrounds
    • Full Collision
    • Animation
    • Mouse and Keyboard Input
    • Multiple Simultaneos Sounds
    • Support for an Event-Driven Model

    History

    The idea of being able to create your own video game is probably what got me started in programming as a child.  And it would be fair to say that I have spent many, many hours experimenting with all kinds of game design ideas.  I have attempted to build a number of game-engine frameworks over the years using VB.Net, with Xarcade and Xarcade Vengence being example games that grew with the development of those engines. 

    Just prior to the announcement of XNA, I had been working on a 2D game-engine framework based on DirectX.  I was to the point of needing to design what I would later learn is called a "content pipeline" when I learned that the XNA Framework as just around the corner.  And when I looked into it, I found that it was nearly identical in concept to what I was building, only it was a complete DirectX implementation, whereas mine was a very narrow implementation and was only supporting 2D rendering of textures.

    So I abandoned that engine and began to learn XNA.  It turned out to be an amazing framework, if a bit overwhelming when first starting out.  And as of this writing, when one is ready to create a real, modern video-game, something XBox-worthy, then XNA is still one of the premier development frameworks to choose from.

    More recently though a development environment and game-engine named Unity3D has arrived.  And this is true game development power made simple.  One can think of Unity3D as a prebuilt "XNA" game engine which is cross-platform compatible.  I don't believe it is actually XNA, but it has a similar design concept and is completely scriptable in C#.

    But the point is that Unity provides not only the game development framework like XNA, but also a completely rigged game-engine which is ready to be configured via the Unity IDE.  So it is something akin to having Visual Studio's drag-and-drop/property grid-based form designer, only for a 3D world space with GameObjects that automatically support rendering, collision, input, updating, and all of the things a game engine needs to provide.

    I became a big fan quickly, and created a beta of Xarcade Invasion which is really only lacking an artistic team for completion. This beta demonstrates most of the major components of a complete game, and plays pretty well with a wired XBox controller plugged into your PC's USB port (keyboard works too, but is harder to use).

    Here's the thing though: even as well designed and easy to use as Unity is, it is even more overwhelming than XNA when you first get started.  And there are quite a few foundational concepts about 3D graphics which one may need to learn before some portions of Unity's options make sense.

    So I thought: "what if we took the same pre-designed game-engine concept as Unity, but simplified it down to only handle a classic 2-D style video game?" The answer I came up with is the GdiGaming API.

    Overview

    The primary component of the GdiGaming API is the GameEngine.  The GameEngine componet handles the execution of the primary game loop and performs the following operations on each game loop iteration:

    1. Updates Input
      This includes updating the GameInput object, calling the OnInput method for each GameObject, and testing for/calling the mouse input methods for each object.
    2. Updates Each Background
    3. Updates Each Game Object and Checks Collision
    4. Draws Each Background and GameObject
    5. Updates the Playing Audio
    6. Raises the GameLoop Event for the Containing Form
    7. Waits any Remaining Time in this Frame
    8. Updates the GameTime and Frame Rate
    9. Raises the FrameComplete Event for the Containing Form
    10. Restarts the Game Loop

    The GameEngine contains a collection of GameScene objects, and has a CurrentSceneName property.  When the containing form loads, it creates instances of the required GameScene objects and adds them to the GameEngine.  It then sets the CurrentSceneName on the GameEngine to the game's initial scene.

    The GameScene is an abstract class which the developer inherits from in order to create specific scenes for the game.  Each scene is simply a named container for a group of backgrounds and game objects.  Individual scenes can represent individual levels in a game, or one scene might contain logic to handle multiple levels.  Typically things like the game's startup screen, main game screen, and menu screen would each be individual GameScene classes.  The scene will override the Name property to return a unique name, and then will typically override the OnLoad method to setup all necessary scene backgrounds, gameobjects, and data.

    The GameObject is an abstract class which the developer inherits from in order to create the interactive elements of a game such as players, enemies, items, and logic-components.  The GameObject provides the following overridable methods which allow the developer to implement game-logic:

    • OnCollision
    • OnDraw
    • OnLoad
    • OnInput
    • OnMouseDown
    • OnMouseUp
    • OnUpdate

    Each method provides an eventargs reference which contains a reference to the game engine, game time, and possibly other appropriate data according to the method.

    So every game will have one instance of a GameEngine component and one instance of a RenderCanvas control added to the main form, and then will define individual GameObject and GameScene classes in order to create all of the game's content.

    Asset management is handled by the GdiGaming.AssetManager object on the GameEngine.  When you develop your game project, you add a folder to the project called "Assets".  All of the game's image and sound files are then added to this folder.  You only need to set each file's "Copy to Output Directory" property to "Always", and then the AssetManager will allow your code to refer to any asset by its file name.  The asset manager will take care of loading an asset from disk the first time it is requested, and then reusing that asset for subsequent requests.  Each GameScene will also cache the assets it uses, so all game-logic should go through the GameEngine.CurrentScene to request assets.

    Performance Considerations

    The GameEngine defaults to a framerate of 30FPS; it is suggested that you do not change this default value.

    The performance it highly dictated by the number of sprites being drawn to the screen, the number of objects serving as colliders, and the number of objects handling collision (an object can cause collison with another object, but have no capacity to respond to that collision itself).

    The GameEngine can support thousands of GameObjects, but the more of them which require advanced rendering (such as rotation, scaling, multiple sprites, etc) or use full collision, the lower the total becomes.  The GameEngine reveals a FrameRate property and IsRunningSlow property which can be used to montior performance and limit the number of new objects added to the game at one time.  IsRunningSlow will return true whenever the current framerate has dropped below 80% of the target framerate.  This allows the game logic to tune itself before the drop in performance becomes noticeable to the player.

    In my testing on a powerful computer, the API can handle ~2400 objects, all of which are colliders and 10-20 of which handle their collision.  A framerate of 27-29 FPS is maintained without exceeding 50% the processor core used.  If the total number of objects is reduced to ~1000, then over 100 objects with full collision can be supported at 28-29 FPS.  So every game will have to balance its total number of visible game objects with collision, based on overall needs.

    Also, note that each simultaneous sound requires a new instance of the MediaPlayer class, so care should be taken to not play too many sound effects too rapidly.  The framework will limit simultaneous playing sounds to 20 by default.

    Getting Started

    After downloading and extracting the GdiGaming API zip file to a location on your hard drive, start a new Windows Forms Application project.

    Make a few minor changes to the default Form1 in the designer:

    1. Set the FormBorderStyle to Fixed3D
    2. Set the MaximizeBox to False
    3. Set the Size to 810,630

    Next, right-click an item in the ToolBox and select Add Tab.  Name the new tab "GdiGaming Components".  Right-click this new tab and select "Choose Items...".  When the dialog opens, click the Browse button, locate the GdiGaming.dll, and click OK.  This will add the GameEngine and RenderCanvas items to the .Net Framework Components tab and should have them checked.  Click OK again.

    Now the GameEngine and RenderCanvas should be in your ToolBox and you can drag one of each onto Form1.

    Go to the properties for the RenderCanvas and set its Location to 0,0.

    Go to the properties for the GameEngine, select its Canvas property and use the DropDown to select RenderCanvas1.

    Now you can go to the Code View for Form1 and paste in the following template code:

    Public Class Form1
        Private Sub Form1_Activated(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Activated
            GameEngine1.StartGame()
        End Sub
    
        Private Sub Form1_Deactivate(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Deactivate
            If GameEngine1.EngineState = GdiGaming.GameEngineState.Started Then
                GameEngine1.PauseGame()
            End If
        End Sub
    
        Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
            If Not GameEngine1.EngineState = GdiGaming.GameEngineState.Stopped Then
                GameEngine1.EndGame()
                e.Cancel = True
            End If
        End Sub
    
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            'Add the game's initial scene to the engine; replace "ExampleScene" with your custom scene
            GameEngine1.Scenes.Add(New ExampleScene)
        End Sub
    
        Private Sub GameEngine1_FrameComplete(ByVal sender As Object, ByVal e As GdiGaming.GameEngineEventArgs) Handles GameEngine1.FrameComplete
            'Uncomment the following line to easily monitor the framerate during development:
            'Me.Text = e.Engine.FrameRate.ToString
        End Sub
    
        Private Sub GameEngine1_GameStopped(ByVal sender As Object, ByVal e As System.EventArgs) Handles GameEngine1.GameStopped
            Me.Close()
        End Sub
    End Class
    
    

    This basic procedure can be used for each new game and you then only need to modify the Scene objects which are created during Form.Load.

     

    Finally begin creating your custom GameObjects and Scenes!

    Example

    An example set of classes and assets will be provided in a subsequent post to this thread.

    Summary

    The GdiGaming API is designed as a beginner's learning tool for creating video games in .Net.  The API provides a simplistic, yet complete, game-engine and development framework.  The API is suitable for small "classic-style" 2D games with up to a couple of thousand game objects, a few hundred of which are serving as colliders, with about 10% of the colliders actually handing their unique collisions.

    While initial testing has shown this framework to perform pretty well given the amount of functionality it provides, it still needs to be tested on slower computers.  While there may still be bugs to discover, and there is certainly still functionality to modify or add, the current features seem to be performing as intended and with the expected system resource utilization (which is relatively high for the type of game, due to GDI not being entirely suited to this type of interaction, but still well within reason for a Windows Forms application).

    Comments and documentation will be added in future releases; I'd like to get some initial feedback and usage suggestions before writing too much documentation, in case any major issues are discovered.

    Terms of Use

    The GdiGaming API is authorized solely for non-commercial use in personal and/or educational projects.

    Decompiling or otherwise deconstructing the compiled assembly is strictly prohibited.

    You are hereby granted the right to redistribute this API in any non-commercial personal and/or education product, providing that these terms are included with your compiled assembly.

    This API is provided "as-is" without warranty of any kind either expressed or implied, and is not guaranteed to be fit for any purpose.

     


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Saturday, September 10, 2011 8:16 PM
    Moderator

All replies

  • Reed,

    I've never played a computer game in my life! (seriously, never once), but I do recognize the value of the technology behind it; indeed it's development through the years has profoundly influenced many aspects of things which we use in programming all the time and just take for granted.

    I wanted to commend you on putting this together and putting it out for the community here at large and offer you congratulations on it as well as an earnest "thank you". I feel confident that I speak for many others here as well.

    Way to go Reed!

    :-D

    Saturday, September 10, 2011 8:44 PM
  • Example 1

    Part 1

    In this, and subsequent posts, I will provide an example of creating a game using the API.

    The first example will build a simple top-down shooter, where we will have the player pilot a helicopter and try to shoot down enemy spaceships.

    Part 1 will setup the initial player, enemy, background, and scene classes, and will implement movement for the player.  Just to see what they look like, we will also load 10 enemy ships (though they won't do anything yet).

    In following posts, I'll discuss a little about what these classes are actually doing and we will begin to work in more actual game-logic.  For now, follow these steps and play with the sample code.  Feel free to experiment and see if you can get your helicopter to push the spaceships around when you fly into them!

    Steps:

    1) Prepare a new project as discussed in the "Getting Started" section of the initial post, except set the form size to 650, 511.

    2) Add a folder named "Assets" to the project.

    3) Download the Example1Assets1 zip package for this example, and extract the two image files.

    4) Right click the Assets folder, select Add Existing Items and browse to the two image assets.

    5) Select each asset file in the solution explorer, then go to the properties window and set Copy to Output Directory to Always.

    6) Add the following new classes to the project and paste in the provided code:

    CloudyBackground

    Imports GdiGaming
    
    Public Class CloudyBackground
        Inherits ScrollingBackground
    
        Public Sub New()
            _AutoScroll = True
            _BackgroundImage = "Clouds"
            _InvertDirection = True
            _ScrollDirection = Orientation.Vertical
            _ScrollSpeed = 3.0F
        End Sub
    End Class
    

    PlayerChopper

    Imports GdiGaming
    
    Public Class PlayerChopper
        Inherits GameObject
    
        Private _Speed As Single = 6.0F
        Public Property Speed() As Single
            Get
                Return _Speed
            End Get
            Set(ByVal value As Single)
                _Speed = value
            End Set
        End Property
    
        Public Sub New()
            Dim chopperSprite As New AnimatedSprite
            chopperSprite.SpriteSheet = "AirVehicles"
            chopperSprite.AnimationRate = 0.25F
            chopperSprite.Frames.Add(New Rectangle(0, 0, 48, 48))
            chopperSprite.Frames.Add(New Rectangle(48, 0, 48, 48))
            chopperSprite.Frames.Add(New Rectangle(96, 0, 48, 48))
            chopperSprite.Frames.Add(New Rectangle(144, 0, 48, 48))
            chopperSprite.Name = "Chopper"
            Sprites.Add(chopperSprite)
            HasCollision = True
            _Name = "Player"
            _ProcessInput = True
            _ZOrder = 1
        End Sub
    
        Protected Overrides Sub OnCollision(ByVal e As GdiGaming.CollisionEventArgs)
            MyBase.OnCollision(e)
        End Sub
    
        Protected Overrides Sub OnInput(ByVal e As GdiGaming.GameEngineEventArgs)
            MyBase.OnInput(e)
            Dim input As GameInput = e.Engine.Input
            If input.IsKeyDown(Keys.W) OrElse input.IsKeyDown(Keys.Up) Then
                Position += New Vector2(0, -_Speed)
                _RotationAngle = 0.0F
            End If
            If input.IsKeyDown(Keys.S) OrElse input.IsKeyDown(Keys.Down) Then
                Position += New Vector2(0, _Speed)
                _RotationAngle = 180.0F
            End If
            If input.IsKeyDown(Keys.A) OrElse input.IsKeyDown(Keys.Left) Then
                Position += New Vector2(-_Speed, 0)
                _RotationAngle = 270.0F
            End If
            If input.IsKeyDown(Keys.D) OrElse input.IsKeyDown(Keys.Right) Then
                Position += New Vector2(_Speed, 0)
                _RotationAngle = 90.0F
            End If
        End Sub
    
        Protected Overrides Sub OnUpdate(ByVal e As GdiGaming.GameEngineEventArgs)
            MyBase.OnUpdate(e)
        End Sub
    End Class
    

    EnemySpaceship

    Imports GdiGaming
    
    Public Class EnemySpaceship
        Inherits GameObject
    
        Public Sub New()
            Dim spaceshipSprite As New AnimatedSprite
            spaceshipSprite.SpriteSheet = "AirVehicles"
            spaceshipSprite.AnimationRate = 0.75F
            spaceshipSprite.Frames.Add(New Rectangle(0, 48, 48, 48))
            spaceshipSprite.Frames.Add(New Rectangle(48, 48, 48, 48))
            spaceshipSprite.Frames.Add(New Rectangle(96, 48, 48, 48))
            spaceshipSprite.Frames.Add(New Rectangle(144, 48, 48, 48))
            spaceshipSprite.Name = "Spaceship"
            Sprites.Add(spaceshipSprite)
            HasCollision = True
            _ZOrder = 2
        End Sub
    End Class
    

    ExampleScene

    Imports GdiGaming
    
    Public Class ExampleScene
        Inherits GameScene
    
        Public Overrides ReadOnly Property Name As String
            Get
                Return "Example Scene 1"
            End Get
        End Property
    
        Protected _Player As PlayerChopper
        Public ReadOnly Property Player As PlayerChopper
            Get
                Return _Player
            End Get
        End Property
    
        Protected Overrides Sub OnLoad(ByVal e As GdiGaming.GameEngineEventArgs)
            Backgrounds.Add(New CloudyBackground)
    
            _Player = New PlayerChopper
            _Player.Position = New Vector2(320, 400)
            Objects.Add(_Player)
    
            Dim x As Single = 208.0F
            Dim y As Single = 30.0F
            For i As Integer = 1 To 10
                Dim ship As New EnemySpaceship
                ship.Position = New Vector2(x, y)
                Objects.Add(ship)
                If i = 5 Then
                    x = 208.0F
                    y += 56.0F
                Else
                    x += 52.0F
                End If
            Next
    
            MyBase.OnLoad(e)
        End Sub
    End Class
    

    7) Paste the template form code from the original post over the default Form1 code in your project.

    8) Run the example and fly the helicopter!

     

     


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
    Sunday, September 11, 2011 1:35 AM
    Moderator
  • Reed,

    I've never played a computer game in my life! (seriously, never once), but I do recognize the value of the technology behind it; indeed it's development through the years has profoundly influenced many aspects of things which we use in programming all the time and just take for granted.

    I wanted to commend you on putting this together and putting it out for the community here at large and offer you congratulations on it as well as an earnest "thank you". I feel confident that I speak for many others here as well.

    Way to go Reed!

    :-D


    Thanks Frank.  :)

    There always seems to be plenty of interest from beginners when it comes to making games, and it is a great gateway into programming in general.  Hopefully this API will turn out to be as solid as it appears and can then help get that budding game-developer up and running with minimal frustation, and without being overwhelmed by the big-boy frameworks.  If I can get enough support, testing, and feedback then I think there is a strong possibility of it all working out!


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
    Sunday, September 11, 2011 1:39 AM
    Moderator
  • Example 1

    Part 2

    Next we will begin to add some life to our enemies, and we'll give the player object a bit more logic as well.

    So far our player object is just taking the direct input from the keyboard and updating its location.  But this means that the helicopter can go anywhere, including off-screen.  So we are going to modify the player object's input to calculate a new position, but not actually move there.  Then we will use the OnUpdate method to test whether or not the new position is valid, and then only move there if we are still on screen.  We'll also update the helicopter to rotate on angles.

    NOTE: I realized that there is a bug in the input and currently the arrow keys are not being handled - I'll have to make some modifications to the input routine.  For now, we will stick with using WASD to move.

    We'll also add some logic to push enemy ships out of the way if the player helicopter hits them.  Later, we can change this routine to inflict damage on the player.

    Currently our enemy ships just sit there in a nice little formation.  So we will give the enemy ships some logic to move randomly around the screen while trying to stay out of each others way.

    Steps:

    1) Replace the following two code files from Step 1 with the updated classes below:

    PlayerChopper

    Imports GdiGaming
    
    Public Class PlayerChopper
        Inherits GameObject
    
        Private _NewPosition As Vector2
    
        Private _Speed As Single = 6.0F
        Public Property Speed() As Single
            Get
                Return _Speed
            End Get
            Set(ByVal value As Single)
                _Speed = value
            End Set
        End Property
    
        Public Sub New()
            Dim chopperSprite As New AnimatedSprite
            chopperSprite.SpriteSheet = "AirVehicles"
            chopperSprite.AnimationRate = 0.25F
            chopperSprite.Frames.Add(New Rectangle(0, 0, 48, 48))
            chopperSprite.Frames.Add(New Rectangle(48, 0, 48, 48))
            chopperSprite.Frames.Add(New Rectangle(96, 0, 48, 48))
            chopperSprite.Frames.Add(New Rectangle(144, 0, 48, 48))
            chopperSprite.Name = "Chopper"
            Sprites.Add(chopperSprite)
            HasCollision = True
            CollisionRadius = 14
            _Name = "Player"
            _ProcessInput = True
            _ZOrder = 1
        End Sub
    
        Protected Overrides Sub OnCollision(ByVal e As GdiGaming.CollisionEventArgs)
            MyBase.OnCollision(e)
            If TypeOf e.CollisionObject Is EnemySpaceship Then
                Dim spaceShip As EnemySpaceship = DirectCast(e.CollisionObject, EnemySpaceship)
                spaceShip.Position -= Vector2.GetVectorToward(spaceShip.Position, Position, CollisionRadius)
            End If
        End Sub
    
        Protected Overrides Sub OnInput(ByVal e As GdiGaming.GameEngineEventArgs)
            MyBase.OnInput(e)
            Dim input As GameInput = e.Engine.Input
            _RotationAngle = 0.0F
            _NewPosition = Vector2.Empty
            If input.IsKeyDown(Keys.W) Then
                _NewPosition += New Vector2(0, -_Speed)
            End If
            If input.IsKeyDown(Keys.S) Then
                _NewPosition += New Vector2(0, _Speed)
                _RotationAngle = 180.0F
            End If
            If input.IsKeyDown(Keys.A) Then
                _NewPosition += New Vector2(-_Speed, 0)
                If input.IsKeyDown(Keys.W) Then
                    _RotationAngle = 315.0F
                ElseIf input.IsKeyDown(Keys.S) Then
                    _RotationAngle = 225.0F
                Else
                    _RotationAngle = 270.0F
                End If
            End If
            If input.IsKeyDown(Keys.D) Then
                _NewPosition += New Vector2(_Speed, 0)
                If input.IsKeyDown(Keys.W) Then
                    _RotationAngle = 45.0F
                ElseIf input.IsKeyDown(Keys.S) Then
                    _RotationAngle = 135.0F
                Else
                    _RotationAngle = 90.0F
                End If
            End If
        End Sub
    
        Protected Overrides Sub OnUpdate(ByVal e As GdiGaming.GameEngineEventArgs)
            MyBase.OnUpdate(e)
            Dim bounds As Rectangle = e.Engine.Canvas.DisplayRectangle
            bounds.Inflate(-24, -24)
            _NewPosition += Position
            If bounds.Contains(_NewPosition.ToPoint) Then
                Position = _NewPosition
            End If
        End Sub
    End Class
    

    EnemySpaceship

    Imports GdiGaming
    
    Public Class EnemySpaceship
        Inherits GameObject
    
        Private Shared _Random As New Random
    
        Private _Destination As Vector2
    
        Private _Speed As Single = 4.0F
        Public Property Speed() As Single
            Get
                Return _Speed
            End Get
            Set(ByVal value As Single)
                _Speed = value
            End Set
        End Property
    
        Public Sub New()
            Dim spaceshipSprite As New AnimatedSprite
            spaceshipSprite.SpriteSheet = "AirVehicles"
            spaceshipSprite.AnimationRate = 0.75F
            spaceshipSprite.Frames.Add(New Rectangle(0, 48, 48, 48))
            spaceshipSprite.Frames.Add(New Rectangle(48, 48, 48, 48))
            spaceshipSprite.Frames.Add(New Rectangle(96, 48, 48, 48))
            spaceshipSprite.Frames.Add(New Rectangle(144, 48, 48, 48))
            spaceshipSprite.Name = "Spaceship"
            Sprites.Add(spaceshipSprite)
            HasCollision = True
            CollisionRadius = 24
            _ZOrder = 2
        End Sub
    
        Protected Overrides Sub OnCollision(ByVal e As GdiGaming.CollisionEventArgs)
            MyBase.OnCollision(e)
            If TypeOf e.CollisionObject Is EnemySpaceship Then
                Position -= Vector2.GetVectorToward(Position, e.CollisionObject.Position, _Speed)
                _Destination = GetRandomDestination(e.Engine.Canvas.DisplayRectangle.Size)
            End If
        End Sub
    
        Protected Overrides Sub OnLoad(ByVal e As GdiGaming.GameEngineEventArgs)
            MyBase.OnLoad(e)
            _Destination = GetRandomDestination(e.Engine.Canvas.DisplayRectangle.Size)
        End Sub
    
        Protected Overrides Sub OnUpdate(ByVal e As GdiGaming.GameEngineEventArgs)
            MyBase.OnUpdate(e)
            If Position.DistanceTo(_Destination) < 2.0F Then
                _Destination = GetRandomDestination(e.Engine.Canvas.DisplayRectangle.Size)
            End If
            Position += Vector2.GetVectorToward(Position, _Destination, _Speed)
        End Sub
    
        Private Function GetRandomDestination(ByVal area As Size) As Vector2
            Return New Vector2((area.Width - CollisionRadius) * _Random.NextDouble, (area.Height - CollisionRadius) * _Random.NextDouble)
        End Function
    End Class
    

    2) Run the updated example and fly the chopper around with the spaceships!

    Once you are comfortable with what the code is doing, try going to the ExampleScene class and change the number of enemy ships to load from 10 to 50.  Run the example again and see what happens!


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
    Sunday, September 11, 2011 6:56 PM
    Moderator
  • Hi Reed

    Congratulations. This looks great!

    Can you post a few screen shots of this example program?

    (Maybe reduce them to half size - for the benefit of those with slow internet ... )

    Also, can this API talk with joysticks?

    I have a Logitech Extreme30Pro joystick.

    Thanks

     


    Leon C Stanley - - A dinky di VB'er - -
    Monday, September 12, 2011 12:42 AM
  • Sure, I'll post a screenie, though there isn't much to see LOL but here is what the Example1 game looks like so far:


    As for a Joystick, no, at this time the API only supports mouse and keyboard.  I'm trying to stay completely within navite managed code so that this DLL works with VB or C# Express right out of the box and in a way that would be fairly easy to reproduce at the source-code level.

    But since the GameInput object still needs work, I will be looking into other ways of capturing input and will include joystick support if I can find a suitable way to do so.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
    Monday, September 12, 2011 1:15 AM
    Moderator
  • Example 1

    Part 3

    Let's blow stuff up! :)

    Our game isn't much of a game until our player and enemies can interact with the ability to destroy one another.  We are going to start by giving the player a means to destroy the enemy - firing a missile.

    For this part we are going to need some more assets.  The AirVehicles.png file has been updated to now include a missile animation.  There is also now an Explosion.png sprite sheet.  You can acquire the new and updated assets from the Example1Assets2.zip file.

    Steps

    1) Download the updated assets.

    2) Add the new Explosion.png to your Assets folder in the project. 

    3) Delete the current AirVehicles.png from the solution explorer, and then add the updated copy you just downloaded. 

    4) Remember to set both files to Copy to Output Directory = Copy always!

    A Note About the Graphics

    I just want to say that I hope no one is put off of the engine by these graphics.  The example graphics represent the upper limit of my talent as an artist and only look decent because Adobe Illustrator is such a powerful tool! LOL  Truly, a game made with this API can be as beautiful as the artist drawing the graphics is able to make it.

    Now then, back to the example.

    5) Add the following two new classes to the project:

    ChopperMissile 

    Imports GdiGaming
    
    Public Class ChopperMissile
        Inherits GameObject
    
        Private _Direction As Vector2
    
        Public Sub New(ByVal direction As Vector2, ByVal rotation As Single)
            Dim missileSprite As New AnimatedSprite
            missileSprite.SpriteSheet = "AirVehicles"
            missileSprite.AnimationRate = 0.25F
            missileSprite.Frames.Add(New Rectangle(0, 96, 48, 48))
            missileSprite.Frames.Add(New Rectangle(48, 96, 48, 48))
            missileSprite.Frames.Add(New Rectangle(96, 96, 48, 48))
            missileSprite.Frames.Add(New Rectangle(144, 96, 48, 48))
            missileSprite.Name = "Missile"
            Sprites.Add(missileSprite)
            IsCollider = True
            CollisionRadius = 3
            _ZOrder = 1
            _Direction = direction
            _RotationAngle = rotation
        End Sub
    
        Protected Overrides Sub OnUpdate(ByVal e As GdiGaming.GameEngineEventArgs)
            MyBase.OnUpdate(e)
            Position += _Direction
            Dim bounds As Rectangle = e.Engine.Canvas.DisplayRectangle
            If Not bounds.Contains(Position.ToPoint) Then
                e.Engine.CurrentScene.Objects.Remove(Me)
            End If
        End Sub
    End Class
    
    

    Explosion
    Imports GdiGaming
    
    Public Class Explosion
        Inherits GameObject
    
        Private _ElapsedTime As Single
        Private _DisplayTime As Single = 0.5F
    
        Public Sub New()
            Dim explosion As New AnimatedSprite
            explosion.SpriteSheet = "Explosion"
            explosion.AnimationRate = _DisplayTime
            For y As Integer = 0 To 3
                For x As Integer = 0 To 3
                    explosion.Frames.Add(New Rectangle(48 * x, 48 * y, 48, 48))
                Next
            Next
            explosion.Name = "Explosion"
            Sprites.Add(explosion)
            _ZOrder = 3
        End Sub
    
        Protected Overrides Sub OnUpdate(ByVal e As GdiGaming.GameEngineEventArgs)
            MyBase.OnUpdate(e)
            If _ElapsedTime >= _DisplayTime Then
                e.Engine.CurrentScene.Objects.Remove(Me)
            End If
            _ElapsedTime += e.Time.LastFrame
        End Sub
    End Class
    
    

    6) Update the following two existing classes by pasting this code over top of what you have so far:  

    PlayerChopper 

    Imports GdiGaming
    
    Public Class PlayerChopper
        Inherits GameObject
    
        Private _NewPosition As Vector2
    
        Private _Speed As Single = 6.0F
        Public Property Speed() As Single
            Get
                Return _Speed
            End Get
            Set(ByVal value As Single)
                _Speed = value
            End Set
        End Property
    
        Public Sub New()
            Dim chopperSprite As New AnimatedSprite
            chopperSprite.SpriteSheet = "AirVehicles"
            chopperSprite.AnimationRate = 0.25F
            chopperSprite.Frames.Add(New Rectangle(0, 0, 48, 48))
            chopperSprite.Frames.Add(New Rectangle(48, 0, 48, 48))
            chopperSprite.Frames.Add(New Rectangle(96, 0, 48, 48))
            chopperSprite.Frames.Add(New Rectangle(144, 0, 48, 48))
            chopperSprite.Name = "Chopper"
            Sprites.Add(chopperSprite)
            HasCollision = True
            CollisionRadius = 14
            _Name = "Player"
            _ProcessInput = True
            _ZOrder = 1
        End Sub
    
        Protected Overrides Sub OnCollision(ByVal e As GdiGaming.CollisionEventArgs)
            MyBase.OnCollision(e)
            If TypeOf e.CollisionObject Is EnemySpaceship Then
                Dim spaceShip As EnemySpaceship = DirectCast(e.CollisionObject, EnemySpaceship)
                spaceShip.Position -= Vector2.GetVectorToward(spaceShip.Position, Position, CollisionRadius)
            End If
        End Sub
    
        Protected Overrides Sub OnInput(ByVal e As GdiGaming.GameEngineEventArgs)
            MyBase.OnInput(e)
            Dim input As GameInput = e.Engine.Input
            _RotationAngle = 0.0F
            _NewPosition = Vector2.Empty
            If input.IsKeyDown(Keys.W) Then
                _NewPosition += New Vector2(0, -_Speed)
            End If
            If input.IsKeyDown(Keys.S) Then
                _NewPosition += New Vector2(0, _Speed)
                _RotationAngle = 180.0F
            End If
            If input.IsKeyDown(Keys.A) Then
                _NewPosition += New Vector2(-_Speed, 0)
                If input.IsKeyDown(Keys.W) Then
                    _RotationAngle = 315.0F
                ElseIf input.IsKeyDown(Keys.S) Then
                    _RotationAngle = 225.0F
                Else
                    _RotationAngle = 270.0F
                End If
            End If
            If input.IsKeyDown(Keys.D) Then
                _NewPosition += New Vector2(_Speed, 0)
                If input.IsKeyDown(Keys.W) Then
                    _RotationAngle = 45.0F
                ElseIf input.IsKeyDown(Keys.S) Then
                    _RotationAngle = 135.0F
                Else
                    _RotationAngle = 90.0F
                End If
            End If
            If input.WasKeyPressed(Keys.Space) Then
                FireMissile(e.Engine)
            End If
        End Sub
    
        Protected Overrides Sub OnUpdate(ByVal e As GdiGaming.GameEngineEventArgs)
            MyBase.OnUpdate(e)
            Dim bounds As Rectangle = e.Engine.Canvas.DisplayRectangle
            bounds.Inflate(-24, -24)
            Dim deltaPosition As Vector2 = Position + _NewPosition
            If bounds.Contains(deltaPosition.ToPoint) Then
                Position = deltaPosition
            End If
        End Sub
    
        Private Sub FireMissile(ByVal engine As GameEngine)
            Dim missileHeading As Vector2 = _NewPosition
            If missileHeading = Vector2.Empty Then
                missileHeading = New Vector2(0, -_Speed)
            End If
            missileHeading *= 1.5F
            Dim missile As New ChopperMissile(missileHeading, _RotationAngle)
            missile.Position = Position
            engine.CurrentScene.Objects.Add(missile)
        End Sub
    End Class
    

    EnemySpaceship
    Imports GdiGaming
    
    Public Class EnemySpaceship
        Inherits GameObject
    
        Private Shared _Random As New Random
    
        Private _Destination As Vector2
    
        Private _Speed As Single = 4.0F
        Public Property Speed() As Single
            Get
                Return _Speed
            End Get
            Set(ByVal value As Single)
                _Speed = value
            End Set
        End Property
    
        Public Sub New()
            Dim spaceshipSprite As New AnimatedSprite
            spaceshipSprite.SpriteSheet = "AirVehicles"
            spaceshipSprite.AnimationRate = 0.75F
            spaceshipSprite.Frames.Add(New Rectangle(0, 48, 48, 48))
            spaceshipSprite.Frames.Add(New Rectangle(48, 48, 48, 48))
            spaceshipSprite.Frames.Add(New Rectangle(96, 48, 48, 48))
            spaceshipSprite.Frames.Add(New Rectangle(144, 48, 48, 48))
            spaceshipSprite.Name = "Spaceship"
            Sprites.Add(spaceshipSprite)
            HasCollision = True
            CollisionRadius = 24
            _ZOrder = 2
        End Sub
    
        Protected Overrides Sub OnCollision(ByVal e As GdiGaming.CollisionEventArgs)
            MyBase.OnCollision(e)
            If TypeOf e.CollisionObject Is ChopperMissile Then
                e.Engine.CurrentScene.Objects.Remove(Me)
                e.Engine.CurrentScene.Objects.Remove(e.CollisionObject)
                Dim boom As New Explosion
                boom.Position = Position
                e.Engine.CurrentScene.Objects.Add(boom)
            End If
            If TypeOf e.CollisionObject Is EnemySpaceship Then
                Position -= Vector2.GetVectorToward(Position, e.CollisionObject.Position, _Speed)
                _Destination = GetRandomDestination(e.Engine.Canvas.DisplayRectangle.Size)
            End If
        End Sub
    
        Protected Overrides Sub OnLoad(ByVal e As GdiGaming.GameEngineEventArgs)
            MyBase.OnLoad(e)
            _Destination = GetRandomDestination(e.Engine.Canvas.DisplayRectangle.Size)
        End Sub
    
        Protected Overrides Sub OnUpdate(ByVal e As GdiGaming.GameEngineEventArgs)
            MyBase.OnUpdate(e)
            If Position.DistanceTo(_Destination) < 2.0F Then
                _Destination = GetRandomDestination(e.Engine.Canvas.DisplayRectangle.Size)
            End If
            Position += Vector2.GetVectorToward(Position, _Destination, _Speed)
        End Sub
    
        Private Function GetRandomDestination(ByVal area As Size) As Vector2
            Return New Vector2((area.Width - CollisionRadius) * _Random.NextDouble, (area.Height - CollisionRadius) * _Random.NextDouble)
        End Function
    End Class
    

    7) Run the example and blow up all of the spaceships!

    Right now the player has a pretty unfair advantage (besides being invulnerable!) - they can fire missiles as rapidly as they can press the spacebar, and they have unlimited ammo.  These are things we will modify in the next part of the example.

    In the meantime, can you make your player chopper explode if it gets hit by a spaceship just one time? 


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Tuesday, September 13, 2011 1:10 AM
    Moderator
  • Oops! Forgot the screen shots:

     


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
    Tuesday, September 13, 2011 2:46 AM
    Moderator
  • Wow, somebody had some spare time!

    This is pretty impressive! I think it needs a web site of its own. Great work, Reed!

    Hmm...I've been thinking of creating an Arkanoid clone...

     


    Stephen J Whiteley
    Tuesday, September 13, 2011 12:14 PM
    Moderator
  • @SJ:

    Thanks for the encouragement!

    This API project has been a labor of love LOL.  I'd say I've got a week's worth of evenings, and a weekend or two, in it so far.

    I figure if I can get from CTP through full Beta here on the forums, then the API should be fairly "proven" and ready to go stand on its own.  I'll also hopefully have some decent example content to harvest from this thread.  At that point I would definately see about getting it up on its own website somewhere.

    If I can keep it simple enough all the way through (right now the API is a total of about 29 objects and one enum), then I'd like to ultimately provide the API source code as a tutorial itself.

    I'm also considering reimplementing the entire API in SlimDX to make a high-performance version of this engine... and this would be the version that I might try to build a GUI around.  I think it could be really cool to use VS extensibility along with a DirectX-Powered version of this API to build something akin to Enterbrain's "maker" programs; only instead of standalone IDEs, they would be directly in VS 2010.  To go this far though, I'd actually love to round up interested parties and make a team effort of it.

    I actually debated on creating a Break-Out-clone for the example, but decided the first example should not have to deal with the math for the angles... but if you were to create the basic shell of yours as a 1-2-3 (then go crazy with features after that), and posted that here as say, Example 1B, it would not hurt my feelings in the least.  ;)  (LOL they also wouldn't be hurt if it didn't happen).  Either way, I'd love to download a finished exe if you ever decided to build it. =)

     


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
    Tuesday, September 13, 2011 1:43 PM
    Moderator
  • Example 1

    Part 4

    It's time to level the playing field.

    At least a little bit.  :)  Part 4 of the example is going to focus on a few changes in the PlayerChopper class only.  No game is complete without some kind of risk-versus-reward factor; that is, we need a way to win as well as a way to loose. 

    In the following code, we will make some changes to the PlayerChopper to introduce risk - we will cause collisions with a spaceship to do "damage" and we will only allow the chopper to withstand 10 hits before it is destroyed.

    We will also slow down that button-masher player, and only allow a limited number of shots-per-second (in this case, 3).

    Although the changes are relatively small, we have a few extra things to consider this time, so let's look at the code first, and then talk about the changes.

    Steps

    1) Copy the following code over top of the PlayerChopper code you have so far:

    PlayerChopper 

    Imports GdiGaming
    
    Public Class PlayerChopper
        Inherits GameObject
    
        Private _BlinkTime As Single
        Private _FireWait As Single
        Private _FramesHidden As Integer
        Private _NewPosition As Vector2
    
        Private _FireRate As Single
        Public Property FireRate() As Single
            Get
                Return _FireRate
            End Get
            Set(ByVal value As Single)
                _FireRate = value
            End Set
        End Property
    
        Private _HitsRemaining As Integer
        Public Property HitsRemaining() As Integer
            Get
                Return _HitsRemaining
            End Get
            Set(ByVal value As Integer)
                _HitsRemaining = value
            End Set
        End Property
    
        Private _Speed As Single = 6.0F
        Public Property Speed() As Single
            Get
                Return _Speed
            End Get
            Set(ByVal value As Single)
                _Speed = value
            End Set
        End Property
    
        Public Sub New()
            Dim chopperSprite As New AnimatedSprite
            chopperSprite.SpriteSheet = "AirVehicles"
            chopperSprite.AnimationRate = 0.25F
            chopperSprite.Frames.Add(New Rectangle(0, 0, 48, 48))
            chopperSprite.Frames.Add(New Rectangle(48, 0, 48, 48))
            chopperSprite.Frames.Add(New Rectangle(96, 0, 48, 48))
            chopperSprite.Frames.Add(New Rectangle(144, 0, 48, 48))
            chopperSprite.Name = "Chopper"
            Sprites.Add(chopperSprite)
            HasCollision = True
            CollisionRadius = 14
            _Name = "Player"
            _ProcessInput = True
            _ZOrder = 1
            _HitsRemaining = 10
            _FireRate = 3.0
        End Sub
    
        Protected Overrides Sub OnCollision(ByVal e As GdiGaming.CollisionEventArgs)
            MyBase.OnCollision(e)
            If _BlinkTime = 0.0F Then
                If TypeOf e.CollisionObject Is EnemySpaceship Then
                    Dim spaceShip As EnemySpaceship = DirectCast(e.CollisionObject, EnemySpaceship)
                    spaceShip.Position -= Vector2.GetVectorToward(spaceShip.Position, Position, CollisionRadius)
                    _BlinkTime = 2.0F
                    _HitsRemaining -= 1
                End If
            End If
        End Sub
    
        Protected Overrides Sub OnInput(ByVal e As GdiGaming.GameEngineEventArgs)
            MyBase.OnInput(e)
            Dim input As GameInput = e.Engine.Input
            _RotationAngle = 0.0F
            _NewPosition = Vector2.Empty
            If input.IsKeyDown(Keys.W) Then
                _NewPosition += New Vector2(0, -_Speed)
            End If
            If input.IsKeyDown(Keys.S) Then
                _NewPosition += New Vector2(0, _Speed)
                _RotationAngle = 180.0F
            End If
            If input.IsKeyDown(Keys.A) Then
                _NewPosition += New Vector2(-_Speed, 0)
                If input.IsKeyDown(Keys.W) Then
                    _RotationAngle = 315.0F
                ElseIf input.IsKeyDown(Keys.S) Then
                    _RotationAngle = 225.0F
                Else
                    _RotationAngle = 270.0F
                End If
            End If
            If input.IsKeyDown(Keys.D) Then
                _NewPosition += New Vector2(_Speed, 0)
                If input.IsKeyDown(Keys.W) Then
                    _RotationAngle = 45.0F
                ElseIf input.IsKeyDown(Keys.S) Then
                    _RotationAngle = 135.0F
                Else
                    _RotationAngle = 90.0F
                End If
            End If
            If input.WasKeyPressed(Keys.Space) Then
                If _FireWait = 0.0F Then
                    _FireWait = 1.0F / _FireRate
                    FireMissile(e.Engine)
                End If
            End If
        End Sub
    
        Protected Overrides Sub OnUpdate(ByVal e As GdiGaming.GameEngineEventArgs)
            MyBase.OnUpdate(e)
            Dim bounds As Rectangle = e.Engine.Canvas.DisplayRectangle
            bounds.Inflate(-24, -24)
            Dim deltaPosition As Vector2 = Position + _NewPosition
            If bounds.Contains(deltaPosition.ToPoint) Then
                Position = deltaPosition
            End If
            If _HitsRemaining < 1 Then
                e.Engine.CurrentScene.Objects.Remove(Me)
                Dim boom As New Explosion
                boom.Position = Position
                e.Engine.CurrentScene.Objects.Add(boom)
            End If
            If _BlinkTime > 0 Then
                If _FramesHidden = 0 Then
                    _Visible = Not _Visible
                    _FramesHidden = 5
                End If
                _FramesHidden -= 1
                _BlinkTime -= e.Time.LastFrame
            Else
                _BlinkTime = 0.0F
                _FramesHidden = 0
                _Visible = True
            End If
            If _FireWait > 0 Then
                _FireWait -= e.Time.LastFrame
            Else
                _FireWait = 0.0F
            End If
        End Sub
    
        Private Sub FireMissile(ByVal engine As GameEngine)
            Dim missileHeading As Vector2 = _NewPosition
            If missileHeading = Vector2.Empty Then
                missileHeading = New Vector2(0, -_Speed)
            End If
            missileHeading *= 1.5F
            Dim missile As New ChopperMissile(missileHeading, _RotationAngle)
            missile.Position = Position
            engine.CurrentScene.Objects.Add(missile)
        End Sub
    End Class
    
    

    2) Run the exmaple and shoot down the spaceships, but be more careful now!

     

    More Information

    One of the things you may have noticed if you ran the sample already is that the chopper "blinks" briefly when it is hit.  You've probably seen this behavior in lots of 2D games like this.  But why do we do it?  Because the player expects it, and its a good way to show that we've taken damage?

    Well, in part, yes, this is why we do it.  But also, we have to consider how our logic and engine loop are executing...  If the player can get hit only 10 times, and we check collision and mark that we have been been hit 30 times per second, then if we did not flag ourself as having "just been hit" we would wind up consuming all 10 of our "lives" in the first 1/3 of the first second in which we hit an enemy!  In just 10 frames worth of time, we would be dead - from the perspective of the player, we only got 1 hit, not 10.

    So this is where the blinking plays an important role behind the scenes - it is our "just been hit" flag.  So long as our blink time variable is greater than 0, we are going to go through the logic of blinking and ignore subsequent collisions until we are done blinking.  In this way, if we blink for 2 seconds, we can only be hit once every two seconds.  Perhaps you already suspect that you could then increase difficulty at higher levels by lowering the blink time!

    Implementing the fire rate limit is much the same as the blinking.  We just need to record how long it has been since we last fired, and supress firing again until a certain "cool-down" has elapsed.  Again, are you suspecting that we can create different weapons with different rates of fire, just by changing our "cool-down" value?!

    So we've added an intitialized a few new local fields.  And we've made a few minor modifications to the OnCollision and OnInput routines.  But lets look at the state of our OnUpdate routine after this last round of changes...

    It is starting to look a little unwieldy... So far it isn't bad, but if we are not careful, it could grow into something which becomes difficult to manage.  Right now we are implementing very simple logic in order to keep things easy to follow.  But it won't be long before our code may begin to require some additional helper-methods and/or objects to facilitate our game logic and keep things neat, organized, and easy to follow (oh, and efficient!).  So just be prepared to see more substantial changes to some of these classes as the game evolves, and always try to be thinking a step ahead in your development as you design your own game objects.

    We'll end Part 4 with a good challenge:

    Can you implement a GameObject which serves as a "GUI Label" to Draw a String at the top of the Canvas which shows the number of remaining hits?  Here's a hint: if you need a new graphics-related object while drawing, create it, use it, and dispose it - don't try to keep it hanging around.

     


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
    Wednesday, September 14, 2011 2:50 AM
    Moderator
  • The DLL cannot be added to the toolbox in VS2008 the message is "... is not a Microsoft .Net module." The same DLL can be added to 2010.

    For VB Express 2010, if the steps you describe are followed precisely, on first accessing the designer after adding the additional classes, there is a message 'Value is not within expected range".  The code or location can't be identified (line 0, column 0).  The designer will not display.  Closing the designer and re-opening it shows the canvas correectly and the error does not reoccur.

    There is a small typo in Part 3:
    4) Remember to set both files to Copy to Output Directory = Copy always for both files!

    Wednesday, September 14, 2011 4:01 AM
  • The DLL cannot be added to the toolbox in VS2008 the message is "... is not a Microsoft .Net module." The same DLL can be added to 2010.

    For VB Express 2010, if the steps you describe are followed precisely, on first accessing the designer after adding the additional classes, there is a message 'Value is not within expected range".  The code or location can't be identified (line 0, column 0).  The designer will not display.  Closing the designer and re-opening it shows the canvas correectly and the error does not reoccur.

    There is a small typo in Part 3:
    4) Remember to set both files to Copy to Output Directory = Copy always for both files!


    Ok, I should have clearly stated that the API is targeting:

    .Net Framework 4 Client Profile and is intended for use with Visual Studio 2010.

    Good catch, Acamar.

    Now on the Express 2010 issue, this is odd.  I have created several new projects without incident, but I am using Premium.  I will install Express somewhere though and check this out.  Can anyone else confirm this issue? -edit- lol forgot to say, thank you for the detailed report! /edit

    Finally, LOL on the typo, thanks!  I'll go fix that.  :)

     

     


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
    Wednesday, September 14, 2011 4:21 AM
    Moderator
  • For anyone who was following this and wondered if it might have died, fear not! (lol) the project will now live on at CodePlex.  You can join the current project at:

    http://gdigaming.codeplex.com

    I'm about to get real busy again so the project may not grow quickly, but at least this new home gives it a place where perhaps it can withstand my severely fractured interests.  =P


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
    Thursday, December 29, 2011 11:54 PM
    Moderator