An Introduction to ElectroServer and PushButton Engine: Part One
Mar/103
There are many options available when deciding on which Flash engine or framework to use before development begins on your next single-player project. Should you decide to add multi-player capability, the list gets shorter. This is especially true if your game crosses over into the MMOG or Virtual World domains. Code reusability, game asset management and premium performance are all key to successfully reaching that goal. PushButton Engine satisfies these criteria and more by offering a component-based architecture, dependency injection, a robust engine core and a strong community.
When deciding on which server technology to use it’s quite a different story. The list of viable options is much shorter. Scalability, maintainability, extensibility and price all come to mind when shopping around for the right product. The RedDwarf server (formerly Project Darkstar) is tempting due to it’s cost (free) but developers who are interested in simply writing their game code may become discouraged due to it’s steep learning curve. Enter, ElectroServer. When I recently started exploring ElectroTank’s flagship product, I was hooked on it’s highly-polished API and how dead-simple it was to get up and running. Couple that experience with it’s potential to scale to hundreds of thousands of simultaneous users and it’s difficult to find a better option.
Goals
The goal of this tutorial is not to have you up and running with a full-blown, ready to ship product. Instead, the focus is on just how little you have to do to get something started. I hope it will help illustrate how powerful the combination of PushButton Engine and ElectroServer can be. Although introductory in nature, I believe you’ll see the incredible value merging these two great technologies can provide.
Prerequisites
I’m assuming you’re comfortable moving around an IDE or text editor, have some fairly advanced ActionScript skills, some Java experience and generally aren’t afraid to get your hands dirty. If you’ve never used ElectroServer or PushButton Engine, I would urge you at least check out their respective starter lessons to get you up to speed.
The Server
Before a client application can start a game, it needs something to connect to. ElectroServer is built on a plugin architecture which allows developers to easily extend it’s core functionality. Plugins can be scoped to the server, a room or a player. In this example, we’ll focus on server initialization, user login and finally the game logic itself. Let’s start digging into some code!
Initialization
Our first plugin is scoped to the server and is in charge of configuring the primary game plugin.
File: /ESPBEDemo-server/src/com/philperon/espbedemo/plugins/InitPlugin.java
package com.philperon.espbedemo.plugins;
import com.electrotank.electroserver4.extensions.Plugin;
import com.electrotank.electroserver4.extensions.PluginLifeCycle;
import com.electrotank.electroserver4.extensions.api.PluginApi;
import com.electrotank.electroserver4.extensions.api.value.EsObject;
import com.electrotank.electroserver4.extensions.api.value.EsObjectRO;
import com.electrotank.electroserver4.extensions.api.value.ExtensionComponentConfiguration;
import com.electrotank.electroserver4.extensions.api.value.GameConfiguration;
import com.electrotank.electroserver4.extensions.api.value.RoomConfiguration;
// This is the plugin that gets everything started.
public class InitPlugin implements Plugin, PluginLifeCycle {
// Define a private handle to the plugin api (used in the getter/setter below)
private PluginApi api;
// init is called when the object based on this class is instantiated.
@Override
public void init(EsObjectRO esObj) {
// Create the plugin variable that will contain the plugin.
ExtensionComponentConfiguration plugin = new ExtensionComponentConfiguration();
// Define which extension the plugin will be associated with.
plugin.setExtensionName("ESPBEDemo");
// Set the handle which is how the plugin will be accessed when instantiated in a room.
plugin.setHandle("GamePlugin");
// Set the name that will be used to label this plugin in the Extension.xml file.
plugin.setName("GamePlugin");
// Create the room configuration for the room that will host the plugin.
RoomConfiguration roomConfig = new RoomConfiguration();
// Set the user connection limit.
roomConfig.setCapacity(25);
// Now add the plugin to the room configuration.
roomConfig.addPlugin(plugin);
// Create an ElectroServer object that will contain extra details that should be relayed to the user.
EsObject gameDetails = new EsObject();
// Set the type so that the client knows how to request access after it connects.
gameDetails.setString("type", "game");
// Create a new game configuration instance.
GameConfiguration gameConfig = new GameConfiguration();
// Add the game details.
gameConfig.setInitialGameDetails(gameDetails);
// Add the room configuration we defined above to the game config object.
gameConfig.setRoomConfiguration(roomConfig);
// We're not interested in listening for room list updates in this demonstration...
gameConfig.setReceivingRoomListUpdates(false);
// ...or room variable updates.
gameConfig.setReceivingRoomVariableUpdates(false);
// Make sure we receive user list updates...
gameConfig.setReceivingUserListUpdates(true);
// ...as well as user variable updates.
gameConfig.setReceivingUserVariableUpdates(true);
// We're not interested in video events.
gameConfig.setReceivingVideoEvents(false);
// Finally, register our "game" using the game configuration object we just created.
getApi().registerGameConfiguration("game", gameConfig);
}
@Override
public PluginApi getApi() {
return api;
}
@Override
public void setApi(PluginApi api) {
this.api = api;
}
@Override
public void destroy() {
// Clean up any loose ends.
}
}
That wraps up the initialization process. Before jumping into the game plugin, we’ll need to think about how user logins are handled.
Accepting User Logins
Event Handlers are another way the ElectroServer API allows you to extend functionality. At this point, the server is aware of the game plugin but we need some way to send this information to a connected client. This is where the LoginEventHandler comes into play.
File: /ESPBEDemo-server/src/com/philperon/espbedemo/handlers/LoginEventHandler.java
package com.philperon.espbedemo.handlers;
import com.electrotank.electroserver4.extensions.BaseLoginEventHandler;
import com.electrotank.electroserver4.extensions.ChainAction;
import com.electrotank.electroserver4.extensions.LoginContext;
import com.electrotank.electroserver4.extensions.api.value.EsObject;
// Like the other event handlers, there are base classes you extend to get started quickly.
public class LoginEventHandler extends BaseLoginEventHandler {
// This method fires after a player logs in.
public ChainAction executeLogin(LoginContext login) {
// Create a new EsObject to hold values that will soon be passed to the client.
EsObject uservars = new EsObject();
// Set a gameType value to the name of our plugin, "game".
uservars.setString("gameType", "game");
// Add the user variables to the login context.
login.addUserVariable("uservars", uservars);
// And let the server know that all is well.
return ChainAction.OkAndContinue;
}
}
Now, any time a user logs in, they will be sent the information they need to request access to the game. Ideally, the server would insert a player automatically but because that functionality is not yet available in the current version of ElectroServer, we’ll deal with that later on in the client code.
Writing the Game Plugin
Now to our server-side game code! The objective here is to listen for various player action events such as entering and exiting the game as well as moving around the game world.
File: /ESPBEDemo-server/src/com/philperon/espbedemo/plugins/GamePlugin.java
package com.philperon.espbedemo.plugins;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.electrotank.electroserver4.extensions.BasePlugin;
import com.electrotank.electroserver4.extensions.ChainAction;
import com.electrotank.electroserver4.extensions.api.value.EsObject;
import com.electrotank.electroserver4.extensions.api.value.EsObjectRO;
import com.electrotank.electroserver4.extensions.api.value.UserEnterContext;
// Extend the BasePlugin class to take advantage of the user events it exposes.
public class GamePlugin extends BasePlugin {
// Define our game protocol...
// Player enters
private static final int PLAYER_INIT = 1;
// Opponent enters
private static final int OPPONENT_ENTERED = 2;
// Player state changes
private static final int STATE_UPDATE = 3;
// Player leaves
private static final int PLAYER_LEFT = 4;
// Create a thread-safe map to store players currently in-game.
private ConcurrentMap<String,EsObject> playerMap = new ConcurrentHashMap<String,EsObject>(25);
// When the game initializes, turn on message queueing.
@Override
public void init(EsObjectRO esObj) {
// This will send packaged messages five times per second.
getApi().startQueue(200);
}
// Define what happens when a player enters the game.
@Override
public ChainAction userEnter(UserEnterContext context) {
// Grab the user's name.
String name = context.getUserName();
// Create a new EsObject that will represent a player.
// We're only concerned with their name and position in this demonstration.
EsObject player = new EsObject();
// Add their name.
player.setString("name", name);
// And their starting position.
player.setNumber("x", 400);
player.setNumber("y", 300);
// Add the player to the room's player map.
playerMap.put(name, player);
// Create a EsObject to represent a new player message.
EsObject uservars = new EsObject();
// Add the opcode that represents a new player being initialized.
uservars.setInteger("opcode", PLAYER_INIT);
// Add the player object defined above.
uservars.setEsObject("player", player);
// Send the player list along so opponents can be properly rendered on the client.
uservars.setEsObjectArray("playerlist", (EsObject[]) playerMap.values().toArray(new EsObject[playerMap.size()]));
// And send the message privately to the user.
getApi().sendPluginMessageToUser(context.getUserName(), uservars);
// Now, define another player object for others to process.
EsObject userObj = new EsObject();
// Add the opcode for new opponents entering.
userObj.setInteger("opcode", OPPONENT_ENTERED);
// Add the player object.
userObj.setEsObject("player", player);
// And send the message to everyone in the room.
getApi().sendPluginMessageToRoom(getApi().getZoneId(), getApi().getRoomId(), userObj);
return ChainAction.OkAndContinue;
}
// Define what happens when a player leaves the game.
@Override
public void userExit(String name) {
// Create a new message object.
EsObject eso = new EsObject();
// Add the opcode that represents a player leaving.
eso.setInteger("opcode", PLAYER_LEFT);
// Remove the player from the player map and add the name to the message.
eso.setString("name", playerMap.remove(name).getString("name"));
// Now, send the message to the room notifying everyone that the player has left.
getApi().sendPluginMessageToRoom(getApi().getZoneId(), getApi().getRoomId(), eso);
}
// Define what happens when a player sends a message to the room.
@Override
public void request(String userName, EsObjectRO requestParameters) {
// If the user's message carries an opcode, process it.
if(requestParameters.variableExists("opcode")) {
// If the opcode represents a state update, update it here.
if(requestParameters.getInteger("opcode") == STATE_UPDATE) {
// Grab a handle of the player from the player map using it's name as a key.
EsObject player = playerMap.get(userName);
// And set it's new position.
player.setNumber("x", requestParameters.getNumber("x"));
player.setNumber("y", requestParameters.getNumber("y"));
}
// Finally, add the state update message to the message queue to be sent to
// everyone in the room during the next 200ms cycle.
getApi().sendQueuedPluginMessageToRoom(userName, requestParameters);
}
}
// Clean up if and when the game plugin (room) is removed.
@Override
public void destroy() {
getApi().stopQueue();
}
}
A quick note regarding message queueing. Due to limitations of the Flash Player and general good practice when designing a multi-player game, you don’t want to send a high number of messages per second. By wrapping up various messages per packet, the Flash Player remains happy and you still have enough information to render other players on the client adequately enough. I suggest 200 milliseconds per package here, but others might say that between 250 and 500 milliseconds is enough for anything outside a fast-twitch, action game. It really depends on your situation.
Deployment
Deploying a new ElectroServer is a snap. Simply define an extension.xml file, drop the relevant files in the server’s extension folder, restart the server and activate them. The Extension.xml file for this demonstration looks like this.
File: /ESPBEDemo-server/src/extension.xml
<?xml version="1.0" encoding="utf-8" ?>
<Extension>
<!-- The name of our extension -->
<Name>ESPBEDemo</Name>
<!-- Define all event handlers here. -->
<EventHandlers>
<LoginHandlers>
<!-- Our login handler goes here. -->
<LoginHandler>
<!-- Define the name of the handle that will be used to access it -->
<Handle>LoginEventHandler</Handle>
<!-- Define the type. We've been using Java but ActionScript 1 is also an option -->
<Type>Java</Type>
<!-- Set the path to the actual login event handler class. -->
<Path>com.philperon.espbedemo.handlers.LoginEventHandler</Path>
</LoginHandler>
</LoginHandlers>
</EventHandlers>
<!-- Define all plugins here -->
<Plugins>
<!-- First, the initialization plugin -->
<Plugin>
<Handle>InitPlugin</Handle>
<Type>Java</Type>
<Path>com.philperon.espbedemo.plugins.InitPlugin</Path>
</Plugin>
<!-- And then the game plugin -->
<Plugin>
<Handle>GamePlugin</Handle>
<Type>Java</Type>
<Path>com.philperon.espbedemo.plugins.GamePlugin</Path>
</Plugin>
</Plugins>
</Extension>
Create two directories in [your electroserver install directory]/server/extenstions/ called, “ESPBEDemo” and “classes”. Copy the extension.xml file to the root of that directory and move the fully pathed .class files from your java project folder inside “classes”. When you’re finished you should have a structure that looks like this:
$EXTENSION_DIR/ESPBEDemo/extension.xml
$EXTENSION_DIR/ESPBEDemo/classes/com/philperon/espbedemo/handlers/LoginEventHandler.class
$EXTENSION_DIR/ESPBEDemo/classes/com/philperon/espbedemo/plugins/InitPlugin.class
$EXTENSION_DIR/ESPBEDemo/classes/com/philperon/espbedemo/plugins/GamePlugin.class
Now restart the server and activate the server level components, LoginEventHandler and InitPlugin by selecting “Extensions” from the top navigation bar in the ElectroServer admin interface and then, “Create New Server-level Component” on the left side of the page.

Extension name should be the same as you created above (ESPBEDemo) and the name and handle fields should be “InitPlugin”. Once that’s saved, do the same for the LoginEventHandler.

Now restart ElectroServer. When it initializes, you should see something close to the following:

Log back into the administration tool and check the extensions. If you expand the ESPBEDemo extension, it should look like this:

Congrats! You’ve successfully built and deployed a Java extension for ElectroServer!
In part two, we’ll build the PBE client and connect it all together. Stay tuned!
You can download the source code covered in this post here.
For more information on all things ElectroServer, check out their wiki or visit their forums.

12:48 pm on April 1st, 2010
I get this error
Extension ESPBEDemo failed to start (java 1=”com.philperon.espbedemo.plugins.InitPlugin” language=”.lang.ClassNotFoundException:”)
4:07 am on April 5th, 2010
Double-check the package structure and make sure you’re deploying the compiled classes inside a “classes” folder as mentioned above.