After long pause, here’s the next edition of XAML gaming series. If you are serious game developer, you should check out some of the gaming frameworks, such as Unity or Cocos2D. You could also be interested in Win2D library if you prefer XNA type of programming. The idea of this series is to have fun and learn XAML/C# and Windows features at the same time.
In this episode we bring our project to Windows 10, and add Xbox 360/One controller support for our game. In next part we talk about adding sounds and some other fun things.
You will need to have Windows 10 and any edition of Visual Studio 2015 installed to go on with this project. First, make a backup of your game, and copy it somewhere safe before proceeding with this guide, just in case something goes haywire while converting the project as the following steps include irreversable deleting of files.
Conversion preparations
Start your project in Visual Studio 2015 and now that you have the backup safely in different folder, you can delete the MySpaceInvanders.WindowsPhone project from the solution. Next you select all the files in the Shared-project, and cut/paste them to the MySpaceInvanders.Windows project. Don’t forget to cut/paste also Assets and Common –folders and their content.
Open the App.xaml.cs file, and find all code which is inside #if WINDOWS_PHONE_APP -block and remove it. Remove also the line #ELSE (not the code inside the block) and #ENDIF line. Now you should have no conditional compilation inside the App.xaml.cs file. We handle the other files later.
There’s no AnyCPU project in Windows 10 by default, so we remove it from our solution. Open the Configuration Manager (the small arrow next to AnyCPU text), and choose Configuration Manager, and from Active Solution Platform dropdown choose Edit... Now select AnyCPU and click Remove and accept the removal. Now you can close the Configuration Manager. Select x86 configuration as active configuration.
Conversion
Unfortunately there’s no conversion tool for Windows Universal apps to Windows 10 build inside the Visual Studio, but luckily Andy Wigley and Jerry Nixon have provided such script for public availability.
Open https://github.com/Win10DevGuideMVA/ProjectUpgradeUtility in your browser, and download the zip file. Unpack these script files to your MySpaceInvanders.Windows folder, and open command prompt, navigating to that folder and type to command prompt:
cd c:\source\MySpaceInvanders\MySpaceInvanders.Windows
where the folder matches your local folder and press enter. You are ready to do the actual conversion, so just run the script by typing:
Run_Upgrade_to_uwp.bat
Your output should match something like the following screenshot (Two Trues and one Done without any errors):
You need to do couple of more steps before you can run your solution.
First open the Package.AppxManifest in code (right click the file in Solution Explorer and select View Code). You must change the text Square30x30Logo to Square44x44Logo. You need later on to upgrade the graphics to match this size as the old icon is too small and will give you error if you try to submit your game to the store.
If you have section called build:Metadata you should remove it completely. Now save the file, change the Build Configuration to ARM and X64 and repeat the previous steps for both configurations, saving the file every time and select lastly the x86 configuration back.
Now you need to remove one more #if WINDOWS_PHONE_APP conditional section from the code, this time from Startpage.xaml.cs where it checks the screen width and height. Your StartPage constructor should look like this:
public StartPage() { this.InitializeComponent(); ApplicationView view = ApplicationView.GetForCurrentView(); bool isInFullScreenMode = view.IsFullScreenMode; if (!isInFullScreenMode) { view.TryEnterFullScreenMode(); } Loaded += (sender, args) => { CreateStar(); Move.Completed += MoveStars; Move.Begin(); }; App.ScreenWidth = Window.Current.Bounds.Width; App.ScreenHeight = Window.Current.Bounds.Height; }
After you’ve done that, you should be able to compile the project without errors and try it out.
Post-conversion
When you start the game, you notice that all the texts from the start screen are missing!
This is a great opportunity to learn a little bit of error resolving with Visual Studio 2015 and about a great new feature called Live Visual Tree. While your app is running, switch back to Visual Studio with alt+tab and go to Debug, Windows and select Live Visual Tree. Expand the window so that you can see it clearly.
You should see tree nodes called StartPage and under it Grid and StartButton. There are two TextBlock items there as well. Now go on and select one of them. Right click it and select Show properties. You can see what values have been set to the control while it’s running and you are able to edit them while your game is running.
First thing to check is that what color we have set for the text foreground. There’s no Foreground set on Local -section, so it must be inherited from somewhere else. You can see those values in the Default -section.
Now that you found the Foreground, it is showing Black. Where did that come from? In Windows 10 there’s been one big change in application color schemas, the default theme has been set to light. In light theme the text is black and background is white. There’s easy fix for us – stop the debugger, go to the beginning of the xaml -file, and add the following property to the page element:
RequestTheme="Dark"
Add it also to the GamePage.xaml in the same place, for example as a last property just after mc:Ignorable=”d” -text but before the closing tag.
Now all the text should be visible again and you’re good to go.
You could add a small visual candy to the start screen at this point. In the XAML designer select the UNIVERSAL -text block and click Brush in the property window (bottom right of VS), and select the third box below it (Gradient Brush). Repeat the same for the INVANDERS -text. Change the Black in GradientStop color to DarkGrey for both textblocks in XAML of StartPage.xaml and run the app. Now there’s a small chrome effect in the texts.
Let’s get on with the show and continue the conversion. Open the Ship.xaml.cs and remove the #if WINDOWS_PHONE_APP section (and #else and #endif -lines as well). Now do the same in the GamePage.xaml.cs as well.
We need to change shipHorizontalPosition -variable initializer in GamePage.xaml.cs to following (remove readonly and adjust the size):
private double shipHorizontalPosition = App.ScreenHeight - 80;
Windows 10 has changed so that the Store apps can be windowed as well. Right now we don’t add the code to handle resizing (subject for later posts) but add request to run in full screen mode, which is logical for games. Open the StartPage.xaml.cs and add to the top:
using Windows.UI.ViewManagement;
Next add the following code to constructor after the InitializeComponent:
ApplicationView view = ApplicationView.GetForCurrentView(); bool isInFullScreenMode = view.IsFullScreenMode; if (!isInFullScreenMode) { view.TryEnterFullScreenMode(); }
Now you are ready to run the game!
Xbox 360/One controller support
Now we can finally add some Windows 10 goodies to the project. I think adding the controller support is really fitting as it’s useful not only to games but good to learn because it will be relevant for apps as well to enable your app to be controller on Xbox One later on when the Store opens for submissions for it.
We need to take the controller in use in only one place and then use that object through our app. Open the App.xaml.cs file and add to the top:
using Windows.Gaming.Input;
and following member variable:
public static Gamepad GameController;
When we are starting our app, we need to listen to when the app gets the Xbox controller connection by adding handler for GamepadAdded event in constructor after this.Suspending += this.OnSuspending; -line:
Gamepad.GamepadAdded += Gamepad_GamepadAdded;
Add the following method just below the constructor:
private void Gamepad_GamepadAdded(object sender, Gamepad e) { GameController = Gamepad.Gamepads.First(); }
By adding the code above, you have now access to Xbox controller through out your app. At this point your App.xaml.cs should look something like this:
using System; using System.Linq; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using MySpaceInvanders.Common; using Windows.Gaming.Input; // XBOX namespace MySpaceInvanders { /// <summary> /// Provides application-specific behavior to supplement the default Application class. /// </summary> public sealed partial class App : Application { public static double ScreenWidth { get; set; } public static double ScreenHeight { get; set; } public static int Highscore { get; set; } public static Gamepad GameController; // XBOX /// <summary> /// Initializes the singleton instance of the <see cref="App"/> class. This is the first line of authored code /// executed, and as such is the logical equivalent of main() or WinMain(). /// </summary> public App() { this.InitializeComponent(); this.Suspending += this.OnSuspending; Gamepad.GamepadAdded += Gamepad_GamepadAdded; // XBOX } private void Gamepad_GamepadAdded(object sender, Gamepad e) { GameController = Gamepad.Gamepads.First(); } /// <summary> /// Invoked when the application is launched normally by the end user. Other entry points /// will be used when the application is launched to open a specific file, to display /// search results, and so forth. /// </summary> /// <param name="e">Details about the launch request and process.</param> protected async override void OnLaunched(LaunchActivatedEventArgs e) { #if DEBUG if (System.Diagnostics.Debugger.IsAttached) { this.DebugSettings.EnableFrameRateCounter = true; } #endif Frame rootFrame = Window.Current.Content as Frame; // Do not repeat app initialization when the Window already has content, // just ensure that the window is active if (rootFrame == null) { // Create a Frame to act as the navigation context and navigate to the first page rootFrame = new Frame(); //Associate the frame with a SuspensionManager key SuspensionManager.RegisterFrame(rootFrame, "AppFrame"); // TODO: change this value to a cache size that is appropriate for your application rootFrame.CacheSize = 1; if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) { // Restore the saved session state only when appropriate try { await SuspensionManager.RestoreAsync(); } catch (SuspensionManagerException) { // Something went wrong restoring state. // Assume there is no state and continue } } // Place the frame in the current Window Window.Current.Content = rootFrame; } if (rootFrame.Content == null) { // When the navigation stack isn't restored navigate to the first page, // configuring the new page by passing required information as a navigation // parameter if (!rootFrame.Navigate(typeof(StartPage), e.Arguments)) { throw new Exception("Failed to create initial page"); } } // Ensure the current window is active Window.Current.Activate(); } /// <summary> /// Invoked when application execution is being suspended. Application state is saved /// without knowing whether the application will be terminated or resumed with the contents /// of memory still intact. /// </summary> private async void OnSuspending(object sender, SuspendingEventArgs e) { var deferral = e.SuspendingOperation.GetDeferral(); await SuspensionManager.SaveAsync(); deferral.Complete(); } } }
Let’s start by adding ship movement and firing to the game. Open the Gamepage.xaml.cs -file, and add the following using statement:
using Windows.Gaming.Input;
For better gameplay experience, I have added also bit which prevents Xbox controller autofiring all the time when the trigger is pressed. If you don’t like this, you can just comment it out later on. Add the following member variable:
bool fireSuppressor; // XBOX, prevent autofire
We need a place where we can read the controller values enough often without affecting the gameplay. I have opted in using the MoveStars -method which is not necessary the elegant solution but works anyway nicely. Add the following code to the very beginning of MoveStars -method:
if (App.GameController != null) { GamepadReading reading = App.GameController.GetCurrentReading(); // Use 0.4 so the stick don't have to be exactly to right or left, but is more forgiving if ((double)reading.RightThumbstickX < -0.4) { MoveShip(-5); } else if ((double)reading.RightThumbstickX > 0.4) { MoveShip(5); } if ((int)reading.RightTrigger > 0 && fireSuppressor == false) { fireSuppressor = true; OnFire(null, null); } else if((int)reading.RightTrigger == 0) // require depress of trigger before shoot another { fireSuppressor = false; } }
In the code above we ask from the controller what are all the current readings in it. After that we check if the right thumb stick has been moved. I checked values above and below 0.4 so the user don’t have to move the stick exactly horizontally for the ship to move. After moving the ship I’m checking if the right trigger has been pressed, and it has been released between previous visit to this loop, forcing the player to pull the trigger for each shot.
Now you can test the game and you should be able to move the ship with right thumb stick and shoot with right trigger. I tested it with connecting Xbox One controller with the charging cable to the PC and it worked like a charm. The same code should run as it is with Xbox 360 controller as well.
One more addition is that we should be able to start the game with the controller as well. Go to the Startpage.xaml, change the StartButton Content to:”Start (A)”. Open the StartPage.xaml.cs and add the following using:
using Windows.Gaming.Input; // XBOX
After you’ve done that, find the MoveStars -method. Add the following code to the beginning of it:
if (App.GameController != null) { GamepadReading reading = App.GameController.GetCurrentReading(); if (reading.Buttons == GamepadButtons.A) { OnStart(null, null); } }
Find the OnStart -method, and add the following to the beginning of it:
Move.Completed -= MoveStars; Move.Stop();
That’s it, now you can test your game and you should be able to start it by pressing the A-button on your Xbox controller, move your ship and shoot. I hope you are having fun with these and do leave comments what you would like to see next incorporated to the game (sounds are coming next, otherwise I’m open to suggestions).