At this point, you can see that we can add C# statements to the Draw method to change what is drawn on the screen. You also know that XNA uses a Color structure to lump together information that describes a particular color and that you can create your own Color
variables in the game that contain a specific amount of red, green, and
blue. Finally, you have managed to make a program that uses a color
variable to set the screen to any color that you like.
Next, you want the light
to change color over time, to get a nice soothing mood light effect.
This sounds like hard work (and like every great programmer, I really
hate hard work), but actually it turns out to be quite easy. To discover
how to do this, you have to find how XNA is connected to the game
programs that you write. The way this works uses C# classes.
1. Games and Classes
The game program is actually a class called Game1.
A class is a collection of abilities (methods) and data (variables)
that forms part of a program. You can put as much stuff as you like
inside a single class. A class is usually constructed to look after one
particular part of a system. In the commercial world, you might find classes called "Receipt," "Invoice," and "StockItem."
When it created our project, XNA Game Studio gave the game class the name Game1. However, you can rename this if you wish.
1.1. Classes and Behaviors
A behavior is something
that a class can be asked to do. A particular method performs a
particular behavior. You have already used the Clear behavior of the GraphicsDevice class. When you use Clear, this causes the code in the Clear method to be obeyed to clear the screen. You don’t need to know how Clear works; you just need to know that you can feed it with information to tell it what color you want to use.
1.2. Drawing and Updating in Games
The Game1 class provides Update and Draw behaviors (among others) so that XNA can ask Game1 to update the state of the game and draw it on the display. Draw and Update are methods that you provide for use by XNA.
In the programs you have written up to now, you have done all the work in the Draw method. However, this is not really how games should work. The Draw method should do nothing other than draw the display, and the game should be updated by using the Update method. You might be wondering why we have this split between Draw and Update. Why can’t Update do everything, including the drawing part?
The answer to this
question has to do with the way that games work. It is very important
that the game world is updated at constant speed. If Update
is called less frequently than it should be, players would find that
time in the game goes into "slow motion," which would be very
frustrating for them because the game would not respond properly to
their inputs to the gamepad. However, a game can usually get away with
calling the Draw method less often—all that happens is that the display becomes more jerky as it is redrawn less frequently.
I’ve played a few games
that do this, usually when there are a large number of objects on the
screen at the same time. What is happening is that the display is
running more slowly, but behind the scenes, the game is being updated
properly, so gameplay itself is not affected. If the update and draw
behaviors were not separated, it would not be possible to run them at
different rates.
1.3. Sharing Game World Data Between Draw and Update
When you create a
game, you must create the variables that hold the state of the game
itself. In a driving game, this state would include the speed of the car
the player is driving, the car position on the track, and the position
and speed of the other cars. This could be called the game world data.
The game world data that you are going to use in the mood light is the
amount of red, green, and blue that defines the color of the light. The
present version of Draw is entirely self-contained. It has a local variable that is set with the color that is to be drawn:
protected override void Draw(GameTime gameTime)
{
Color backgroundColor;
backgroundColor = new Color(255,255,0);
GraphicsDevice.Clear(backgroundColor);
base.Draw(gameTime);
}
Local variables are used
when you just want to manipulate some data for a very short time. In
this case, the program makes a color value that can be fed into the Clear method. At the moment, the value of backgroundColor
is constructed from the values 255, 255, and 0, which give the amount
of red, green, and blue in the color. We want to construct the color
value from game data values that are set up by the Update method. To make your light work the way that XNA does, the program must store this game data in a place where both the Draw and Update methods can use it. In other words, you need to set up some game world data. Figure 1 shows how the Update and the Draw methods are part of the Game1 class, along with the intensity variables that make up the game world.
The job of the Update method is to update the game world data in the game (that is, adjust the intensity values). The job of the Draw
method is to use the game world data to draw the display (that is,
create a color from these values and clear the screen with it).
The XNA system calls Draw and Update at regular intervals when the game is running. You have already used methods provided by other classes; you know that the Clear method can be called to clear the display to a particular color. We are going to make the Update method set the value of the color to be used, and the Draw method will just draw using that color. Values that are shared among methods in a class are called members of the class.
2. Classes as Offices
You can think of Update and Draw as two people sitting in an office called Game1.
Each of them has his or her own telephone and pad of paper for taking
notes (local storage). In the middle of the office is a desk (the
description of the game world) with bits of paper on it.
Every now and then, Mr.
Draw’s phone rings, and a voice on the other end of the line tells him
that a sixtieth of a second has gone by. Mr. Draw then jumps up, gets
the value of the background intensities from the Game World data on the
desk in the office, creates a color value on his notepad, and then uses
his phone to call Ms. Clear in the GraphicsDevice
office down the hall and ask her to clear the screen to that color. She
has a set of paint cans and can fill the screen with any color that she
is asked to use.
At a similar interval, the Update phone in the Game1
office rings, and a voice tells Mrs. Update that a sixtieth of a second
has gone by. She jumps up, goes to the table in the office, and updates
the Game World information on the bits of paper. You can see how this
would look in Figure 2.
The people/methods in our
office/classes perform actions for each other, and data is the
information that the class stores within itself. When a class wants to
use a method, it calls it.
In our first version of the Game1
class, the information on the table is the color that Mr. Draw uses to
color the graphics display. You change what happens when the screen is
drawn by changing what Mr. Draw does (the content of the Draw method). You change what happens when the game itself is updated by changing what Mrs. Update does (the content of the Update method).
Note
that no method has to know exactly how the other methods work. Mr. Draw
has no idea about cans of paint and displays, but he does know that if
he asks Ms. Clear to clear with yellow paint, this results in a yellow
screen being drawn. A call of a method is equivalent to calling up
someone in an office and asking her or him to perform a task.
3. Game World Data
The Game World data must be held as part of the class so that the Draw and Update
methods can make use of it. For the MoodLight game the data will be the
brightness of the red, green, and blue components of the color of the
light to be produced.
class Game1 {
// The Game World - our color values
byte redIntensity ;
byte greenIntensity ;
byte blueIntensity ;
// TODO: Draw method goes here
// TODO: Update method goes here
}
The preceding code declares three variables inside the Game1 class. These are part of the class; they are often called members of the class and can be used by any methods that are also members of the class. They have the identifiers redIntensity, greenIntensity, and blueIntensity. You can think of these as separate pieces of paper on the desk in the Game1 office. Figure 3 shows how a class can contain members.
There are two kinds of members: methods (which do something) and data (which hold information). The Game1 class you are working on has both kinds of members; it has the Draw method and the Update
method, as well as the three data members, which are going to be used
to hold the color values for the changing background. The intensity data
members are of type byte.