Search code examples
c#visual-studiomonogame

c# monogame visual studio - game menu not working, game over screen not working when enemy hit


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);
        }
    }
}

Solution

  • 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)