SFS2X Docs / ExamplesFlash / battlefarm
» BattleFarm
» Overview
BattleFarm is a fast-paced realtime multiplayer action game made with Adobe Flash. The game was created in 2008 as a case study to highlight the productivity and performance features of SmartFoxServer PRO and its accompanying tools: SmartFoxBits and the BlueBox. The ActionScript 2 source code of the game was never released... until today, after we decided to port it to AS3 and of course SmartFoxServer 2X.
Just like in 2008, this complete game example is aimed at showing the capabilities of SmartFoxServer 2X when creating realtime multiplayer games, the stunning performance of its BlueBox 2X module and the flexibility of the SmartFoxBits 2X components in creating user interfaces.
In BattleFarm two players compete with each other and against the time to collect the fruits found in the game maze, and using bombs to hit the opponents in order to make them loose what they have collected.
When the timer goes to zero, an exit door (in the form of a pit) will randomly appear on the map, and the player with the highest number of collected items will be able to go out and win the round. In this last phase the other player can try to block the opponent or make him loose his fruits so that he can't leave the map.
>> DOWNLOAD the source files <<
IMPORTANT
The BattleFarm game and tutorial are distributed for educational purposes only. You must retain all the copyright notices appearing in the user interface, source code and any other accompanying file.
Please read the license file contained in the game folder for more informations.
Contact us if you are interested in buying a commercial license which removes the above restrictions.
» Getting started
» Running the game
In order to run the game follow these steps:
- copy the battleFarm folder from the /deploy/extensions folder to your SFS2X installation folder, under /SFS2X/extensions;
- copy the BattleFarm.zone.xml file (containing the Zone configuration) from the /deploy/zones folder to your SFS2X installation folder, under /SFS2X/zones;
- start SmartFoxServer 2X;
- make sure the client will connect to the right IP address by editing the <ip> entry in the xml files available under the /deploy/client/config folder;
- open the /deploy/client/index.html file in a browser.
» Source code setup
The client-side assets are contained in the /source/client folder and they don't require a specific setup: simply open the .fla file in Adobe Flash. All the classes linked by the main document and game assets can be found under the /source/client/code folder.
BattleFarm features a server-side Extension, as described further on. In order to access its code, create and setup a new project in your Java IDE of choice as described in the Writing a basic Extension document. Copy the content of the /source/server/src folder to your project's source folder and link the libraries contained in the /source/server/lib folder.
» Game architecture
The architecture of the game is pretty simple: there is one main lobby Room defined statically in the BattleFarm Zone configuration. Each users is initially connected to the lobby and he can choose to join a game or create a new one. Each game is a separate Room.
A server-side Java Extension is responsible for handling the game logic and coordinating the players' events using the highly efficient binary protocol of SmartFoxServer 2X.
The Lobby system was built in just a few hours with the SmartFoxBits components, which allowed to quickly arrange the typical lobby elements such as the Rooms list, users list, public and private chat boxes, etc. Almost all user interaction in the lobby is handled by the SmartFoxBits, which don't require additional coding except for the more advanced skinning and styling requirements. The SmartFoxBits provide a simple and effective way to skin the visual components so that they perfectly integrate with the rest of the GUI design.
In particular, each component is responsible of the following tasks:
- the Connector performs the connection to the server, showing the connection type (socket or http... more on this later on);
- the LoginBox allows the player to login and join the BattleFarm Zone by entering his name (for sake of simplicity no access control system has been added);
- the RoomList takes care of joining the player in the initial lobby Room by means of the value set for the "Room to join" property in the Component Parameters panel; also, by setting the "Show chat rooms" property to false in the same panel, it is possible to make the component display only the game Rooms and hide the lobby itself;
- the ChatBox allows the players to chat publicly, both in the lobby and later during the game;
- the UserList shows the users currently in the lobby, allowing private chatting by means of a nested ChatBox component.
From the code organization point of view, on the client-side the main document class takes care of instantiating the different "scenes" which compose the application: login, chat (the lobby) and the actual game.
On the server-side the standard approach to Extension development is used, together with some good OOP practice.
» Features highlights
» BlueBox
SmartFoxServer 2X features the BlueBox 2X, a module specifically designed to allow connections behind firewall and proxies through and advanced HTTP tunneling technique. If a direct socket connection is not available, the client is transparently redirected to the BlueBox.
The BlueBox enables players under restricted network configurations to play and enjoy fast multiplayer applications and games with little to no noticeable performance loss. The great news is that it is not necessary to write different code depending on the type of connection: SmartFoxServer and its client API handle the "tunneled" connections behind the scenes, making them appear, from outside, just like standard socket connections.
The main advantage of the BlueBox compared to other tunneling solutions is that you can fine tune it to the requirements of your application enabling very good performance even with real time games, like BattleFarm itself proves.
In BattleFarm, in order to be able to force the BlueBox connection for demonstration purposes, two separate client configuration files have been created (see the /deploy/client/config folder in the game package): in the sfs-config_http.xml file the <port> parameter has been set to 0000, which causes the switch to the tunneling solution. The file to be loaded is determined by an external variable passed to the game's SWF file by the container html page (see the client code snippets below).
After a connection to the server is established, the Connector's led in the bottom right corner of the game shows the connection type:
- > socket connection established
- > http connection established
» Server-side Extension
While most of the lobby features are automatically managed by SmartFoxServer and SmartFoxBits, the game logic must be custom developed using a server-side Extension plugged at Zone level, which allows to control the status of all the game Rooms running in the application.
During a match, each client sends the following requests to the Extension:
- ready: the player is ready to start the match. This request is sent right after the game Room is joined: as soon as the Extension receives two "ready" commands from two clients in the same Room, the match can start.
- mv (move): the player's character has changed it position inside the game maze, and its coordinates are sent to the server so that the opponent's client can be updated accordingly. Additionally, the server checks if the player stumbled upon a fruit, making him collect it.
- bb (bomb): the player set off a bomb; its position is sent to the server so that it will make it appear in the opponent's client too. Also, the Extension starts a timer which will cause the bomb to explode.
- restart: as soon as the match finishes (when one of the players enters the exit pit), a popup panel shows the game result; if one of the players click it, this request is sent to the Extension which restarts the game.
The followings are the commands or responses sent by the Extension to the clients:
- map: after the client declares that it is ready to play (using the ready command above), the server sends the data which define the maze layout and the graphics to be used to render it, the fruits positions, the player starting positions, etc.
- go: after both players sent the ready command and received the map data, the Extension sends this command which states that the match can start. When this command is received, each client creates a main thread (based on the ENTER_FRAME event) which controls the user keyboard interaction, the characters animation, etc.
- mv (move): when the server receives the specular mv request sent by one of the players (see above), the Extension validates it (checking if the player stumped upon a collectible item or the exit pit) and sends the new position to the opponent through this command.
- bb (bomb): when the server receives the specular bb request sent by one of the players (see above), the Extension sends its position to the opponent through this command. The Extension also keeps a reference to this bomb and will make it explode later.
- gi (get item): when, during the movement of one of the characters, the Extension detects that a collectible fruit has been reached, this command is sent to the clients so that they can make the item disappear from the map and update the collected items counters accordingly.
- xp (explode): a thread controls if a specific amount of time has passed since a bomb was placed on the map. When this occurs, this command is sent to both players so their clients can render the explosion. The Extension is also in charge of checking if the bomb explosion hits the players, making them lose the collected items, which are redistributed randomly on the map.
- od (open door): a timed event in the Extension is responsible of sending this command to the players. It makes the exit pit appear on the map.
- win: when one of the players steps on the exit pit, the Extension checks if the game is over (the player must have collected more items than his opponent) and, in case, sends this command which terminates the round making the client display the current score (the number of rounds won since the game was joined).
- stop: if the server detects that one of the players left the game Room (voluntarily or due to a sudden disconnection), this command informs the opponent and put it in standby for another player to join the Room.
In BattleFarm all the communication during the game action is performed using the efficient SFS2X binary protocol to pack the messages, which allows sending small messages very fast and with optimal bandwidth usage. The default TCP network protocol is used to transfer the messages from the client to the server and viceversa.
» Player synchronization
In order to keep a perfect synchronization between players we only need to send a minimal amount of updates and use a mix of client-side and server-side validation to achieve the best user experience.
Instead of validating each player movement on the server-side before actually animating the characters, we perform this check on the client side and notify the server about it. This way the game always feels smooth and highly responsive, regardless of the network lag.
Collision checking with the collectible items is done on the server-side instead, because we need to make sure that only one player grabs an item from the map in case both of them step on the same item at the same time.
The bomb explosion is controlled on the server-side: when a user drops a bomb the client notifies the server which in turn starts a countdown to the explosion. When the timer expires all clients are informed that the explosion should take place and the animation is performed on the client.
One of the most important aspects of the client-side of the game is using time-based animations, which ensure consistent execution of all game animations regardless of the computer CPU speed, memory, video card, etc.
In fact one of the difficulties of client synchronization is represented by the potential differences in rendering performance of the Flash Player. In other words a player running an old computer with a few hundreds of megabytes of RAM would experience slower animation rendering than another player running a shiny new multi-core machine with several gigabytes of RAM.
By ensuring that each animation takes the exact same time on any machine we eliminate the risk of additional client lag and we just need to deal with the possible delays caused by the network lag.
» Handling slower connections
In order to keep up with possible connection slow downs the client maintains a queue of moves sent by the server and executes them serially by grabbing the first item from the queue and processing it before moving on to next one.
If the client connection becomes congested for a while, many of the incoming messages aren't received for some time until the network becomes available again: at that point all the messages suddenly arrive and populate the client queue.
If those messages are too many, the client automatically skips them in order to keep up with the latest character positions, and the player will probably notice a certain amount of frame-skip in the opponent animations.
The following diagram shows a "regular" client queue state. As new messages are enqueued in the client they are processed and rendered:
In case the client can't keep up with the server message speed rate (e.g. after a network congestion) older messages are discarded to keep up with the latest game state:
This technique is very effective to avoid breaking the game synchronization even in case of clients with problematic connections.
» Client code highlights
As soon as the LoginScene is initialized, the loadConfig method is called and all the required listeners are added to the SmartFox client API, whose reference is stored in the main document class (which in turn got it from the Connector's instance). The method also takes care of loading the proper client configuration file depending on an external variable passed to the SWF by the html page.
private function loadConfig():void { // activate smartfox listeners refDocument.smartFox.addEventListener(SFSEvent.CONFIG_LOAD_FAILURE, onConfigLoadFailure); refDocument.smartFox.addEventListener(SFSEvent.CONFIG_LOAD_SUCCESS, onConfigLoadSuccess); refDocument.smartFox.addEventListener(SFSEvent.CONNECTION, onConnection); refDocument.smartFox.addEventListener(SFSEvent.CONNECTION_LOST, onConnectionLost); refDocument.smartFox.addEventListener(SFSEvent.LOGIN, onLogin); // load smartfox XML config file // a separate config file is loaded in case we need to force the BlueBox usage // (a parameter passed by the html page to the swf file url forces this) if (root.loaderInfo.parameters.conn == "http") refDocument.smartFox.loadConfig("config/sfs-config_http.xml", true); else refDocument.smartFox.loadConfig("config/sfs-config_socket.xml", true); }
The onLogin handler moves the game to the ChatScene, where the player joins the initial lobby Room. In this scene the Create button allows users to create new matches based on the selected map and title. During the Room creation process, a Room Variable is also set to save the id of the selected map.
private function createNewGame(evt:MouseEvent):void { if (name_ti.text != "") { var roomFound:Boolean = false; for (var i:int = 0; i < refDocument.smartFox.roomList.length; i++) { if ((refDocument.smartFox.roomList[i] as Room).name == name_ti.text) { roomFound = true; break; } } if (!roomFound) // prevent create room errors { // create room variable var roomVar:SFSRoomVariable = new SFSRoomVariable("map", refDocument.availableMaps[refDocument.selectedMapIndex].id); roomVar.isPrivate = false; roomVar.isPersistent = true; // add room variable to an array var roomVars:Array = new Array(); roomVars.push(roomVar); // create room settings var roomSettings:RoomSettings = new RoomSettings(name_ti.text); roomSettings.password = password_ti.text; roomSettings.maxUsers = 2; roomSettings.maxSpectators = 0; roomSettings.isGame = true; roomSettings.variables = roomVars; // create new game room with above parameters and join it refDocument.smartFox.send(new CreateRoomRequest(roomSettings, true, refDocument.smartFox.lastJoinedRoom)); } } }
In the GameScene the onGameExtensionResponse listener takes care of handling all commands and responses sent by the server-side Extension, dispatching them to specific sub-handler methods.
private function onGameExtensionResponse(evt:SFSEvent):void { var extParams:SFSObject = evt.params.params; switch (evt.params.cmd) { case "map": initGameMap(extParams); break; case "go": startGame(); break; case "mv": handleOpponentMove(extParams.getInt("px"), extParams.getInt("py")); break; case "bb": handleOpponentBomb(extParams.getInt("bId"), extParams.getInt("bx"), extParams.getInt("by")); break; case "gi": handlePickItem(extParams.getInt("px"), extParams.getInt("py"), extParams.getInt("who"), extParams.getInt("sc")); break; case "xp": handleExplosion(extParams.getInt("bb"), extParams.getInt("uid"), extParams.getInt("bx"), extParams.getInt("by"), extParams.getInt("kp1"), extParams.getUtfString("it1"), extParams.getInt("kp2"), extParams.getUtfString("it2"), extParams.getInt("sc1"), extParams.getInt("sc2")); break; case "od": handleOpenDoor(extParams.getInt("px"), extParams.getInt("py")); break; case "win": handleWinner(extParams.getInt("id")); break; case "stop": handleStopGame(); break; default: break; } }
NOTE
This example makes use of some of the SmartFoxBits user interface components. Please read the corresponding paragraph in the introduction to the ActionScript 3 examples.
» Server code highlights
Following the recommended approach in Extensions development, on the server-side each request sent by the clients is handled by a dedicated handler. Also, a list of games in progress is maintained (to save the status of each game) and a global game controller takes care of all the time-based events in all games (bombs explosions, exit pit appearance).
@Override public void init() { // load maps information gameMapsInfoBean = GameMapBsn.loadMaps(this); // initialize games list games = new ConcurrentHashMap<Integer,GameBean>(); // initialize game controller gameController = new GameController(this); gameController.start(); // register request handlers // get map list addRequestHandler(Commands.CMD_MAP_LIST, MapListHandler.class); // ready to start a game addRequestHandler(Commands.CMD_READY, ReadyHandler.class); // restart game addRequestHandler(Commands.CMD_RESTART, RestartHandler.class); // player movements addRequestHandler(Commands.MV, MovementHandler.class); // handle bombs requests addRequestHandler(Commands.BOMB, BombHandler.class); // register event handlers ... }
» More resources
You can learn more about the mentioned features by consulting the following resources: