We've now covered most of the
fundamental options available for 2D rendering, but there are a few
important details still left to look at, so let's run through these now.
1. Rendering in Full Screen Mode
All the examples so far have operated in so-called windowed
mode, which in the context of Windows Phone simply means that the
status bar is visible at the top of the screen. In many instances users
may prefer to have this available because it gives them a window into
the phone: they can see their battery level, their signal reception
level, and so on. Some games may benefit from having the game run in full screen
mode, however, with this bar hidden. It is usually polite to offer a
configuration option so that this mode can be configured per user
preference.
Setting full screen mode is easy; just add the line of code shown in Listing 1 to your Game class's constructor:
Example 1. Starting up in full screen mode
// Set the graphics device manager into full screen mode
_graphics.IsFullScreen = true;
|
If you wish to change the full screen state of your game at a later time, the easiest way is to call the GraphicsDeviceManager's ToggleFullScreen method. You can check the current state at any time by reading back the IsFullScreen property value.
2. Supporting Portrait and Landscape Orientations
This is
the orientation that the user is likely to be using for general-purpose
interaction with the device, and if your game works well in this
orientation it is a good idea to allow your game to run in this
arrangement.
If your game would benefit from a landscape
orientation, however, this is easy to implement. In fact, the default
behavior of XNA is to present the game in landscape mode.
Windows Phone 7 devices have special hardware present
to support orientation, which means that they cost absolutely no
processing time whatsoever.
Let's look at the options for supporting different orientations within your games.
2.1. Using Portrait Orientation
To set a game to run in portrait mode, we can set the
width and height of the back buffer in the game class constructor so
that its height is greater than its width. We can set the width to 480
and the height to 800 to match the device resolution, as shown in Listing 2.
Example 2. Setting the back buffer for portrait orientation
// Display using portrait orientation
_graphics.PreferredBackBufferWidth = 480;
_graphics.PreferredBackBufferHeight = 800;
|
With this configuration active, the game will always
appear in portrait mode, regardless of how the device is physically
rotated. You can test rotation on the emulator by clicking the rotate
buttons that appear to the right of the device window and see how the
graphics on the screen respond. With the game running in portrait mode,
the graphics will display sideways when the device is in a landscape
orientation.
You can see this in effect by running the Orientation
sample project. This also displays the name of the orientation that the
device is currently using, the size of the back buffer, and the size of
the game window. Notice that they do not change when the device is
rotated.
2.2. Using Landscape Orientation
To operate your game in landscape orientation, don't
specify any preferred back buffer size in the class constructor; the
game will default to landscape mode. Try commenting out the back buffer
configuration in the Orientation project and run it to see the results.
If you leave the device or the emulator in portrait
orientation, the display will appear sideways because the game is now
supporting only landscape orientations.
Note that if you switch the device over between being rotated to the
left and to the right, it automatically rotates the screen content to
match. The Current orientation display from the program will switch between LandscapeLeft and LandscapeRight as you do this.
The game itself doesn't need to know anything about such a rotation because the screen size is absolutely unchanged.
2.3. Allowing Landscape and Portrait Orientations
You may wish to support your game in both landscape
and portrait orientations. Doing so is easy to accomplish from the point
of view of the phone, but may introduce additional work into your game
because you will probably need to resize or reposition your game
elements to fit into the new window dimensions.
To enable all possible orientations, comment out the
code that sets the preferred back buffer size and replace it with the
code shown in Listing 3.
Example 3. Allowing landscape and portrait orientations
// Allow portrait and both landscape rotations
_graphics.SupportedOrientations = DisplayOrientation.Portrait |
DisplayOrientation.LandscapeLeft |
DisplayOrientation.LandscapeRight;
|
If you run the project now, you will find that it automatically adjusts to whatever orientation the device is held in.
One important point is the way that the Window size and Back buffer size
values are reported by the example project. The window size remains
completely unaffected by the rotation because at heart the device is
always operating in portrait mode, so this is the size that it considers
its window to be. The back buffer size (retrieved from the BackBufferWidth and BackBufferHeight properties of the _graphics.GraphicsDevice.PresentationParameters
object) always reflects the dimensions of the window as it is actually
being rendered. If you are supporting rotation, be careful to read the
buffer size in the correct way.
You can, of course, provide any set of orientations that you wish for the SupportedOrientations property. Setting it to just DisplayOrientation.LandscapeLeft
would prevent portrait or landscape-right orientations from being
displayed. Generally if you are allowing landscape orientation, you
should allow both modes so that users can hold the device in whichever
way they are comfortable with.
To change the supported orientations after the game has started, set the SupportedOrientations property as needed and then call the _graphics.ApplyChanges method. The updated orientations will become immediately effective.
If you need to detect the orientation changing so
that you can update your game accordingly, the easiest way to do it is
to subscribe to the game window's OrientationChanged event. This is fired each time the orientation changes, and the new orientation can be retrieved from the Window.CurrentOrientation property. The Orientation example project uses this event to display the new orientation to the debug window each time it changes.
3. Graphic Scaling
Compared with modern game consoles, Windows Phone 7
devices are fairly low-powered in terms of graphical capability, despite
the hardware that they have at their disposal. Yet they still run at a
fairly high resolution: 480 × 800 pixels is a substantial amount of
screen data to update.
In many cases, the hardware will be entirely capable
of handling this, but when the graphic updates are more intense the
phone may struggle. To assist with this scenario, the device has a
hardware image scaler built in, which enables XNA to render at any size
that it likes, allowing the scaler to increase the size of the rendered
graphics so that they fill the screen. Just as with rotation, special
hardware is present to perform this task, so it takes no processor time
whatsoever. The scaler smoothes the graphics as it processes them, so it
avoids displaying pixelated graphics. The scaler is particularly
effective with fast-moving graphics, which further helps to disguise the
scaling that is taking place.
To use the scaler, we just set the preferred back
buffer size in the game constructor to whatever size we need. If the
game is running in portrait mode, we can scale everything to twice its
size by using the code shown in Listing 4.
Note that the size provided here is exactly half the actual screen
size. This means that we can render into the screen area 240 pixels wide
by 400 pixels high (instead of the normal 480 × 800 pixels) and still
have it fill the screen. This is one-quarter of the volume of pixels,
resulting in a lot less work for the device to do.
Example 4. Double-size scaling in portrait mode
// Scale up by 2x
_graphics.PreferredBackBufferWidth = 240;
_graphics.PreferredBackBufferHeight = 400;
|
If your game is using landscape mode, set the
preferred back buffer size so that the width is greater than the height,
as shown in Listing 5. This is the correct approach if using both landscape and portrait modes via the SupportedOrientations
property too: XNA will automatically swap the back buffer width and
height when switching into portrait mode, so always provide the buffer
size with the dimensions that will be used for the landscape display
orientation.
Example 5. Double-size scaling in landscape mode
// Scale up by 2x
_graphics.PreferredBackBufferWidth = 400;
_graphics.PreferredBackBufferHeight = 240;
|
The scaler can also work with smaller resolutions
than this if desired, but the more dramatic the scaling, the more
pixelated and distorted the final image will be.
To change the scaling after the game has started, set
the preferred back buffer size as required, just as we have seen in the
constructor, and then call the _graphics.ApplyChanges method. The scaler will immediately start or stop work as required.
4. Suppressing Drawing
The update/draw loop that XNA employs is generally
very good for presenting a game to the screen and offers great
opportunities for rich-looking graphic updates. But sometimes the
graphics being drawn to the screen aren't actually moving at all. XNA is
unaware of this, however, and continues to clear and repaint the same
identical scene frame after frame, using the CPU and graphics hardware
in the process and wasting the battery as a result.
These is, however, a way to indicate to XNA that it
doesn't need to redraw the screen and can just re-present the most
recently drawn screen. This is achieved simply by calling the Game class's SuppressDraw method from within the Update function.
A simple approach to managing this is to create a bool variable within Update called objectsMoved, initially set to false. As soon as your Update code causes anything to happen that needs the screen to be redrawn, it sets this variable to true. At the end of the function, the code can then check this variable and call SuppressDraw if it still contains its initial false value.