• Examples (Unity)
• Examples (iOS)
• Examples (Android)
• Examples (C++)
Server API Documentation

 

» Object Movement

» Overview

The Object Movement example shows how to use the User Variables as a mean to make simple 3D objects move without server-side Extension usage.

User Variables are one of the three server variable objects available in SmartFoxServer to store data to be shared among the clients of an application or game (the other two are Room Variables and Buddy Variables, which are out of the scope of this tutorial). As the word says, a server variable is stored on the server and broadcasted to the other clients when a change occurs. In particular, a User Variable is broadcasted to all the other users in the same Room where the owner of that variable is located. This means that a change in a User Variable will be reflected on all other clients within the same Room so that, for example, they can update the scene accordingly.

In this example all the users are represented by a basic shape that they move around in a simple environment (which represents the server Room): so we need to collect the "avatar" position and rotation updates from all the clients to keep the scene in sync. This can be easily done with User Variables: when we create or update the variables representing those informations, all the other clients in the Room are notified and they can put the avatars of their owners in the right place.
In addition to synchronizing avatar positions, each user can also select what model their avatar should use as well as the color. These are also set using User Variables.

You should notice that using User Variables is not the most efficient and scalable way to send object positions over and over again, in particular in realtime games. A better but more complex approach involves sending updates using the UDP protocol to a server-side Extension, but this will be the topic of a more advanced example.

>> 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. start SmartFoxServer 2X (v2.13 or later is highly recommended);
  3. start Unity (v5.6 or later required);
  4. in the Projects panel click on the Open icon and browse to the /client/ObjectMovement folder, then click the Open button;
  5. wait for the project setup completion (Unity needs to regenerate some libraries);
  6. 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);
  7. 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;
  8. 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 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.

» Code Highlights

The example is split into two Unity scenes. The Connection scene is responsible of connecting the client to the server, making the user login and joining the SFS2X Room where the game takes place. The Game scene represents the game world in which the avatars move.

» Connection and login

The approach in the Connection scene is very similar to the one in the Tris example Login scene: the connection is established, a reference to the SmartFox instance is set in the singleton SmartFoxConnection class and the user login is performed. In addition here, upon login, the Room called "Game Room" is joined (or created, if it doesn't exist yet because the user is the first player to enter it).
In this scene, the script containing the relevant code is attached to the game object called UI.

private void OnLogin(BaseEvent evt) {
    string roomName = "Game Room";

    // We either create the Game Room or join it if it exists already
    if (sfs.RoomManager.ContainsRoom(roomName)) {
        sfs.Send(new JoinRoomRequest(roomName));
    } else {
        RoomSettings settings = new RoomSettings(roomName);
        settings.MaxUsers = 40;
        sfs.Send(new CreateRoomRequest(settings, true));
    }
}

If the Room is successfully joined, the user is automatically moved to the game world by loading the Game scene.

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

    // Go to main game scene
    SceneManager.LoadScene("Game");
}

NOTE
Before loading a new scene, it is very important to remove all the SFS2X event listeners added by the current scene. Otherwise events generated in the next scene could trigger the handlers in this scene, with possible unwanted side effects or memory leaks. This can easily be done using the SmartFox.RemoveAllEventListeners() method.

» Spawning the avatars

In the Game scene, the relevant code is contained in the GameUI script attached to the UI game object, which controls the sliding panel where to change the avatar settings, and the GameManager script, where the communication with SmartFoxServer occurs and the main logic of the game is executed.
When the scene starts, the GameManager script gets the reference to the SmartFox instance from the SmartFoxConnection singleton and adds the required event handlers (see the Start() method).

sfs = SmartFoxConnection.Connection;
            
// Register callback delegates
sfs.AddEventListener(SFSEvent.OBJECT_MESSAGE, OnObjectMessage);
sfs.AddEventListener(SFSEvent.CONNECTION_LOST, OnConnectionLost);
sfs.AddEventListener(SFSEvent.USER_VARIABLES_UPDATE, OnUserVariableUpdate);
sfs.AddEventListener(SFSEvent.USER_EXIT_ROOM, OnUserExitRoom);
sfs.AddEventListener(SFSEvent.USER_ENTER_ROOM, OnUserEnterRoom);

Each client keeps track of the local avatar model as well as all the remote avatars in the Room. The remote players are tracked using the following Dictionary to look up their avatar using teir SFSUser object as a key.

private Dictionary<SFSUser, GameObject> remotePlayers = new Dictionary<SFSUser, GameObject>();

Each avatar's GameObject is represented by a mesh (cube, sphere or cylinder) as well as a material in different colors. These are randomly set for each client when entering the game.

int numModel = UnityEngine.Random.Range(0, playerModels.Length);
int numMaterial = UnityEngine.Random.Range(0, playerMaterials.Length);
SpawnLocalPlayer(numModel, numMaterial);

When remote players join, the local client needs to spawn an avatar for them in the local simulation. This is done when a given client receives an update of User Variables from a new client. The following code shows how the User Variables for position, rotation, model and material are read and used for spawning.

public void OnUserVariableUpdate(BaseEvent evt) {
    ...
    
    if (!remotePlayers.ContainsKey(user)) {
        // New client just started transmitting - lets create remote player
        Vector3 pos = new Vector3(0, 1, 0);
        if (user.ContainsVariable("x") && user.ContainsVariable("y") && user.ContainsVariable("z")) {
            pos.x = (float)user.GetVariable("x").GetDoubleValue();
            pos.y = (float)user.GetVariable("y").GetDoubleValue();
            pos.z = (float)user.GetVariable("z").GetDoubleValue();
        }
        float rotAngle = 0;
        if (user.ContainsVariable("rot")) {
            rotAngle = (float)user.GetVariable("rot").GetDoubleValue();
        }
        int numModel = 0;
        if (user.ContainsVariable("model")) {
            numModel = user.GetVariable("model").GetIntValue();
        }
        int numMaterial = 0;
        if (user.ContainsVariable("mat")) {
            numMaterial = user.GetVariable("mat").GetIntValue();
        }
        SpawnRemotePlayer(user, numModel, numMaterial, pos, Quaternion.Euler(0, rotAngle, 0));
    }
    
    ...
}

» Moving the avatar

As the local player moves around using the arrow keys on the keyboard, his avatar position and rotation change continuously. Transmitting the updated User Variables is done automatically as part of the FixedUpdate() Unity callback in the GameManager script. Not to spam remote clients sending updates even if the local user did not move or rotate his avatar, the PlayerController class uses a dirty flag to track input keypresses. When the flag is set to dirty, then position and rotation are transmitted.

void FixedUpdate() {
    if (sfs != null) {
        sfs.ProcessEvents();
        
        // If we spawned a local player, send position if movement is dirty
        if (localPlayer != null && localPlayerController != null && localPlayerController.MovementDirty) {
            List userVariables = new List();
            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;
        }
    }
}

Similarly, material and model changes are controlled via User Variables and triggered via UI interaction, using the sliding Settings panel. The following code shows how the material is being set locally and the selection sent to the other players via User Variables.

public void ChangePlayerMaterial(int numMaterial) {
    localPlayer.GetComponentInChildren().material = playerMaterials[numMaterial];

    List userVariables = new List();
    userVariables.Add(new SFSUserVariable("mat", numMaterial));
    sfs.Send(new SetUserVariablesRequest(userVariables));
}

When a remote user disconnects, then this is tracked via the OnUserExitRoom() event handler. The code simply cleans up and removes the remote avatar from the scene.

public void OnUserExitRoom(BaseEvent evt) {
    // Someone left - lets make certain they are removed if they didn't nicely send a remove command
    SFSUser user = (SFSUser)evt.Params["user"];		
    RemoveRemotePlayer(user);
}

private void RemoveRemotePlayer(SFSUser user) {
    if (user == sfs.MySelf) return;
    
    if (remotePlayers.ContainsKey(user)) {
        Destroy(remotePlayers[user]);
        remotePlayers.Remove(user);
    }
}

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

» More resources

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