None of the example projects that we've
used so far has created large volumes of objects, so their performance
has all been very good, animating smoothly on the phone. Real games are
likely to be much more complex and will result in substantially more
objects being drawn to the screen during each update.
To allow us to monitor how the game is performing, it is very useful to use a benchmark.
This is a measurement of how well the game is performing and allows us
to see how many frames of animation are being displayed per second
while the game is running.
In the constructor of the Game class, we tell XNA the frame rate that we would ideally like it to run at. This defaults for Windows Phone projects to 1/30th of a second, as shown in Listing 1.
Example 1. Setting the target frame rate
// Frame rate is 30 fps by default for Windows Phone. TargetElapsedTime = TimeSpan.FromSeconds(1.0f / 30);
|
In theory, this means that we will receive 30 calls to Update per second and 30 corresponding calls to Draw
per second. This might well be the case with small numbers of objects
in motion, but how about if there are thousands of them? Or tens of
thousands? A benchmark will allow us to see exactly how the game
performs under these conditions.
Another more important use for the benchmark is to
allow us to spot unexpected low levels of performance and provide us
with an opportunity to identify and resolve them to improve the way
that a game runs. We will look at an example of such a problem later in
this section and see what can be done to resolve it.
1. The BenchmarkObject Class
To easily obtain frame rate information in any game we develop, another class is present within the GameFramework project: BenchmarkObject.
Because the task of this class is essentially to display some text
(containing the benchmark information we are trying to obtain), it is
derived from the TextObject class.
The class needs to be able to count the number of calls to Update and Draw
that occur each second. On a periodic basis (once per second is usually
sufficient), we should determine exactly how much time has passed since
our last measurement, and how many updates and draws have taken place.
The count of the updates and draws can then be divided by the elapsed
time to calculate the count per second.
The GameObject base class already counts the number of updates in its UpdateCount property, so the benchmark class doesn't need to do that. It doesn't count the number of calls to Draw, however, so the benchmark class creates a class-level variable named _drawCount and adds 1 to this each time its Draw method is called.
In order to tell how many calls have occurred since
the last time the frame rate was calculated, the class also stores the
previous draw and update counts. The newly elapsed counts can then be
obtained by subtracting the last counts from the current counts. The
class-level variables _lastDrawCount and _lastUpdateCount are defined for this purpose.
Finally we need to know when the frame rate was last
measured so that we can determine the time period during which the new
updates and draws have occurred. This is stored in the class-level _lastUpdateMilliseconds property. The full set of variables is shown in Listing 2.
Example 2. The class-level variables required by BenchmarkObject
//------------------------------------------------------------------------------------- // Class variables private double _lastUpdateMilliseconds; private int _drawCount; private int _lastDrawCount; private int _lastUpdateCount;
|
This gives us everything we need to measure the frame rate. First, the Draw method is overridden to update the draw count. The frame rate calculation is then performed in the Update method, as shown in Listing 3.
Example 3. The BenchmarkObject Update method
override void Update(GameTime gameTime) { StringBuilder message = new StringBuilder(); int newDrawCount; int newUpdateCount; double newElapsedTime;
// Allow the base class to do its stuff base.Update(gameTime);
// Has 1 second passed since we last updated the text? if (gameTime.TotalGameTime.TotalMilliseconds > _lastUpdateMilliseconds + 1000) { // Find how many frames have been drawn within the last second newDrawCount = _drawCount - _lastDrawCount; // Find how many updates have taken place within the last second newUpdateCount = UpdateCount - _lastUpdateCount; // Find out exactly how much time has passed newElapsedTime = gameTime.TotalGameTime.TotalMilliseconds - _lastUpdateMilliseconds;
// Build a message to display the details and set it into the Text property message.AppendLine("Object count: " + Game.GameObjects.Count.ToString()); message.AppendLine("Frames per second: " + ((float)newDrawCount / newElapsedTime * 1000).ToString("0.0")); message.AppendLine("Updates per second: " + ((float)newUpdateCount / newElapsedTime * 1000).ToString("0.0")); Text = message.ToString();
// Update the counters for use the next time we calculate _lastUpdateMilliseconds = gameTime.TotalGameTime.TotalMilliseconds; _lastDrawCount = _drawCount; _lastUpdateCount = UpdateCount; } }
|
The code refreshes the frame rate only once per
second, detected by comparing the current total game time's elapsed
milliseconds against the value stored in _lastUpdateMilliseconds. If a second has elapsed, the number of new calls to the Draw and Update
methods are determined, the exact amount of time elapsed is calculated,
and everything is built into a string for display onscreen. All the
current values are then stored in the class variables for use the next
time the frame rate is updated.
2. Using BenchmarkObject
BenchmarkObject can easily be used in any project by simply adding an instance of it to the GameObjects collection. Its constructor expects a font, position, and text color to be provided, but because it is derived from TextObject it can also be scaled or rotated if needed.
The Benchmark example project shows this in action, and its output can be seen in Figure 1.
As Figure 4
shows, there are 21 game objects (10 planets, 10 moons, and the
benchmark object), and the game is achieving 28.3 drawn frames per
second and 30.3 updates per second.