This guide assumes a certain level of knowledge. If it is confusing, perhaps you should brush up on some of these concepts.
It is expected the reader is comfortable in an object oriented environment. All important parts are encapsulated into classes.
This guide makes use of the Design Patterns "Model View Controller" (MVC) and "Mediator". If this sounds foreign to you, I reccommend checking out the book "Design Patterns" by Gamma et al. or just surfing the web for tutorials. You may be able to follow along without having previous exposure to Design Patterns, as their purpose is quickly evident to people familiar with Object Oriented programming.
It's always a good idea to sketch out your game either with pictures or text before you begin coding.
We will start by trying to create a program where a little man moves around a grid of nine squares. This is a overly simple example, but easily extendable so we won't get tied up in the game rules, instead we can focus on the structure of the code.
The choice of MVC should be pretty obvious where a graphical game is concerned. The primary Model will be discussed later under the heading The Game Model. The primary View will be a PyGame window displaying graphics on the monitor. The primary Controller will be the keyboard, supported by PyGame's internal pygame.event module.
We haven't even got to the Model yet, and already we have a difficulty. If you are familiar with using PyGame, you are probably used to seeing a main loop like this:
#stolen from the ChimpLineByLine example at pygame.org
In this example the Controller (the "Handle Input Events" part) and the View (the "Draw Everything" part) are tightly coupled, and this is generally how PyGame works, at every iteration of the main loop, it is expected that we will check for input events, update all the visible sprites, and redraw the screen. However the MVC pattern requires the View and the Controller to be separate. Our solution is to introduce a Tick() function that the constantly looping main loop can call for both the View and the Controller. That way there will not be View-specific code in the same location as Controller-specific code. Here is a rough example:
Here is some more info on the MVC pattern: http://ootips.org/mvc-pattern.html
Let's examine the infinite while loop in the last bit of code. What is it's job? It basically sends the Tick() message out to the View and the Controller as fast as the CPU can manage. In that sense it can be viewed as a piece of hardware sending messages into the program, just like the keyboard; it can be considered another Controller.
Perhaps if time affects our game there will be even another Controller that sends messages every second, or perhaps there will be another View that spits text out to a log file. We now need to consider how we are going to handle multiple Views and Controllers. This leads us to the next pattern in our architecture, the Mediator.
We implement the Mediator pattern by creating an EventManager object. This middleman will allow multiple listeners to be notified when some other object changes state. Furthermore, that changing object doesn't need to know how many listeners there are, they can even be added and subtracted dynamically. All the changing object needs to do is send an Event to the EventManager when it changes.
If an object wants to listen for events, it must first register itself with the EventManager. We'll use the weakref WeakKeyDictionary so that listeners don't have to explicitly unregister themselves.
We will also need an Event class to encapsulate the events that can be sent via the EventManager.
Here is a rough idea how this might be integrated with the previous code.
As we get more and more listeners, we may find that it's inefficient to spam every listener with every event. Perhaps some listeners only care about certain events. One way to make things more efficient is to classify the events into different groups.
For the purpose of this guide, we'll just use one kind of event, so every listener gets spammed with every event.
Here is some more info on the Mediator pattern: http://ootips.org/observer-pattern.html
If you try to use this particular Event Manager class for your own project, you might notice it has some shortcomings. In particular, if a block of code generates events A and B sequentially, and a listener catches event A and generates event C, the above Event Manager class will process the events in the order A,C,B, instead of the desired order of A,B,C. In Part 3, we will see an example of a more advanced Event Manager.
Here's the basic Model:
Game is mainly a container object. It contains the Players and the Maps. It might also do things like Start() and Finish() and keep track of whose turn it is.
A Player object represents the actual human (or computer) that is playing the game. Common attributes are Player.score and Player.color. Don't confuse it with Charactor. Pac Man is a Charactor, the person holding the joystick is a Player.
A Charactor is something controlled by a player that moves around the Map. Synonyms might be "Unit" or "Avatar". It is intentionally spelled "Charactor" to avoid any ambiguity with Character which can also mean "a single letter" (also, you cannot create a table in PostgreSQL named "Character"). Common Charactor attributes are Charactor.health and Charactor.speed.
In our example, "little man" will be our sole Charactor.
A Map is an area that Charactors can move around in. There are generally two kinds of maps, discrete ones that have Sectors, and continuous ones that have Locations. A chess board is an example of a discrete map. The screen in Scorched Earth, or a level in Super Mario are examples of continuous Maps.
In our example, the Map will be a discrete Map having a simple list of nine sectors.
A Sector is part of a Map. It is adjacent to other sectors of the map, and might have a list of any such neighbors. No Charactor can be in between Sectors. If a Charactor is in a Sector, it is in that sector entirely, and not in any other Sector (I'm speaking functionally here. It can look like it is in between Sectors, but that is an issue for the View, not the Model)
In our example, we will allow no diagonal moves, only up, down, left and right. Each allowable move will be defined by the list of neighbors for a particular Sector, with the middle Sector having all four.
We won't get into Locations of a continuous Map, as they don't apply to our example.
You'll notice that Item is not explicitly connected to anything. This is left up to the developer. You could have a design constraint that Items must be contained by Charactors (perhaps in an intermidiate "Inventory" object), or maybe it makes more sense for your game to keep a list of a bunch of Items in the Game object. Some games might call for Sectors having Items lying around inside them.
This example makes use of everything covered so far. It starts out with a list of possible events, then we define our middleman, EventManager, with all the methods we showed earlier.
Next we have our Controllers, KeyboardController and CPUSpinnerController. You'll notice keypresses no longer directly control some game object, instead they just generate events that are sent to the EventManager. Thus we have separated the Controller from the Model.
Next we have the parts of our PyGame View, SectorSprite, CharactorSprite, and PygameView. You'll notice that SectorSprite does keep a reference to a Sector object, part of our model. However we don't want to access any methods of this Sector object directly, we're just using it to identify which Sector object the SectorSprite object corresponds to. If we wanted to make this limitation more explicit we could use the id() function.
The Pygame View has a background group of green square sprites that represent the Sector objects, and a foreground group containing our "little man" or "red dot". It is updated on every TickEvent.
Finally we have the Model objects as discussed above and ultimately the main() function.
Here is a diagram of the major incoming and outgoing events.
... More of this tutorial can be found at http://sjbrown.ezide.com/games/writing-games.html