SUMMARY: One of my long-term projects is to create a simple reusable 3D engine for playing with interactivity. I have a bunch of code from an earlier project that I’m refactoring into something that will serve as an example of a more complex XNA application. These notes are pretty rough, as they reflect speculative thinking about work in progress. These notes are also actively edited, so the contents may change unexpectedly.
I’ve ported some really basic stuff, refactoring along the way:
- BaseGame.cs split into partial classes
- Singleton Pattern, made less twiddly to use, the basis for all Manager classes
- SettingsManager.cs, cleaned up
I’m now at the point that I want to port over something that actually draws on the screen. This is a complex subject, because we need to have a system for drawing, with a well-defined order of operations coordinated between multiple system objects.
THE OLD SYSTEM
Just about every modern video game uses multiple framebuffers, showing a completed frame while drawing on a hidden one before switching. Drawing a frame usually consists of multiple drawing passes, such as a 3D view with a 2D user interface superimposed on top of it. The drawing process itself may require multiple passes as well for special effects.
In our old game, we had the notion of “Screens” managed by a “ScreenManager”. Screens shared a common subclass that defined typical game loop behaviors, namely:
- Update() – processing time to do calculations
- Paint() – the command to draw to the current framebuffer
- HandleButtons() – event handling for gamepad button presses
- HandleKey() – event handling for key presses
- HandleRepeatedKey() – event handling for repeating key presses
- Prologue() – pre-run initialization of data structures
- Epilogue() – post-run cleanup and release of data structures
There were three types of Screens defined: GameScreen, GUIScreen, and DebugScreen. ScreenManager managed them as a stack, drawn in the order listed.
What I didn’t like about this arrangement is that GameScreen is essentially the game program. It’s buried in the class hierarchy under Classes/Layout/GameScreen. I would never have thought to look there for game initialization and running, expecting it to be more top-level in the hierarchy. The second issue I had was that a Screen seems more like a display device to me, not something that would even have high-level logic in it. I can accept the notion of a Screen being smart enough to draw a display list, but I don’t like it having player AI and level logic stored in it. I’d prefer that to be somewhere else.
The other main screen is DebugScreen, which is what draws the debug console. It works in concert with the DebugManager, which is what keeps track of all the debug messages and processes commands. DebugScreen knows how to display that data using drawing commands, which I have no problem with. It is a cleaner separation between display code and data management. GUIScreen is a hacky screen that draws between GameScreen and DebugScreen, and was used for display giant bitmaps as signage when no one was playing the game in the museum space. It’s used by WorldPlayer, which is what implements the game logic through its various stages. This is a little screwed up too, so I’d want to clean it up.
THE NEW SYSTEM
I’m moving the game logic out of GameScreen. Screens and the ScreenManager will be implemented differently, as sets of display lists that are bound with data from other system players and objects. Instead of having game-functional names, they will have be organized by displaytype. For example:
- ThreeDeeScreen – draws a 3D world from its displaylist consisting of Visuals
- ConsoleScreen – draws 2D text from its displaylist consisting of lines of text and other properties
- SpriteScreen – draws 2D sprites from a displaylist consisting of sprite elements
The ScreenManager will maintain a stack of these screens, and will also be called directly by the Game Loop. ScreenManager will know how to construct its screen stack based on XML files. Screens can be active or not, toggled by external controllers.
To handle actual text input, there’s a new class called InputManager that consolidates the gamepad button and keyboard state into one place. It’s called once during Game Loop so that all interested subscribers can receive notification. The DebugManager will be one of these, as will any Players that may need control input. There are some additional complexities that may be added, but for now we can just implement this pretty simply.
I’ve just written InputManager, and at a minimum I need to rewrite the Screen and ScreenManager so I can write ConsoleScreen. While the ScreenManager will eventually be initialized from a config file, for now I can just hard-code the creation chain.
Has notions of visibility, event consumption, viewport, and name. Virtual methods are Load(), Unload(), and Paint().
A Screen needs to have a source of data to draw, the “display list”. Should the base class have the notion of a DisplayList bound to it at creation time? Maybe something like ScreenDisplayList, ScreenDisplayList generics. ScreenDisplayLists would accept an ADD command. The subclassers of Screen are responsible for implementing storage of a ScreenDisplayList.
This might be overly fancy for what I need, but we’ll see how it goes.