Flash 10 Multiplayer Game: The Lobby and New Game Screen Implementation

Exclusive offer: get 50% off this eBook here
Flash 10 Multiplayer Game Essentials

Flash 10 Multiplayer Game Essentials — Save 50%

Create exciting real-time multiplayer games using Flash

$26.99    $13.50
by Prashanth Hirematada | July 2010 | Open Source Web Development

With Pulse SDK, we hardly need to write code for either on the server side, which is already taken care of by the server out of the box or on the client-side, which is taken care of by the PulseUI framework. In the previous article we were introduced to the Lobby and Room Management. In this article by Prashanth Hirematada, author of Flash 10 Multiplayer Game Essentials, we will see how we can directly use the PulseUI framework to jumpstart our multiplayer game as well as customize it to suit the needs for your game. Finally, once players either create a room or join an existing one, we will see how the game screen gets initialized and displayed for the game to start.

More specifically, we will learn the following:

  • Creating a new game room
  • Player management within game rooms
  • Initializing the game screen

(For more resources on Flash and Games, see here.)

The lobby screen implementation

In this section, we will learn how to implement the room display within the lobby.

Lobby screen in Hello World

Upon login, the first thing the player needs to do is enter the lobby. Once the player has logged into the server successfully, the default behavior of the PulseGame in PulseUI is to call enterLobby API.

The following is the implementation within PulseGame:

protected function postInit():void {
m_netClient.enterLobby();
}

Once the player has successfully entered the lobby, the client will start listening to all the room updates that happen in the lobby.

These updates include any newly created room, any updates to the room objects, for example, any changes to the player count of a game room, host change, etc.

Customizing lobby screen

In the PulseUI, the lobby screen is the immediate screen that gets displayed after a successful login. The lobby screen is drawn over whatever the outline object has drawn onto the screen. The following is added to the screen when the lobby screen is shown to the player:

  • Search lobby UI
  • Available game rooms
  • Game room scroll buttons
  • Buttons for creating a new game room
  • Navigation buttons to top ten and register screens

When the lobby is called to hide, the lobby UI elements are taken off the screen to make way for the incoming screen. For our initial game prototype, we don't need to make any changes. The PulseUI framework already offers all of the essential set of functionalities of a lobby for any kind of multiplayer game.

However, the one place you may want to add more details is in what gets display for each room within the lobby.

Customizing game room display

The room display is controlled by the class RoomsDisplay, an instance of which is contained in GameLobbyScreen. The RoomsDisplay contains a number of RoomDisplay object instances, one for each room being displayed. In order to modify what gets displayed in each room display, we do it inside of the class that is subclassed from RoomDisplay.

The following figure shows the containment of the Pulse layer classes and shows what we need to subclass in order to modify the room display:

In all cases, we would subclass (MyGame) the PulseGame. In order to have our own subclass of lobby screen, we first need to create class (MyGameLobbyScreen) inherited from GameLobbyScreen. In addition, we also need to override the method initLobbyScreen as shown below:

protected override function initLobbyScreen():void {
m_gameLobbyScreen = new MyGameLobbyScreen();
}

In order to provide our own RoomsDisplay, we need to create a subclass (MyRoomsDisplay) inherited from RoomsDisplay class and we need to override the method where it creates the RoomsDisplay in GameLobbyScreen as shown below:

protected function createRoomsDisplay():void {
m_roomsDisplay = new MyRoomsDisplay();
}

Finally, we do similar subclassing for MyRoomDisplay and override the method that creates the RoomDisplay in MyRoomsDisplay as follows:

protected override function
createRoomDisplay (room:GameRoomClient):RoomDisplay {
return new MyRoomDisplay(room);
}

Now that we have hooked up to create our own implementation of RoomDisplay, we are free to add any additional information we like. In order to add additional sprites, we now simply need to override the init method of GameRoom and provide our additional sprites.

Filtering rooms to display

The choice is up to the game developer to either display all the rooms currently created or just the ones that are available to join. We may override the method shouldShowRoom method in the subclass of RoomsDisplay (MyRoomsDisplay) to change the default behavior. The default behavior is to show rooms that are only available to join as well as rooms that allow players to join even after the game has started.

Following is the default method implementation:

protected function
shouldShowRoom(room:GameRoomClient):Boolean {
var show:Boolean;
show = (room.getRoomType() ==
GameConstants.ROOM_ALLOW_POST_START);
if(show == true)
return true;
else {
return (room.getRoomStatus() ==
GameConstants.ROOM_STATE_WAITING);
}
}

Lobby and room-related API

Upon successful logging, all game implementation must call the enterLobby method.

public function enterLobby(gameLobbyId:String = "DefaultLobby"):void

You may pass a null string in case you only wish to have one default lobby. The following notification will be received again by the client whether the request to enter a lobby was successful or not. At this point, the game screen should switch to the lobby screen.

function onEnteredLobby(error:int):void

If entering a lobby was successful, then the client will start to receive a bunch of onNewGameRoom notifications, one for each room that was found active in the entered lobby. The implementation should draw the corresponding game room with the details on the lobby screen.

function onNewGameRoom(room:GameRoomClient):void

The client may also receive other lobby-related notifications such as onUpdateGameRoom for any room updates and onRemoveGameRoom for any room objects that no longer exist in lobby.

function onUpdateGameRoom(room:GameRoomClient):void
function onRemoveGameRoom(room:GameRoomClient):void

If the player wishes to join an existing game room in the lobby, you simply call joinGameRoom and pass the corresponding room object.

public function joinGameRoom(gameRoom:GameRoomClient):void

In response to a join request, the server notifies the requesting client of whether the action was successful or failed via the game client callback method.

function onJoinedGameRoom(gameRoomId:int, error:int):void

A player already in a game room may leave the room and go back to the lobby, by calling the following API:

public function leaveGameRoom():void

Note that if the player successfully left the room, the calling game client will receive the notification via the following callback API:

function onLeaveGameRoom(error:int):void

Flash 10 Multiplayer Game Essentials Create exciting real-time multiplayer games using Flash
Published: July 2010
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

(For more resources on Flash and Games, see here.)

New game screen implementation

NewGameScreen is where a new game room is created when the player clicks on the new game room button in the lobby screen.

New game screen in Hello World

The lobby screen gets replaced by the NewGameScreen or that of the subclass we have set up. In all cases, you must subclass the NewGameScreen and override at least the createNewGameRoom method. This method is called when the player finally clicks on OK.

The following is the overridden method from the Hello World sample:

protected override function createNewGameRoom():void {
// Create the new Game room object
var room:GameRoomClient = new GameRoomClient();
// Set the name of the room from what is entered
// in the text field
room.setRoomName(m_ti.text);
// Limit maximum players that can
// enter the room to three
room.setMaxPlayerCount(3);
// Let players join the game
// even after it is started
room.setRoomType(GameConstants.ROOM_ALLOW_POST_START);
// set the player to be in ready
// state as soon as they enter the room
room.setAutoReady(1);
// Send a request to the server
// to create the game room
PulseGame.getInstance().getGameClient().
createGameRoom(room);
}

All samples provided with the Pulse package simply take in a name for the room. But for a real game, you may want to provide more options, such as password, max players, and others, for a player to control.

Customizing the new game screen

In order to provide our own implementation for the new game screen, we need to override the initNewGameScreen defined in the PulseGame.

The subclass of game screen must then override init where we create all the sprites needed. The show and hide methods are overridden to display them onto the screen and then remove them respectively.

protected override function initNewGameScreen():void
{
m_newGameScreen = new MyNewGameScreen();
m_newGameScreen.init();
}

Next, we subclass NewGameScreen defined in PulseUI called NewGameRoom screen and override just one method called createNewGameRoom. In the method, we specify what kind of room needs to be created. For this sample, we will keep it real simple:

protected override function createNewGameRoom():void {
var room:GameRoomClient = new GameRoomClient();
room.setRoomName(m_ti.text);
room.setMaxPlayerCount(3);
room.setRoomType(GameConstants.ROOM_ALLOW_POST_START);
room.setAutoReady(1);
PulseGame.getInstance().getGameClient().
createGameRoom(room);
}

First, we create a game room object of class GameRoomClient, which is defined in the Pulse package. We then set the properties on the object. We give it a name as entered by the player and limit the number of players joining the room to three. We also set the room type to say that players may join the room for which the game has already started. We set the auto-ready to 1 (true) meaning the player status will be set to ready as soon as they join the game, and this will allow the host to start the game anytime. Note that, in this sample, we don't provide any UI to set the status of the player to ready or wait.

Finally, we call the Pulse API createGameRoom(...) and pass the instance of the room. Once the server creates the room successfully, the new room will appear for every player that is currently in the lobby.

New game room API

To create a new game room as we saw in the HelloWorld example, you simply need to create the room object, set the necessary properties, and call createGameRoom API.

public function createGameRoom(gameRoom:GameRoomClient):void

The game client will receive the following notification to let the game client know if the request was successfully created or had any errors.

function onCreateGameRoom(gameRoomId:int, error:int):void;

Note that players in the lobby will be notified of the newly created room so that their respective UI can be updated.

Designing the game screen

The heart of any game—this is where all the action takes place. Because this is where the game will be implemented, most code is written here to implement the game.

The standard init, show, and hide methods must be overridden and implemented to initialize, add, and remove the needed sprites respectively. In addition to the three methods, let us examine a few other methods that get called when we need to implement.

If the game is designed for players to join after the game has already started, the game code should not wait for the startGame method to be called. Instead, the show method must check if the game has already started and update the game screen with all the game states that it receives.

The method startGame gets called when the host starts the game; this method gets called in all the clients that are in the room. In this method, we begin the race in case of a racing game or we start distributing the cards in the case of a card game, and so on.

The show method must additionally check if the player is the game host. If so, then the game screen should show the start sprite and for all other players (non-hosts) the player should show wait sprite.

Finally, in case the game has not started and the host quits the room for some reason, the method onHostAlert would be called. In this case, the player that becomes the host must have the wait sprite replaced with the start sprite.

Implementing the game screen

The HelloGameScreen is subclassed from GameScreen defined in PulseUI package. We will add two buttons for adding and removing game states from the game instance. Like other subclassing where we will need to override the specific methods, the PulseUI framework will take care of calling them at the right time. For example, the init method is overridden to initialize any sprites that you may need during the game, the show method must be overridden to actually put the sprites onto the screen, and finally, the hide method should be overridden to remove them from the screen.

Note that the init method is called once, but the show and hide may be called several times, that is every time when the player enters or leaves the game room.

Here is a partial listing of the class that shows the property definitions, and the overriding of the init, show, and hide methods:

public class HelloGameScreen extends GameScreen
{
private var m_addBtn:Button;
private var m_removeBtn:Button;
private var m_gs:Map = new Map();
// keep track of selected.
private var m_selected:GameStateSprite;

public function HelloGameScreen()
{
super();
}

public override function init():void {
super.init();
initBtns();
}
public override function show():void {
super.show();
var gc:GameClient;
gc = PulseGame.getInstance().getGameClient();
// If the game has already started, add the buttons
if ( gc.getMyGameRoom().getRoomStatus() ==
GameConstants.ROOM_STATE_PLAYING ) {
addChild(m_addBtn);
addChild(m_removeBtn);
}
}
public override function hide():void {
// Remove all the sprites and cleanup
var states:Array = m_gs.values();
for ( var i:int=0; i<states.length; i++ ) {
removeChild(states[i]);
}
m_gs.reset();
if ( m_addBtn.parent != null )
removeChild(m_addBtn);
if ( m_removeBtn.parent != null )
removeChild(m_removeBtn);
super.hide();
}

Notice that along with the needed sprite for the buttons, we also keep track of the game state sprites in a hash map (m_gs), as well as keeping track of the colored circle that was last clicked on (m_selected).

Another important method to override is the start method, which is called when the host starts the game. Since in this sample we allow the players to enter even after the game has started, the show method does a check if the room status is already in playing state. If so, then the add and remove buttons are drawn.

public override function startGame():void {
super.startGame();
// Game started, add the buttons
addChild(m_addBtn);
addChild(m_removeBtn);
}

As we saw in the implementation of the main class, there were three methods that would be called when we received a new game state, when a game state was removed, and when it was modified. The implementation of this is simply delegated to this game screen. Now let us examine the code to see what happens in each of the three cases.

When a new game state is added, we create a new game state sprite and add it to the screen. After creating the game state sprite, we add it to the map so that we can access it later during removal or update. We also hook up the mouse event listeners so that we can implement the drag and selection capabilities. Finally, we set the x and y coordinates for the colored circle from the values found in the game state object and add it to the screen.

private function
newGameState(gameState:HelloGameStateClient):void {
// Create and show a corresponding
// sprite for the game state
var s:Sprite = new GameStateSprite(gameState);
m_gs.put(gameState.getId(), s);
s.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
s.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
s.x = gameState.getX();
s.y = gameState.getY();
addChild(s);
}

When we receive a notification from the server that a game state was removed, we simply remove the corresponding game state sprite from the screen.

public function onRemoveGS(gameState:GameStateClient):void {
// Received from server to remove a game state
var s:GameStateSprite;
s = m_gs.getValue(gameState.getId());
if ( s != null ) {
removeChild(s); // remove from screen
m_gs.remove(gameState.getId()); // remove from cache
}
}

We also make sure to remove it from the hash map.

When we receive a notification that a game state was updated, find the corresponding game state sprite in the hash map and update the x and y from the received updated game state:

public function
onUpdateGS(gameState:GameStateClient):void {
// An update game state was received from server
var gs:HelloGameStateClient;
gs = gameState as HelloGameStateClient;
var s:GameStateSprite = m_gs.getValue(gs.getId());
if ( s == null ) {
// This can happen when the player
// joins in the middle of the game
newGameState(gs);
}
else {
s.x = gs.getX();
s.y = gs.getY();
}
}

Now that we have handled the notification from the servers when a game state was added, removed, or modified, how do we generate these game states in response to player actions? Let us examine them now one by one. The player may add a new game state by clicking on the Add button. The callback method that gets called when the player clicks on the Add button is shown below:

private function addGS(e:Event):void {
// Add button was clicked!
var newGS:HelloGameStateClient;
newGS = new HelloGameStateClient();
newGS.setX(300);
newGS.setY(300);
PulseGame.getInstance().
getGameClient().addGameState(newGS);
}

When a player wishes to add a new game state, we simply create a new game state object, initialize the properties, and call the Pulse API to add the game state. Upon the server receiving the message, it will be broadcasted to all the players in the room, including the one who sent it in the first place. This will result in the calling of the method newGameState in all the players' rooms. So in effect, the colored circle is not drawn until a notification is received from the server, including for the player that created it.

The code below shows how the add and remove buttons are created and initialized. This method is called from the init method, which we saw earlier:

private function initBtns():void {
m_addBtn = new Button();
m_addBtn.x = 200; m_addBtn.y = 150;
m_addBtn.height = 20; m_addBtn.width = 40;
m_addBtn.label = "Add";
m_addBtn.addEventListener(MouseEvent.CLICK, addGS);
m_removeBtn = new Button();
m_removeBtn.x = 250; m_removeBtn.y = 150;
m_removeBtn.height = 20; m_removeBtn.width = 80;
m_removeBtn.label = "Remove";
m_removeBtn.addEventListener(MouseEvent.CLICK, removeGS);
}

Similar to the add mechanics, the removal of a game state is triggered when the player clicks on the remove button.

private function removeGS(e:Event):void {
if ( m_selected != null ) {
PulseGame.getInstance().
getGameClient().
removeGameState(m_selected.m_gs);
}
}

During the callback that is fired when the remove button is clicked, we check if there is any selection of a colored circle. If so, we call the pulse API to remove the game state. The server, on receiving the request, will broadcast to all the players in the game room including the one sending the request. When the request is received by the clients, the onRemoveGS method is called and the game state sprite gets removed from the screen as shown above.

In order to demonstrate the game state update, the Hello World sample allows the players to drag the colored circle around within the game screen. For this, we need to implement the mouse event handlers. The handlers also keep track of the currently selected sprite. Here is the code for the mouse event handlers.

The mouse down handler will first figure out the sprite on which the click was made and initiate the sprite drag.

private function mouseDownHandler(event:MouseEvent):void {
//trace("mouseDownHandler");
var sprite:Sprite;
sprite = Sprite(event.target);
sprite.addEventListener(MouseEvent.MOUSE_MOVE,
mouseMoveHandler);
sprite.startDrag();
}

When the mouse button is released we stop the mouse drag process. We also record the final position of the sprite. We then get the corresponding game state associated with the colored circle and send in the request to the server to modify the game state. We also update the m_selected property.

private function mouseUpHandler(event:MouseEvent):void {
//trace("mouseUpHandler");
var sprite:GameStateSprite;
sprite = Sprite(event.target) as GameStateSprite;
// trace("x: " + sprite.x + " y: " + sprite.y);
sprite.removeEventListener(MouseEvent.MOUSE_MOVE,
mouseMoveHandler);
sprite.stopDrag();
var gs:HelloGameStateClient;
gs = sprite.m_gs;
if ( gs != null ) {
gs.setX(sprite.x);
gs.setY(sprite.y);
PulseGame.getInstance().
getGameClient().updateGameState(gs);
}
setSelected(sprite);
}

This concludes the walk-through of the Hello World sample. The readers are encouraged to study the sample from the downloaded package and give it a test drive.

Customizing the game screen

The game screen—where all the action happens—also inherits from Screen class. We must inherit this class to provide the gameplay.

To supply our own implementation of MyGameScreen, we need to override the initGameScreen method defined in PulseGame. So the overridden method in MyGame looks like the following:

protected override function initNewGameScreen():void
{
m_newGameScreen = new NewJigsawGameScreen();
m_newGameScreen.init();
}

Summary

In this article, we dealt with the implementation and user interface customizations as related to a game lobby and a room within the lobby. We saw how we can leverage the PulseUI framework to quickly have the functionality available for our new game. We also saw how (if needed) to customize the lobby screen and the rooms within it. We saw how the new game rooms are created and how to customize the user interface for the same. Finally, we saw how the players enter into the game screen and perform essential state-specific checks related to the game host if the game has already started.


Further resources on this subject:


Flash 10 Multiplayer Game Essentials Create exciting real-time multiplayer games using Flash
Published: July 2010
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

About the Author :


Prashanth Hirematada

Prashanth Hirematada, is the founder of Gamantra, a game technology company focused on Network engine & server platform. Prior to founding Gamantra in 2006, he was a Chief Architect at Shanda Interactive Entertainment Ltd., where he was responsible for creating a common game development platform for all MMOG initiatives at Shanda. He joined Shanda in 2004 through Shanda's acquisition of Zona, Inc., an MMOG game technology company, headquartered in Santa Clara, USA. At Zona, as a Technical Chief Architect, he was responsible for server-side architecture and implementation of MMOG framework. Prior to joining Zona in 2001, Prashanth worked in various Silicon Valley-based technology start-up companies developing software at various levels for well over 7 years.

His Master's thesis was about a distributed implementation of the Message Passing Library (MPI) on a heterogeneous network of workstations including Solaris, HP-UX, OpenStep, and Windows NT. He received his MS in Computer Science, from the California State University, Sacramento, California, in 1994 and his BS in Computer Science, from Bangalore University, Bangalore, India in 1992. He can be contacted at prash@gamantra.com.

 

Books From Packt


Unity Game Development Essentials
Unity Game Development Essentials

JavaFX 1.2 Application Development Cookbook
JavaFX 1.2 Application Development Cookbook

WordPress and Flash 10x Cookbook
WordPress and Flash 10x Cookbook

Flash with Drupal
Flash with Drupal

Joomla! with Flash
Joomla! with Flash

Papervision3D Essentials
Papervision3D Essentials

TYPO3 4.3 Multimedia Cookbook
TYPO3 4.3 Multimedia Cookbook

3D Game Development with Microsoft Silverlight 3: Beginner's Guide
3D Game Development with Microsoft Silverlight 3: Beginner's Guide


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software