Game Development: 2.Screen Management
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.
Subscribe to:
Post Comments
(
Atom
)
No comments :
Post a Comment