Building a simple game using XAML/C#: Part 4 – Make it fun
At this point, you have a simple version of a “Simon” game that works: you can start, restart, win, and lose. That said, the game still lacks the pizazz that really could make it stand out as something fresh and the game still needs to keep the player’s score.
Who is keeping score?
There’s plenty of places that we could logically keep the score, but I’m going to opt for keeping it in the Game object. Make sure that you have added an integer for storing the score in the Game class. Next, we’ll have to figure out a way to increment it. First, we’ll add two simple accessor methods, addScore and getScore for incrementing and retrieving the score.
//Game.cs //------------ // score accessors public void addScore(int _score){ score += _score; } public int getScore(){ return score; }
Next, we’ll have to figure out when we’ll be giving the player points. I like using the clicks on the game squares as the source for updating the score and also incrementing the score when the user completes a level.
Change your button click handlers to increment the score when the user clicks them and also add tons of points when the user completes a level.
//Game.cs //----------- void redRect_PointerPressed(object sender, Windows.UI.Xaml.Input.PointerEventArgs e) { if (theState != GameState.responseUser) return; triggerOnRed(); if (!theLevel.solve(GameSwitches.red)) { theState = GameState.levelFailed; } else { addScore(theLevel.getDifficulty()); if (theLevel.isComplete()) { addScore(theLevel.getDifficulty() * 100); doLevelComplete(); } } }
Finally, we’ll have to update the score to show while the game is running. We’ll also update the current game level when we do this. The following methods will update the score and level text in the XAML.
MainPage.xaml.cs ------------------ void updateScore() { scoreBox.Text = "Score: " + theGame.getScore().ToString(); } void updateLevel() { // restart on first entry if (theGame.getLevel() == null) return; // normal operation levelBox.Text = "Level: " + theGame.getLevel().getDifficulty().ToString(); }
The following changes to the Game loop will update the score while the player plays:
MainPage.xaml.cs: GameLoop method ------------------------------------ // TODO, use a proper timer, wth is up with the new threading? switch (theGame.getState()) { case GameState.challengeUser: statusBox.Text = "Simon is telling you the secret code."; if (theGame.getLevel().isCreated()) { theGame.setState(GameState.responseUser); } else { // The following condition will ensure we don't pump too fast if ((count > initialWait) && ((count % currentSpeed) == 0)) { theGame.addLevel(); } } break; // start gameloop changes for score and level snippet case GameState.levelComplete: statusBox.Text = "Nice work homeslice! Reward is harder level."; updateScore(); // TODO: using the status box reference is hacky theGame.LevelUp(ref status2Box); updateLevel(); // TODO: move into game logic if (currentSpeed > minSpeedValue) currentSpeed -= 5; status2Box.Text = ""; break; case GameState.levelFailed: statusBox.Text = "You fail. Game over man."; _gameLoop.Stop(); //AppBar.IsOpen = true; break; case GameState.responseUser: statusBox.Text = "Simon is waiting for you to enter the code."; updateScore(); break; // end gameloop changes for score and level snippet case GameState.notRunning: statusBox.Text = "Shall we play a game?"; break; default: break; }
If you build and run the game now, you will notice the score and level updates in the UI.
Where did I put that trap door…
To add a new twist to the game, let’s make to board spin around once the user reaches the 5th level. Before we get ahead of ourselves, let’s first figure out how to spin the board.
We will be adding a new method to the Game class which will let us rotate the board. First, add a variable that will be used to hold the current board rotation.
Game.cs ---------------
double boardRotation = 0.0;
Next, we’ll add a new method, rotateBoard that will take in a value for the number of degrees to rotate. The way that the rotation will have to work is that all of the squares will rotate about the middle of the board, like a pinwheel. The following image shows how the rotation will work for each of the squares:
In this rotation, the x and y center positions stay the same and the squares all are rotated about that center point. To perform the transformation, there is a convenient object, the CompositeTransform, that lets us easily transform the shapes about the gameboard’s center. The following code shows how this comes together:
// Rotate the entire board public void rotateBoard(double degrees) { boardRotation += degrees; Rectangle current; foreach (GameSwitches gameSwitch in Enum.GetValues(typeof(GameSwitches))) { switch (gameSwitch) { // bottom left triangle case GameSwitches.blue: current = blueRect; centerX = blueRect.ActualWidth; centerY = 0; break; // top right triangle case GameSwitches.green: current = greenRect; centerX = 0; centerY = greenRect.ActualHeight; break; // top right triangle case GameSwitches.red: current = redRect; centerX = redRect.ActualWidth; centerY = redRect.ActualHeight; break; // bottom right triangle case GameSwitches.yellow: current = yellowRect; centerX = 0; centerY = 0; break; default: // impossible? current = null; break; } CompositeTransform transform = new CompositeTransform(); transform.CenterX = centerX; transform.CenterY = centerY; transform.Rotation = boardRotation; current.RenderTransform = transform; } }
What this code does is just iterate through all of the game switches and rotates them about the game board center, pretty simple…
Now, that we have the ability to rotate the board, we should test it! You may have noticed that I added an extra button to the AppBar for turning on and off test features. Clicking this button will flip the enableTestFeature Boolean in the MainPage.xaml.cs partial class. In this partial, there is a section at the top of the GameLoop method that gets enabled or disabled when that button gets clicked. While we create and debug the rotation code, it is convenient to put the code into that section.
To test the rotation code, I added the following code to the enableTestFeature section of the game loop:
MainPage.xaml.cs ---------------------- // start test code feature snippet if (enableTestFeature) { // insert fun weird tweaks in here // I used to have the rotation code here... theGame.rotateBoard(.2); } // end test code feature
Note that the value should be pretty small because the rotation code will be called frequently by the GameLoop. If you build and run, you can turn on and off the rotation feature by clicking the ??? button on the App Bar. Now that we have verified that the rotation code works, it’s time to insert some logic into the game that will enable the rotation code when the user reaches a certain level and will “unrotate” the board when the game is reset. For the first case, making the board rotate when the user reaches a certain level, I added the following code:
// If we're Level 5+, start spinning :) if (theGame.getLevel() != null && theGame.getLevel().getDifficulty() > 5) { theGame.rotateBoard(.01 * theGame.getLevel().getDifficulty()); } // end rotation gameplay section snippet
For resetting the board position, I added the following code to the resetGame function in the Game class:
MainPage.xaml.cs ---------------------- public void resetGame() … boardRotation = 0.0; rotateBoard(0); …
Now the board will rotate after reaching level 5 and resetting the game will place the board back to its original position. The following screenshot shows the game running with the board turning:
Also, hax!
I’m a huge fan of Easter eggs, and so… I thought it would be fun to add a nifty little cheat to show the current pattern while you’re playing. This is actually pretty helpful when you’re testing gameplay into higher levels and aren’t the greatest “Simon” player. The way that the cheat will work is that we’ll have an invisible rectangle that when clicked, will write the queue of moves to the “status” text area box.
First, we’ll add and initialize a rectangle for the invisible button when we set up the rest of the game board:
cheatRect = new Rectangle(); Canvas.SetLeft(cheatRect, 0); Canvas.SetTop(cheatRect, 0); cheatRect.Width = 50; cheatRect.Height = 50; cheatRect.Fill = new SolidColorBrush(Colors.Transparent); cheatRect.PointerPressed += new Windows.UI.Xaml.Input.PointerEventHandler(cheatRect_PointerPressed); gameCanvas.Children.Add(cheatRect);
Next, we’ll add the handler for when the button is clicked that will trigger cheat mode on the level:
void cheatRect_PointerPressed(object sender, Windows.UI.Xaml.Input.PointerEventArgs e) { if (theLevel != null) theLevel.toggleCheat(); }
Finally, we’ll implement the “cheat mode” on the actual level. This is the code that will show the last played while the colors are presented.
First, update the add section to ensure that it will present the played colors into the status box when the cheat mode is enabled. The following code shows how this works:
Level.cs ----------- public Level(int _difficulty, ref TextBlock _statusBlock) { chimesAdded = 0; lastQueued = -1; difficulty = _difficulty; statusBlock = _statusBlock; if (cheat) { statusBlock.Text = "Cheating: "; } ...
Next, add an accessor method that will enable cheating:
public void toggleCheat() { if (cheat) { cheat = false; } else { cheat = true; } }
Toggle a break point to your application in the toggleCheat block, build and run your game and then click around until you find the “cheat” area of the game (if you used my code, clicking the top left of the screen will trigger the cheat mode). Clicking it will bring you to the toggleCheat breakpoint. Continue and run and see how easy the game is with cheating enabled .
In the following screenshot you can see the pattern being displayed to the player to make their life way too easy.
Conclusions
So that’s pretty much it for this game, we’ll be working on another one soon. Hope you had fun!
You can download the source for the Metro Simon C#/XAML app which should have the complete working sample as well as a few other minor improvements like programming Windows 8 orientation changes in C#.
This post is the fourth in a series. The previous post in the series was Building a simple game for Windows 8 using XAML/C#: Part 3 – Make it a game
Thank you for a great tutorial…
Do you have recommendations for moving on to more advanced programming if I wish to write a game for Windows 8 Metro? I am interested in: collision detection, arcade-style animations, adding a soundtrack, and such other game-development tasks…
Thanks, Jim
The Metro Games development node on MSDN will have more resources for you when the planned content is complete. The games development learning path on the MSDN App Hub has some good resources for getting started with the XNA libraries and also cover basic concepts that would map to Metro, however, I’m not sure you will be able to publish XNA games as Metro style apps. An alternative would be to start development using a game engine or learning about how that game engine works by checking out the sources. Finally, you should check out this thread on 2D JavaScript game development on stack overflow.
How to implement Leaderboards in Windows store apps with XAML and C#? User should be able to submit scores with their live ID.