• Examples (iOS)
• Examples (Java/Android)
• Examples (C++)
Server API Documentation


» MMO Demo


» Overview

The MMO Demo shows how to work with the new objects from the MMO API introduced in SmartFoxServer 2.8.0. The example extends the Object Movement tutorial from this very section, using an MMORoom instead of a regular Room and showing how to work with an Area Of Interest (AoI).

This example also features a server side Extension which accomplishes two tasks: (1) provides an optimization in the user positioning logic inside the MMORoom and (2) simulates any number of active users via the creation of automated NPCs moving around the map randomly.

» Prerequisites

In order to follow this tutorial you will need to be familiar with the Object Movement example and also have read the MMO API Overview article. Make sure to check these resources, if necessary:

This simple demo will show how we can handle hundreds or thousands of users inside a single Room without worrying about overloading the clients with excessive updates. We will also familiarize with how an MMORoom manages the local user list, and learn how to create and configure MMORooms.

>> DOWNLOAD the source files <<

» Setup & run

In order to setup and run the example, follow these steps:

  1. make sure your SmartFoxServer 2X installation contains the BasicExamples Zone definition;
  2. copy the /server/Extension_Java/deploy/MMORoomDemo folder to the server's /SFS2X/extensions folder;
  3. start SmartFoxServer 2X (v2.13 or later is highly recommended);
  4. start Unity (v2017.1 or later required);
  5. in the Projects panel click on the Open icon and browse to the /client/MMORoomDemo folder, then click the Open button;
  6. wait for the project setup completion (Unity needs to regenerate some libraries);
  7. go to the Project panel, click on the Assets/Scenes folder and double click on one of the scene files to open it (i.e. Connection scene);
  8. if SmartFoxServer and Unity are not running on the same machine, change the IP address of the server in the inspector of the UI game object in the Connection scene;
  9. click on the Play button to run the example in the Unity Editor, or go to the Build settings panel and build it for your target platform of choice.

All relevant client assets are contained in the Assets folder; in particular the C# code is in the Scripts subfolder and the SmartFoxServer 2X client API DLLs are in the Plugins subfolder. Read the introduction to understand why multiple DLLs are used.

» Extension

The game features a server-side Extension, as mentioned in the overview. In order to access its code, create and setup a new project in your Java IDE of choice as described in the Writing the first Java Extension document of the Java Extensions Development section. Copy the content of the /server/Extension_Java/source/src folder to your Java project's source folder.

» How it works

The example application allows the user to enter the MMO Room and interact with other players / NPCs, just like in the Object Movement demo. The main difference is that the player will exclusively receive events from users and entities within his Area of Interest (AoI), thus limiting the amount of data exchanged in the Room.

This approach allows to build extremely large maps and game areas that can be mapped to a single server Room, of type MMORoom, and host thousands of players without causing slow downs or excessive network usage. The server will take care of all the details, calculate which client should receive which events according to its position in 3D space, and keep everyone synchronized.

» Code highlights

» Starting up the MMORoom

We will skip the usual connection and login phase because there is nothing new to say: they work exactly as in the previous examples. Instead we will take a look at how the MMORoom is created from client side. Open the ConnectionUI script, attached to UI game object in the Connection scene.

	private void OnLogin(BaseEvent evt) {
	    string roomName = "UnityMMODemo";
	    // We either create the Game Room or join it if it exists already
	    if (sfs.RoomManager.ContainsRoom(roomName)) {
	        sfs.Send(new JoinRoomRequest(roomName));
	    } else {
	        MMORoomSettings settings = new MMORoomSettings(roomName);
	        settings.DefaultAOI = new Vec3D(25f, 1f, 25f);
	        settings.MapLimits = new MapLimits(new Vec3D(-100f, 1f, -100f), new Vec3D(100f, 1f, 100f));
	        settings.MaxUsers = 100;
	        settings.Extension = new RoomExtension("MMORoomDemo", "sfs2x.extension.mmo.MMORoomDemoExtension");
	        sfs.Send(new CreateRoomRequest(settings, true));

As soon as we are logged in the Zone we check if the game Room already exists, and if it doesn't, we create a new one, setting the relevant parameters. For this small scale example we set the MMORoomSettings.MaxUsers property to 100, but you are free to experiment with thousands of users provided that you also enlarge the physical map so that more players have space to walk around.

The MMORoomSettings.DefaultAOI parameter is the "crucial" setting that will determine how far around the player events will be detected. Considering that the demo map extends 200 units on the X and Z axis (-100 to 100), we chose to use a size of 25 units for the X and Z axis as the AoI, and 1 unit for the Y axis, since there's no up/down freedom of motion in this example.

Keep in mind that when we specify the size of the AoI for one axis we mean in any one direction: in other words 25 units on the X axis mean 25 on the left side and 25 on the right side, for a total of 50 units around the player.

We also define the lowest and highest coordinate available in our map to make sure that we don't exceed the physical size of the 3D world. Finally we attach the Java Extension to the MMORoom which will create NPCs to demonstrate the Room's features: in fact you will see the NPCs move around and "disappear" when they leave the player's AoI.

» Joining the MMORoom

The CreateRoomRequest described before takes a flag as the 2nd parameter to indicate whether or not we want to auto-join the Room and a third parameter to specify which previous Room we might want to leave upon joining.

In our example we do want the auto-join feature, but this is our first and only Room so we pass a null to indicate that there's no Room to leave. The server will join the player and fire a ROOM JOIN event when we're inside. Let's see what the relative event handler does:

	private void OnRoomJoin(BaseEvent evt) {
	    // Remove SFS2X listeners and re-enable interface before moving to the main game scene
	    // Go to main game scene

Everything is pretty easy. We essentially just load a new Unity scene called "Game" and proceed by removing all event listeners from the current one. This is because we want the new scene to take control of the SmartFox API and respond to the next game events.

It's now time to open to the GameManager script attached to the Game object in the Game scene and take a look at the Start() method.

	void Start() {	
	    if (!SmartFoxConnection.IsInitialized) {
	    sfs = SmartFoxConnection.Connection;
	    // Register callback delegates
	    sfs.AddEventListener(SFSEvent.CONNECTION_LOST, OnConnectionLost);
	    sfs.AddEventListener(SFSEvent.USER_VARIABLES_UPDATE, OnUserVariableUpdate);
	    sfs.AddEventListener(SFSEvent.PROXIMITY_LIST_UPDATE, OnProximityListUpdate);
	    // Get random avatar and color and spawn player
	    int numModel = UnityEngine.Random.Range(0, playerModels.Length);
	    int numMaterial = UnityEngine.Random.Range(0, playerMaterials.Length);
	    SpawnLocalPlayer(numModel, numMaterial);
	    // Update settings panel with the selected model and material
	    GameUI ui = GameObject.Find("UI").GetComponent("GameUI") as GameUI;

Here we take a reference to the current SmartFox API object and proceed with registering three event listeners to detect a disconnection, the User Variables update and a new type of event called PROXIMITY LIST UPDATE, which we'll describe in a moment.

Before our avatar can appear in the Room we have to set a number of User Variables which you have already seen in the Object Movement tutorial:

All this is done in the SpawnLocalPlayer() method:

	private void SpawnLocalPlayer(int numModel, int numMaterial) {
	    Vector3 pos;
	    Quaternion rot;
	    // See if there already exists a model - if so, take its pos+rot before destroying it
	    if (localPlayer != null) {
	        pos = localPlayer.transform.position;
	        rot = localPlayer.transform.rotation;
	        Camera.main.transform.parent = null;
	    } else {
	        pos = new Vector3(0, 1, 0);
	        rot = Quaternion.identity;
	    // Lets spawn our local player model
	    localPlayer = GameObject.Instantiate(playerModels[numModel]) as GameObject;
	    localPlayer.transform.position = pos;
	    localPlayer.transform.rotation = rot;
	    // Assign starting material
	    localPlayer.GetComponentInChildren<Renderer>().material = playerMaterials[numMaterial];
	    // Since this is the local player, lets add a controller and fix the camera
	    localPlayerController = localPlayer.GetComponent<PlayerController>();
	    localPlayer.GetComponentInChildren<TextMesh>().text = sfs.MySelf.Name;
	    Camera.main.transform.parent = localPlayer.transform;
	    // Lets set the model, material and position and tell the others about it
	    // NOTE: we have commented the UserVariable relative to the Y Axis because in this example the Y position is fixed (Y = 1.0)
	    // In case your game allows moving on all axis we should transmit all positions
	    List<UserVariable> userVariables = new List<UserVariable>();
	    userVariables.Add(new SFSUserVariable("x", (double)localPlayer.transform.position.x));
	    //userVariables.Add(new SFSUserVariable("y", (double)localPlayer.transform.position.y));
	    userVariables.Add(new SFSUserVariable("z", (double)localPlayer.transform.position.z));
	    userVariables.Add(new SFSUserVariable("rot", (double)localPlayer.transform.rotation.eulerAngles.y));
	    userVariables.Add(new SFSUserVariable("model", numModel));
	    userVariables.Add(new SFSUserVariable("mat", numMaterial));
	    // Send request
	    sfs.Send(new SetUserVariablesRequest(userVariables));

The code is pretty straightforward: we instantiate the model for the player, set the material and finally set the text inside the model to the user name of our player.

Next we set all User Variables we mentioned earlier to propagate our avatar settings in the Room. This is where things differ from a regular SmartFoxServer Room until version 2.8. Instead of broadcasting the update to everyone, the system will first check the user position and then, based on the MMORoom's default AoI, will propagate the update only to the players falling within that area.

The trick is accomplished via the server side Extension, which is listening to the User Variables update event and uses the the X and Z coordinates to tell the MMORoom where the player is located.
This could be accomplished on the client too (via the SetUserPositionRequest request), but using an Extension allows to optimize the network usage (because we just send one request — the SetUserVariablesRequest — instead of two — SetUserVariablesRequest and SetUserPositionRequest), which of course is fundamental in a realtime context.

Here's the relevant server code:

	private class UserVariablesHandler extends BaseServerEventHandler
		public void handleServerEvent(ISFSEvent event) throws SFSException
			List<UserVariable> variables = (List<UserVariable>) event.getParameter(SFSEventParam.VARIABLES);
			User user = (User) event.getParameter(SFSEventParam.USER);
			// Make a map of the variables list
			Map<String, UserVariable> varMap = new HashMap<String, UserVariable>();
			for (UserVariable var : variables)
				varMap.put(var.getName(), var);
			if (varMap.containsKey("x") && varMap.containsKey("z"))
				Vec3D pos = new Vec3D
				mmoAPi.setUserPosition(user, pos, getParentRoom());

It should be fairly easy to understand what this snippet does: we put all the variables sent by the client into a map, for convenience, and check if a positional update was sent. If the X or Z position of the player was changed we invoke the MMOApi.setUserPosition() method which is at the core of the MMORoom functioning.

In other words this call tells the MMORoom where the player is located on the map in terms of 2D or 3D coordinates. This in turn allows the MMORoom to keep track of everybody and decide which users should receive which events.

It is important to note that the way in which you keep track of the user position and how the MMORoom keeps track of it are not the same and work independently of one another. From the developer's point of view, we need to keep track of the players' position and we are free to use any system that best suits our game design. Whether it is User Variables or custom values transmitted via Extension it doesn't matter.

On the other hand the MMORoom also needs to be updated about each player position via the MMOApi.setUserPosition(). This is because only by knowing each player's position the server can synchronize clients via their AoI.

The reason why these two tracking systems (the developer's and the server's) are separate is because there is a large variety of games, all requiring different strategies. With this approach you are free to use any game logic that best works for your game and, separately, update the MMORoom state via it's own API method.

It is not necessary to call the setUserPosition() method on every move of the player. A player moving a few pixels on the left or right won't do any difference to the MMORoom. If you are sending tens of positional updates per second for each player, you may want to reduce the calls to setUserPosition() to a few per second. You can learn more about this topic in the additional resources found at the end of this article and in the next SpaceWar tutorial.

» The proximity list

The fundamental difference between a regular Room and an MMORoom lies in how the local user list is handled. In the latter, the client does not have a full view of all users joined in the Room; instead it receives a "Proximity User List" which represents a list of all players falling within the Room's AoI range.

The client side events USER ENTER and USER EXIT are no longer available inside an MMORoom; they are substituted by a single event called PROXIMITY LIST UPDATE, which provides a number of parameters:

Whenever a player is entering or leaving the user's AoI an event of this type will be received providing detailed information about what changed.

For the sake of this tutorial, we are only interested in the lists of added and removed users. MMOItems are an interesting advanced feature that is not discussed in this tutorial. You can learn more by consulting the links at the bottom.

Let's now take a look at the OnProximityListUpdate() event handler:

	public void OnProximityListUpdate(BaseEvent evt)
	    var addedUsers = (List<User>) evt.Params["addedUsers"];
	    var removedUsers = (List<User>) evt.Params["removedUsers"];
	    // Handle all new Users
	    foreach (User user in addedUsers)
	        SpawnRemotePlayer (
	            (SFSUser) user, 
	            new Vector3(user.AOIEntryPoint.FloatX, user.AOIEntryPoint.FloatY, user.AOIEntryPoint.FloatZ),
	            Quaternion.Euler(0, (float) user.GetVariable("rot").GetDoubleValue(), 0)
	    // Handle removed users
	    foreach (User user in removedUsers)
	        RemoveRemotePlayer((SFSUser) user);

When we receive this notification we execute two simple tasks:

When adding new users we need to know at which coordinates we should position the avatar model. This information is provided via a new property added to the User class, called AoiEntryPoint, of type Vec3D.

» Player movement

Similarly to the Object Movement tutorial, we are going to update our player movement via the FixedUpdate() method:

	void FixedUpdate() {
	    if (sfs != null) {
	        // If we spawned a local player, send position if movement is dirty
	         * NOTE: We have commented the UserVariable relative to the Y Axis because in this example the Y position is fixed (Y = 1.0).
	         * In case your game allows moving on all axis you should transmit all positions.
	         * On the server side the UserVariable event is captured and the coordinates are also passed to the MMOApi.SetUserPosition(...) method to update our position in the Room's map.
	         * This in turn will keep us in synch with all the other players within our Area of Interest (AoI).
	        if (localPlayer != null && localPlayerController != null && localPlayerController.MovementDirty) {
	            List<UserVariable> userVariables = new List<UserVariable>();
	            userVariables.Add(new SFSUserVariable("x", (double)localPlayer.transform.position.x));
	            //userVariables.Add(new SFSUserVariable("y", (double)localPlayer.transform.position.y));
	            userVariables.Add(new SFSUserVariable("z", (double)localPlayer.transform.position.z));
	            userVariables.Add(new SFSUserVariable("rot", (double)localPlayer.transform.rotation.eulerAngles.y));
	            sfs.Send(new SetUserVariablesRequest(userVariables));
	            localPlayerController.MovementDirty = false;

On every iteration of this method we are going to check the MovementDirty flag in our localPlayerController object and send a User Variable update with the position and rotation of our Avatar.

It is important to point out that this approach is good for local testing but it's not optimized for online playing. This way we will be sending too many updates per second, quickly saturating the internet connection. It is beyond the scope of this example to go in the details of how to optimize the message rate. If you want to learn more please check the next tutorials.

» Other players updates

Last but not least, we want to take a look at how the other players in the MMORoom are animated in the scene. The main event handler driving their motion is the OnUserVariablesUpdate() method:

	public void OnUserVariableUpdate(BaseEvent evt) {
	    List<string> changedVars = (List<string>)evt.Params["changedVars"];
	    ArrayList changedVars = (ArrayList)evt.Params["changedVars"];
	    SFSUser user = (SFSUser)evt.Params["user"];
	    if (user == sfs.MySelf) return;
	    // Check if the remote user changed his position or rotation
	    if (changedVars.Contains("x") || changedVars.Contains("y") || changedVars.Contains("z") || changedVars.Contains("rot")) {
	        // Move the character to a new position...
	            new Vector3((float)user.GetVariable("x").GetDoubleValue(), 1, (float)user.GetVariable("z").GetDoubleValue()),
	            Quaternion.Euler(0, (float)user.GetVariable("rot").GetDoubleValue(), 0),
	    // Remote client selected new model?
	    if (changedVars.Contains("model")) {
	        SpawnRemotePlayer(user, user.GetVariable("model").GetIntValue(), user.GetVariable("mat").GetIntValue(), remotePlayers[user].transform.position, remotePlayers[user].transform.rotation);
	    // Remote client selected new material?
	    if (changedVars.Contains("mat")) {
	        remotePlayers[user].GetComponentInChildren<Renderer>().material = playerMaterials[ user.GetVariable("mat").GetIntValue() ];

We begin by checking who is the user sending the update. In case it's our own local player we don't need to do anything, otherwise we proceed by checking if any of the three coordinate values changed and update the avatar on screen.

Finally we check if the other avatar properties where modified and proceed to update them as well: the name of the avatar, its material and the model.

» Tweaking the NPCs

A final note on the server side Extension used for this tutorial: you can experiment with the settings of the Extension to generate more or less NPCs in the MMORoom. In order to do so you can change the number of auto-generated NPCs via the MAX_NPC constant defined in the code. If you prefer to remove the NPCs from the demo you can set the value to zero.

» More resources

You can learn more about the SmartFoxServer concepts discussed in this example by consulting the following resources: