Screens
Screens are what I've been using, prior to implementing Cameras. So they may change. Here's how it all works now, and while the names may change in the future, the basic concept will probably remain mostly the same.
In Tutorial 7 we defined custom game objects as separate classes in our project. In doing so we abstracted a lot of detail out of our game class; not only the construction of the little ship & turret tree, but also the Update code that moved a ship back and forth across the screen. The primary focus on putting these things into a Ship class was to make them readily reusable - but did you notice it really helped tidy up the Game class? In part there is a philosophical benefit to abstraction in that it simply helps to keep things looking tidy, if you're into that sort of thing.
In the previous article A forest for your trees, we simulated this strategy somewhat in a hypothetical Game class that defined some make-believe game objects as members:
We ended up with 8 game objects or trees, like so:


And in theory you would manage (update/draw) all 8 of them directly from within the game class. That's not too bad, but, half of these objects are meant for the main menu game state, and the other half belong to the actual "playing the game" game state. The only way to know which ones even belong to which state is to read the comments above the member declarations! I'm no coding guru, but even I know that's turning into a sketchy situation.
If we take the time to define custom Screens, just like we defined custom game objects in Tutorial 7, we can perform yet another level of abstraction that will not only help organize us, and help us maintain game state, it will again make our game class a more tidy, simple thing to work with.
Tutorial 7 also walked us through creating custom game objects by deriving new classes from Sprite. You do something like this...
We can do the very same thing to create custom Screen objects from our base Screen class. Let's build some hypothetical screens to abstract those 8 game objects into 2 screens representing their respective states. First here's a pseudo Main Menu Screen class:
It looks like this:

Now how about a pseudo game screen class:
There we go:

Nevermind the naming mis-match on the diagram; the "game" node represents the "TheGameItself".
Now instead of adding 8 game objects to our game class and organizing them with code comments, we can use two Screens with names that convey their purpose:
This gains us a few benefits. With the addition of a Screen node, we can now transform (move rotate scale etc) an entire Screen of "stuff", be it the main menu or the game itself, something we couldn't do easily prior to implementing Screens. This also gives us something of a "choke point" for state management - if we want to draw the Main Menu, we just tell the main menu screen to .Update or .Draw and thanks to the underlying Sprite class, the rest more or less runs itself. All our Game really has to worry about now is calling the Update & Draw methods of whichever Screen(s) are appropriate for the current Game State.
XNA.Framework.Game.Components and the ScreenManager
This has all been very cute, but it seems that all we've done is shuffled the question of which Game Objects to draw to which Screens to draw. What we've done so far helps organize us, but it hasn't helped us answered that important question.
You may be pleased to know we can eliminate the need to ask this question all together. We can simply update/draw whichever Screens happen to be Enabled and/or Visible at the moment. For this, we will borrow upon the Components member of the XNA.Framework.Game class.
In brief, XNA.Framework.Game has a built-in Components collection. This collection is meant to hold objects which derive from GameComponent and/or DrawableGameComponent, but in truth can hold any object which implements the IGameComponent/IUpdateable/IDrawable interface(s). The Game.Components collection is handy because, similar to how our Sprite class was created to automatically draw & update it's children (if any), XNA.Framework.Game understands that if it finds anything in it's Components collection, it should Update and Draw whatever it finds in there. There are a few useful features of this which we're going to borrow upon:
- The IUpdateable and IDrawable interfaces have UpdateOrder and DrawOrder properties, respectively. When XNA.Framework.Game processes it's Components collection, it uses these values to determine which objects get Updated first and which ones get Drawn first
- The IUpdateable interface has an Enabled property (Boolean). The Game class will only Update components where Enabled=True
- Similar to Enabled, the IDrawable interface has a Visible property and only objects which are Visible will be Drawn
Now you may have noticed some Interfaces being included in the base Screen class in the previous article, let's get another look at that:
Implementing these interfaces allows our Screen class to be "registered" or added to the Game.Component collection. Alternatively, you could be dealing with classes that inherit from GameComponent or DrawableGameComponent, which Implement these interfaces for you. Since our Screen inherits from Sprite, this hasn't been done for us so I'll just have the Screen class implement the necessary interfaces.
The Screen Manager class is going to be our middle-man between our game code and the Screens stored in the Game.Components collection. The Screen Manager is going to add our Screens to the Collection for us, set DrawOrder for us, set Enabled & Visible for us, all that stuff. We'll just use the "Add", "Load" and "Unload" methods of the Screen Manager, for the most part. Here's the class, which I'll post for your amusement. If I get around to making a video about this I can go into more detail:
You'll see some funky oddities in there but it's not too scary.
Let's sum this situation up here before I get carpel tunnel:
- Sprites are the building blocks - they contain the code to invoke the Matrix cascade and contain the code necessary to facilitate the creation of tree nodes.
- Game Objects are reusable custom Sprite classes, typically with their own custom Update code and usually contain other child sprites to create small trees.
- Screens are the building blocks of State management. They derive from Sprite but typically don't require a texture, per se. They mainly exist to act as parent nodes so that we can graft related Game Objects and Sprites together into a Game State context.
- We use the Screen Manager to Register screens with the Component collection. We use the "Load" and "Unload" methods of the Screen Manager to basically set the DrawOrder, Enabled and Visible properties of Screens, which in turn causes the Component collection to change their order, start/stop Updating, or start/stop Drawing them.
- Game State is defined by whatever the state of our Enabled/Visible properties are on the Screens in the component collection - when a game starts, it registers it's Screens through the Screen Manager and Loads the Main Menu Screen. The Game State simply remains on the Main Menu Screen until changed, leaving no real need to question Game State (we simply allow all Enabled/Visible components in the Game.Component collection to Update & Draw as per their Enabled/Visible properties).
Once you've got this in place and you make a game, add some screens with the screen manager and Load one, you essentially Update and Draw your game by calling MyBase.Update and MyBase.Draw from your game class. This will trigger the underlying XNA.Framework.Game to Draw & Update, which will cause it to process it's Components collection where your Screens live. In the end, you remove all Update and Draw calls to custom objects & screens from your game class and just let the underlying XNA Game process it's Components collection, using the Screen Manager to control which Screens in the collection end up getting Updated & Drawn.
My apologies for the verbosity, I guess it's a lot to cover. There's still more to come...
0 comments:
Post a Comment