1/14/08

A forest for your trees

Trees

The "parent/child" relationship afforded by the Sprite class can be thought of as an unordered tree structure:



In Star Maze, I've got a game object called PlayerShip. Like all game objects it inherits from Sprite:

Public Class PlayerShip
 Inherits Sprite
 'Update, Draw, etc.
End Class

Considering Sprites to be tree nodes, any class inheriting from Sprite (including PlayerShip) becomes a valid tree node and can be diagrammed as such:



It's not much to look at on it's own so let's use the AddChild method of the PlayerShip to attach a Turret sprite:

PlayerShip.AddChild(Turret)



The thrusters for the player's ship are separate game objects. Like the PlayerShip (and like all game objects we'll make with this engine), a Thruster is another game object derived from Sprite:

Public Class Thruster
 Inherits Sprite
 'Update, Draw, etc.
End Class

We'll add a couple afterburner sprites to the Thruster,

Thruster.AddChild(YellowAfterburner)
Thruster.AddChild(BlueAfterburner)

and here's the resultant tree diagram for the Thruster object:


Now we've got two different game objects, both of which inherit from Sprite: "Ship" and "Thruster". You can also interpret this as having two sprite trees: the PlayerShip tree and the Thruster tree:



So far in the tutorials we've been using the AddChild method to append simple "leaf" nodes to sprites, but we can also do something slightly more profound, and that is to graft one entire tree onto another:

PlayerShip.AddChild(Thruster)



By adding the root node of the Thruster tree as a child of PlayerShip, the entire Thruster tree is grafted to the PlayerShip.

Thinking about Sprites as tree nodes just gives us an alternate way to describe or illustrate the "parent/child" system Sprites employ.

Trees in a Forest

The "games" created in the tutorials thus far are simple programs meant to demonstrate classes like Sprite and VBContentManager (et al). None of the tutorials have put much thought into organizational techniques yet. A typical tutorial is structured like this:

Public Class MyGameTutorial
 Inherits Game

 Private GameObject1 As SpriteTree1
 Private GameObject2 As SpriteTree2

 'etc.

 Protected Overrides Sub Update()

 GameObject1.Update()
 GameObject2.Update()

 'etc.

 End Sub

 Protected Overrides Sub Draw()

 GameObject1.Draw()
 GameObject2.Draw()

 'etc.

 End Sub

End Sub

There may be some game object classes defined somewhere, but the general structure so far has been to create instances of game objects as private members of the Game class, and then simply Update & Draw them from within the Game's Update and Draw methods. Which is fine for a quick tutorials dealing with a limited number of sprites and game objects. But when we eventually get to the point where we're creating an actual game, we're going to end up with a lot more than just "a limited number of sprites & game objects". Our lack of organization will turn into a problem.

Consider a tiny, theoretical game with 1 player, 2 bad guys, and a high score. This game starts up on a "main menu" with various options (new game, continue, quit, graphic settings). You can assume game object types such as BadGuyShip and PlayerShip (etc) to be irrelevant, all that matters is they derived from Sprite and are therefore valid tree structures of some kind:

Public Class MyGameTutorial
 Inherits Game

 'Main menu stuff
 Private objNewGame As NewGame
 Private objGraphics As Graphics
 Private objQuit As Quit
 Private objContinue As Sprite

 'Game stuff
 Private objShip1 As BadGuyShip
 Private objShip2 As BadGuyShip
 Private objPlayer As PlayerShip
 Private objHighScore As Sprite

 'Initialization, etc.

 Protected Overrides Sub Update()
 'Which ones do I update?
 End Sub

 Protected Overrides Sub Draw()
 'Which ones do I draw?
 End Sub

End Sub

Here's all of our game objects, in tree view.



When this game runs and the update cycle comes around... which game objects do we update? High Score? BadGuy #2? The "Quit" menu item? We're now having to ask ourselves a new question: "Where am I?".

There's a few ways to answer this question; you could just define a string called WhereAmI and if WhereAmI = "MainMenu" then you update/draw main menu stuff, and if WhereAmI = "PlayingTheGame" you update/draw the game stuff. And that would work, but as you add new features and possible "places" you could potentially be in the program (main menu, the game, the inventory screen, the game over screen, any of those but paused, etc) it becomes apparent that there may be better approaches.

First of all, we need to get organized. We need a better method of organization for our game objects. Knowing where we are, ergo what to update & draw, will tie closely into our organizational strategy.

Ironically enough one path to organization can be found in adding more Sprites. Not just any Sprite though; a special game object which is a bit of a Sprite hack, to tell the truth. It's called a Screen:

Public MustInherit Class Screen
 Inherits Sprite
 Implements IGameComponent
 Implements IDrawable
 Implements IUpdateable

 'etc

End Class

Screens are going to help us turn this:



Into this:




Once you're comfortable with the idea of Sprites as tree nodes, this is pretty simple; you define a MainMenu Screen and then just graft all your main menu game objects via MainMenu.AddChild(). A Screen is basically a container, a bucket into which we can toss game objects; it's the forest of our trees.

Now anything we do to the Screen node - position, rotation, scale - the matrix cascade is going to apply that to every single attached child as well. This is our secret weapon that will let us make fancy screen transitions without too much of a problem.

Ok this is just the beginning of what I want to get into with this, but it's taken me 2 days just to get this much done so I'm going to publish this and start working on part 2 - the Screen Manager, which is what we'll use to show / hide / order screens, we can even use it to show screens & dialogs "modal", continuing to update and draw our game but without loosing our place in code. And then, the great paradigm shift: Cameras, which may change all of it.

0 comments: