Exercise 1: Basic XNA Framework Game with Game State Management

If you have ever wanted to make your own games, Microsoft® XNA® Game Studio 4.0 is for you. Whether you are a student, hobbyist or an independent game developer, you can create and share great games using XNA Game Studio.

XNA Game Studio 4.0 is a game development product from Microsoft that is built on top of Microsoft Visual Studio 2010 and included in the Windows Phone Developer Tools, giving game developers the power and simplicity of the C# language and the .NET libraries. XNA Game Studio 4.0 includes the XNA Framework and XNA Framework Content Pipeline:

XNA FrameworkA collection of application programming interfaces (APIs) that greatly simplify common game development tasks, such as graphical rendering and timing, audio playback, input processing, and more.

XNA Framework Content PipelineProvides an easy and flexible way to import three-dimensional (3D) models, textures, sounds, and other assets into your game.

During this lab, you will build a full XNA Framework game for Windows Phone 7. The game you will build, Catapult Wars, is a single-player game for Windows Phone 7 where the player controls a catapult and attempts to destroy the computer's catapult. The first side to achieve five points by destroying the opposing catapult wins the game.

XNA Game Studio Basics

While this game will be composed of a single game screen, other games might be composed of several screens, each representing a different level. Multiple levels can be created by reusing game screens while slightly altering the game logic.

A game usually has three states:

  • Loading – In this state, the system loads resources, initializes game-related variables, and performs any other tasks that have to be performed before the game actually begins. This state typically occurs only once in the game’s life cycle. More complicated games may divide loading among the levels or stages of a game as the user progresses.
  • Update – In this state, the system needs to update the game-world state. Usually this means calculating new positions of the acting entities, updating their positions and actions, recalculating the score, and performing other game logic as is relevant to the game. These updates occur regularly throughout the time that the game engine is active.
  • Draw – In this state, the system draws the changes, calculated in the update state, to the output graphics device. Drawing occurs regularly throughout the time that the game engine is active.

In the XNA Framework, the Update and Draw stages can occur up to 60 times per second on a PC or Xbox 360® and up to 30 times per second on a Zune®, Zune HD or Windows Phone 7 device.

General Architecture

Catapult Wars is built on another sample, Windows Phone Game State Management (found at https://create.msdn.com/en-US/education/catalog/sample/game_state_management), which provides some of the assets for this lab. The game includes the following screens:

  • Main menu (MainMenuScreen class)
  • Instructions screen (InstructionScreen class)
  • Playing the game (GameplayScreen class)
  • Paused (PauseScreen class)

The Game performs game-specific content loading during the GameplayScreen class’s initialization.

When launched, the game’s first action is to load and display the background screen and then the main menu screen. Once the main menu screen is loaded, the menus animate onto the screen, after which the user can access the game itself.

We start by implementing the GameplayScreen class, which serves as the actual games. The other screens are discussed in the next exercise.

The completed game will have a screen like that in Figure 1.

Figure 1. Catapult Wars

First Image of the Game

GameplayScreen and Game Classes

Technically, the game’s update and drawing logic will be contained in the GameplayScreen class. However, the GameplayScreen itself will not directly handle all of the work, as some of it will be the responsibly of the relevant game classes.

Let us review some of the game classes and their intended purpose:

  • Player: The player class will represent one of the two players participating in the game and will be responsible for drawing its associated catapult on the screen. Two different sub-classes of the Player class will actually represent each of the players. The Human class will represent the human player and will contain additional logic for handling input and providing visual feedback, while the AI class will represent the computer player and will contain additional logic for automatically aiming and firing at the opposing human player.
  • Catapult: The catapult class will encapsulate all the drawing and logic related to one of the catapults in the game. The class will keep track of its associated catapult’s state and will animate it according to that state.
  • Projectile: This class will represent a projectile fired by one of the catapults. It will be responsible for rendering the projectile and updating its position, but not for determining whether or not the projectile has hit anything, as this will be the job of the Catapult class.
  • Animation: A helper class for displaying animations.
  • AudioManager: A helper class for playing sounds.

Task 1 – Rendering Basic Game

This task will introduce you to the use of resources and the ScreenManager class. The real focus of this task is to add most of the initial rendering code to the game. We also delve into gameplay logic, where necessary, and implement very basic versions of some of the game classes.

  1. Start Visual Studio 2010 or Visual C# 2010 Express.
  2. Open the starter solution located in the Source\Ex1-BasicGame\Begin folder.
  3. Select the Screens folder and add all existing files from the lab install folder under Assets\Code\Screens. To add existing items, right-click the Screens folder in the solution explorer and select Add | Existing items:
  4. A file selection dialog will appear. Navigate to the path specified in the previous step, select all source files, and click the Add button.
  5. Review the solution explorer after performing the last few steps, the following folders should appears under Screens folder: BackgroundScreen.cs, GameplayScreen.cs, InstructionsScreen.cs, MainMenuScreen.cs, PauseScreen.cs and MusicSelectionScreen.cs
  6. Open the file name GameplayScreen.cs located under Screens folder.
  7. Add the following field definitions to the class. We will use these fields for loading the textures/fonts used to draw the screen (though some will not be used until much later in the exercise) and also to position some of the assets on the screen:

    C#

    // Texture Members Texture2D foregroundTexture; Texture2D cloud1Texture; Texture2D cloud2Texture; Texture2D mountainTexture; Texture2D skyTexture; Texture2D hudBackgroundTexture; Texture2D ammoTypeTexture; Texture2D windArrowTexture; Texture2D defeatTexture; Texture2D victoryTexture; SpriteFont hudFont; // Rendering members Vector2 cloud1Position; Vector2 cloud2Position;

  8. Create a new method and name it “LoadAssets”. This method loads the gameplay screen’s resources and initialize some of its variables:

    C#

    public void LoadAssets() { // Load textures foregroundTexture = Load<Texture2D>("Textures/Backgrounds/gameplay_screen"); cloud1Texture = Load<Texture2D>("Textures/Backgrounds/cloud1"); cloud2Texture = Load<Texture2D>("Textures/Backgrounds/cloud2"); mountainTexture = Load<Texture2D>("Textures/Backgrounds/mountain"); skyTexture = Load<Texture2D>("Textures/Backgrounds/sky"); defeatTexture = Load<Texture2D>("Textures/Backgrounds/defeat"); victoryTexture = Load<Texture2D>("Textures/Backgrounds/victory"); hudBackgroundTexture = Load<Texture2D>("Textures/HUD/hudBackground"); windArrowTexture = Load<Texture2D>("Textures/HUD/windArrow"); ammoTypeTexture = Load<Texture2D>("Textures/HUD/ammoType"); // Load font hudFont = Load<SpriteFont>("Fonts/HUDFont"); // Define initial cloud position cloud1Position = new Vector2(224 - cloud1Texture.Width, 32); cloud2Position = new Vector2(64, 90); }

  9. The GameScreen class defines some core game functionality matching the three states described in the exercise preface: LoadContent, Update, and Draw. Override the base class’s LoadContent functionality:

    C#

    public override void LoadContent() { LoadAssets(); base.LoadContent(); }

    You may wonder why we did not simply place the code from the “LoadAssets” method inside the preceding override. The reason is that the asset loading operation is rather lengthy, and in the next exercise, we see how we can introduce a loading prompt so that the game does not appear unresponsive. For that purpose, we want to be able to load the assets independently of the gameplay screen’s own LoadContent override.

  10. Override the Draw method so that the gameplay screen will be able to draw itself onto the screen:

    C#

    public override void Draw(GameTime gameTime) { float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds; ScreenManager.SpriteBatch.Begin(); // Render all parts of the screen DrawBackground(); // DrawComputer(gameTime); // DrawPlayer(gameTime); // DrawHud(); ScreenManager.SpriteBatch.End(); }

    Notice that the Draw method is implemented using several helper methods which draw different aspects of the game. Most of them are currently commented out; we deal with them later in the exercise.

    When this method is called, the gameTime argument contains the time that passed since the last call to Draw was made.

  11. Add the DrawBackground helper method which draws the various background elements:

    C#

    private void DrawBackground() { // Clear the background ScreenManager.Game.GraphicsDevice.Clear(Color.White); // Draw the Sky ScreenManager.SpriteBatch.Draw(skyTexture, Vector2.Zero, Color.White); // Draw Cloud #1 ScreenManager.SpriteBatch.Draw(cloud1Texture, cloud1Position, Color.White); // Draw the Mountain ScreenManager.SpriteBatch.Draw(mountainTexture, Vector2.Zero, Color.White); // Draw Cloud #2 ScreenManager.SpriteBatch.Draw(cloud2Texture, cloud2Position, Color.White); // Draw the Castle, trees, and foreground ScreenManager.SpriteBatch.Draw(foregroundTexture, Vector2.Zero, Color.White); }

    This code simply draws the game’s background image to the screen. The code uses the SpriteBatch class from the Microsoft.Xna.Framework.Graphics namespace to draw to the graphics device. It enables a group of sprites (2D graphics) to be drawn quickly by reusing similar rendering settings.

    It is now possible for a user to see the gameplay screen, but we must first connect it to the rest of the game. In order to accomplish that, we revisit some of our code from exercise 1.

  12. The gameplay screen can now be made visible. To do that, we are required to alter the game class CatapultGame. Open the CatapultGame.cs file from the solution.
  13. Add a constructor to the class which will add the gameplay screen to the screen manager:

    C#

    public CatapultGame() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; // Frame rate is 30 fps by default for Windows Phone. TargetElapsedTime = TimeSpan.FromTicks(333333); //Create a new instance of the Screen Manager screenManager = new ScreenManager(this); Components.Add(screenManager); //Switch to full screen for best game experience graphics.IsFullScreen = true; // TODO: Start with menu screen screenManager.AddScreen(new GameplayScreen(), null); // AudioManager.Initialize(this); }

    Note the “TODO” marker comment in the preceding code. In the next exercise, we change the code directly below it, which adds the gameplay screen to the screen manager, to add a menu screen as the initial screen instead.

    Also, note that the constructor also contains a commented-out initialization of an “AudioManager” class. We will deal with this class in the next exercise (and un-comment the relevant code to initialize it).

  14. Build the project and deploy it. Once the game starts, you should see a screen like that in Figure 2.

    Figure 2. First look at the gameplay screen

    Background of the Game

    At this point, the gameplay screen is somewhat barren, so next we add the Heads-up-display (HUD) for the game. The HUD is the portion of the game’s interface that displays vital information to the user such as the current score.

    However, we also need some additional variables to keep track of the information we are about to display, and this is a great opportunity to introduce some of the game classes in order to encapsulate some of the information.

    Our first task, therefore, will be to create basic versions of the Player class and its two sub-classes: Human and AI.

  15. Open the file name Player.cs under the Players project folder.
  16. Add the following variable declarations to the Player class:

    C#

    protected CatapultGame curGame; protected SpriteBatch spriteBatch; // Constants used for calculating shot strength public const float MinShotStrength = 150; public const float MaxShotStrength = 400; // Public variables used by Gameplay class // TODO enable this: public Catapult Catapult; public int Score { get; set; } public string Name { get; set; }

    These variables will give the Player class access to the game object and to a SpriteBatch that can be used for visual output, and will allow it to keep track of the player’s score and name. Notice the commented-out member variable, Catapult, which represents the catapult associated with the player. We restore the Catapult member at a later point. Also notice the two constants defined, which are used later to perform some calculations related to firing projectiles.

  17. Change the class constructor as it’s shown below:

    C#

    public Player(Game game)
    FakePre-192ada50c2504eab8b8dafe3fcf37ee0-51b483dbc15e40ab87e037845a8e767eFakePre-fcfe4361bd944dd78e9ddd5ea3ef4d82-473d71f3570d4275b9ef961c70c3cd28curGame = (CatapultGame)game;FakePre-6cbcfce78df845b6bfc7e5d9949d947f-5543fc86899e47f1b614e11e49b62a7b

  18. Add a set of initialization methods for the Player class. One of the methods is a constructor and the second is an override of the DrawableGameComponent’s Initialize method, which is typically used for loading resources required by the component before displaying it:

    C#

    public Player(Game game, SpriteBatch screenSpriteBatch) : this(game) { spriteBatch = screenSpriteBatch; } public override void Initialize() { Score = 0; base.Initialize(); }

  19. Open the file name Human.cs under the Players project folder.
  20. Add the following constructor inside the Human class:

    C#

    public Human(Game game, SpriteBatch screenSpriteBatch) : base(game, screenSpriteBatch) { // TODO: Initialize catapult }

    We will later initialize the player’s catapult as part of the second constructor.

  21. Open the file name AI.cs under the Players project folder.
  22. Add the following constructor inside the AI class:

    C#

    public AI(Game game, SpriteBatch screenSpriteBatch) : base(game, screenSpriteBatch) { // TODO: Initialize catapult }

    Now that our basic versions of the player classes are ready, it is time to utilize them in GameplayScreen.

  23. Open the file GameplayScreen.cs located inside the Screens project folder and add additional variable definitions to the GameplayScreen class. Place them just under the existing variable definitions (old code has been colored gray):

    C#

    ... Vector2 cloud1Position; Vector2 cloud2Position; Vector2 playerHUDPosition; Vector2 computerHUDPosition; Vector2 windArrowPosition; // Gameplay members public Human player; public AI computer; Vector2 wind; bool changeTurn; public bool isHumanTurn; bool gameOver; Random random; const int minWind = 0; const int maxWind = 10; // Helper members bool isDragging;

  24. Add the following code to LoadAssets method by adding additional initialization as shown in the following code:

    C#

    public void LoadAssets() { ... // Define initial HUD positions playerHUDPosition = new Vector2(7, 7); computerHUDPosition = new Vector2(613, 7); windArrowPosition = new Vector2(345, 46); // Initialize human & AI players player = new Human(ScreenManager.Game, ScreenManager.SpriteBatch); player.Initialize(); player.Name = "Player"; computer = new AI(ScreenManager.Game, ScreenManager.SpriteBatch); computer.Initialize(); computer.Name = "Phone"; // TODO: Initialize enemy definitions }

    Notice that we have left a placeholder in the code where future code will designate the human and AI players as enemies.

  25. Add the following highlighted coded inside GameplayScreen class’s LoadContent override so that it looks like the following:

    C#

    public override void LoadContent()
    FakePre-dc2371d0e6144df7889fc8f73f0bc306-a67efc5c60654e0f8b00ca3905654447FakePre-5e6cdf11c55344cc99f1d53331e3975a-db49e2cfbb684716b276803c1b66c00cFakePre-34fa9d71d29d41ed82d7044beae4c943-a59adbac28ac460686ab55695e62706dFakePre-7317a5186e5d458db12dae72da13b4e8-200bc3ba7beb43c08e8c5260ba62dad3FakePre-0557d694b81b4dddaf4e594d1f0712c4-8cb88f7673164ad48e78f96d3cd0145e// Start the gameFakePre-cb28c0fa93874cf48da95f74f6c5c96f-23fa0b5d897c4ff0b94146385f497d74FakePre-d4fa7699d31240d7abe56218ef09cf50-063a0e407da44b14bebb693256d7626f

  26. Add the Start helper method, which deals with initializations directly related to the beginning of the game:

    C#

    void Start() { // Set initial wind direction wind = Vector2.Zero; isHumanTurn = false; changeTurn = true; // computer.Catapult.CurrentState = CatapultState.Reset; }

    The line that is currently commented out in the preceding code will later integrate with other game logic in order to properly set up the game’s turn cycle.

  27. Add a pair of methods to the GameplayScreen class. These methods will be used to draw text to the screen with a shadow effect:

    C#

    // A simple helper to draw shadowed text. void DrawString(SpriteFont font, string text, Vector2 position, Color color) { ScreenManager.SpriteBatch.DrawString(font, text, new Vector2(position.X + 1, position.Y + 1), Color.Black); ScreenManager.SpriteBatch.DrawString(font, text, position, color); } // A simple helper to draw shadowed text. void DrawString(SpriteFont font, string text, Vector2 position, Color color, float fontScale) { ScreenManager.SpriteBatch.DrawString(font, text, new Vector2(position.X + 1, position.Y + 1), Color.Black, 0, new Vector2(0, font.LineSpacing / 2), fontScale, SpriteEffects.None, 0); ScreenManager.SpriteBatch.DrawString(font, text, position, color, 0, new Vector2(0, font.LineSpacing / 2), fontScale, SpriteEffects.None, 0); }

    The preceding helper methods draw shadowed text by drawing two instances of a specified string, one colored black and with a slight offset from the other. The second variation of the method allows scaling of the written text.

  28. Change GameplayScreen’s Draw method by restoring the call to the DrawHud method. The method should now look like this:

    C#

    public override void Draw(GameTime gameTime)
    FakePre-76d55f12baf8408da9f32d42babb1e93-47d5635a773345a2aca9a52f73ea2738FakePre-d7b15586dbe047e4a1a88c89d656d526-6936eeb324ed44049c2e0292246245aaFakePre-4d04c2cee634400db680f05c126361de-e788acdd999e4163938f9abbca43d0ceFakePre-8546eb1b7f2e44fd864d56ffd1247ebb-adb684a0cd8c4ab99150efe48db7a18eFakePre-2c5af17a467e451998d1b35a33ef59f9-133ffe9e2f414bb590dc7bc42f295638FakePre-c561ad229f9d49619ac91f4e887f642e-161bbe559db149ab93a967573bbbe3beFakePre-352ece94162e4b1684ed3c616d5f6721-52f6df26b0b64a17a1fdbb84d6b2e241FakePre-fb8aaa356a6e4b48b34b8218ee5a74ab-16bee08317e9435f9ac71a37c2ce01daFakePre-edce0036d923456a927a1f30a2846d4d-3974553109a143f189893551422b1133DrawHud();FakePre-8bb5f42761de41fd88a47f5913ccc355-b01c20761e8b4a57922f0c212a014ffaFakePre-e4267e74e8384c5fab09b9d56821f26f-086c4c2788c84e598d94b1f5aedbb00cFakePre-7ddc060d0c9740a4b0edfdc358b435fd-87e22b4cfbda4e959db34188222393ff

  29. Add the DrawHud method:

    C#

    void DrawHud() { if (gameOver) { Texture2D texture; if (player.Score > computer.Score) { texture = victoryTexture; } else { texture = defeatTexture; } ScreenManager.SpriteBatch.Draw( texture, new Vector2(ScreenManager.Game.GraphicsDevice.Viewport.Width / 2 - texture.Width / 2, ScreenManager.Game.GraphicsDevice.Viewport.Height / 2 - texture.Height / 2), Color.White); } else { // Draw Player Hud ScreenManager.SpriteBatch.Draw(hudBackgroundTexture, playerHUDPosition, Color.White); ScreenManager.SpriteBatch.Draw(ammoTypeTexture, playerHUDPosition + new Vector2(33, 35), Color.White); DrawString(hudFont, player.Score.ToString(), playerHUDPosition + new Vector2(123, 35), Color.White); DrawString(hudFont, player.Name, playerHUDPosition + new Vector2(40, 1), Color.Blue); // Draw Computer Hud ScreenManager.SpriteBatch.Draw(hudBackgroundTexture, computerHUDPosition, Color.White); ScreenManager.SpriteBatch.Draw(ammoTypeTexture, computerHUDPosition + new Vector2(33, 35), Color.White); DrawString(hudFont, computer.Score.ToString(), computerHUDPosition + new Vector2(123, 35), Color.White); DrawString(hudFont, computer.Name, computerHUDPosition + new Vector2(40, 1), Color.Red); // Draw Wind direction string text = "WIND"; Vector2 size = hudFont.MeasureString(text); Vector2 windarrowScale = new Vector2(wind.Y / 10, 1); ScreenManager.SpriteBatch.Draw(windArrowTexture, windArrowPosition, null, Color.White, 0, Vector2.Zero, windarrowScale, wind.X > 0 ? SpriteEffects.None : SpriteEffects.FlipHorizontally, 0); DrawString(hudFont, text, windArrowPosition - new Vector2(0, size.Y), Color.Black); if (wind.Y == 0) { text = "NONE"; DrawString(hudFont, text, windArrowPosition, Color.Black); } if (isHumanTurn) { // Prepare human prompt message text = !isDragging ? "Drag Anywhere to Fire" : "Release to Fire!"; size = hudFont.MeasureString(text); } else { // Prepare AI message text = "I'll get you yet!"; size = hudFont.MeasureString(text); } DrawString(hudFont, text, new Vector2( ScreenManager.GraphicsDevice.Viewport.Width / 2 - size.X / 2, ScreenManager.GraphicsDevice.Viewport.Height - size.Y), Color.Green); } }

    Let us review this rather lengthy method.

    First, we check to see whether the game is over, drawing a banner for victory or defeat, according to how the game ended.

    If the game has not yet ended, we then draw two nearly identical elements portraying the status of both players. Each element is composed of (1) a background image, (2) text depicting the player’s name and score, and (3) a graphical representation of the type of ammunition the player is currently using. (Though our final game will not actually present the player with different types of ammunition, this serves as an extension point for such a feature.)

    After drawing both players’ statuses, we draw an indicator that notifies the player of the direction of the wind. The purpose of the wind within the frame of the game is to make it more challenging for the player to aim, because it affects the course of his shot. The wind in the game will blow to the left or right at a varying strength; it may also not blow at all. Instead of representing the wind as a scalar value, it is represented by the “wind” 2D vector. The wind’s X component denotes its direction and its Y component denotes its strength. If we examine the code that draws the wind indicator, we can see that first the word “WIND” is drawn on the display, followed either by an arrow representing the magnitude of the wind that currently blows or by the text “NONE” if there is currently no wind.

    Finally, we draw a text prompt at the bottom of the screen. The exact text depends on whether the human player is currently the active player or not, and whether the player is currently in the process of taking a shot.

  30. Compile and deploy the project. You should now see an image like Figure 3.

    Figure 3. The gameplay screen, with the new HUD

    While the game screen now contains much more information, it is still missing a key aspect, which is also the namesake of the game—the catapults. We now focus on adding the Catapult class, which is responsible for drawing the game’s catapults and which will eventually be responsible for much of the game’s logic.

  31. Open the file name Catapult.cs under the Catapult project folder.
  32. Add the following using statement to the top of the newly created file:

    C#

    using Microsoft.Devices;

  33. The preceding using statements requires us to add an assembly reference to the project. This will allow the project to utilize the services implemented by the referenced assembly. Add an additional reference to the CatapultGame project. The reference is for the Microsoft.Devices.Sensors assembly.
  34. Add the following variable declarations to the Catapult class:

    C#

    // MARK: Fields start CatapultGame curGame = null; SpriteBatch spriteBatch; Texture2D idleTexture; string idleTextureName; bool isAI; SpriteEffects spriteEffects; // MARK: Fields end

    Now the class can store its associated game, a SpriteBatch with which to display assets, and a texture which shows the catapult in its idle state. We also added a variable to represent whether the catapult is AI controlled or human controlled and an additional rendering variable we will not immediately use.

  35. Add the following properties and backing fields to the class. One will be used to represent the catapult’s position, and another to represent the catapult’s state:

    C#

    Vector2 catapultPosition; public Vector2 Position { get { return catapultPosition; } } CatapultState currentState; public CatapultState CurrentState { get { return currentState; } set { currentState = value; } }

  36. Change the constructor as it shown below:

    C#

    public Catapult(Game game)
    FakePre-ee9e1159c4674fe8b240d92bef2a8fbb-203e5b6a408341c4b5e8a6dd266143e3FakePre-07f7fc19d9a54f709ed698cb0178945d-e21ff35d4140407f997ba3ee0115a2e6curGame = (CatapultGame)game;FakePre-da30ab7f02344983ba3b46cef925bbea-f26b64940a7947bfbb65afa1e793a07c

  37. Add the following constructor to the catapult class:

    C#

    public Catapult(Game game, SpriteBatch screenSpriteBatch, string IdleTexture, Vector2 CatapultPosition, SpriteEffects SpriteEffect, bool IsAI) : this(game) { idleTextureName = IdleTexture; catapultPosition = CatapultPosition; spriteEffects = SpriteEffect; spriteBatch = screenSpriteBatch; isAI = IsAI; // splitFrames = new Dictionary<string, int>(); // animations = new Dictionary<string, Animation>(); }

    This constructor contains some lines that are commented out, which refer to members that we add at a later stage.

  38. In the Catapult class, override the DrawableGameComponent’s Initialize method:

    C#

    public override void Initialize() { // Define initial state of the catapult currentState = CatapultState.Idle; // Load the idle texture idleTexture = curGame.Content.Load<Texture2D>(idleTextureName); base.Initialize(); }

  39. Finally, we override the Draw method so that the Catapult can be drawn to the screen:

    C#

    public override void Draw(GameTime gameTime) { spriteBatch.Draw(idleTexture, catapultPosition, null, Color.White, 0.0f, Vector2.Zero, 1.0f, spriteEffects, 0); base.Draw(gameTime); }

    The catapults can now be drawn, but in order for that to actually happen, we need to revisit the player classes and the gameplay screen.

  40. Open the Player.cs file and restore the field definition for the “Catapult” field. The relevant portion of the file should now look like this:

    C#

    ... protected CatapultGame curGame; protected SpriteBatch spriteBatch; // Public variables used by Gameplay class public Catapult Catapult { get; set; } public int Score; public string Name; ...

  41. Override the Draw method in the Player class:

    C#

    public override void Draw(GameTime gameTime) { // Draw related catapults Catapult.Draw(gameTime); base.Draw(gameTime); }

  42. Open the Human.cs file and locate the TODO marker we have left in the Human class’s constructor. Change it so that the constructor would look like the following:

    C#

    public Human(Game game, SpriteBatch screenSpriteBatch) : base(game, screenSpriteBatch) { Catapult = new Catapult(game, screenSpriteBatch, "Textures/Catapults/Blue/blueIdle/blueIdle", new Vector2(140, 332), SpriteEffects.None, false); }

  43. Override the base class’s Initialize method:

    C#

    public override void Initialize() { // TODO: Load textures Catapult.Initialize(); base.Initialize(); }

    Notice that we have left a placeholder where we will later load additional textures used by the Human class.

  44. Open the AI.cs file and locate the TODO marker we have left in the AI class’s constructor. Change it so that the constructor would look as follows:

    C#

    public AI(Game game, SpriteBatch screenSpriteBatch) : base(game, screenSpriteBatch) { Catapult = new Catapult(game, screenSpriteBatch, "Textures/Catapults/Red/redIdle/redIdle", new Vector2(600, 332), SpriteEffects.FlipHorizontally, true); }

  45. Override the base class’s Initialize method:

    C#

    public override void Initialize() { // TODO: Additional initialization Catapult.Initialize(); base.Initialize(); }

    Notice that we have left a placeholder where we will later perform further initialization.

  46. Open the GameplayScreen.cs file and add the DrawComputer and DrawPlayer helper methods to the GameplayScreen class:

    C#

    void DrawPlayer(GameTime gameTime) { if (!gameOver) player.Draw(gameTime); } void DrawComputer(GameTime gameTime) { if (!gameOver) computer.Draw(gameTime); }

  47. Finally, we must make the GameplayScreen class render both players. Navigate to the “Draw” method and remove the comments on the lines which call the “DrawComputer” and “DrawPlayer” helper methods. The relevant portion of the code should now look like this:

    C#

    ... DrawBackground(); DrawComputer(gameTime); DrawPlayer(gameTime); DrawHud(); ...

  48. Compile and deploy the project. After navigating to the game screen, the catapults should now be visible, as shown in Figure 4.

    Figure 4. The catapults can now be seen

    This concludes the first task of this exercise. During the next task, we will implement the game’s logic; at this point, the game merely draws its various elements to the screen.

Task 2 – Making the Game Logic

In the course of this task, we add many elements that are still missing from the game. We add a turn cycle that alternates between the human and computer players, giving each a chance to take a shot at the other player. This includes adding projectiles and their associated physics, handling user input, writing the AI logic and so on.

  1. Open the class name Projectile under Catapult folder. Both players fire projectiles, and the projectiles need to behave in accordance to the laws of physics, taking the wind into account as well.
  2. Add the following field and property definitions to the Projectile class:

    C#

    SpriteBatch spriteBatch; Game curGame; Random random; // Textures for projectile string textureName; // Position and speed of projectile Vector2 projectileVelocity = Vector2.Zero; float projectileInitialVelocityY; Vector2 projectileRotationPosition = Vector2.Zero; float projectileRotation; float flightTime; bool isAI; float hitOffset; float gravity; Vector2 projectileStartPosition; public Vector2 ProjectileStartPosition { get { return projectileStartPosition; } set { projectileStartPosition = value; } } Vector2 projectilePosition = Vector2.Zero; public Vector2 ProjectilePosition { get { return projectilePosition; } set { projectilePosition = value; } } // Gets the position where the projectile hit the ground. // Only valid after a hit occurs. public Vector2 ProjectileHitPosition { get; private set; } Texture2D projectileTexture; public Texture2D ProjectileTexture { get { return projectileTexture; } set { projectileTexture = value; } }

    Most of the preceding fields and properties have names that make it possible to deduce their purpose, but this will become clearer as we implement more of the projectile’s code.

  3. Change the constructor as below

    C#

    public Projectile(Game game)
    FakePre-023e79b397a9493ba90ff4373b278a5a-b83e694c3e5341d5bbf5def00b7b2900FakePre-76bb3e1d7cd74e4a93c2468e8811a426-b55ffaea9ca64e38a54641f8d93b8b45curGame = game;FakePre-cbd188240e1d4cc599d6beb390f84b17-2dd7e05113984dc9a0439da1573dfd7eFakePre-e28e56340e264fe0aebc2a33f52c8844-cbda6df475cc46b0b7e2a50df4505659

  4. Add the following constructor to the Projectile class:

    C#

    public Projectile(Game game, SpriteBatch screenSpriteBatch, string TextureName, Vector2 startPosition, float groundHitOffset, bool isAi, float Gravity) : this(game) { spriteBatch = screenSpriteBatch; projectileStartPosition = startPosition; textureName = TextureName; isAI = isAi; hitOffset = groundHitOffset; gravity = Gravity; }

    The preceding constructor simply initialize the class’s various fields and are used later by the Catapult class to create new projectiles.

  5. Override the DrawableGameComponent’s Initialize method to load the projectile’s texture:

    C#

    public override void Initialize() { // Load a projectile texture projectileTexture = curGame.Content.Load<Texture2D>(textureName); }

  6. Override the DrawableGameComponent’s Draw method:

    C#

    public override void Draw(GameTime gameTime) { spriteBatch.Draw(projectileTexture, projectilePosition, null, Color.White, projectileRotation, new Vector2(projectileTexture.Width / 2, projectileTexture.Height / 2), 1.0f, SpriteEffects.None, 0); base.Draw(gameTime); }

    The Draw method is written so that the projectile can be rotated by updating the “projectileRotation” field.

  7. Next, add the most important projectile method—the one which updates the projectile while it is in flight:

    C#

    public void UpdateProjectileFlightData(GameTime gameTime, float wind, float gravity, out bool groundHit) { flightTime += (float)gameTime.ElapsedGameTime.TotalSeconds; // Calculate new projectile position using standard // formulas, taking the wind as a force. int direction = isAI ? -1 : 1; float previousXPosition = projectilePosition.X; float previousYPosition = projectilePosition.Y; projectilePosition.X = projectileStartPosition.X + (direction * projectileVelocity.X * flightTime) + 0.5f * (8 * wind * (float)Math.Pow(flightTime, 2)); projectilePosition.Y = projectileStartPosition.Y - (projectileVelocity.Y * flightTime) + 0.5f * (gravity * (float)Math.Pow(flightTime, 2)); // Calculate the projectile rotation projectileRotation += MathHelper.ToRadians(projectileVelocity.X * 0.5f); // Check if projectile hit the ground or even passed it // (could happen during normal calculation) if (projectilePosition.Y >= 332 + hitOffset) { projectilePosition.X = previousXPosition; projectilePosition.Y = previousYPosition; ProjectileHitPosition = new Vector2(previousXPosition, 332); groundHit = true; } else { groundHit = false; } }

    Let us review the preceding function. First we keep track of the projectile’s total flight time by incrementing the value we have previously stored with the time elapsed since this method was last invoked (provided by the caller using the gameTime parameter). Next, we calculate the projectile’s new position according to its initial velocity, the wind and the effects of gravity. After calculating the projectile’s new position, we rotate it according to how fast it is travelling and check whether it has hit the ground. If the projectile has hit the ground, we alter its position slightly so that it does not appear as if it has entered the ground and store the hit position for later use.

  8. Now add one last method to the Projectile class:

    C#

    public void Fire(float velocityX, float velocityY) { // Set initial projectile velocity projectileVelocity.X = velocityX; projectileVelocity.Y = velocityY; projectileInitialVelocityY = projectileVelocity.Y; // Reset calculation variables flightTime = 0; }

    The preceding method initializes a projectile after it has been “fired” by one of the catapults.

    The projectile class is ready and we can now set our sights on expanding the Catapult class.

  9. It is time to add some additional fields and constants to the Catapult class. To make things simple, replace all code between the two comments “// MARK: Fields start” and “// MARK: Fields end”:

    C#

    // MARK: Fields start CatapultGame curGame = null; SpriteBatch spriteBatch; Random random; const int winScore = 5; public bool AnimationRunning { get; set; } public string Name { get; set; } public bool IsActive { get; set; } // In some cases, the game needs to start second animation while first // animation is still running; // this variable defines at which frame the second animation should start // UNCOMMENT: Dictionary<string, int> splitFrames; Texture2D idleTexture; // UNCOMMENT: Dictionary<string, Animation> animations; SpriteEffects spriteEffects; // Projectile Projectile projectile; string idleTextureName; bool isAI; // Game constants const float gravity = 500f; // State of the catapult during its last update CatapultState lastUpdateState = CatapultState.Idle; // Used to stall animations int stallUpdateCycles; // MARK: Fields end

    You will notice some comments that begin with “UNCOMMENT”. We introduce the fields that these comments hide later, but they are not required yet; one of them relies on a class that we have yet to implement. You might remember that we have already encountered these fields when implementing one of the Catapult class’s constructors.

  10. Add the following properties to the Catapult class:

    C#

    float wind; public float Wind { set { wind = value; } } Player enemy; internal Player Enemy { set { enemy = value; } } Player self; internal Player Self { set { self = value; } } // Describes how powerful the current shot being fired is. The more powerful // the shot, the further it goes. 0 is the weakest, 1 is the strongest. public float ShotStrength { get; set; } public float ShotVelocity { get; set; } // Used to determine whether the game is over public bool GameOver { get; set; }

    These properties allow a catapult to keep track of the wind, its associated player, the enemy player, the current shot being fired, and the state of the current game.

  11. Now alter the Catapult class’s “Initialize” method by adding some code just before the call to base.Initialize. This code will initialize the new fields we have added:

    C#

    public override void Initialize() { // Load the textures idleTexture = curGame.Content.Load<Texture2D>(idleTextureName); // Initialize the projectile Vector2 projectileStartPosition; if (isAI) projectileStartPosition = new Vector2(630, 340); else projectileStartPosition = new Vector2(175, 340); // TODO: Update hit offset projectile = new Projectile(curGame, spriteBatch, "Textures/Ammo/rock_ammo", projectileStartPosition, 60, isAI, gravity); projectile.Initialize(); IsActive = true; AnimationRunning = false; stallUpdateCycles = 0; // Initialize randomizer random = new Random(); base.Initialize(); }

    While we have set the projectile’s hit offset to 60 in the preceding code, we will later change this so that the size is relative to the size of the catapult’s graphical asset.

  12. Locate the Update method in the Catapult class, giving it an opportunity to update its own state and keep track of fired projectiles, and uncomment the code inside this method:

    C#

    public override void Update(GameTime gameTime)
    FakePre-e860aad07762498284c353d35056e11e-32aac4e9eaf84b95a43b1ecad6bb55f9//bool isGroundHit;FakePre-1842119496dc4481a8bcefdebb48f1d1-d3e4c546c25b40b39829d41e03c6506bFakePre-4351e3cd518a4e629b24daacdfa25bdb-6b4077369a914abc82df98609c08ccebFakePre-ca4cf804e8aa45eeae5ea2f219e73d63-73cbd4d20f3d4b5bbcc5c28950519f28FakePre-f7a30b4519ef4cf284a1c32fa0a1c05a-51539d31e64749408d70ecd82b89248eFakePre-dc908cffa45d4673863e578e1276577b-7668ca27fd3c4607b77500fd97461217FakePre-cd0645aaf4234b0089542797e27ed7ae-e6152c2531774af3b27a04a2b2be3fce

    As you can see, the method mainly updates the catapult’s own state and the state of its fired projectile. Notice the many placeholders, which we later use to animate the catapult according to its state, to play sounds, and to cause the device to vibrate.

  13. Implement the “CheckHit” method, which appears in the Update method in the preceding code block. This method is responsible for determining whether a projectile has hit one of the catapults:

    C#

    private bool CheckHit() { bool bRes = false; // Build a sphere around the projectile Vector3 center = new Vector3(projectile.ProjectilePosition, 0); BoundingSphere sphere = new BoundingSphere(center, Math.Max(projectile.ProjectileTexture.Width / 2, projectile.ProjectileTexture.Height / 2)); // Check Self-Hit - create a bounding box around self // TODO: Take asset size into account Vector3 min = new Vector3(catapultPosition, 0); Vector3 max = new Vector3(catapultPosition + new Vector2(75, 60), 0); BoundingBox selfBox = new BoundingBox(min, max); // Check enemy - create a bounding box around the enemy // TODO: Take asset size into account min = new Vector3(enemy.Catapult.Position, 0); max = new Vector3(enemy.Catapult.Position + new Vector2(75, 60), 0); BoundingBox enemyBox = new BoundingBox(min, max); // Check self hit if (sphere.Intersects(selfBox) && currentState != CatapultState.Hit) { // TODO: Play self hit sound // Launch hit animation sequence on self Hit(); enemy.Score++; bRes = true; } // Check if enemy was hit else if (sphere.Intersects(enemyBox) && enemy.Catapult.CurrentState != CatapultState.Hit && enemy.Catapult.CurrentState != CatapultState.Reset) { // TODO: Play enemy hit sound // Launch enemy hit animaton enemy.Catapult.Hit(); self.Score++; bRes = true; currentState = CatapultState.Reset; } return bRes; }

    This method simply uses intersection checks built into the XNA Framework to determine whether the projectile intersects with (that is, has hit) a catapult. You will notice placeholders for sound playback and might notice that we once more use constants in place of sizes relative to the catapult asset. We do this only temporarily, as we will later retrieve asset sizes using the Animation game class, which we have yet to implement.

  14. Implement the “Hit” method used in the CheckHit method. This method simply updates a catapult to represent the fact it has been hit:

    C#

    public void Hit() { AnimationRunning = true; // TODO: Start animations currentState = CatapultState.Hit; }

  15. Now that the catapult has varying states, we create a more sophisticated Draw override, which takes these states into account. Initially, however, it does not do much, because most work will be animating the catapults, something we do not do until the next exercise. Create a new method named “DrawIdleCatapult” as seen in the following code:

    C#

    private void DrawIdleCatapult() { spriteBatch.Draw(idleTexture, catapultPosition, null, Color.White, 0.0f, Vector2.Zero, 1.0f, spriteEffects, 0); }

  16. Now change the “Draw” override so that it looks like this:

    C#

    public override void Draw(GameTime gameTime) { if (gameTime == null) throw new ArgumentNullException("gameTime"); switch (lastUpdateState) { case CatapultState.Idle: DrawIdleCatapult(); break; case CatapultState.Aiming: // TODO: Handle aiming animation break; case CatapultState.Firing: // TODO: Handle firing animation break; case CatapultState.Firing | CatapultState.ProjectileFlying: case CatapultState.ProjectileFlying: // TODO: Handle firing animation projectile.Draw(gameTime); break; case CatapultState.ProjectileHit: // Draw the catapult DrawIdleCatapult(); // TODO: Handle projectile hit animation break; case CatapultState.Hit: // TODO: Handle catapult destruction animation // TODO: Handle explosion animation break; case CatapultState.Reset: DrawIdleCatapult(); break; default: break; } base.Draw(gameTime); }

  17. Add the Fire method, which instructs the Catapult class to fire a projectile:

    C#

    public void Fire(float velocity) { projectile.Fire(velocity, velocity); }

    This concludes our current iteration for the Catapult class. We will now move yet again to the various player classes and expand them further.

  18. Open the Player.cs file and add the following two properties to the Player class:

    C#

    public Player Enemy { set { Catapult.Enemy = value; Catapult.Self = this; } } public bool IsActive { get; set; }

  19. The last thing to do in the Player class is to override the Update method. This will cause the player’s associated catapult to update:

    C#

    public override void Update(GameTime gameTime) { // Update catapult related to the player Catapult.Update(gameTime); base.Update(gameTime); }

  20. Open the AI.cs file and revise the Initialize method. It should now look like this:

    C#

    public override void Initialize()
    FakePre-0d757675e2fa43f794934015c69a6dfb-18c1022606a14f0db599ebe9f745c380//Initialize randomizerFakePre-268845573c01488c86e3bbddccf4b91c-331a6a27006d46699ff2b6db62c99b15FakePre-5a65fe5475214df0aa8ddee686076d98-e6aa0ce6845344fb95426e1a767ac501FakePre-ef18d3de4072443d8d65d3b04bbd8f32-37373be6a8094c7aa57eed4ac2308bf6FakePre-3c3fc0e23f104249b18e1a4faa6d2b5b-b3ab32d9ade3466eb29f4390e2291d46FakePre-564e5c1e45f34a30919d585154ad9ea8-8227d9da3b3d4dd6a6071077b2c6ef5aFakePre-b03d68339c2c4453bf6f79164ede7724-38c7d3b69e8b422d9948f7445065a921

  21. Finally, we will override the Update method in the AI class. This will be used as an opportunity for the computer player to shoot at the human player:

    C#

    public override void Update(GameTime gameTime) { // Check if it is time to take a shot if (Catapult.CurrentState == CatapultState.Aiming && !Catapult.AnimationRunning) { // Fire at a random strength float shotVelocity = random.Next((int)MinShotStrength, (int)MaxShotStrength); Catapult.ShotStrength = (shotVelocity / MaxShotStrength); Catapult.ShotVelocity = shotVelocity; } base.Update(gameTime); }

    This concludes all work on the AI class, and we can move on to the Human class. The Human class presents a new challenge, because this is where we will introduce the input handling required to respond to the user’s actions.

  22. Open the Human.cs file and add the following fields to the Human class:

    C#

    GestureSample?prevSample;GestureSample? firstSample; public bool isDragging { get; set; } // Constant for longest distance possible between drag points readonly float maxDragDelta = (new Vector2(480, 800)).Length(); // Textures & position & spriteEffects used for Catapult Texture2D arrow; float arrowScale; Vector2 catapultPosition = new Vector2(140, 332);

  23. Alter the Human class’s second constructor (the one which receives two arguments) to look like the following:

    C#

    public Human(Game game, SpriteBatch screenSpriteBatch) : base(game, screenSpriteBatch) { Catapult = new Catapult(game, screenSpriteBatch, "Textures/Catapults/Blue/blueIdle/blueIdle", catapultPosition, SpriteEffects.None, false); }

    The only change is that we use the field added in the previous step to specify the catapult’s location.

  24. Revise the Initialize method. We replace the comment beginning with “TODO” with code that loads the texture used to draw visual feedback for the user:

    C#

    public override void Initialize()
    FakePre-178cf32232a94f2a86dcb79eb7b54866-2334f97a13bf4cfeb2f05723eefa8ac1arrow = curGame.Content.Load<Texture2D>("Textures/HUD/arrow");FakePre-1780e9449b3f49e9b90a6f2cc1d2bae2-0cd3d3f9bb314993b5f956121d4bd800FakePre-de52a294e31843628c0b75234d82f52e-3de37f67ef454310a1872c0e6ca1f173FakePre-663ef8bdba95453887195b17d4db4021-9d0c0ecafe134095b769d76e366af068FakePre-2c8768bfb3074e02b8119bb2c258bee5-e3a9635af6c14ad6bcdf06af3afc1c25FakePre-a8a6a539d52a406ba891b4723aa4dba2-fe4f7340f24a480fbeb1c4a0c2d31bc0

  25. Create a new method in the Human class called HandleInput. This method is used to react to the user’s touch gestures, which are used to shoot at the computer player. It is worth mentioning that this method is not invoked automatically; we will invoke it later from the GameplayScreen.

    C#

    public void HandleInput(GestureSample gestureSample) { // Process input only if in Human's turn if (IsActive) { // Process any Drag gesture if (gestureSample.GestureType == GestureType.FreeDrag) { // If drag just began save the sample for future // calculations and start Aim "animation" if(null== firstSample) { firstSample = gestureSample; Catapult.CurrentState = CatapultState.Aiming; } // save the current gesture sampleprevSample = gestureSample; // calculate the delta between first sample and current // sample to present visual sound on screen Vector2 delta = prevSample.Value.Position - firstSample.Value.Position; Catapult.ShotStrength = delta.Length() / maxDragDelta; float baseScale = 0.001f; arrowScale = baseScale * delta.Length(); isDragging = true; } else if (gestureSample.GestureType == GestureType.DragComplete) { // calc velocity based on delta between first and last // gesture samples if (null!= firstSample) { Vector2delta = prevSample.Value.Position - firstSample.Value.Position;Catapult.ShotVelocity = MinShotStrength + Catapult.ShotStrength * (MaxShotStrength - MinShotStrength); Catapult.Fire(Catapult.ShotVelocity); Catapult.CurrentState = CatapultState.Firing; } // turn off dragging state ResetDragState(); } } }

    While lengthy, the preceding method is rather simple. In order to fire, the user will drag a finger across the device’s display and the shot’s strength will be calculated based on the distance between the point where the user first touched the screen to the point where the user lifted the finger from the display. This is exactly what the preceding method is responsible for, all the while updating a variable which will be used to draw visual feedback as the user drags the finger across the display.

  26. Create a new method in the Human class called ResetDragState. This method will reset the dragging state of human-controlled catapult.

    C#

    publicvoidResetDragState(){ firstSample =null;prevSample =null;isDragging =false; arrowScale = 0;Catapult.ShotStrength = 0; }

  27. Finally, we will override the Draw method inside the Human class and add an additional helper method named DrawDragArrow. These methods will display an arrow on screen that depicts the strength of the shot as the user drags a finger across the display:

    C#

    public override void Draw(GameTime gameTime) { if (isDragging) DrawDragArrow(arrowScale); base.Draw(gameTime); } public void DrawDragArrow(float arrowScale) { spriteBatch.Draw(arrow, catapultPosition + new Vector2(0, -40), null, Color.Blue, 0, Vector2.Zero, new Vector2(arrowScale, 0.1f), SpriteEffects.None, 0); }

    You may notice how the arrow’s origin is relative to the position of the catapult.

    This concludes our update to the game classes at this stage. We now move to the GameplayScreen class to implement the final pieces of game logic.

  28. Open the GameplayScreen.cs class, and add the following constructor to the GameplayScreen class:

    C#

    public GameplayScreen() { EnabledGestures = GestureType.FreeDrag | GestureType.DragComplete | GestureType.Tap; random = new Random(); }

    This will enable support for drag and tap in the game.

  29. Navigate to the “LoadAssets” method and locate the comment “// TODO: Initialize enemy definitions”. Add the following code directly beneath it (the following block shows surrounding code as well, with existing code colored gray as before):

    ... computer.Initialize(); computer.Name = "Phone"; // Initialize enemy definitions player.Enemy = computer; computer.Enemy = player; ...

  30. Locate the Update method, and uncomment the code inside this method

    C#

    public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
    FakePre-41f13bb906f24dc09dd0ff491f4009e7-d6cbd5d715f64a4d9e9455f1af156ee3//float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;FakePre-91d719014cb5488185dd7affa4d9c5d8-dfb9630bb5f7439b880848a897992183FakePre-eb987964cb5f4220ab2a74d84d111540-ed54325f4aa24ef79cbb4660a60c5412FakePre-2ceb4f25619e444790145ee8bf942008-6791139e6e064914bc929189a12ead6aFakePre-7acaaabfa55a45208085850e71dd6ce5-44941a5bbb134565bf1e7f05d3a159a5FakePre-c367566de02d42c5ad116065cfb8c4f5-0d6c7da64dd842b58555edcbe2dc3265FakePre-845a1cf69a9946988a9e06c3bfedc3a0-6b41420d28a64af3807d68c73859c794FakePre-a84666bd8e3b4dd096c8f71eecfa5111-1aa5969a7e84450f9f0a1d82afb075e4

    This method is divided into several main segments. We first check whether the game is over, which happens when one of the players reaches a score of five points, and update a flag accordingly. (In the upcoming exercise, we also play appropriate sounds.) We then check whether the current turn is over and reset some fields for the next turn, depending on which type of player has just played (either the human player or the computer player). In addition, assuming a turn has ended, we update the wind. Finally, we ask both players to perform respective updates and change the position of the clouds inside the UpdateClouds helper, which we will implement next.

  31. Add a method called UpdateClouds to the GameplayScreen class:

    C#

    private void UpdateClouds(float elapsedTime) { // Move the clouds according to the wind int windDirection = wind.X > 0 ? 1 : -1; cloud1Position += new Vector2(24.0f, 0.0f) * elapsedTime * windDirection * wind.Y; if (cloud1Position.X > ScreenManager.GraphicsDevice.Viewport.Width) cloud1Position.X = -cloud1Texture.Width * 2.0f; else if (cloud1Position.X < -cloud1Texture.Width * 2.0f) cloud1Position.X = ScreenManager.GraphicsDevice.Viewport.Width; cloud2Position += new Vector2(16.0f, 0.0f) * elapsedTime * windDirection * wind.Y; if (cloud2Position.X > ScreenManager.GraphicsDevice.Viewport.Width) cloud2Position.X = -cloud2Texture.Width * 2.0f; else if (cloud2Position.X < -cloud2Texture.Width * 2.0f) cloud2Position.X = ScreenManager.GraphicsDevice.Viewport.Width; }

    This simply updates the positions of the clouds according to the wind, and causes them to wrap around the screen should they exit its boundaries.

  32. Override the HandleInput method. This is used to actually handle the user’s input:

    C#

    public override void HandleInput(InputState input) { if (input == null) throw new ArgumentNullException("input"); if (gameOver) { if (input.IsPauseGame(null)) { FinishCurrentGame(); } foreach (GestureSample gestureSample in input.Gestures) { if (gestureSample.GestureType == GestureType.Tap) { FinishCurrentGame(); } } return; } if (input.IsPauseGame(null)) { PauseCurrentGame(); } else if (isHumanTurn && (player.Catapult.CurrentState == CatapultState.Idle || player.Catapult.CurrentState == CatapultState.Aiming)) { // Read all available gestures foreach (GestureSample gestureSample in input.Gestures) { if (gestureSample.GestureType == GestureType.FreeDrag) isDragging = true; else if (gestureSample.GestureType == GestureType.DragComplete) isDragging = false; player.HandleInput(gestureSample); } } }

    The preceding method handles the input that instructs the game to pause or end, using helper methods that we will soon implement, and passes gesture information to the Human class for processing, if it is the player’s turn.

  33. Implement the following two methods in the GameplayScreen class. They will be used to pause and end the game:

    C#

    private void FinishCurrentGame() { ExitScreen(); } private void PauseCurrentGame() { // TODO: Display pause screen }

    Notice the preceding placeholder comment beginning with “TODO”. We change that portion of the code later to display a pause screen, which we create during the next exercise.

  34. Navigate to the “Start” method in the GameplayScreen class and restore the final line of code. The method should now look like this:

    C#

    void Start()
    FakePre-273f9ad6c00d4ffba34b177df49da014-33656f395ee14efcbb064d5168175efdFakePre-73a7cda065d04b26b985baf982f2d4fa-e1b18780c6fe44a596dd52c7ec82557bFakePre-c791c3ad013f42c8a60ac3e0e9e966f3-0f347788ea724661ae75519d38a56dffFakePre-cf4fe567a0814ffc957026d945dc4c32-a5fd5ee06a44451a97c566f74d82dd4bFakePre-50060a2d180d49b19b00ef1449ef6969-44d13af6d36f4f1682447ba3b153476fFakePre-210710c5464c45a2a2c2d01b454d4116-bafa0f62c84c4d6ea371d302fce22f8bcomputer.Catapult.CurrentState = CatapultState.Reset;FakePre-296982f393fc46b68243ef17eba176e5-d4aa5f1280074af2985c63e490cbb851

  35. Compile and deploy the project. The game should now be completely playable, though severely lacking in polish. Having left some placeholders to support the addition of animations, the catapults will actually disappear during various stages of the game. Additionally, once the game ends in either victory or defeat, tapping the display will advance to a blank screen. In the next exercise, we will add sounds and animations to improve the game experience.