Platformer Game: implementation of Gamestatemanagement
From XNAWiki
Adding Gamestate to Platformer Game (XNA 3.1):
- Download and unzip the [http://creators.xna.com/en-US/samples/gamestatemanagement]GameStateManagementSample.zip from XNA's Creators Club Website
- Open your platformer project in Visual Studio
- Right click your Solution and click "Add Existing Project" and browse to GameStateManagementWindows.csproj
- You will need to run through the wizard that upgrades this project to 3.1
- With in Visual Studio...copy over all the "Content" from the GameStateManagementWindows to the "HighResolutionContent" of your platformer project
- background.png
- blank.png
- gamefont.spritefont
- gradient.png
- menufont.spritefont
- While still in Visual Studio...copy over the ScreenManager and Screen directories (with all the classes inside) from GameStateManagementWindows to the root directory of your Platform Project
- Copy Game.cs from GameStateManagementWindows project to your platformer project
- We no longer need the GameStateManagementWindows project. Right click it and click "Remove"
- Your platformer project should now look like this[http://i170.photobucket.com/albums/u267/Exadon/content.jpg] (with all the correct content from GamestateManagementWindows inside "HighResolutionContent")
- Delete "Program.cs" from your Platform project (we already have a main entry point with Game.cs)
- For each class inside "ScreenManager" and "Screens" you will need to refactor their namespace to match your own project. Example:
- change from: namespace GameStateManagement
- to:namespace Platformer1
- Find GameplayScreen.CS
- By default the GameplayScreen comes with text that loads on the screen that the player can move around. We don't want this. Remove all code that relates to it. Your code should look like the following (Note: change to your namespace)
#region File Description
//-----------------------------------------------------------------------------
// GameplayScreen.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
#endregion
//Note: Change to YOUR NAME SPACE
namespace HowToAddMenu
{
/// <summary>
/// This screen implements the actual game logic. It is just a
/// placeholder to get the idea across: you'll probably want to
/// put some more interesting gameplay in here!
/// </summary>
class GameplayScreen : GameScreen
{
#region Fields
ContentManager content;
SpriteFont gameFont;
#endregion
#region Initialization
/// <summary>
/// Constructor.
/// </summary>
public GameplayScreen()
{
TransitionOnTime = TimeSpan.FromSeconds(1.5);
TransitionOffTime = TimeSpan.FromSeconds(0.5);
}
/// <summary>
/// Load graphics content for the game.
/// </summary>
public override void LoadContent()
{
if (content == null)
content = new ContentManager(ScreenManager.Game.Services, "Content");
gameFont = content.Load<SpriteFont>("gamefont");
// once the load has finished, we use ResetElapsedTime to tell the game's
// timing mechanism that we have just finished a very long frame, and that
// it should not try to catch up.
ScreenManager.Game.ResetElapsedTime();
}
/// <summary>
/// Unload graphics content used by the game.
/// </summary>
public override void UnloadContent()
{
content.Unload();
}
#endregion
#region Update and Draw
/// <summary>
/// Updates the state of the game. This method checks the GameScreen.IsActive
/// property, so the game will stop updating when the pause menu is active,
/// or if you tab away to a different application.
/// </summary>
public override void Update(GameTime gameTime, bool otherScreenHasFocus,
bool coveredByOtherScreen)
{
base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
if (IsActive)
{
// Apply some random jitter to make the enemy move around.
const float randomization = 10;
// Apply a stabilizing force to stop the enemy moving off the screen.
Vector2 targetPosition = new Vector2(200, 200);
// TODO: this game isn't very fun! You could probably improve
// it by inserting something more interesting in this space :-)
}
}
/// <summary>
/// Lets the game respond to player input. Unlike the Update method,
/// this will only be called when the gameplay screen is active.
/// </summary>
public override void HandleInput(InputState input)
{
if (input == null)
throw new ArgumentNullException("input");
// Look up inputs for the active player profile.
int playerIndex = (int)ControllingPlayer.Value;
KeyboardState keyboardState = input.CurrentKeyboardStates[playerIndex];
GamePadState gamePadState = input.CurrentGamePadStates[playerIndex];
// The game pauses either if the user presses the pause button, or if
// they unplug the active gamepad. This requires us to keep track of
// whether a gamepad was ever plugged in, because we don't want to pause
// on PC if they are playing with a keyboard and have no gamepad at all!
bool gamePadDisconnected = !gamePadState.IsConnected &&
input.GamePadWasConnected[playerIndex];
if (input.IsPauseGame(ControllingPlayer) || gamePadDisconnected)
{
ScreenManager.AddScreen(new PauseMenuScreen(), ControllingPlayer);
}
else
{
}
}
/// <summary>
/// Draws the gameplay screen.
/// </summary>
public override void Draw(GameTime gameTime)
{
// This game has a blue background. Why? Because!
ScreenManager.GraphicsDevice.Clear(ClearOptions.Target,
Color.CornflowerBlue, 0, 0);
// Our player and enemy are both actually just text strings.
SpriteBatch spriteBatch = ScreenManager.SpriteBatch;
// If the game is transitioning on or off, fade it out to black.
if (TransitionPosition > 0)
ScreenManager.FadeBackBufferToBlack(255 - TransitionAlpha);
}
#endregion
}
}- Open up PlatformGame.cs and copy all of the using statements that GamePlayScreen lacks and paste them inside. The using statement inside GamePlayScreen should now look as follows:
#region Using Statements using System; using System.IO; using System.Threading; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; using Microsoft.Xna.Framework.Media; #endregion
- Go back to the PlatformGame.cs. Copy all of the vars before the class constructor paste them into the "Fields" region of your GameplayScreen.cs
#region Fields ContentManager content; SpriteFont gameFont; // Resources for drawing. private GraphicsDeviceManager graphics; private SpriteBatch spriteBatch; // Global content. private SpriteFont hudFont; private Texture2D winOverlay; private Texture2D loseOverlay; private Texture2D diedOverlay; // Meta-level game state. private int levelIndex = -1; private Level level; private bool wasContinuePressed; // When the time remaining is less than the warning time, it blinks on the hud private static readonly TimeSpan WarningTime = TimeSpan.FromSeconds(30); #if ZUNE private const int TargetFrameRate = 30; private const int BackBufferWidth = 240; private const int BackBufferHeight = 320; private const Buttons ContinueButton = Buttons.B; #else private const int TargetFrameRate = 60; private const int BackBufferWidth = 1280; private const int BackBufferHeight = 720; private const Buttons ContinueButton = Buttons.A; #endif #endregion
- Go back to the PlatformGame.cs and copy all the code inside the "public override void LoadContent()" method and paste it inside the "public override void LoadContent()" method of GameplayScreen.cs under the "gameFont = content.Load< SpriteFont >(style="font-size: 13px; color: #a31515;">"gamefont");" line of code. You will need to refactor all references of "Content" to "content" inside GameplayScreen.cs. You will also need to change
"spriteBatch = new SpriteBatch(GraphicsDevice);"
to
spriteBatch = new SpriteBatch(ScreenManager.GraphicsDevice);
Your LoadContent method inside GameplayScreen.cs should now look like the following:
public override void LoadContent()
{
if (content == null)
content = new ContentManager(ScreenManager.Game.Services, "Content");
gameFont = content.Load<SpriteFont>("gamefont");
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(ScreenManager.GraphicsDevice);
// Load fonts
hudFont = content.Load<SpriteFont>("Fonts/Hud");
// Load overlay textures
winOverlay = content.Load<Texture2D>("Overlays/you_win");
loseOverlay = content.Load<Texture2D>("Overlays/you_lose");
diedOverlay = content.Load<Texture2D>("Overlays/you_died");
MediaPlayer.IsRepeating = true;
MediaPlayer.Play(content.Load<Song>("Sounds/Music"));
LoadNextLevel();
// once the load has finished, we use ResetElapsedTime to tell the game's
// timing mechanism that we have just finished a very long frame, and that
// it should not try to catch up.
ScreenManager.Game.ResetElapsedTime();
}- Inside PlatformerGame.cs refactor "HandleInput();" to "HandleGameInput() (Note: This is sloppy, and a quick fix. You may want to combine the methods from HandleInput and HandleGameInput later on)
- Add the following two lines of code to your GameplayScreen.cs "update" method in the "if (IsActive)" statement
- HandleGameInput();
- level.Update(gameTime); it should look like the following:
public override void Update(GameTime gameTime, bool otherScreenHasFocus,
bool coveredByOtherScreen)
{
base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
if (IsActive)
{
HandleGameInput();
level.Update(gameTime);
}
}- Then add the HandleGameInput method (from the PlatformerGame.cs) bellow your Update method in the GameplayScreen.cs
private void HandleGameInput()
{
KeyboardState keyboardState = Keyboard.GetState();
GamePadState gamepadState = GamePad.GetState(PlayerIndex.One);
bool continuePressed =
keyboardState.IsKeyDown(Keys.Space) ||
gamepadState.IsButtonDown(ContinueButton);
// Perform the appropriate action to advance the game and
// to get the player back to playing.
if (!wasContinuePressed && continuePressed)
{
if (!level.Player.IsAlive)
{
level.StartNewLife();
}
else if (level.TimeRemaining == TimeSpan.Zero)
{
if (level.ReachedExit)
LoadNextLevel();
else
ReloadCurrentLevel();
}
}
wasContinuePressed = continuePressed;
}
- Finally add the draw code. It is getting early here and I am getting more lazy as I go. So I will just give you the draw code that you can replace your current one inside Gameplayscreen.cs
public override void Draw(GameTime gameTime)
{
// This game has a blue background. Why? Because!
ScreenManager.GraphicsDevice.Clear(ClearOptions.Target,
Color.CornflowerBlue, 0, 0);
// Our player and enemy are both actually just text strings.
SpriteBatch spriteBatch = ScreenManager.SpriteBatch;
level.Draw(gameTime, spriteBatch);
DrawHud();
base.Draw(gameTime);
// If the game is transitioning on or off, fade it out to black.
if (TransitionPosition > 0)
ScreenManager.FadeBackBufferToBlack(255 - TransitionAlpha);
}
private void DrawHud()
{
Rectangle titleSafeArea = ScreenManager.GraphicsDevice.Viewport.TitleSafeArea;
//Rectangle titleSafeArea = GraphicsDevice.Viewport.TitleSafeArea;
Vector2 hudLocation = new Vector2(titleSafeArea.X, titleSafeArea.Y);
Vector2 center = new Vector2(titleSafeArea.X + titleSafeArea.Width / 2.0f,
titleSafeArea.Y + titleSafeArea.Height / 2.0f);
// Draw time remaining. Uses modulo division to cause blinking when the
// player is running out of time.
string timeString = "TIME: " + level.TimeRemaining.Minutes.ToString("00") + ":" + level.TimeRemaining.Seconds.ToString("00");
Color timeColor;
if (level.TimeRemaining > WarningTime ||
level.ReachedExit ||
(int)level.TimeRemaining.TotalSeconds % 2 == 0)
{
timeColor = Color.Yellow;
}
else
{
timeColor = Color.Red;
}
DrawShadowedString(hudFont, timeString, hudLocation, timeColor);
// Draw score
float timeHeight = hudFont.MeasureString(timeString).Y;
DrawShadowedString(hudFont, "SCORE: " + level.Score.ToString(), hudLocation + new Vector2(0.0f, timeHeight * 1.2f), Color.Yellow);
// Determine the status overlay message to show.
Texture2D status = null;
if (level.TimeRemaining == TimeSpan.Zero)
{
if (level.ReachedExit)
{
status = winOverlay;
}
else
{
status = loseOverlay;
}
}
else if (!level.Player.IsAlive)
{
status = diedOverlay;
}
if (status != null)
{
// Draw status message.
Vector2 statusSize = new Vector2(status.Width, status.Height);
spriteBatch.Draw(status, center - statusSize / 2, Color.White);
}
}
private void DrawShadowedString(SpriteFont font, string value, Vector2 position, Color color)
{
spriteBatch.Begin();
spriteBatch.DrawString(font, value, position + new Vector2(1.0f, 1.0f), Color.Black);
spriteBatch.DrawString(font, value, position, color);
spriteBatch.End();
}
#endregion- And that's it. Now you should have a the menu management with your game. There will need to be some tweaking done like screen size. Also this was built from the default form of the platformer starter kit. If you added level scrolling then the spritebatch drawing methods will change a bit. I hope this helps. Bellow you can fine the full file of GameplayScreen.cs. Sorry for any typos, I am typing this at 2:29 am est time.
GameplayScreen.cs
#region File Description
//-----------------------------------------------------------------------------
// GameplayScreen.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using System.IO;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Media;
#endregion
namespace Platformer1
{
/// <summary>
/// This screen implements the actual game logic. It is just a
/// placeholder to get the idea across: you'll probably want to
/// put some more interesting gameplay in here!
/// </summary>
class GameplayScreen : GameScreen
{
#region Fields
ContentManager content;
SpriteFont gameFont;
// Resources for drawing.
private GraphicsDeviceManager graphics;
private SpriteBatch spriteBatch;
// Global content.
private SpriteFont hudFont;
private Texture2D winOverlay;
private Texture2D loseOverlay;
private Texture2D diedOverlay;
private Texture2D healthBar;//Steve Added
private Texture2D healthBarUnderlay;//Steve Added
// Meta-level game state.
private int levelIndex = -1;
private Level level;
private bool wasContinuePressed;
// When the time remaining is less than the warning time, it blinks on the hud
private static readonly TimeSpan WarningTime = TimeSpan.FromSeconds(30);
private const Buttons ContinueButton = Buttons.A;
#endregion
#region Initialization
/// <summary>
/// Constructor.
/// </summary>
public GameplayScreen()
{
TransitionOnTime = TimeSpan.FromSeconds(1.5);
TransitionOffTime = TimeSpan.FromSeconds(0.5);
}
/// <summary>
/// Load graphics content for the game.
/// </summary>
public override void LoadContent()
{
if (content == null)
content = new ContentManager(ScreenManager.Game.Services, "Content");
gameFont = content.Load<SpriteFont>("gamefont");
hudFont = content.Load<SpriteFont>("Fonts/Hud");
// Load overlay textures
winOverlay = content.Load<Texture2D>("Overlays/you_win");
loseOverlay = content.Load<Texture2D>("Overlays/you_lose");
diedOverlay = content.Load<Texture2D>("Overlays/you_died");
healthBar = content.Load<Texture2D>("Sprites\\Player\\healthBar"); //Steve Added
healthBarUnderlay = content.Load<Texture2D>("Sprites\\Player\\healthBarUnderlay");//Steve Added
MediaPlayer.IsRepeating = true;
MediaPlayer.Play(content.Load<Song>("Sounds/Music"));
LoadNextLevel();
// once the load has finished, we use ResetElapsedTime to tell the game's
// timing mechanism that we have just finished a very long frame, and that
// it should not try to catch up.
ScreenManager.Game.ResetElapsedTime();
}
/// <summary>
/// Unload graphics content used by the game.
/// </summary>
public override void UnloadContent()
{
content.Unload();
}
#endregion
#region Update and Draw
/// <summary>
/// Updates the state of the game. This method checks the GameScreen.IsActive
/// property, so the game will stop updating when the pause menu is active,
/// or if you tab away to a different application.
/// </summary>
public override void Update(GameTime gameTime, bool otherScreenHasFocus,
bool coveredByOtherScreen)
{
base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
if (IsActive)
{
HandleGameInput();
level.Update(gameTime);
}
}
private void HandleGameInput()
{
KeyboardState keyboardState = Keyboard.GetState();
GamePadState gamepadState = GamePad.GetState(PlayerIndex.One);
bool continuePressed =
keyboardState.IsKeyDown(Keys.Space) ||
gamepadState.IsButtonDown(ContinueButton);
// Perform the appropriate action to advance the game and
// to get the player back to playing.
if (!wasContinuePressed && continuePressed)
{
if (!level.Player.IsAlive)
{
level.StartNewLife();
}
else if (level.TimeRemaining == TimeSpan.Zero)
{
if (level.ReachedExit)
LoadNextLevel();
else
ReloadCurrentLevel();
}
}
wasContinuePressed = continuePressed;
}
private void LoadNextLevel()
{
// Find the path of the next level.
string levelPath;
// Loop here so we can try again when we can't find a level.
while (true)
{
// Try to find the next level. They are sequentially numbered txt files.
levelPath = String.Format("Levels/{0}.txt", ++levelIndex);
levelPath = Path.Combine(StorageContainer.TitleLocation, "Content/" + levelPath);
if (File.Exists(levelPath))
break;
// If there isn't even a level 0, something has gone wrong.
if (levelIndex == 0)
throw new Exception("No levels found.");
// Whenever we can't find a level, start over again at 0.
levelIndex = -1;
}
// Unloads the content for the current level before loading the next one.
if (level != null)
level.Dispose();
// Load the level.
level = new Level(ScreenManager.Game.Services, levelPath);
}
private void ReloadCurrentLevel()
{
--levelIndex;
LoadNextLevel();
}
/// <summary>
/// Lets the game respond to player input. Unlike the Update method,
/// this will only be called when the gameplay screen is active.
/// </summary>
public override void HandleInput(InputState input)
{
if (input == null)
throw new ArgumentNullException("input");
// Look up inputs for the active player profile.
int playerIndex = (int)ControllingPlayer.Value;
KeyboardState keyboardState = input.CurrentKeyboardStates[playerIndex];
GamePadState gamePadState = input.CurrentGamePadStates[playerIndex];
// The game pauses either if the user presses the pause button, or if
// they unplug the active gamepad. This requires us to keep track of
// whether a gamepad was ever plugged in, because we don't want to pause
// on PC if they are playing with a keyboard and have no gamepad at all!
bool gamePadDisconnected = !gamePadState.IsConnected &&
input.GamePadWasConnected[playerIndex];
if (input.IsPauseGame(ControllingPlayer) || gamePadDisconnected)
{
ScreenManager.AddScreen(new PauseMenuScreen(), ControllingPlayer);
}
else
{
}
}
/// <summary>
/// Draws the gameplay screen.
/// </summary>
public override void Draw(GameTime gameTime)
{
// This game has a blue background. Why? Because!
ScreenManager.GraphicsDevice.Clear(ClearOptions.Target,
Color.CornflowerBlue, 0, 0);
spriteBatch = ScreenManager.SpriteBatch;
level.Draw(gameTime, spriteBatch);
DrawHud();
// If the game is transitioning on or off, fade it out to black.
if (TransitionPosition > 0)
ScreenManager.FadeBackBufferToBlack(255 - TransitionAlpha);
}
private void DrawHud()
{
spriteBatch.Begin();
Rectangle titleSafeArea = ScreenManager.Game.GraphicsDevice.Viewport.TitleSafeArea;
Rectangle healthBarRect; //Steve Added
Rectangle healthBarUnderlayRect; //Steve Added
Vector2 hudLocation = new Vector2(titleSafeArea.X, titleSafeArea.Y);
Vector2 center = new Vector2(titleSafeArea.X + titleSafeArea.Width / 2.0f,
titleSafeArea.Y + titleSafeArea.Height / 2.0f);
// Draw time remaining. Uses modulo division to cause blinking when the
// player is running out of time.
string timeString = " TIME: " + level.TimeRemaining.Minutes.ToString("00") + ":" + level.TimeRemaining.Seconds.ToString("00");
Color timeColor;
if (level.TimeRemaining > WarningTime ||
level.ReachedExit ||
(int)level.TimeRemaining.TotalSeconds % 2 == 0)
{
timeColor = Color.Yellow;
}
else
{
timeColor = Color.Red;
}
DrawShadowedString(hudFont, timeString, hudLocation, timeColor);
// Draw score
float timeHeight = hudFont.MeasureString(timeString).Y;
DrawShadowedString(hudFont, "SCORE: " + level.Score.ToString(), hudLocation + new Vector2(0.0f, timeHeight * 1.2f), Color.Yellow);
// Draw Health
//Steve Added
healthBarUnderlayRect = new Rectangle(titleSafeArea.X + 8, titleSafeArea.Y + 57, healthBarUnderlay.Width, healthBarUnderlay.Height);
healthBarRect = new Rectangle(titleSafeArea.X + 10, titleSafeArea.Y + 60, level.Player.HealthPercent, 10);
spriteBatch.Draw(healthBarUnderlay, healthBarUnderlayRect, Color.Gray);
spriteBatch.Draw(healthBar, healthBarRect, Color.Gray);
//float scoreHeight = hudFont.MeasureString(timeString).Y;
//DrawShadowedString(hudFont, "HEALTH: " + level.Player.Health, hudLocation + new Vector2(0.0f, scoreHeight * 2.2f), Color.Red);
// Determine the status overlay message to show.
Texture2D status = null;
if (level.TimeRemaining == TimeSpan.Zero)
{
if (level.ReachedExit)
{
status = winOverlay;
}
else
{
status = loseOverlay;
}
}
else if (!level.Player.IsAlive)
{
status = diedOverlay;
}
if (status != null)
{
// Draw status message.
Vector2 statusSize = new Vector2(status.Width, status.Height);
spriteBatch.Draw(status, center - statusSize / 2, Color.White);
}
spriteBatch.End();
}
private void DrawShadowedString(SpriteFont font, string value, Vector2 position, Color color)
{
spriteBatch.DrawString(font, value, position + new Vector2(1.0f, 1.0f), Color.Black);
spriteBatch.DrawString(font, value, position, color);
}
#endregion
}
}