In this article we are going to discuss the architecture of a MMO(*) Virtual World featuring the typical community tools (avatar chat, buddy lists, shop, games, trading tools, etc...) found in applications of this genre. (Habbo Hotel, Mokitown, Club Penguin...)
» A little background
In the past 2-3 years we've seen a conspicuous growth of Flash-based MMO Communities inspired by popular websites like Habbo Hotel, Mokitown and similar virtual worlds.
The idea behind these applications is to create a highly interactive world where users can not only meet and chat together, but also create their own customizable "spaces" (rooms, apartments, houses), play online games (both single and multi player), publish and exchange pictures and a lot more.
In the early days MMO communities were essentially based on "thick clients" (platform dependent executables, usually for Windows only) that users had to download to their local drives, install and run.
At the beginning of 2000 various startups were experimenting with technologies like Java and Shockwave to create similar applications and run them in a browser, making it possible to reach a wider audience and reducing the software dependencies to a single browser plugin.
Among those first experiments there were DeviousMUD which was later renamed RunEscape and Habbo Hotel.
Both MMOGs quickly became extremely popular, with millions of registered users from all over the world.
That experience inspired many other companies to research and experiment with similar technologies, including the emerging Macromedia Flash (today Adobe Flash).
Back in 2001-2002 Flash was rapidly gaining popularity but it was missing the same rendering power of its competitors (those were the days of Flash 5 and Flash MX). Additionally Shockwave had a dedicated server technology provided by Macromedia (Shockwave Multiuser Server) which helped simplifying the development and deployment of online MMOGs.
In just a few years things have evolved very quickly and today Flash is the leading platform for any type of browser-based applications and games, including MMOGs. The rendering speed has improved dramatically with the release of version 8 of the Flash Player and the Actionscript 2.0 and 3.0 languages provide a rock-solid platform for building any type of object oriented code.
As of today (June 2007) the latest Flash Player (version 9) is available for all 3 major operating systems (Windows, MacOS X and Linux-x86) providing a re-written virtual machine with new features like JIT compiling, support for binary sockets, advanced display programming framework and video support.
» The idea: VirtuaPark
In order to illustrate the architecture behind a similar application we will analyze the design of "VirtuaPark", a fictional community-driven MMO with customizable avatars interacting in 3D like environments (isometric), online and offline messaging system, shopping system and various multi-player games where users can challenge each others.
One of the crucial requirements for VirtuaPark is extensibility: we would like the project to be open to new features (new modules, games etc...) without major hassles, so that new components can be easily plugged to the existing code base.
The application will be 100% browser based, using the latest Flash Player.
Behind the scenes SmartFoxServer PRO and a MySQL database will do the hard work to keep everyone online and synchronized while interacting, chatting, playing etc...
The following diagram shows how the application modules are organized:
We have defined four main "modules" that will allow the user to access all the available features:
-
Main Menu. It presents a list of all the "tools" and "games" available.
By clicking one of these items the tool/game will be launched.
-
Control Panel. A tabbed pane with the following controls:
- Chat History (the transcript of all chat messages)
- Room List (the list of rooms/locations available)
- User List (the list of users available in the current location)
- My buddies (a list of buddies with their online/offline status)
-
Chat Message Bar. A simple control bar where users can type their chat messages
with tools for customizing the text and adding smilies
-
Avatar Chat. The most important area of the application,
where the avatar chat and multiplayer games will be running. The Avatar Chat will feature a scrollable isometric view in the style of Habbo and the like. When a game is launched the chat will be hidden and the game will take its place.
Each module in the application will be responsible for handling one specific task
(avatar customization, game, user list etc...) and it will be able to send and receive events from the others.
To give you an idea of the communication between modules check this example:
- the user clicks on Customize Avatar in the Main Menu
- the tool is loaded and launched in a new window inside the application
- the user modifies the look of his character and commits the change
- the tool sends a request to SmartFoxServer which in turn stores the new profile in the database and fires an event
back to the Avatar Chat module so that everyone gets updated.
Side Note:
Creating a tile-based isometric engine for an Avatar Chat is not trivial and it may take several months of hard work to build it and integrate it with the server. Additionally a level-editor is usually needed to build the chat locations, adding more hours of development.
The good news is that there are third party avatar engines like TheoWorld's TheoAvatar which is already integrated with the SmartFoxServer APIs. You can check a live demo here.
» Shell-based application
It is time to dive into the architecture of the MMO and we'll do it (oddly enough) by starting on the client side. In order to make all the application modules work together in a seamless way we need a special module that acts as a glue for all the others. This special module will be called the "Shell" and it is represented in the following diagram:
The Shell Module will have a lot of responsibilities:
-
Configuration. It will load the application main configuration file and it will make it available to all other modules.
-
Module loading/unloading. It will load and activate the other modules when the application is started, launch additional tools and unload those that are not needed anymore.
-
Connection sharing. The shell will be responsible for connecting to the socket server and share the connection among all the other components. Each module will be then responsible for registering to the events that they need to listen to.
-
UI management. It will manage dialogue boxes and error messages that come from the server and that should be displayed on top of all the other modules. It may also activate/deactivate modules in case an important message or an error should stop the interaction with the application.
-
Multi-language support. The shell will dynamically load external language data from a database or XML file and populate all the GUI components (labels, buttons, lists, forms etc...) accordingly. This way we can also handle localized help pages, error messages and even currencies.
In order to develop the Shell and its modules you will have a good range of choices since SmartFoxServer supports both Actionscript 2.0 (from Flash 6 to 8) and Actionscript 3.0 (Flash Player 9, using Flash CS3 and Flex Builder 2).
If you are planning to use AS 2.0 we would also recommend to check the SmartFoxBits, a complete set of UI components that can dramatically speed up the process of creating Lobbies, Chat applications etc...
» Server side architecture
Now that we have defined the structure of the client side part, you are probably wondering what is going to happen
on the server side: how will you handle the many possible requests,
how are you going to manage assets, persistence etc...
Before we jump into the technical details of the server side extension, it would be better if we examine the components that we will use on the backend.
Starting from low to top we have the client who connects through his browser to our VirtuaPark website.
The website together with all the swf files and other assets can be comfortably handled by the Jetty web server embedded in SmartFoxServer.
With the embedded server you can easily serve static contents and create dynamic web pages
using regular Java/JSP servlets or with the help of an integrated Python interpreter,
to speed up the process.
The Server Side Extension will handle all the requests coming from each module, avatar chat and games plus it will
access the database server to store and retrieve the user data, application status and whatnot.
In this particular scenario an ORM tool is highly recommended to help simplifying the access to the persistent data and make your server coding easier. Hibernate and IBatis are probably the most popular in the world of Java, but there are many others that you can check.
Another important aspect of ORM tools is that they provide a layer of abstraction between your application logic
and the actual database server, making your Extension code independent from the DB technology. If for any reason
you need to migrate from a database technology to another one it will be very simple to perform the switch and your
code won't be affected.
SmartFoxServer allows developers to easily integrate such tools with both Java extensions and script extensions (Actionscript and Python). If you plan to use the latter you'll be able to directly access all the ORM objects from your scripts without the need of writing additional Java code.
To give you an idea of how powerful is the mix of scripting languages (Python in this case) with an ORM like Hibernate here's a snippet of code that shows a simple login method that retrieves a user from the database and access a few of its properties:
-
-
class ChatModule(object):
-
def handleReq(self, cmd, params, who, roomId):
-
print "Handling Chat Request: ", cmd
-
-
class ShopModule(object):
-
def handleReq(self, cmd, params, who, roomId):
-
print "Handling Shop Request: ", cmd
-
-
class GameModule(object):
-
def handleReq(self, cmd, params, who, roomId):
-
print "Handling Game Request: ", cmd
-
-
-
handlersTable = {}
-
-
def init():
-
global handlersTable
-
-
print "Initializing"
-
-
# Instantiate modules
-
chatModule = ChatModule()
-
shopModule = ShopModule()
-
gameModule = GameModule()
-
-
# Add modules to the handlers table
-
handlersTable["chat"] = chatModule
-
handlersTable["shop"] = shopModule
-
handlersTable["game"] = gameModule
-
-
-
def destroy():
-
print "Stopping"
-
-
#
-
# Handle client requests
-
#
-
def handleRequest(cmd, params, who, roomId, protocol):
-
separatorPosition = cmd.find("##")
-
-
# Look for the separator
-
if separatorPosition > -1:
-
moduleName = cmd[0:separatorPosition]
-
reqName = cmd[separatorPosition + 2:]
-
-
# Get the handler module and pass the request parameters
-
if handlersTable.has_key(moduleName):
-
handler = handlersTable[moduleName]
-
handler.handleRequest(reqName, params, who, roomId)
-
-
else:
-
print "Unknown Module: ", moduleName
-
-
else:
-
print "Invalid request: ", cmd
-
-
#
-
# Handle server events
-
#
-
def handleInternalEvent(evt):
-
pass
-
Hibernate allows you to query the objects in the database using a SQL-like language called HQL. As you can see instead of writing long SQL statements we just need to specify the criteria for our search. Once the object is retrieved we can access all its fields (including those that are mapped on linked tables) with simple getters and setters.
» The Server Side Extension
The Server Extension is the core of the application and probably the most complex part to build.
As we have outlined in the previous diagrams we will run VirtuaPark in its own Zone
and use a single extension plugged at Zone-level.
( If you're not familiar with the basics of the SmartFoxServer framework we would suggest you to pause the reading
and review the articles found at Chapter 4 of our documentation ).
The first problem that we need to face when building the extension is how to organize the many requests that each client side module can send to the server.
An efficient solution to our problem is organizing the Server Extension in modules as well, just like we did on the client, and make them handle the specific requests coming from each client side module. (Chat module, Shop module, Game module etc...)
The following is a visual representation of the solution:
As you can see from the diagram, the main Extension file will act as a dispatcher
for the other modules, which are essentially custom classes designed to handle the requests of the client modules.
In order for this mechanism to work we need to establish a naming convention between client and server
for the request names. As you may remember each Extension message sent and received by the server
has a cmd property which identifies the command (request) name.
For our VirtuaPark we will be using the
following convention:
moduleName##requestName
where:
- moduleName: is the name of the target module
- ##: the separator
- requestName: the name of the request for the target module
Here are a few examples of requests based on the above convention:
chat##moveAvatar
profile##storeData
shop##buyItem
The following Python code example should clarify the whole process:
-
-
class ChatModule(object):
-
def handleReq(self, cmd, params, who, roomId):
-
print "Handling Chat Request: ", cmd
-
-
class ShopModule(object):
-
def handleReq(self, cmd, params, who, roomId):
-
print "Handling Shop Request: ", cmd
-
-
class GameModule(object):
-
def handleReq(self, cmd, params, who, roomId):
-
print "Handling Game Request: ", cmd
-
-
-
handlersTable = {}
-
-
def init():
-
global handlersTable
-
-
print "Initializing"
-
-
# Instantiate modules
-
chatModule = ChatModule()
-
shopModule = ShopModule()
-
gameModule = GameModule()
-
-
# Add modules to the handlers table
-
handlersTable["chat"] = chatModule
-
handlersTable["shop"] = shopModule
-
handlersTable["game"] = gameModule
-
-
-
def destroy():
-
print "Stopping"
-
-
#
-
# Handle client requests
-
#
-
def handleRequest(cmd, params, who, roomId, protocol):
-
separatorPosition = cmd.find("##")
-
-
# Look for the separator
-
if separatorPosition > -1:
-
moduleName = cmd[0:separatorPosition]
-
reqName = cmd[separatorPosition + 2:]
-
-
# Get the handler module and pass the request parameters
-
if handlersTable.has_key(moduleName):
-
handler = handlersTable[moduleName]
-
handler.handleRequest(reqName, params, who, roomId)
-
-
else:
-
print "Unknown Module: ", moduleName
-
-
else:
-
print "Invalid request: ", cmd
-
-
#
-
# Handle server events
-
#
-
def handleInternalEvent(evt):
-
pass
-
We have defined 3 simple classes (ChatModule, ShopModule and GameModule) that represent the chat module,
shop module and a game module respectively. Each class has a handleReq
method that will be invoked by the main Extension file.
In the init() method of our Extension we instantiate all three classes and we add them
to a global dictionary called
handlersTable. ( A dictionary in Python is the equivalent of an associative array / HashMap )
When a client request is received by the handleRequest() method we will do the following:
-
Look for the ## separator
-
Split the cmd string into the module name and request name
(remember the convention we established?)
-
Check if the wanted module exists in the handlersTable
-
Finally dispatch the request to the module
If you prefer to use Java for your Extensions you should be able to re-create the same code
structure by defining
an interface that all module classes will implement.
-
public interface ModuleRequestHandler
-
{
-
public void init();
-
public void destroy();
-
public void handleRequest (String cmd, JSONObject params, User user, int roomId );
-
}
» Conclusions (Part 1)
Now that we have defined the general architecture of both client and server side you can see how the main goal of our
VirtuaPark design (extensibility) is successfully achieved.
By using a modularized architecture on both sides of the application we will be able to easily add new features and games
to the virtual world.
The 2nd part of this article will analyze the remaining parts of the puzzle and provide further advices for the development.
|