I am building this game in Visual Studio using C# and the monogame framework. The aim of the game is that you use Eric to dodge the falling enemies which get faster as time proceeds, and if you hit an enemy then it's game over.
There is a main menu which loads before the game which asks you to press enter to start the game, if you press this the game loads, and a game over screen will appear if you hit an enemy, whereby you return to the main menu by pressing A.
However, at the moment, my game is not opening the main menu first or showing the game over screen. These screens use the same background image as the main game itself but have dark red font for text to make the states easy to distinguish.
Here is my code, please can someone advise
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.Collections.Generic;
using System;
using System.Media;
using Microsoft.Xna.Framework.Audio;
namespace AttackOfTheUnknown
{
/// <summary>
/// Constructor for Game.
/// This is called when the Game object is created.
/// </summary>
public class SuperAlianorRescue : Game
{
// --- GAME STATE ---
enum GameState
{
GameMenu = 0,
GamePlay = 1,
GameOver = 2,
}
GameState currentGameState;
// --- GRAPHICS ---
// Manages the graphics
GraphicsDeviceManager graphics;
// Used to draw sprite images (textures)
SpriteBatch spriteBatch;
// Area which should be visible on all TVs (OK to draw inside)
Rectangle safeBounds;
// Percentage of the screen on every side is the safe area
const float safeAreaPortion = 0.05f;
// --- CHARACTER ---
// Used for character sprite
Texture2D characterTexture;
// Used to store current character position
Vector2 characterPosition;
// Used to calculate next position of character given user input
const int characterSpeed = 10;
// Used to state character has collided with enemy
bool characterHit = false;
//--- ENEMIES ---
// Used for enemy sprite
Texture2D enemyTexture;
// The list of enemies currently in the game
List<Vector2> enemyPositions = new List<Vector2>();
// Probability that an enemy will be spawned
float enemySpawnProbability = 0.05f;
// Speed of enemy
public float speedIncrement = 0.01f;
public float enemySpeed = 0.005f;
public float speed = 0;
// Used to generate random colours
Random random = new Random();
// Current enemy colour
Color enemyColor = Color.White;
// IMPORT GAME BACKGROUND
Texture2D backgroundTexture;
// SOUND
// enemy spawn sound
SoundEffect enemySpawnSound;
SoundEffectInstance enemySpawnSoundInstance;
// --- IN_GAME INFORMATION ---
// Player score
long score;
// Highest score
long highScore;
// Font used to display score
SpriteFont scoreFont;
// Used to hold current on-screen information
string scoreText = "SCORE: 0";
string highScoreText = "HI: 0";
string gameName = "Super Alianor Rescue";
string aimText = "Aim: Move Eric with WASD keys to dodge the enemies.";
string menuWelcome = "Welcome to Super Alianor Rescue.";
string menuHowToPlay = "Press ENTER To Start New Game.";
string GameOverText = "Game Over";
string GameOverClose = "Press Escape To Quit Game";
Song theSong;
public SuperAlianorRescue()
{
graphics = new GraphicsDeviceManager(this);
graphics.PreferredBackBufferHeight = 600;
graphics.PreferredBackBufferWidth = 650;
Content.RootDirectory = "Content";
}
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
base.Initialize();
// Calculate safe bounds based on current resolution
Viewport viewport = graphics.GraphicsDevice.Viewport;
safeBounds = new Rectangle(
(int)(viewport.Width * safeAreaPortion),
(int)(viewport.Height * safeAreaPortion),
(int)(viewport.Width * (1 - 2 * safeAreaPortion)),
(int)(viewport.Height * (1 - 2 * safeAreaPortion)));
// Start the player in the centre along the bottom of the screen
characterPosition.X = (safeBounds.Width - characterTexture.Width) / 2;
characterPosition.Y = safeBounds.Height - characterTexture.Height;
// Reset score
score = 0;
highScore = 0;
MediaPlayer.IsRepeating = true;
MediaPlayer.Play(theSong);
MediaPlayer.Volume = 0.01f;
// Set the initial game state
currentGameState = GameState.GameMenu;
// Reset score
score = 0;
highScore = 0;
}
/// <summary>
/// LoadContent will be called once per game and is the place to load
/// all of your content.
/// </summary>
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
// Load textures
enemyTexture = Content.Load<Texture2D>("enemy");
characterTexture = Content.Load<Texture2D>("eric2");
backgroundTexture = Content.Load<Texture2D>("gameback");
// create the font
scoreFont = Content.Load<SpriteFont>("GameFont");
theSong = Content.Load<Song>("Komiku_-_63_-_The_Zone");
// create the sound effect
enemySpawnSound = Content.Load<SoundEffect>("Zombie Demon");
enemySpawnSoundInstance = enemySpawnSound.CreateInstance();
enemySpawnSoundInstance.Volume = 1.0f;
enemySpawnSoundInstance.Pitch = 1.0f;
}
/// <summary>
/// UnloadContent will be called once per game and is the place to unload
/// game-specific content.
/// </summary>
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
// Get input
KeyboardState keyboard = Keyboard.GetState();
GamePadState gamePad = GamePad.GetState(PlayerIndex.One);
// use game state
switch (currentGameState)
{
case GameState.GameMenu:
// Allows the game to exit
if (gamePad.Buttons.Back == ButtonState.Pressed ||
keyboard.IsKeyDown(Keys.Escape))
{
this.Exit();
}
if (gamePad.Buttons.Start == ButtonState.Pressed ||
keyboard.IsKeyDown(Keys.Enter))
{
// Start the player in the center along the bottom of the screen
characterPosition.X = (safeBounds.Width - characterTexture.Width) / 2;
characterPosition.Y = (safeBounds.Height - characterTexture.Height);
// Set the game state to play
currentGameState = GameState.GamePlay;
// Reset score
score = 0;
}
break;
case GameState.GameOver:
// If game is over, the game allows return to main menu if key A is pressed
if (gamePad.Buttons.A == ButtonState.Pressed ||
keyboard.IsKeyDown(Keys.A))
{
currentGameState = GameState.GameMenu;
}
break;
case GameState.GamePlay:
// Press X during game play to return to main menu
if (gamePad.Buttons.X == ButtonState.Pressed ||
keyboard.IsKeyDown(Keys.X))
{
currentGameState = GameState.GameMenu;
}
// Press ESC to quit game during game play
if (gamePad.Buttons.Back == ButtonState.Pressed ||
keyboard.IsKeyDown(Keys.Escape))
{
this.Exit();
}
//Move the player left and right with arrow keys or d-pad
if (keyboard.IsKeyDown(Keys.Left) || gamePad.DPad.Left == ButtonState.Pressed)
{
characterPosition.X -= characterSpeed;
}
if (keyboard.IsKeyDown(Keys.Right) || gamePad.DPad.Right == ButtonState.Pressed)
{
characterPosition.X += characterSpeed;
}
// Prevent the character from moving off of the screen
characterPosition.X = MathHelper.Clamp(characterPosition.X,
safeBounds.Left, safeBounds.Right - characterTexture.Width);
// Get the bounding rectangle of the character
Rectangle characterRectangle =
new Rectangle((int)characterPosition.X, (int)characterPosition.Y,
characterTexture.Width, characterTexture.Height);
// Spawn new enemy
if (random.NextDouble() < enemySpawnProbability)
{
float x = (float)random.NextDouble() *
(Window.ClientBounds.Width - enemyTexture.Width);
enemyPositions.Add(new Vector2(x, -enemyTexture.Height));
// play the enemy spawn sound
enemySpawnSoundInstance.Play();
}
// Increase enemy speed as game progresses
enemySpeed += speedIncrement;
if (speed >= speedIncrement) speed = enemySpeed;
// Update each enemy
characterHit = false;
for (int i = 0; i < enemyPositions.Count; i++)
{
// Animate this enemy
enemyPositions[i] =
new Vector2(enemyPositions[i].X,
enemyPositions[i].Y + enemySpeed);
// Get the bounding rectangle of this enemy
Rectangle enemyRectangle =
new Rectangle((int)enemyPositions[i].X, (int)enemyPositions[i].Y,
enemyTexture.Width, enemyTexture.Height);
// Check collision with character
if (characterRectangle.Intersects(enemyRectangle))
characterHit = true;
// Remove this enemy if it has fallen off the screen
if (enemyPositions[i].Y > Window.ClientBounds.Height)
{
enemyPositions.RemoveAt(i);
// When removing an enemy, the next enemy will have the same index
// as the current enemy. Decrement i to prevent skipping an enemy.
i--;
}
}
// Reset game if character has been hit
if (characterHit)
{
// check for highscore
if (score > highScore)
highScore = score;
// reset score to zero
score = 0;
// empty the enemies list
enemyPositions = new List<Vector2>();
// change color of enemies to indicate a new game
enemyColor = new Color(random.Next(0, 255), random.Next(0, 255), random.Next(0, 255));
// finish game by loading gameover screen
currentGameState = GameState.GameOver;
}
else
{
// update score (character has been hit)
score = score + 1;
}
// update on-screen information variables
scoreText = "SCORE: " + score.ToString();
highScoreText = "HIGH SCORE: " + highScore.ToString();
break;
}
base.Update(gameTime);
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice device = graphics.GraphicsDevice;
// use game state
switch (currentGameState)
{
case GameState.GameMenu: // load game menu and clear once enter key is pressed
spriteBatch.Begin();
spriteBatch.Draw(backgroundTexture, Vector2.Zero, Color.White);
spriteBatch.DrawString(scoreFont, menuWelcome,
new Vector2((float)safeBounds.Right / 2 - scoreFont.MeasureString(menuWelcome).X / 3,
30), Color.DarkRed);
spriteBatch.DrawString(scoreFont, menuHowToPlay,
new Vector2((float)safeBounds.Right / 2 - scoreFont.MeasureString(menuHowToPlay).X / 4,
30), Color.DarkRed);
spriteBatch.End();
device.Clear(Color.DarkBlue);
break;
case GameState.GameOver: // load game menu and clear once escape is pressed
spriteBatch.Begin();
spriteBatch.Draw(backgroundTexture, Vector2.Zero, Color.White);
spriteBatch.DrawString(scoreFont, GameOverText,
new Vector2((float)safeBounds.Right / 2 - scoreFont.MeasureString(GameOverText).X / 3,
30), Color.DarkRed);
spriteBatch.DrawString(scoreFont, GameOverClose,
new Vector2((float)safeBounds.Right / 2 - scoreFont.MeasureString(GameOverClose).X / 4,
30), Color.DarkRed);
spriteBatch.End();
device.Clear(Color.DarkBlue);
break;
case GameState.GamePlay: // load the sprite batch for main game play
// Open sprite batch
spriteBatch.Begin();
spriteBatch.End();
break;
}
device.Clear(Color.CornflowerBlue);
// "Open" the sprite batch
spriteBatch.Begin();
// Clear game screen with background colour
device.Clear(Color.DarkBlue);
// Draw background
spriteBatch.Draw(backgroundTexture, Vector2.Zero, Color.White);
// Draw character
spriteBatch.Draw(characterTexture, characterPosition, Color.White);
// Draw enemies
foreach (Vector2 enemyPosition in enemyPositions)
spriteBatch.Draw(enemyTexture, enemyPosition, enemyColor);
// Draw on-screen game information
spriteBatch.DrawString(scoreFont, scoreText, new Vector2(30, 30), Color.Black);
spriteBatch.DrawString(scoreFont, highScoreText,
new Vector2((float)safeBounds.Right - scoreFont.MeasureString(highScoreText).X,
30), Color.Black);
spriteBatch.DrawString(scoreFont, gameName,
new Vector2((float)safeBounds.Right / 2 - scoreFont.MeasureString(gameName).X / 2,
30), Color.Black);
spriteBatch.DrawString(scoreFont, aimText,
new Vector2((float)safeBounds.Right / 2 - scoreFont.MeasureString(gameName).X / 1,
60), Color.Black);
// "Close" the sprite batch
spriteBatch.End();
break;
base.Draw(gameTime);
}
}
}
The draw code for your gameplay state is outside of the switch statement, so blanks out any menu or game over sprites that are drawn and draws the gameplay sprites over the top. This is caused by this piece of code:
case GameState.GamePlay: // load the sprite batch for main game play
// Open sprite batch
spriteBatch.Begin();
spriteBatch.End();
break;
}
To fix this you need to move your gameplay state draw code to within your switch statement, as follows:
protected override void Draw(GameTime gameTime)
{
GraphicsDevice device = graphics.GraphicsDevice;
// use game state
switch (currentGameState)
{
case GameState.GameMenu: // load game menu and clear once enter key is pressed
device.Clear(Color.DarkBlue);
spriteBatch.Begin();
spriteBatch.Draw(backgroundTexture, Vector2.Zero, Color.White);
spriteBatch.DrawString(scoreFont, menuWelcome,
new Vector2((float)safeBounds.Right / 2 - scoreFont.MeasureString(menuWelcome).X / 3,
30), Color.DarkRed);
spriteBatch.DrawString(scoreFont, menuHowToPlay,
new Vector2((float)safeBounds.Right / 2 - scoreFont.MeasureString(menuHowToPlay).X / 4,
30), Color.DarkRed);
spriteBatch.End();
break;
case GameState.GameOver: // load game menu and clear once escape is pressed
device.Clear(Color.DarkBlue);
spriteBatch.Begin();
spriteBatch.Draw(backgroundTexture, Vector2.Zero, Color.White);
spriteBatch.DrawString(scoreFont, GameOverText,
new Vector2((float)safeBounds.Right / 2 - scoreFont.MeasureString(GameOverText).X / 3,
30), Color.DarkRed);
spriteBatch.DrawString(scoreFont, GameOverClose,
new Vector2((float)safeBounds.Right / 2 - scoreFont.MeasureString(GameOverClose).X / 4,
30), Color.DarkRed);
spriteBatch.End();
device.Clear(Color.DarkBlue);
break;
case GameState.GamePlay: // load the sprite batch for main game play
device.Clear(Color.CornflowerBlue);
// Open sprite batch
spriteBatch.Begin();
// Draw background
spriteBatch.Draw(backgroundTexture, Vector2.Zero, Color.White);
// Draw character
spriteBatch.Draw(characterTexture, characterPosition, Color.White);
// Draw enemies
foreach (Vector2 enemyPosition in enemyPositions)
spriteBatch.Draw(enemyTexture, enemyPosition, enemyColor);
// Draw on-screen game information
spriteBatch.DrawString(scoreFont, scoreText, new Vector2(30, 30), Color.Black);
spriteBatch.DrawString(scoreFont, highScoreText,
new Vector2((float)safeBounds.Right - scoreFont.MeasureString(highScoreText).X,
30), Color.Black);
spriteBatch.DrawString(scoreFont, gameName,
new Vector2((float)safeBounds.Right / 2 - scoreFont.MeasureString(gameName).X / 2,
30), Color.Black);
spriteBatch.DrawString(scoreFont, aimText,
new Vector2((float)safeBounds.Right / 2 - scoreFont.MeasureString(gameName).X / 1,
60), Color.Black);
// "Close" the sprite batch
spriteBatch.End();
break;
}
base.Draw(gameTime);
}
Previously your gameplay state in the switch statement created an empty sprite batch and closed the switch statement, which was then followed by the gameplay draw code.
In the above I've moved your gameplay draw code to within the switch statement (and moved the device.clear()
statements to the top of each switch block for clarity)