The Mailing List
On This Web Page
Design ConceptsThis page outlines the design concepts that went into the GridSlammer toolkit. It will first discuss the basic object oriented structure of system and how it is intended to be used. It will then discuss the specifics of how images are rendered and how the pseudo-3D effect is achieved (it is not as simple as you would first think).
The Object Oriented ApproachWhen I first started GridSlammer, I was working as a Windows Visual C++ developer. Because of this, some Windows'ish naming conventions have found their way into GridSlammer. I've hung onto the ones that I found useful, such as prepending 'm_' to all member variables and 'C' in front of class names. Otherwise, GridSlammer is designed to be as cross-platform and moduler as possible. There are four primary classes used by the toolkit:
In a typical game, it all works like this:
A single CGridView object is instantiated. As part of instantiating the CGridView, an appropriate sized game window is created, or the computer switches to the appropriate fullscreen resolution. A single CGridStage is instantiated. Memory is allocated for the two dimensional array of grid squares and the list of active grid objects. These are initially empty. The LoadGrid method is called to load a specific game map. Grid Objects are instantiated and inserted into the grid where appropriate.
The 'Process' method of the CGridStage object is called. This generates one iteration of interaction between all of the game objects. Game characters that are in motion move another step. Explosions that are in progress expand a little more, and so on. None of this is displayed on the screen, it is only updating the state of internal variables that represent the game simulation. The Process method does this by looping through the list of active grid objects and calling their individual 'Process' methods. Each object is responsible for changing its position in the grid, managing collisions with other objects, and even creating or destroying objects as needed. For example, if a missle object discover that its latest step causes it to collide with a tank object, it might call a method on the tank which lowers the tank's armor count. It would then create an explosion object and delete itself.
After the 'Process' call to the CGridStage object has returned, the 'Render' method is called. This takes the current state of objects within the grid and renders a visual representation of them into the window represented by the CGridView object. Only those objects that are within the viewable area are rendered. This is accomplished by examining a two dimensional array CGridObj pointers. Each position in the array represents a single grid square. The pointer in a given position is the head of a linked list containing all of the grid objects occupying that grid square. As an Object moves through the grid (in the Process phase), it must update the linked lists to properly reflect its position. As it leaves a square and enters a new one, it removes itself from the old square's linked list and inserts itself into the new square's list. Because of this, the CGridObj array can be used as a shortcut to locating all of the objects in a specific grid region; it is not necessary to iterate through all active objects to locate those in a given square.
The game continues with repeated calls to the CGridStage Process and Render methods.
NOTE: A grid square always starts on a X,Y position in which X and Y are whole numbers. It is exactly one distance unit across. Thus the grid is defined by gridlines running at the whole number positions. A grid object can occupy a non-whole (fractional) position in the grid.As mentioned above, the GridSlammer toolkit provides the CGridObj base class as well as the CGridActor class derived from it. It is a rare occasion, however, that you would use these classes directly. It is more likely that you will derive your own subclasses and override the Process and Render methods. By overriding the Process method, you can customize the behavior of your grid objects. This method is where you would provide the controlling 'AI' for your game characters. By overriding the Render methor, you can customize how your object is drawn. If your object is always represented by a single, static image, then it is not necessary to override this method. If, however, you want to show a different image depending on the direction the object is facing, or show your character's limbs in different positions, you will need to override the Render method.
NOTE: The CGridStage class deals only with pointers to CGridObj objects, even when the objects pointed to really represent a derived class. For this reason, the Process and Render function in CGridObj are declared as 'virtual'. This causes the appropriate version of Process or Render to be called for an object, even when the pointer to the object is not of the appropriate (derived) type.
The Rendering ProcessIn the above section, I spoke a little bit about the rendering process. This section will cover that topic in more depth and will discuss some of the complexities involved in creating the psuedo 3D effect. The algorithm used for rendering has changed and improved much since the early versions of GridSlammer, so even if you have read this section before, I recommend reading it again to make sure you know about these changes.
The view of the grid is that of an observer hanging in the air looking down at the grid at angle, facing in the direction of the positive X,Y corner. Thus if we consider the positive Y direction to be north and the positive X direction to be east, the observer is always facing northeast. When we render a scene, we want to render more distant object first and the nearer objects last (relative to our airborn viewer).
Grid tiles are drawn in horizontal stripes across the screen. Because we are looking at the grid 'from an angle' in the isometric perspective, this means tiles are rendered diagonally through the grid. We start with the tile that would be in the upper right extreme of the screen. We then move in the negative X direction in the grid and the positive Y direction. This results in a horizontal strip of tiles being rendered. We actually render the stripe in three passes. First the floor tiles are draw, then the mobile objects (game characters) are drawn, and finally the wall and other tall tiles are drawn. This is done so objects that partially obscure other objects are draw in the correct order. Eventually, this algorithm will be modified into a one pass technique that actually looks at object or tile height to sort the render order.
After we have rendered a stripe of tiles, we change our stripe starting point to the next 'closest' stripe, the one that would appear just below the previously rendered one. We alternate between decrementing the X and Y value of the starting point. This allows us to walk down the right edge of the screen, selecting the appropriate starting tile for each successively closer horizontal stripe.
Goto the API Specifications Page