Network State Management Sample

This sample shows you how to implement the user interface for a multiplayer networked game. It provides menus for creating, finding, and joining sessions, a lobby screen, and robust network error handling.

Sample Overview

This sample builds on top of the Game State Management sample. It adds the user interface screens needed by a multiplayer networked game. If you are not familiar with the underlying concepts of the ScreenManager and GameScreen classes, you should read the documentation for the Game State Management sample.

At the main menu, players can choose between Single Player, LIVE, or System Link game modes. If they choose a networked mode, they will be prompted to sign in a suitable player profile (if one is not already signed in), and then asked to create a new session or to search for existing sessions. The sample displays an animated busy indicator whenever a network operation is in progress, and robustly handles errors by catching network exceptions and turning them into message box popups. Once in the lobby, a list of gamers is displayed along with icons indicating who is currently talking and who has marked themselves as ready. When all the gamers are ready, the sample loads the gameplay screen, at which point the rest is up to you: there is no actual game code included here!

Sample Controls

This sample uses the following keyboard and gamepad controls.

Action Keyboard control Gamepad control
Select a menu entry UP ARROW, DOWN ARROW Left thumb stick, D-pad up and down
Accept the menu selection SPACEBAR, ENTER A, START
Mark players ready in the lobby SPACEBAR, ENTER A, START
Cancel the menu ESC B, BACK
Exit from the lobby ESC B, BACK
Move a game entity. UP ARROW, DOWN ARROW, LEFT ARROW, and RIGHT ARROW Left thumb stick
Pause the game. ESC START, BACK

How the Sample Works

The sample code is organized into three folders:

  • The ScreenManager folder contains the underlying ScreenManager component and GameScreen base class, which will be needed by all games that want to use this screen infrastructure, and usually will not need to be altered from one game to another.
  • The Screens folder contains screen implementations such as the MenuScreen, GameplayScreen, and BackgroundScreen, which you will probably want to customize for your specific game.
  • The Networking folder contains multiplayer networking functionality such as the LobbyScreen and NetworkSessionComponent.

All the text strings used by this sample are stored in a resource file, Resources.resx. This provides a convenient way to gather all the text in one place without cluttering up the program code.

Managing the NetworkSession

One of the challenges in hooking up networking to a generalized user interface is deciding who should own the NetworkSession object. Many different pieces of code need to access the session, but only one should be responsible for updating it, handling errors, and resetting the user interface if it ends unexpectedly. To manage the session instance, this sample creates the NetworkSessionComponent class. This is a game component wrapped around a NetworkSession. The component updates the network session from its update method, gracefully handling any exceptions that may result. It also hooks the NetworkSession.SessionEnded event, which will trigger the LeaveSession method to reset the user interface and display a message box telling the user why the session has ended.

NetworkSessionComponent also registers the NetworkSession as a game service. This provides two different ways for other pieces of code to access the session:

  • Some, for instance the LobbyScreen, take the NetworkSession as a parameter to their constructor, and store it for future use.
  • Others, for instance the LoadingScreen, look up the NetworkSession from Game.Services. This provides a highly decoupled way for other classes to access the session object:
C# 
        NetworkSession session = (NetworkSession)Game.Services.GetService(typeof(NetworkSession));
      

From Lobby to Gameplay

The LobbyScreen displays a list of all the gamers in the session, along with some icons to indicate their status. The gamers can mark themselves as ready by setting the LocalNetworkGamer.IsReady property to true. The IsReady status is synchronized automatically over the network. The host examines this inside the LobbyScreen.Update method to decide when to start the game:

C# 
        if (networkSession.IsHost && networkSession.IsEveryoneReady)
        {
        networkSession.StartGame();
        }
      

When the host starts the game, this status change is sent automatically over the network. Using the LobbyScreen.Update method, each client can examine the session state to decide when to load the gameplay screen:

C# 
        if (networkSession.SessionState == NetworkSessionState.Playing)
        {
        LoadingScreen.Load(ScreenManager, true, new GameplayScreen(networkSession));
        }
      

The process for returning from gameplay to the lobby works in a similar way. The host decides when to return to the lobby, using the "Return to Lobby" option in the PauseMenuScreen, and calls NetworkSession.EndGame whenever this is selected (a real game might want to end the game after a fixed amount of time, or when all the cars reach the end of their third lap, or some other game-specific critera). This status change is sent automatically over the network. Each client can examine the session state inside its GameplayScreen.Update method to decide when to reload the lobby screen:

C# 
        if (networkSession.SessionState == NetworkSessionState.Lobby)
        {
        LoadingScreen.Load(ScreenManager, true, new BackgroundScreen(), new LobbyScreen(networkSession));
        }
      

Although only the host is responsible for deciding when to start or end the game, all the clients will move between the lobby and gameplay screens at the same time.

Note that the code that checks when to return from gameplay to the lobby is in the GameplayScreen.Update method. This seems like it might be a problem because the gameplay screen might have transitioned off to make room for some other screen such as the pause menu. However, we still want to return to the lobby regardless of what screen currently is active. This is acceptable because the screen update method is still called even when a screen has transitioned off as the result of being covered by something else. The update method usually does nothing in this case because it checks the IsActive property before taking any actions. This will be false if the screen is covered up. Our return to lobby logic only checks !IsExiting, rather than IsActive, so it will still run even when some other screen has temporarily covered up the gameplay.

Displaying Notification Messages

In a networked game, it is nice if players can be notified whenever other gamers join or leave the session. This sample provides a generalized IMessageDisplay service, which can be used by any code that wants to display this kind of notification message:

C# 
        IMessageDisplay messageDisplay = (IMessageDisplay)Game.Services.GetService(typeof(IMessageDisplay));

        if (messageDisplay != null)
        {
        messageDisplay.ShowMessage("Hello, World!");
        }
      

The NetworkSessionComponent uses IMessageDisplay to display messages in response to the NetworkSession.GamerJoined and NetworkSession.GamerLeft events.

In this sample, the IMessageDisplay interface is implemented by the MessageDisplayComponent class. If you want to change the appearance of the messages, you can either alter the draw code in this component, or replace it with an entirely different implementation of IMessageDisplay.

Multithreaded Loading

The original Game State Management Sample had a very simple LoadingScreen implementation:

  • Activate the LoadingScreen.
  • Tell all other screens to transition off.
  • Wait until the previous screens are gone.
  • Perform the load.

This presents a problem for networked games. What if the loading takes a long time? Nothing else is happening while the load occurs, and, in particular, when the NetworkSession.Update method is not being called. If loading takes too long, you might be accidentally disconnected from the session. To keep the connection alive, we must take care to call NetworkSession.Update regularly, even during our load operation.

This is accomplished through the use of multithreading. Immediately before it performs the (potentially slow) load operation, the loading screen starts up a background worker thread, executing the BackgroundWorkerThread method. At regular intervals (30 times per second) this background thread updates the network session, and also redraws the loading screen so we can keep the display animating smoothly while we load. Once the load operation has completed, the main thread signals the backgroundThreadExit event, which causes the background thread to exit.

Extending the Sample

This sample does not actually contain any gameplay! It would be good to replace the GameplayScreen with something more interesting.

Once it reaches the GameplayScreen, this sample does not do any real game networking, either. After you add some fun gameplay, you will also want to synchronize this over the network. See the Peer-to-Peer and Client/Server samples for examples.

You will probably want to re-skin the user interface to give your game a unique look. The MenuScreen and MenuItem drawing code, and the image used by the BackgroundScreen, would be good places to start.

You could use the IMessageDisplay service for more than just displaying gamer joined and left notifications. Perhaps you could show a message whenever someone is killed, or when a new record score is set.