Critical Hit Creations

Prop replicas, writing, and creative hobbies

Game Development: 2.Screen Management

No comments
When starting a new project in MonoGame, it can be tempting to jump into the default Game class and start loading content, getting objects to update, and displaying the results onscreen. This is a great way to start experimenting and seeing results quickly. However, if you plan on building out your game, you’re going to need to put some foundations in place to manage the access of the different portions of it.

Having some experience with the basics of loading, updating and drawing objects from XNA, I started my game development learning in MonoGame by looking at screen management. Games tend to have numerous screens: a welcome screen, the main menu, options screens, loading screens, gameplay screens. Separating the game into screens allows us to isolate the different components of the software. Using a Screen Manager, we keep track of the states of the screens currently in use by the game, and load and unload screens as the player transitions through the different parts of the game.

My ScreenManager class is a singleton that inherits from DrawableGameComponent. It is instantiated in the Game.cs constructor and registered with the game:

screenManager = ScreenManager.CreateScreenManager(this);
Components.Add(screenManager);

Registering the component means that its inherited Initialize, Load, Update and Draw methods will be called as appropriate by the game. The ScreenManager holds a reference to the graphics device, and a list of all screens currently in use by the game. AddScreen and RemoveScreen methods provide the functionality for loading screens in and out of the game’s state. As a singleton, the ScreenManager contains a static GetInstance method, allowing a reference to the ScreenManager to be globally accessible:

private static ScreenManager screenManager = null;

public static ScreenManager CreateScreenManager(Game game)
{
  if(screenManager==null)
  {
   screenManager=new ScreenManager(game);
  }
  return screenManager;
}

public static ScreenManager GetInstance()
{
  return screenManager;
}   


All screens in the game inherit from the AbstractScreen abstract class, containing HasFocus, IsActive and IsVisible Boolean properties, which the ScreenManager uses to determine whether to call the screen’s HandleInput, Update and Draw methods respectively:

        public override void Update(GameTime gameTime)
        {
            //...
            foreach (AbstractScreen screen in screens)
            {
                if (screen.HasFocus)
                {
                    screen.HandleInput(gameTime, input);
                }

                if (screen.IsActive)
                {
                    screen.Update(gameTime);
                }
            }
        }
        //...
        public override void Draw(GameTime gameTime)
        {
            //...
            foreach (AbstractScreen screen in screens)
            {
                if (screen.IsVisible)
                {
                    screen.Draw(spriteBatch);
                }
            }
        }


For menu screens, I created Text and Button UI elements, which use Rectangle objects to detect mouse over, mouse exit and click interactions. These elements include functionality to fade in and out when transitioning screens, can be highlighted when they have focus, and fire custom OnClick events when pressed. Each element’s OnClick event can be tied to an event handler in the owning screen, and used to request the ScreenManager to transition screens:

//In main menu screen constructor
        playGameButton.OnClickEvent += NewGame;
            //...
        //New game event handler
        public void NewGame(MenuElement element)
        {
            OnTransitionOut();
            ScreenManager.GetInstance().Game.IsMouseVisible = true;

            //Remove all screens (i.e. this screen)
            ScreenManager.GetInstance().Screens.Clear();

            //Add a loading screen
            ScreenManager.GetInstance().addScreen(new LoadingScreen());

            /*Add the Game screen which performs content loading in a separate thread, 
            allowing the loading screen to update as the game loads*/
            ScreenManager.GetInstance().addScreen(new GameScreen(false));

        }
I created a number of screens that inherit from AbstractScreen, instantiate lists of buttons and tie their OnClick events to event handlers. In these screen classes, the HandleInput method directs input to the buttons in the list, and the Draw method loops through the list and draws each button. By adding the main menu screen to the ScreenManager when the game starts, and using its event handlers to transition to other screens, I’ve built a basic menu structure for a game:

When the player starts playing the game from the menu (i.e. clicking New Game), a gameplay screen is created and added to the ScreenManager’s list of active screens, along with a Loading Screen. The gameplay screen performs content loading in a separate thread, and requests the ScreenManager to remove the Loading Screen upon completion. The gameplay screen acts as the root for all the components of the game itself, including the game’s content, state, and the renderer, which I will look at in a later post.     

No comments :

Post a Comment