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

 

» Tris (Tic-Tac-Toe)

» Overview

The Tris example shows how to create a 3D multiplayer version of the famous "Tic-Tac-Toe" game using Unity and SmartFoxServer 2X. It is fully compatible with the Tris clients from the other APIs, which means users can play together even if they are on different devices.

The following game flow is implemented:

  1. users login and join a Room called "The Lobby" by default; it's a sort of lobby where users can meet and chat before they start or join a game
  2. a user creates a Room where he can play the tic-tac-toe game against another user; as soon as the game is created, he will enter the new Room and wait for the second player
  3. when the second player joins the game (by selecting it in the list), the game starts
  4. SmartFoxServer alternates the players' turns and after every move it checks if there's a winner
  5. the game ends when no more moves are available or one of the player makes a row of three; at the end of the game the user can either start a new game or go back in the lobby

This example shows how to create a SmartFoxServer Room of type "game". Game Rooms are specifically aimed at representing games (or matches) as they can contain two different types of users: players and spectators. A Game Room also provides player indexes: each user joining the room is automatically assigned a unique player index which facilitates the tasks of starting and stopping the game (spectators are not indexed instead).
In this example spectators are not supported, to focus on the core game logic and keep the code simpler.

The Tris example also features a server-side Extension which is in charge of handling the game logic, as opposed to keeping the logic on the client-side only. In general, using a server-side Extension is a more flexible and secure option. Even if SmartFoxServer provides powerful tools for developing application logic on the client, this approach can be limiting when your games start getting more complex. Also keeping sensitive game data on the server-side allows overall better security from hacking attempts, cheating, etc.

The server-side Extension that handles the game is dynamically attached to the Game Room representing the match started by one of the players. It sends the game events to the Unity interface. This way we can split the game logic from the game view, which is handled by the Unity client.

>> 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/Tris folder to the server's /SFS2X/extensions folder;
  3. start SmartFoxServer 2X (v2.13 or later is highly recommended);
  4. start Unity (v5.6 or later required);
  5. in the Projects panel click on the Open icon and browse to the /client/Tris 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/TrisAssets/Scenes folder and double click on one of the scene files to open it (i.e. Login 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 Controller game object in the Login 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/TrisAssets 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 described in the overview. The Extension is available in two versions: Java and JavaScript.

Step 2 above shows how to deploy the Java Extension, which is the default one for the example. 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.

In order to use the JavaScript Extension, in step 2 copy the /server/Extension_JavaScript/deploy/Tris-JS folder to the server's /SFS2X/extensions folder. Then, after importing the example in Unity, switch the EXTENSION_ID and EXTENSION_CLASS constants at the top of the LobbyController script in the Assets/TrisAssets/Scripts folder.

» Client code highlights

The example is split into three Unity scenes, each dedicated to a specific task: connection and login, lobby management and and the actual tic-tac-toe game logic. In each scene, the script containing the specific code is attached to a game object called Controller.

» Connection and login

From the UI perspective, the Login scene is similar to the Lobby example, except the login panel (here contained in a screen-space overlay canvas) doesn't feature a Zone name input field. The value set in the Inspector on the controller is used.
The code contains the expected event handlers for connection and login, with a couple of differences with respect to the Lobby example.

As the game features multiple scenes, it is mandatory to have a static (and as such, global) reference to the SmartFox instance, so that it can be accessed from anywhere in the game. For this purpose the singleton SmartFoxConnection class is used, and the SmartFox instance is passed to it when a successful connection is established with the server, before attempting the login.

private void OnConnection(BaseEvent evt) {
    if ((bool)evt.Params["success"])
    {
        // Save reference to SmartFox instance; it will be used in the other scenes
        SmartFoxConnection.Connection = sfs;

        // Login
        sfs.Send(new Sfs2X.Requests.LoginRequest(nameInput.text));
    }
    else
    ...
}

Upon login, it is time to load the next scene, the Lobby.

private void OnLogin(BaseEvent evt) {
    // Remove SFS2X listeners and re-enable interface
    reset();

    // Load lobby scene
    SceneManager.LoadScene("Lobby");
}

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.

» Lobby

When the Lobby scene is ready and the MonoBehaviour.Awake() method is called, the reference to the SmartFox instance is retrieved and the event handlers required by this scene are added. Also, the initial Room (called "The Lobby", predefined in the BasicExamples Zone) is joined.

void Awake() {
    Application.runInBackground = true;
    
    if (SmartFoxConnection.IsInitialized) {
        sfs = SmartFoxConnection.Connection;
    } else {
        SceneManager.LoadScene("Login");
        return;
    }

    loggedInText.text = "Logged in as " + sfs.MySelf.Name;
    
    // Register event listeners
    sfs.AddEventListener(SFSEvent.CONNECTION_LOST, OnConnectionLost);
    sfs.AddEventListener(SFSEvent.PUBLIC_MESSAGE, OnPublicMessage);
    sfs.AddEventListener(SFSEvent.ROOM_JOIN, OnRoomJoin);
    sfs.AddEventListener(SFSEvent.ROOM_JOIN_ERROR, OnRoomJoinError);
    sfs.AddEventListener(SFSEvent.USER_ENTER_ROOM, OnUserEnterRoom);
    sfs.AddEventListener(SFSEvent.USER_EXIT_ROOM, OnUserExitRoom);
    sfs.AddEventListener(SFSEvent.ROOM_ADD, OnRoomAdded);
    sfs.AddEventListener(SFSEvent.ROOM_REMOVE, OnRoomRemoved);
    
    // Populate list of available games
    populateGamesList();

    // Disable chat controls until the lobby Room is joined successfully
    chatControls.interactable = false;

    // Join the lobby Room (must exist in the Zone!)
    sfs.Send(new JoinRoomRequest("The Lobby"));
}

Again, the UI is structured similarly to the Lobby example (except the users list panel which was removed given its limited benefit). Other than the panel for the public chat, there is now a list of games instead of a list of chat rooms that a client can join. Additionally there are two buttons, to start a match and to disconnect and return to the Login scene. The "Start new game" button requests SmartFoxServer to create a Game Room attaching the Extension to it.

public void OnStartNewGameButtonClick() {
    // Configure Game Room
    RoomSettings settings = new RoomSettings(sfs.MySelf.Name + "'s game");
    settings.GroupId = "games";
    settings.IsGame = true;
    settings.MaxUsers = 2;
    settings.MaxSpectators = 0;
    settings.Extension = new RoomExtension(EXTENSION_ID, EXTENSION_CLASS);

    // Request Game Room creation to server
    sfs.Send(new CreateRoomRequest(settings, true, sfs.LastJoinedRoom));
}

If either we joined an existing Room (clicking on a list item in the UI) or created a new game, the client will receive a ROOM JOIN event, causing the OnRoomJoin() handler to unregister lobby callbacks from the SmartFoxServer API and load the Game scene to actually play the tic-tac-toe game. The else clause takes care of the initial join of "The Lobby" Room.

private void OnRoomJoin(BaseEvent evt) {
    Room room = (Room) evt.Params["room"];

    // If we joined a Game Room, then we either created it (and auto joined) or manually selected a game to join
    if (room.IsGame) {
        // Remove SFS2X listeners
        reset ();

        // Load game scene
        SceneManager.LoadScene("Game");
    } else {
        // Show system message
        printSystemMessage("\nYou joined a Room: " + room.Name);

        // Enable chat controls
        chatControls.interactable = true;
    }
}

» Initializing the game

The Game scene features the 3D game board, a sliding panel to chat with the opponent and, in the lower left corner, a small panel showing the game state and buttons to leave the match or restart it. Again, check the Lobby example to learn how to implement the chat feature.

The GameController script, attached to the Controller game object in the scene, is in charge of updating the panels UI based on the game state. The actual game logic and communication with the server side game Extension is demanded to the TrisGame class instead.
Similarly to the Lobby scene before, the MonoBehaviour.Awake() method in GameController retrieves the reference to the SmartFox instance and adds the handlers required for the chat and the events notifying if the opponent entered or left the game.

void Awake() {
    Application.runInBackground = true;
    
    if (SmartFoxConnection.IsInitialized) {
        sfs = SmartFoxConnection.Connection;
    } else {
        SceneManager.LoadScene("Login");
        return;
    }

    sfs.AddEventListener(SFSEvent.CONNECTION_LOST, OnConnectionLost);
    sfs.AddEventListener(SFSEvent.PUBLIC_MESSAGE, OnPublicMessage);
    sfs.AddEventListener(SFSEvent.USER_ENTER_ROOM, OnUserEnterRoom);
    sfs.AddEventListener(SFSEvent.USER_EXIT_ROOM, OnUserExitRoom);
    
    setCurrentGameState(GameState.WAITING_FOR_PLAYERS);

    // Create game logic controller instance
    trisGame = new TrisGame();
    trisGame.InitGame(sfs);
}

Once the client has joined the Room, loaded the Game scene and is ready to play, this is communicated to the Extension by the TrisGame instance in its InitGame() method.

public void InitGame(SmartFox smartFox) {
    // Register to SmartFox events
    sfs = smartFox;
    sfs.AddEventListener(SFSEvent.EXTENSION_RESPONSE, OnExtensionResponse);

    // Setup my properties
    myPlayerID = sfs.MySelf.PlayerId;

    // Reset game board
    ResetGameBoard();

    // Tell extension I'm ready to play
    sfs.Send(new ExtensionRequest("ready", new SFSObject(), sfs.LastJoinedRoom));
}

The Extension communicates with the clients using the OnExtensionResponse() callback to tell the clients about the following game states:

These Extension responses are handled in the following client code.

public void OnExtensionResponse(BaseEvent evt) {
    string cmd = (string)evt.Params["cmd"];
    SFSObject dataObject = (SFSObject)evt.Params["params"];
    
    switch ( cmd ) {
        case "start":
            StartGame(dataObject.GetInt("t"),
                dataObject.GetInt("p1i"),
                dataObject.GetInt("p2i"),
                dataObject.GetUtfString("p1n"),
                dataObject.GetUtfString("p2n")
                );
            break;

        case "stop":
            UserLeft();
            break;

        case "move":
            MoveReceived(dataObject.GetInt("t"), dataObject.GetInt("x"), dataObject.GetInt("y"));
            break;

        case "win":
            ShowWinner(cmd, (int)dataObject.GetInt("w"));
            break;
                
        case "tie":
            ShowWinner(cmd, -1);
            break;
    }
}

» Handling moves

When the given client has turn and clicks on a non-occupied tile on the board, the following code sends the tile coordinates to the server Extension. The server will update the state and check victory conditions.

public void PlayerMoveMade(int tileX, int tileY) {
    EnableBoard(false);

    SFSObject obj = new SFSObject();
    obj.PutInt("x", tileX);
    obj.PutInt("y", tileY);
    
    sfs.Send(new ExtensionRequest("move", obj, sfs.LastJoinedRoom));
}

As shown before, the client will receive moves via the OnExtensionResponse() callback. This updates the local board with the server state.

private void MoveReceived(int movingPlayer, int x, int y) {
    whoseTurn = ( movingPlayer == 1 ) ? 2 : 1;

    if ( movingPlayer != myPlayerID ) {
        GameObject tile = GameObject.Find("tile" + x + y);
        TileController ctrl = (TileController)tile.GetComponent("TileController");
        ctrl.SetEnemyMove();
    }

    // Update interface
    SetTurnMessage();

    // Enable interface
    EnableBoard(true);
}

» Server code highlights

Please read the Server code highlights paragraph in the Tris game tutorial for Flash. This refers to the Java version of the server side Extension, but the same logic (in a different language) is coded in the JavaScript Extension.

» More resources

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