• Examples (Flash platform)
• Examples (iOS)
• Examples (Android)
• Examples (C++)
Server API Documentation

 

» Simple MMO World

» Overview

The Simple MMO World example shows how the MMO Room type works in SmartFoxServer 2X.

In regular Rooms, whenever a user generates an event (for example sending a public message or setting his own User Variables), this is broadcasted to all users who joined that Room.
In case a Room represents a large virtual space (like in MMO games for example), this is quite a waste of resources because usually the user just "sees" a small portion of the whole space: in 2D or 2.5D games this is usually limited by the viewport size and in 3D games by the camera viewing area and the distance from the user's character. Why delivering an event to a user which is not affected at all by that event?

The MMORoom extends the functionality of regular Rooms by adding an Area of Interest (AoI in short) to determine the distance from a user below which events generated by other users are received. In turn, the MMORoom requires that each user declares its own position in the virtual 3D space representing the MMORoom itself. Detailed informations are available in the documents linked at the bottom of this page.

In this example all users are represented by an avatar (a crash test dummy) that they can move around in a simple environment representing the MMORoom. In order to better show the inner working of MMORooms, instead of the usual fixed viewport and scrolling background, we have a fixed background a a viewport following the user's avatar movements. The AoI is represented by a dashed red line surrounding the avatar of the current user: notice that only the avatars inside this area are visible on the stage at any one time, no matter if the user is moving around or not or if the others are. The reason is that each user is only aware of (in other words receives events related to) the users inside the AoI.

Also, the AoI is larger than the viewport: this is done on purpose because the PROXIMITY_LIST_UPDATE event which updates the client about users entering or leaving his AoI is not triggered in realtime (the update rate can be configured by means of the proximityListUpdateMillis property of the MMORoom). In 2D games in particular, the delay in receiving the update could make an avatar "pop" in or out of the client vieport: the additional space between the AoI and the viewport, if carefully configured based on the avatar speed and background scrolling behavior, can prevent this from happening.
Obscuring the area outside the viewport, using the available slider, provides a good representation of the final result.

As it regards the avatars movement on the stage, this is based on the User Variables update approach described in the Avatar Chat example. In addition to it, the current example also features non-walkable areas (a river) painted on an hidden layer against which the movement target coordinates are validated using a simple hit-test.

>> DOWNLOAD the source files <<

» Installation

» Running the example

In order to run the application follow these steps:

  1. copy the SimpleMMOWorld.zone.xml file (containing the Zone configuration) from the /deploy/zones folder to your SFS2X installation folder, under /SFS2X/zones;
  2. start SmartFoxServer 2X;
  3. make sure the client will connect to the right IP address by editing the <ip> entry in the xml file available under the /deploy/client folder;
  4. open the /deploy/client/SimpleMMOWorld.html file in a browser.

» Source code setup

The example assets are contained in the /source folder and they don't require a specific setup: simply open the .fla file in Adobe Flash.

» Code highlights

The timeline of the example's main FLA is divided into sort of "scenes" identified by these labels: load, connect, join and map. All frames, except load, call a specific initialization method (initConnectView, initJoinView and initMapView respectively) on the document class.

In the initConnectView method we simply add a listener to the Login button. When clicked, the connection to the server is attempted using the SmartFox.loadConfig() method: this causes the external xml configuration file to be loaded and the connection to be performed.

In the onConnection handler, if the connection is successful the login is attempted immediately, otherwise an error is displayed. If the connection is later lost (see the onConnectionLost listener), the initial connect frame is displayed with an error message.

If the login is successful, the playhead moves to the join frame and the initJoinView method is called: the interface now shows a panel to select the map to join (actually in this example there's just one available), corresponding to an MMORoom. The coordinates where the avatar should appear on the map can be selected too (-1 for random coordinates): this is useful for testing purposes, but usually predefined "entry points" should exist (for example saved in the Room Variables).

Clicking on the Join button makes the user enter the MMORoom. The onRoomJoin handler shows the map frame of the Flash timeline and the initMapView method is called. In a real case scenario this is probably the place where the assets to render the map should be loaded. In this example this is not required as we have a fixed scene and all assets are already on the stage.

public function initMapView():void
{
    stop();
    currentView = this.currentLabel;
    
    // Add listeners
    ...
    
    // Create an array that will contain all map objects, including avatars, used for sprites sorting purposes
    // The trees already existing on the map are added to this array now (while making them transparent to clicks)
    mapObjects = new Array();
    
    for (var i:int = 0; i < mapArea.avContainer.numChildren; i++)
    {
        var obj:Sprite = mapArea.avContainer.getChildAt(i);
        obj.mouseChildren = false;
        obj.mouseEnabled = false;
        mapObjects.push(obj);
    }
    
    // Set the random position of the current user on the map (if coordinates are not selected by the user)
    if (accessX < 0)
        accessX = Math.round(Math.random() * mapArea.width);
    if (accessY < 0)
        accessY = Math.round(Math.random() * mapArea.height);
    
    // Avoid making the avatar appear in the middle of the river (non-walkable area) by excluding the map's bottom left corner
    if (accessY > 430)
        accessX = Math.max(accessX, 830);
    
    // Set starting direction to a default value
    var dir:String = AVATAR_DIRECTIONS[2];
    
    // Create current user's avatar
    createAvatar(sfs.mySelf, accessX, accessY, dir);
    
    // The position is saved in the User Variables, so that changing it later will trigger the avatar animation
    setAvatarVariables(accessX, accessY, dir);
    
    // Declare current user's position in the MMORoom, to get the proximity list of users
    updateServerPosition();
}

Other than adding a listener to the mouse click event (which will be used to make the avatar move around) and a few other listeners, in this method we create a container array for all the map objects which in this example include trees, bushes and avatars: this array is used later to sort the sprites on the stage to give them the right depth based on their y coordinate (so that they overlap correctly). In particular, after creating it, with a for cycle we add to this array all the trees and bushes, which are on the stage already.

We then create the user avatar, save its coordinates in the User Variables and declare user's position in the MMORoom. This is a core step when using MMORooms as it (A) triggers the PROXIMITY_LIST_UPDATE event described later on, (B) makes the user "visible" by the others and (C) enables the client to receive the events generated by other users inside his Area of Interest. This is just a matter of sending a SetUserPositionRequest with a vector containing the user coordinates. If the position is not set within a certain amount of time (configurable by means of the userMaxLimboSeconds property of the MMORoom), the user is kicked out of the Room.

private function updateServerPosition():void
{
    var myAvatar:Avatar = getAvatar(sfs.mySelf.id);
    
    // Save the coordinates corresponding to the saved position, to be checked during the next tween update
    myAvatar.lastUpdateX = Math.round(myAvatar.x);
    myAvatar.lastUpdateY = Math.round(myAvatar.y);
    
    var pos:Vec3D = new Vec3D(myAvatar.lastUpdateX, myAvatar.lastUpdateY, 0);
    sfs.send( new SetUserPositionRequest(pos) );
}

As soon as the user position is set in the MMORoom, his client will start receiving the PROXIMITY_LIST_UPDATE event, handled by the onProximityListUpdate listener. This event lets the user know which users entered or left his AoI making it possible to add / remove the corresponding avatars accordingly.

private function onProximityListUpdate(evt:SFSEvent):void
{
    // Loop the removedUsers list in the event params to remove the avatars no more visible
    var removed:Array = evt.params.removedUsers;
    for each (var ru:User in removed)
    {
        var ra:Avatar = getAvatar(ru.id);
        
        if (ra != null)
            removeAvatar(ra);
    }
    
    // Loop the addedUsers list in the event params to display the avatars now visible
    var added:Array = evt.params.addedUsers;
    for each (var au:User in added)
    {x, vec.py, dir);
        
        // Make the avatar move towards the coordinates set in the User Variables if they are different from the entry point
        if (au.containsVariable(USERVAR_X) && au.containsVariable(USERVAR_Y))
        {
            var px:int = au.getVariable(USERVAR_X).getIntValue();
            var py:int = au.getVariable(USERVAR_Y).getIntValue();
            
            if (px != vec.px || py != vec.py)
                moveAvatar(au);
        }
    }
    
    // Arrange map objects sorting
    arrangeMapObjects();
}

In order to know in which position on the stage the avatars have to be created, we use a special property assigned to each User object contained in the list of "added" users: User.aoiEntryPoint. This is a Vec3D object containing the position the user had in the MMRoom when he entered the AoI of the current user.
We can now create the avatars in the right place and check if they are moving through their User Variables. In fact the User Variables contain the movement target coordinates, saved each time a user clicks on the stage. In other words if the AoI entry point and the target coordinates match, the user avatar is standing still; otherwise it is moving on the stage towards the target coordinates and we need to start the motion tween accordingly.

NOTE
Let's highlight a few words from the text above: "the position the user had in the MMRoom when he entered the AoI of the current user". This statement is very important and needs to be emphasized for better understanding.
What is the difference between the current coordinates and those at the time the user entered our Area of Interest, saved in the User.aoiEntryPoint property?
Take a look at this simplified flow:

Client A sets his position at time t0; the server processes the request and if A entered the AoI of client B, it sends an event to B which receives it at time t3. Other than the usual lag between the clients and the server, we have to take into account that the PROXIMITY_LIST_UPDATE is not fired immediately by the server, but a variable time passes, depending on when the timer controlled by the proximityListUpdateMillis property of the MMORoom will fire.
So at time t3 client B receives, through the aoiEntryPoint property, the coordinates that A had at time t0: they aren't the current coordinates of user A, because in the meantime he might have changed his position.

In some circumstances, like the example we are now examining, this is not an issue. In fact we don't need a strict synchronization between the clients: what matters here is just the target position that the avatar must reach, which is saved in the User Variables.
Other scenarios might need a different approach which could lead to entirely ignore the aoiEntryPoint property, compensate the lag, fine tune the proximityListUpdateMillis time, execute a server side "master" simulation and what more.

It is important to note that during the current user's avatar movement, while the target coordinates are set only once upon user click on the map, his position in the MMORoom is updated almost continuously in the tween; this allows users entering and leaving the AoI to be rendered accordingly. We said "almost" because this is optimized taking into account the size difference between the viewport and the actual AoI, as coded in the onAvatarTweenUpdate method.

Lastly, when a user already inside the current user's AoI executes an action, like making his avatar move or send a public message, standard events are received just like in regular Rooms. These are handled in the onUserVarsUpdate and onPublicMessage listeners respectively.

NOTE
You should read the comments to methods and properties in the example source code for additional informations and possible code optimizations.

» More resources

You can learn more about the described feature by consulting the following resources: