Tutorials/helloworld

From CubeiaWiki

Jump to: navigation, search

Contents

Introduction

This tutorial will show you how to develop a simple "hello world" multiplayer game using Firebase.

The game will work like this:

  1. Users can log in
  2. The client will display a list of available "tables"
  3. Users can join tables
    1. When a user joins a table, the server will greet him by name
    2. When joined at a table, the user can chat with other users at the table
  4. Users can leave tables
  5. Users can log out

A word on the vocabulary:

  • "table" - This is an area or logical space, think of it as a poker table or a room, where multiple players interact.
  • "join table" - Entering a table to participate, equivalent of sitting down at a poker table, or signing up for an arena fight.

Prerequisites

We'll use Java server side and Flex for the client.

Writing the Server Game

Create a Maven Project

We'lll start with creating a Maven project for the server code (the command below is line broken for readability, please remove all '\' and new lines if you're on Windows):

mvn archetype:generate \
    -DarchetypeGroupId=com.cubeia.firebase.tools \
    -DarchetypeArtifactId=firebase-game-archetype \
    -DarchetypeVersion=1.8.0 \
    -DarchetypeRepository=http://m2.cubeia.com/nexus/content/groups/public \
    -DgroupId=net.test \
    -DartifactId=helloWorldGame \
    -Dversion=1.0-SNAPSHOT \
    -Dpackage=net.test

The above command sets "groupId", "artifactId", "version" and "package", but feel free to change them. However, it will ask for a "gameId" which you will need later as it is used when clients connect, so please remember it. We'll use "888" for this tutorial:

[...]
Define value for gameId: : 888
Confirm properties configuration:
gameId: 888
version: 1.0-SNAPSHOT
groupId: net.test
artifactId: helloWorldGame
package: net.test
 Y: :
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5 seconds
[INFO] Finished at: Sat Feb 06 12:59:58 CET 2010
[INFO] Final Memory: 8M/21M
[INFO] ------------------------------------------------------------------------

That's it! You have actually created an entire, compilable and deployable game right there! Cool huh?

Server Side Actions

So what do we need to do now? Well, if we look at the way the game is supposed to function, there are a couple of actions the server must undertake:

  • login / logout
  • join / leave
  • handle "hello world" actions
  • greet player on join

As you might expect "login", "logout", "join" and "leave" are handled by Firebase, so all we need to do is:

  • When a player sends a message, we should distribute it to all players at the table
  • When a player joins a table, we should send him a greeting message

Sending Messages

We'll start with sending messages, open the "src/main/java/net/test/Processor.java" and edit the "handle" for game data actions, which are the actions the game receive from clients (as opposed to game object actions which are internal, scheduled actions). Edit the method to look like this:

@Override
public void handle(GameDataAction action, Table table) {
    /*
     * The action contains game data as a byte buffer, 
     * we'll transform it to an array, and then to a string.
     */
    byte[] arr = action.getData().array();
    String message = new String(arr);
    // System.out.println("Incoming message: " + message);
    /*
     * Create a new action to send all players with the message,
     * in this case we could actually just re-send the incoming 
     * action as they will be identical, but for clarity, we'll 
     * create a new action here.
     */
    int tableId = table.getId();
    int playerId = action.getPlayerId();
    GameDataAction outAction = new GameDataAction(playerId, tableId);
    outAction.setData(ByteBuffer.wrap(message.getBytes()));
    /*
     * Now send the action to all players at the table.
     */
    table.getNotifier().notifyAllPlayers(outAction);
}

Notice that we didn't even have to know the other players at the table, Firebase handles that for us. Also, we're cheating alittle, we're assuming the messages comes in the standard platform encoding. If not, you 'd have to do something like this:

[...]
String message = new String(arr, "UTF-8");
[...]
outAction.setData(ByteBuffer.wrap(message.getBytes("UTF-8")));
[...]

Greeting Message

Firebase does not automatically notify you whet a player joins a table. In order to be notified we must use a TableListener. So let's create a new class called GreeterListener, which sends a greeting when a player has joined the table:

public class GreeterListener implements TableListener {

    @Override
    public void playerJoined(Table table, GenericPlayer player) {
	/*
	 * Let's compose a simple greeting for the player.
	 */
	String message = "Greetings " + player.getName() + "!";
	// System.out.println("Greeting player: " + player.getPlayerId());
        /*
         * Create a new action to send to the player with the message.
         */
        int tableId = table.getId();
        int playerId = player.getPlayerId();
        GameDataAction outAction = new GameDataAction(playerId, tableId);
        outAction.setData(ByteBuffer.wrap(message.getBytes()));
        /*
         * Use the table notifier to send the message to the player.
         */
        table.getNotifier().notifyPlayer(playerId, outAction);
    }

    @Override
    public void playerLeft(Table table, int player) { }

    @Override
    public void playerStatusChanged(Table table, int player, PlayerStatus status) { }

    @Override
    public void seatReserved(Table table, GenericPlayer player) { }

    @Override
    public void watcherJoined(Table table, int player) { }

    @Override
    public void watcherLeft(Table table, int player) { }
	
}

Again, we're cheating alittle, we're assuming the messages come in the standard platform encoding. If not, you'd have to do something like this:

[...]
outAction.setData(ByteBuffer.wrap(message.getBytes("UTF-8")));
[...]

And all we need to do now, is providing the above implementation so Firebase can find it. We do it by letting the game, "src/main/java/net/test/GameImpl.java", itself implement TableListenerProvider and returning the new table listener from a 'get' method in the game:

public class GameImpl implements Game, TableListenerProvider {

    [...]
    
    @Override
    public TableListener getTableListener(Table table) {
        return new GreeterListener();
    }

    [...]

}

Compiling and Packaging

We'll use Maven again, and if all is well, you only need to 'package' the code. One minor point, if you havn't done so, you need to change directory into your newly created game first, so...

cd helloWorldGame
mvn package 

This will create a Game Archive (GAR) file for you called "target\helloWorldGame-1.0-SNAPSHOT.gar". This is a special archive used by Firebase for deploying games (you will find that the Maven POM file specifies "firebase-gar" as the packaging type).

Deploying and Running

The above created GAR is ready to be deployed into an installed Firebase. Just copy the GAR to the "game/deploy" folder of you Firebase installation and restart Firebase.

However, there's a much faster way we can use for now, we can run Firebase via Maven! So, while still in you Maven game project, run this:

mvn firebase:run

This will automatically start a Firebase server, with your GAR deployed:

[...]
[INFO] Runtime directory 'c:\Dev\Test\helloWorldGame\target\firebase-run'; deleteOnExit: false
[INFO] Attempting to start Firebase server.
INFO  SystemLogImpl - Firebase Server Platform initializing; Server id: mavenServer1
ServerConfigFirectory:c:\Dev\Test\helloWorldGame\target\firebase-run\firebase-1.7.0-CE\conf
INFO  SystemLogImpl - Deployed: helloWorldGame 1.0-SNAPSHOT; Internal ID: 888; Deployment Revision: 1
INFO  SystemLogImpl - Master node 'mas1' initialized as [primary] master.
INFO  SystemLogImpl - Master node processed handshake for role GAME_NODE from id: gam1[mavenServer1[127.0.0.1:8635]]
INFO  SystemLogImpl - Master node processed handshake for role CLIENT_NODE from id: cli1[mavenServer1[127.0.0.1:8635]]
INFO  SystemLogImpl - Master node processed handshake for role MTT_NODE from id: mtt1[mavenServer1[127.0.0.1:8635]]
----------------------------
 Name: Client MServer
 IFace: /0.0.0.0:4123
----------------------------
INFO  SystemLogImpl - System [Java HotSpot(TM) Client VM 16.0-b13; Windows 7 Windows 7 (x86)]
INFO  SystemLogImpl - Server started [Sat Feb 06 13:00:55 CET 2010]
INFO  SystemLogImpl - Firebase Server Platform [version: 1.7.0-CE]

Here's two things to notice about the print-out above:

  • It says "Deployed: helloWorldGame 1.0-SNAPSHOT; Internal ID: 888; Deployment Revision: 1". Which is good, it means Firebase has picked up and installed our game!
  • See the "IFace: /0.0.0.0:4123"? That's Firebase telling you where to connect you clients. In this case Firebase is listening for clients on all available interfaces, port 4123.

Writing the Flex Client

You can choose if you want to use Flexbuilder or Maven for compiling the client. We'll show you both here. If you have Flexbuilder installed, use it, otherwise go with Maven.

Setting Up a Flexbuilder Project

We'll cheat enormously here, because there's already two nice tutorials written. So...

  • Start with installing the Firebase Flex Wizard if you haven't done so already.
  • And go ahead and create a new project using the Wizard, using the game id "888".

NB: remember the "gameId" when you wrote the server game? This is where you will need it again!

Setting Up a Maven Project

If you dont' want to use Flexbuilder you can actually use Maven for you Flex client as well. Let's start off by creating a new project (again the command below is line broken for readability, please remove all '\' and new lines if you're on Windows):

mvn archetype:generate \
    -DarchetypeGroupId=com.cubeia.firebase.tools \
    -DarchetypeArtifactId=firebase-flex-archetype \
    -DarchetypeVersion=1.8.0 \
    -DarchetypeRepository=http://m2.cubeia.com/nexus/content/groups/public \
    -DgroupId=net.test \
    -DartifactId=helloWorldClient \
    -Dversion=1.0-SNAPSHOT \
    -Dpackage=net.test

The above command sets "groupId", "artifactId", "version" and "package", but feel free to change them. However, it will ask for a "gameId" which is the same as we used for the game server. We're using "888" for this tutorial:

[INFO] Archetype defined by properties
Define value for gameId: : 888
Confirm properties configuration:
gameId: 888
version: 1.0-SNAPSHOT
groupId: net.test
artifactId: helloWorldClient
package: net.test
 Y: :
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 8 seconds
[INFO] Finished at: Sat Feb 06 13:32:31 CET 2010
[INFO] Final Memory: 8M/21M
[INFO] ------------------------------------------------------------------------

Compiling and Running

Right from the bat, your new project is possible to run and connect to you new game.

Maven vs Flexbuilder

Flexbuilder should take care of you compilation automatically. In Maven however, you need to compile the source like this:

mvn package

The above command will create a SWF in you target folder which you can drag to an open browser window to run. Note that if it's your first time running this, Maven will download a lot of files, so please be patient.

Connecting and Login

When you run the project you will be faced with a connection frame first. It will be prepopolated like this:

Firebase Server Address: 127.0.0.1
Port: 4123

And if you started Firebase on your local machine, this should be fine.

Clicking "Connect" will bring up a login screen. And now for a huge caveat:

The "password" must be an integer, Firebase will use it as the player id by default!

Read the notice above one more time. It's ugly but also very handy for debugging: when you connect, your password will also be your user ID, and Firebase wants integers as user ID's. Obviously this can be customized later, but for now login with something like this:

Username: testing1
Password: 1234

Sending and Receiving Messages

The created client stub is already handling joining and leaving tables, but it uses the built-in Firebase chat channels when you've joined a table. We'll modify it to use game events instead. Everything you need to edit is in the "src/main/flex/net/test/TableWindow.mxml" file. First change the "setupPacketHandler" to add a listener for game data packets:

private function setupPacketHandler():void
{
    // setup packet data handler
    HelloWorld.firebaseClient.addEventListener(PacketEvent.PACKET_RECEIVED, onPacketReceived);
    // setup handler for game packets
    HelloWorld.firebaseClient.addEventListener(GamePacketEvent.PACKET_RECEIVED, onGamePacketReceived);
}

Now we need to handle the incoming packets so add a new method (hint, this is actually only an adapted variation of the already existing "handleTableChatPacket" method for chat packets):

private function onGamePacketReceived(packetEvent:GamePacketEvent):void
{

    // create UTF-8 string from binary data
    var data:ByteArray = packetEvent.getPacketData(); 
    var bytes:int = data.bytesAvailable;
    var msg:String = data.readUTFBytes(bytes);

    if ( packetEvent.pid == HelloWorld.playerInfo.pid ) {
        // use BLUE color for our own messages 
        chatOutput.htmlText += "<font color=\"#000080\">" + msg + "</font><br>";
    } else {
        // use RED color for everyone else
        chatOutput.htmlText += "<font color=\"#800000\">" + msg + "</font><br>";
    } 
}

And we don't want to send chat packages, we want to send game packages. Let's start with adding a new method (again, this is an adapted version of the chat method):

private function onSendGameMessage():void
{
    // save text as UTF
    var data:ByteArray = new ByteArray();
    data.writeUTFBytes(chatInput.text);
				
    // create a game transport packet
    var gameTransportPacket:GameTransportPacket = new GameTransportPacket();
    gameTransportPacket.pid = HelloWorld.playerInfo.pid;
    gameTransportPacket.tableid = HelloWorld.tableid;
    gameTransportPacket.gamedata = data
    gameTransportPacket.gamedata.position = 0;
				
    // send packet
    HelloWorld.firebaseClient.send(gameTransportPacket);
				
    // clear input text field
    chatInput.text = "";
    chatInput.selectionBeginIndex = 0;
    chatInput.selectionEndIndex = -1;
    focusManager.setFocus(chatInput);
}

Finally, change the text field to use the above method on click, instead of the chat method:

<mx:Button x="383" y="197" label="Send" enabled="{chatInput.text.length > 0}" 
    click="onSendGameMessage()" id="sendButton" tabIndex="3"  themeColor="#AD3333" 
    color="#FFFFFF" fillAlphas="[1.0, 1.0]" fillColors="[#AD3333, #460A0A]" visible="false"/>

Maven Differences

There's one more thing if you're doing this as a Maven project: As the Flexbuilder and Maven setup are somewhat different, you need to open "src/main/flex/net/test/TableWindow.mxml" one more time and find/replace "HelloWorld" with "ClientStub".

And that's it! Compiling and running this against your game server should not only echo messages between all players joined at the same table, it should also provide you with a nice greeting when you enter the table yourself.

The Boiler Plate

As the created project contained much of the boiler plate to get going, please have a look around in the source. It is documented so you should be pretty comfortable quickly.

Conclusion

That's it. You'll recognize that a lot of the instructions in this tutorial is simply environment setup. And the actual code needlessly verbose to make it easy to under stand.

Something you wonder? Here's some hints:

  • Can I stop players joining a table? Yes, write a TableInterceptor and let the game implement TableInterceptorProvider
  • How many seats does a table have? How can I customize tables? Have a look at the activator section in the manual.
  • Can I schedule events on the server? Yes, there's a scheduler on the table, the objects will come back to the processor as game obejct actions.
  • Can I customize login? Yes, but you will have to write a login handler and deploy it as a service.

More questions? You're welcome!

Personal tools