Auto-Scaling WVGA XNA Games to WXGA & 720P with MonoGame for WP8

XNA can still be used to create games that target Windows Phone 7 handsets with WVGA screen resolution (480×800) and it uses Windows Phone’s hardware scaling capability when running on WP8 handsets that have WXGA and 720P screen resolutions. This automatic hardware scaling in XNA means that the developer doesn’t need to do anything special to get a WVGA resolution game to properly display full-screen on higher resolution phones. In porting my Invasion game from XNA (targeting WP7.1) to MonoGame (targeting WP8), I soon noticed that MonoGame doesn’t yet use the scaler when running on WXGA and 720P resolution phones, so games designed for WVGA will not display full-screen on them. This article provides an easy solution so that your WVGA games display properly on WXGA and 720P phones.

Many blogs suggest designing WP8 games to account carefully for each possible screen resolution, creating different sized graphics for each, and fully utilizing every pixel of these higher resolution screens. But that really doesn’t make sense when you have an existing XNA game that was designed for WVGA and assumed the scaler would take care of dealing with any higher-resolution phones that came along. With my Invasion game on CodeProject.com, all I want to do is quickly port it to MonoGame for WP8, and do so without creating new graphics and new layouts for 720P screens. If MonoGame only used a scaler like XNA, I wouldn’t have to! Fortunately, I found a way to get MonoGame to auto-scale like XNA, and below I’ll show you how I did it!

The goal of this post is to show how to take a WP7.1 XNA game that was written to fit on an 800×480 screen, and automatically scale the graphics when ported to MonoGame for WP8, just like XNA currently does when running WP7.1 games on WP8. The graphics should fit perfectly on a WXGA (1280×768) screen and be full-height centered with letterboxing on a 720P (1280×720) screen with very little effort needed. While I’m doing this for a game with Landscape orientation, the same method can be used for games with a Portrait orientation. And the trick is to get your GamePage’s DrawingSurface object to do the scaling for you.

In GamePage.xaml, your game should have markup like this:

<grid x:Name="LayoutRoot">
<mediaelement></mediaelement>
<drawingsurface x:Name="XnaSurface"></drawingsurface>
</grid>

Note that a DrawingSurface object has:

  1. a RenderTransform field that can be assigned to a ScaleTransform object
  2. a Margin field that can be used to center the game-screen to create letterboxing

So when our game starts, we’d like to be able to determine the phone’s screen-scale/resolution and then use that information to scale and center the game graphics, by setting the XnaSurface object’s RenderTransform and Margin fields. The screen’s ScaleFactor can be obtained via reflection, by putting this code into your Game class constructor:

int? scaleFactor = null;
var content = App.Current.Host.Content;
var scaleFactorProperty = content.GetType().GetProperty("ScaleFactor");

if (scaleFactorProperty != null)
scaleFactor = scaleFactorProperty.GetValue(content, null) as int?;

if (scaleFactor == null)
scaleFactor = 100; // 100% WVGA resolution

At this point, scaleFactor should either be 100 (WVGA), 160 (WXGA), or 150 (WXGA). Since WVGA and WXGA have the same aspect ratio of 1.6666667 and 720P has a different aspect ratio of 1.7777778, scaling from WVGA to WXGA will perfectly fit the screen, but scaling to 720P will require letter-boxing (aka “black bars”). By centering the game-screen, the black bars will be less noticeable to the user.

if (scaleFactor == 150) { // 150% for 720P (scaled to 1200x720 viewport, not 1280x720 screen-res)
// Centered letterboxing - move Margin.Left to the right by 0.5*(1280-1200)/scale
GamePage.Instance.XnaSurface.Margin = new System.Windows.Thickness(40/scale, 0, 0, 0);
}

And now for the XnaSurface scaling…

ScaleTransform scaleTransform = new ScaleTransform();
scaleTransform.ScaleX = scaleTransform.ScaleY = scale;
// The auto-scaling magic happens on the following line!
GamePage.Instance.XnaSurface.RenderTransform = scaleTransform;

What? That last line doesn’t compile? Of course not! Ideally, we’d get the GamePage object reference from MonoGame.Framework.WindowsPhone.WindowsPhoneGameWindow.Page, but unfortunately it’s an internal field that has no public property exposing it. To work around that issue and to get the above code to compile, just add a static Instance field to the GamePage class and initialize it in the GamePage constructor as follows.

public static GamePage Instance = null;

public GamePage() {
InitializeComponent();

if (Instance != null)
throw new InvalidOperationException("There can be only one GamePage object!");
Instance = this;

_game = XamlGame<InvasionGame>.Create("", this); //Where InvasionGame is the name of your game-class
}

To only allow the two Landscape orientations, you’ll need to do the following, just like in XNA:

graphics.SupportedOrientations =
DisplayOrientation.LandscapeLeft | DisplayOrientation.LandscapeRight;

but to get Landscape to work properly in MonoGame, you’ll also need to change the SupportedOrientations in your GamePage.xaml’s phoneapplicationpage markup:

<phone:phoneapplicationpage
...
SupportedOrientations="Landscape" Orientation="Landscape"

I suggest putting all of the C# code above into a SetupScreen() function and calling it from your game’s ctor. That’s all there is to it! Your WVGA game is now auto-scaling to WXGA and 720P screen sizes!

Two small gotchas for you to ponder:

  1. MonoGame doesn’t do anything with graphics.PreferredBackBufferWidth or graphics.PreferredBackBufferHeight – if you used their 800×480 defaults in your XNA game, you’ll find that they’re zeroed in MonoGame and setting them to anything else has no effect on the game’s behavior.
  2. Viewport.Width and Height are adjusted by MonoGame to the scaled viewport sizes in the above function, so the Viewport rectangle will be larger than WVGA graphics resolution on WXGA and 720P screens. In using the function above, any text and graphics that should be visible in your game should be drawn within the WVGA dimensions (rather than the resulting viewport dimension). For example, don’t horizontally-center text within the Viewport dimensions, center it within WVGA’s 800-pixel width instead.

Pretty soon I’ll be posting the ported Invasion source code that uses the above auto-scaling method to this blog and to CodeProject.com. If you have any questions, please feel free to leave a comment below.

Good luck!

Tags: , , , , , , ,

1 Response to "Auto-Scaling WVGA XNA Games to WXGA & 720P with MonoGame for WP8"