Tutorials/helloworld
From CubeiaWiki
Contents |
Introduction
This tutorial will show you how to develop a simple "hello world" multiplayer game using Firebase.
The game will work like this:
- Users can log in
- The client will display a list of available "tables"
- Users can tables
- When a user joins a table, the server will greet him by name
- When joined at a table, the user can chat with other users at the table
- Users can leave tables
- User 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.
- Java (v. 6 or higher)
- Maven (2.0 or higher)
- Flexbuilder 3 (optional)
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.tools \
-DarchetypeArtifactId=firebase-game-archetype \
-DarchetypeVersion=1.7.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's X 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 comes 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.tools \
-DarchetypeArtifactId=firebase-flex-archetype \
-DarchetypeVersion=1.7.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 build-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!

