A quick tour of the Xenocide.XNA codebase.
Assorted Prequel Notes:
This document is intended to give someone who is new to Project Xenocide a quick overview of where the pieces that make it up are, and how they interact.
Also, this document is a work in progress, so if you have additional questions, corrections or comments, please let us know.
This document was as accurate as I could make it at the time of writing (latest revision was 2007-02-05) but it’s probable that some of the information is now out of date.
When paths to files are given below {PX} refers to the root directory you’ve copied the files to.
Architecture Overview:
Xenocide.XNA is written in C#, using the Microsoft XNA framework.
Currently, the only other 3rd party library being used is CeGui#. Which is used to provide UI widgets. (Push buttons, scroll bars, Edit boxes, text, etc.)
As XNA framework is being used, the main “pump” of the program is a class derived from Microsoft.Framework.Xna.Game. In Xenocide, this class is called Xenocide, and is in the file {PX}/Xenocide/Source/Xenocide.cs
As you probably know, the Xna framework is essentially a loop that looks like
while(true)
{
Xenocide.Update(GameTime);
Xenocide.Draw(GameTime);
}
Where Update() is where we update the game’s state (e.g. Position of craft, progress of research, etc.) And Draw() is where we render images to the display.
Now, as Xenocide has a great many screens, there is a separate class for each screen, and a ScreenManager class that keeps track of the screen that is currently being shown. So, the logic flow is:
Xenocide.Update() calls ScreenManger.Update(), which calls Update() for the screen currently being shown.
Likewise, Xenocide.Draw() calls ScreenManger.Draw(), which calls Draw() for the screen currently being shown.
The Screens (and ScreenManger) are found in {PX}/ Xenocide/Source/UI/Screens.
Anatomy of a Screen:
All screens derive from the base class Xenocide.UI.Screens.Screen.
As well as Update() and Draw(), the other major function in each screen is CreateCeguiWidgets(). This is called just before the screen is about to be shown, and it’s function is to create the CeGui# widgets that will appear on the screen.
Each Screen also has a number of event handlers, to respond to the user interacting with the CeGui# widgets. E.g. Clicking on a mouse button.
How updating works
The Screens themselves don’t maintain the “global game state”. This is kept in the GameState class (or one of its contained classes.). Each screen only holds enough game state information to do its job. E.g. consider the “purchase items” screen. The screen will track the number and type of items being requested as the player puts together the order. Then, when the user finally OKs the order, a “shipping container” is created in the GameState and the user's funds are debited
I’ve tried to separate the code into two trees, “Model” and “UI”.
“Model” represents the game state itself, and should have no idea of how it is presented, while “UI” handles the presentation of the game state, and interaction with the user. (It’s essentially the Model/View pattern.)
The GameState tree is rooted at {PX}/ Xenocide/Source/UI/Model.
Screens can change the GameState in one of two ways. Firstly, they can change it directly, in response to user activity. E.g. the purchase items screen mentioned above. Second, they can call GameState’s Update() function. Which tells GameState to update itself to reflect a given period of time passing.
How GameState Update()works
Actually, the above isn’t going to be quite true. The GameState isn’t going to have an Update() function, rather, its contained classes will. To explain further:
“GameState” is the root node for the state.
Note that GameState doesn’t contain just state (i.e. the information we need to save/load to file) it also needs to contain behaviour. (So possibly State in these names should be changed to Model.)
“GameState” can (and probably should) be broken down into smaller sets: e.g. GeoState, Battlestate, Options.
GeoState and Battlestate each need their own GameTime.
So GeoState and Battlestate will have their own update() methods. It’s inside these methods that we do the tick slicing.
The Update() functions of the Geoscape and Battlescape screens will call the Update() functions of the GeoState and Battlestate, respectively.
In many of the screens, game time doesn’t pass. In fact, the only ones where it does are the battlescape, the geoscape (sometimes) and the aeroscape. So in all other screens, Update() is a no-op.
How CeGui# activity (e.g. User clicking on a button) enters the loop
Each screen can have a number of CeGui# components. These derive from Framwork.Xna.GameComponent, so the Xna Framework calls their Update() function after it calls Xenocide’s Update(). And likewise for Draw().
In the CeGui# Update() the controls
Look for user input (mouse/keyboard activity),
Convert this into widget behaviour (e.g. button being pressed, slider being moved, text entered into edit box, etc.)
Call any event handlers the screen has registered for the behaviour
Other notes
As well as separating the View from the Model, I also plan to partition the screens themselves. That is, I plan for screens to be implemented in 3 pieces
Interfacting with CeGui# (and the main loop).
Control logic for the Screen
3D scene(s) on the screen
So,
the screen derived class would provide the CeGui# interface, and the Update() and Draw() hooks.
A second class would class would provide the logic. (e.g. In the purchasing screen, it would keep track of items being ordered, check that the player had sufficient funds and storage space, etc.)
A third class would be provided to encapsulate any 3D scene.
The reason for this is to compartmentalize the things we’re doing, so that when we port to Linux, we change as little code as possible. Currently we’re not sure when (or if) CeGui# or XNA will be available for Linux. So, by putting them into their own parts we can hopefully just replace CeGui# and/or XNA when/if needed.
The GameState class is probably going to get Load() and Save() functions to load/save the game. It will then delegate to the enclosed classes
Need to document time slicing.