Cluster Connector
Overview
The Cluster Connector example represent the implementation of the concepts discussed in Building a cluster client. You will learn how to deal with two connections, one to the Lobby and one to a single Game Node, as this is the most common scenario: users join the Lobby and then, by means of the match-making system, they are sent to a Game Node to play the game in a dedicated Room.
This introductory example shows how to setup and use the SmartFox client API object to establish a connection to both the Lobby and Game Node, and how to deal with the connection states and the additional cluster-related events fired by the SmartFoxServer API.
The Unity project consists of a single scene with a canvas containing two game objects (called "panels") representing the Lobby view and the Game view. The controls on the left allow to interact with the respective servers, simulating the global flow and states of an application in a cluster environment. On the right side, events are logged to better understand the whole process.
The example features a single script component. A few properties exposed in the Editor's Inspector panel allow configuring the Lobby connection parameters.
Download
Click on the following button to download the complete pack of Unity examples for the cluster.
Download examples packSetup & run
In order to setup and run the example, follow these steps:
- unzip the examples pack;
- make sure you have a cluster running in Overcast;
- launch the Unity Hub, click on the Open button and navigate to the /Client/Cluster_Connector folder;
- if prompted, select the Unity Editor version to use (v2021.3 or later is recommended);
- go to the Project panel, click on the /Assets/Scenes folder and double click on the Main scene to open it;
- click on the Controller game object and change the Host setting in the Editor's Inspector panel to the IP address or domain name of your Lobby Node;
- 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.
The client's C# code is in the /Assets/Scripts folder and the SmartFoxServer 2X client API DLLs are in the /Assets/Plugins folder. Read this introduction to understand why multiple DLLs are used.
Introduction to code
The code for this example is contained in the script file called MainController in the /Assets/Scripts folder, attached to the empty Controller game object in the scene. The file is a basic Unity C# script implementing the Start(), Update() and OnApplicationQuit() methods. Additionally, there's a few listeners for the events fired by UI components (buttons, input), some helper methods and the listeners for SmartFoxServer's client API events.
Among the helper methods we have the TraceLobby() and TraceGame() methods, that you will find referenced in many code snippets below. They simply print the passed strings in the respective output panels in the scene.
Fields declaration
At the top of the MainController script, where fields are declared, other than the links to UI components the script interacts with, there's a couple of public properties to configure the client. These settings are exposed in the Inspector panel for the Controller game object.
In particular, Host is the basic setting required to establish a connection with the SmartFoxServer instance acting as the cluster's lobby. As already mentioned before, it must be set to the IP address or domain name of the Lobby Node running in the Overcast Cluster. The TCP and HTTP ports, usually configurable in SmartFoxServer, are declared as constants because they are predefined in the Overcast environment. The same goes for the Zone setting.
The Debug flag can be checked to enable the SmartFox client logging in the Unity Console panel.
Last but not least, the fields declaration contains the two sfsLobby and sfsGame properties, which both keep a reference to the entry point of the SmartFoxServer C# client API, the SmartFox class. The two instances, one for the Lobby Node and one for the Game Node, are used by the client to interact with the respective servers through a set of requests and responses (under the form of events), as always in SmartFoxServer.
Unity Start callback
The Start() method is called by Unity automatically when the scene is loaded and it takes care of the interface initialization: in this simple example it just sets the focus onto the username input.
The method also makes sure that the example will always run in background. Although this is not particularly important in this case, in the next examples this will be useful for testing, when two clients are often opened at the same time on the same computer to test their interactions.
private void Start()
{
// Make the application run in background
Application.runInBackground = true;
// Focus on username input
nameInput.Select();
nameInput.ActivateInputField();
}
Processing SmartFoxServer events
As usual in SmartFoxServer development, we will register handlers (also called listeners) for a number of events fired by both instances of the SmartFox class used to interact with the Lobby and Game Node.
Before discussing the overall flow of the application and the event handlers in detail, a fundamental note on thread safety: Unity implements thread safety by restricting the access to its own API to the main thread only. To avoid blocking the main thread during network communication, the SmartFox API rely on its own threads. In order to access the Unity API (for example to update the game objects on the scene according to an event), the network event handlers can't be called directly by the SmartFox instance: events must be queued internally and Unity's main thread needs to "poll" the queue on a regular basis to process them.
This behavior is controlled by the SmartFox.ThreadSafeMode property: if set to true (default in Unity), the events are queued and the handlers are called when the SmartFox.ProcessEvents() method is executed only. This is done in Unity's Update() callback.
private void Update()
{
// Process the SmartFox events queues
if (sfsLobby != null)
sfsLobby.ProcessEvents();
if (sfsGame != null)
sfsGame.ProcessEvents();
}
Unity OnApplicationQuit callback
The OnApplicationQuit() method is called by Unity before the application quits or when playmode is stopped in the Editor. Here we need to make sure a disconnection from all SmartFoxServer instances is executed.
private void OnApplicationQuit()
{
if (sfsGame != null && sfsGame.IsConnected)
sfsGame.Disconnect();
if (sfsLobby != null && sfsLobby.IsConnected)
sfsLobby.Disconnect();
}
The reason why a disconnection is recommended on application quit is that an active network socket during the quit process can cause a crash on some platforms.
Additionally, when inside the Unity Editor, stopping playmode doesn't interrupt the internal threads launched by the SmartFox API, which may lead to unwanted issues. Forcing a disconnection makes sure all threads are stopped as appropriate.
Overall application flow
The global flow of a game targeted at the Overcast Cluster environment is made of a number of steps, most of which are the same ones implemented when developing a client for standalone SmartFoxServer.
A clustered game requires the client to connect to the Lobby and login; then the user should be able to find an existing Game Room (or create a new one) and join it. As Rooms are located on Game Nodes, an intermediate step is needed to join the proper Node before the actual game can start. Lastly, when the game ends, the client must leave the Game Node.
Let's discuss in detail all the steps as implemented in the example.
Join the Lobby
The entry point to a multiplayer game in a cluster environment is the Lobby, so the first step is to connect to it. Technically two steps are needed: establish a connection and perform the login after requesting the user credentials.
In our simplified example, the top half of the interface is related to the communication with the Lobby. Other than the username input field, the UI features the Enter button which starts the connection & login process. The two steps are executed back-to-back when the user clicks on the button or hits the enter key after typing their username. The two events are handled by the OnEnterButtonClick() and OnNameInputEndEdit() listeners respectively, both calling the Connect() method.
Note that in a real-case scenario the game should actually show a scene or view dedicated to this initial step, where a password input field is displayed too. When the login is attempted, credentials should be validated on the server side by a custom server-side Extension. For sake of simplicity, all examples described in this and other tutorials will feature a guest login, allowing users to access the application or game by just entering a name. If the input field is left empty, a name is auto-generated by the server and assigned to the user.
The first relevant code portion of the Connect() method takes care of creating the SmartFox class instance reserved to the Lobby and registering the event handlers required by the example.
// Initialize SmartFox client used to connect to the cluster lobby node #if !UNITY_WEBGL sfsLobby = new SmartFox(); #else sfsLobby = new SmartFox(UseWebSocket.WS_BIN); #endif // Add event listeners AddSmartFoxLobbyListeners();
The code contains a few conditional compilation instructions to instantiate the main API object in a slightly different way depending on the type of connection we want to establish with SmartFoxServer:
- a TCP socket connection, which is the default one in SmartFoxServer and it is used by all Unity builds except WebGL;
- a WebSocket connection, the only one supported by Unity WebGL build and provided by SmartFoxServer's internal web server.
For more information on conditional compilation, please visit this page in the Unity documentation.
Next we can register the listeners to the SmartFoxServer events required by the Lobby's connection and login process. Also, a couple of listeners for the cluster-specific event types SFSClusterEvent.CONNECTION_REQUIRED and SFSClusterEvent.LOAD_BALANCER_ERROR are added. All listeners are registered in the AddSmartFoxLobbyListeners() method called after creating the SmartFox class instance.
private void AddSmartFoxLobbyListeners()
{
sfsLobby.AddEventListener(SFSEvent.CONNECTION, OnLobbyNodeConnection);
sfsLobby.AddEventListener(SFSEvent.CONNECTION_LOST, OnLobbyNodeConnectionLost);
sfsLobby.AddEventListener(SFSEvent.LOGIN, OnLobbyNodeLogin);
sfsLobby.AddEventListener(SFSEvent.LOGIN_ERROR, OnLobbyNodeLoginError);
sfsLobby.AddEventListener(SFSClusterEvent.CONNECTION_REQUIRED, OnGameNodeConnectionRequired);
sfsLobby.AddEventListener(SFSClusterEvent.LOAD_BALANCER_ERROR, OnLoadBalancerError);
TraceLobby("Lobby node event listeners added");
}
Note that all the listeners are specific to the Lobby; we will need similar listeners to manage the Game Node connection, but they will perform other actions: for this reason we can't reuse the same methods.
In our simplified example, no other listeners are needed for the Lobby.
We'll discuss the above event handlers while progressing in this tutorial.
The next code portion of the Connect() method configures the SmartFox API internal logger, used to debug the messages exchanged between the client and the server and report other internal informations. If the Debug flag is selected in the Editor's Inspector panel, we want the messages to be displayed in the Console panel when in playmode.
// Configure SmartFox internal logger sfsLobby.Logger.EnableConsoleTrace = debug;
Here a few special listeners could also be added by means of the SmartFox.AddLogListener() method: they offer better control on how and where to display debug messages (for example an in-game debug panel). This is not implemented in our code because outside the scope of this tutorial.
In the last part of the connection code, the configuration parameters required to establish a connection with the Lobby are set using the ConfigData object, which is then passed to the SmartFox.Connect() method, to actually start the connection process.
// Set connection parameters ConfigData cfg = new ConfigData(); cfg.Host = host; cfg.Port = TCP_PORT; cfg.Zone = ZONE; cfg.Debug = debug; #if UNITY_WEBGL cfg.Port = HTTP_PORT; #endif // Connect to SmartFoxServer sfsLobby.Connect(cfg);
The code collects the settings provided in the Inspector panel before or as constants and starts the connection by calling the SmartFox.Connect() method. The mandatory parameters are the server's domain name or IP address (Host) and the primary connection port (Port) for TCP socket connection. Please note that, in case of WebGL build, a conditional compilation statement overwrites the port setting with the value required by WebSocket connections. In fact WebSocket connection uses the SmartFoxServer's internal web server HTTP port.
As it regards the other settings instead:
- The
Zoneis where the user will be logged in after the successful connection. This must always be set to the predefinedClustervalue. - If the
Debugparameter is set totrue, the internal logger of the SmartFox API is enabled and debug messages are displayed in Unity's Console panel and possibly passed to log event listeners as discussed before.
Connection established
The OnLobbyNodeConnection() listener is called when the API completes the connection process (whether successfully or not) and dispatches the CONNECTION event. If the connection is established successfully, we can proceed with the login step passing the user credentials to the LoginRequest class instance.
private void OnLobbyNodeConnection(BaseEvent evt)
{
// Check if the connection was established or not
if ((bool)evt.Params["success"])
{
TraceLobby("Connection to lobby node established; mode is: " + sfsLobby.ConnectionMode);
TraceLobby("SFS2X API version: " + sfsLobby.Version);
// Login
sfsLobby.Send(new LoginRequest(nameInput.text));
}
else
{
TraceLobby("Connection to lobby node failed; is the server running at all?", true);
// Reset
RemoveSmartFoxLobbyListeners();
sfsLobby = null;
}
}
At this stage a connection issue should never occur (the only reason could be the unavailability of the Lobby server), and in case it is unlikely that a new attempt a few seconds later would lead to a success. In any case it is good practice to clean the client state by removing all the listeners added previously and setting the SmartFox instance for the Lobby to null. Also, a feedback should be provided to the user.
Login attempted
A soon as the successful login is notified by means of the LOGIN event, the OnLobbyNodeLogin() listener in our example simply provides a feedback to the user, updates the username input (in case it was auto-generated by the server) and enables/disables buttons in the UI based on the current connection state.
As you will see in more advanced examples, in a real-case scenario the client should now move to a new screen or scene where a number of options is offered to the user (for example managing their profile, looking for or interacting with buddies, etc). In particular there should be a way to start a new game or join an existing one, as described in the following section.
private void OnLobbyNodeLogin(BaseEvent evt)
{
TraceLobby("Login to lobby node successful");
User user = (User)evt.Params["user"];
nameInput.text = user.Name;
// Reset user interface
ResetUI();
}
In case an error occurs, the LOGIN_ERROR event is fired and the OnLobbyNodeLoginError() listener is called. Some of the possible errors are: invalid username, Zone is full (no more users can join it), etc. The event parameter contains a message detailing the error reason.
private void OnLobbyNodeLoginError(BaseEvent evt)
{
TraceLobby("Login to lobby node failed due to the following error: " + (string)evt.Params["errorMessage"], true);
// Disconnect
sfsLobby.Disconnect();
}
It is important to note that, if a login error occurs, a disconnection should always be forced. In fact the client is still connected, even if the user couldn't complete the login. The disconnection causes a client reset (see Handling disconnections below), so that a new connection and login can be attempted.
Find a Game Room
In a cluster environment there can be hundreds of thousands of Game Rooms distributed among many servers. Of course it would be impossible to ask the player to choose what Game Node and Room to join, so this is done automatically by means of the Overcast Load Balancing system and the filters provided by SmartFoxServer's Match Expressions.
Choosing the proper criteria to make a Room "findable" is the critical step, because we want the user to be sent to the right Game Room. In this simplified example, which doesn't feature an actual game, we haven't specific criteria other than a check on the Room name. More meaningful examples of search criteria include: the minimum required ranking for the player to be allowed to join the Room, the game type or variant in case of multi-game lobbies, the game level represented by the Room, the maximum (or minimum) number of players supported by the game, the current game state (to prevent users from joining a game already started), etc. The list could be endless and the applicable criteria vary a lot depending on the actual game.
When the user clicks the Join or Create Room button in the Lobby section of the interface, the JoinOrCreateRoom() method tries to find a Game Room to join the user in. If it can't be found, the Lobby Node will take care of creating a new Room and join the user, who will then wait for other players to reach them.
public void JoinOrCreateRoom()
{
// Disable button
joinCreateButton.interactable = false;
// Show waiting animation
waitingControls.SetActive(true);
TraceLobby("Sending request to join or create a public Room");
// Set the common Room settings to create a new game Room if needed
ClusterRoomSettings settings = new ClusterRoomSettings("GR_" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
settings.GroupId = "games";
settings.MaxUsers = 10;
settings.MaxSpectators = 0;
settings.IsGame = true;
settings.IsPublic = true;
// Set the match expression to search for an existing game Room
MatchExpression exp = new MatchExpression(RoomProperties.NAME, StringMatch.STARTS_WITH, "GR_");
// Send request
sfsLobby.Send(new ClusterJoinOrCreateRequest(exp, new List() { "games" }, settings));
}
In this method we execute the following steps:
- Update the UI, in particular showing a "waiting" animation and a Cancel button to interrupt the search of a suitable Game Room, as better explained below, when discussing how to deal with errors. In an actual game this could be a dedicated Searching for opponent view.
- Create a
ClusterRoomSettingsinstance containing the configuration of the Game Room, in case a new one should be created. Note that the Room will belong to a Group named "games". - Setup the Match Expression used by the Game Node to search for an existing Game Room. Here the match is very simple and based on the initial characters of the Room name, according to the Room name passed to the
ClusterRoomSettingsinstance constructor. In other words this Match Expression only finds Rooms created by this example. - Send the appropriate request to the Lobby, passing the Match Expression, a list of Room Groups where to look for existing Game Rooms (which just contains the "games" Group always assigned to new Rooms as per
ClusterRoomSettings) and the settings in case a Room is not found and must be created.
After sending the request, it is then up to the Lobby to locate the target Game Node through the Load Balancing logic, execute the Match Making logic and, if a Room was found or created, notify the client.
In case, for any reason, a Game Node couldn't be located by the Load Balancing system, the LOAD_BALANCER_ERROR event is fired by the sfsLobby instance. This error should rarely occur and it could be due to the cluster state. For example when all Game Nodes have reached their maximum capacity and new Nodes should be launched, there could be a few seconds in which they are not available yet, and the error is thrown. The event can be useful to schedule a new attempt after a few seconds, with or without providing a visual feedback to the user.
In our example, the OnLoadBalancerError() listener calls the ScheduleJoinOrCreateRoom(), which instructs Unity to invoke the JoinOrCreateRoom() method again after a couple of seconds.
private void OnLoadBalancerError(BaseEvent evt)
{
TraceLobby("A load balancer error occurred: you should investigate the cluster state", true);
// Schedule another request to join or create a game room
ScheduleJoinOrCreateRoom();
}
private void ScheduleJoinOrCreateRoom()
{
TraceLobby("Trying again to join or create a Game Room in 2 seconds...");
// Schedule another request to join or create a game
Invoke("JoinOrCreateRoom", 2);
}
From now on, the "waiting" animation will keep spinning until the join process is fully complete, which means all its steps have been executed successfully: connect and login to the Game Node, find a matching Game Room or create a new one, join it. In case an error occurs at any one of these steps, the process is restarted as shown above, unless the user clicks the Cancel button and gives up. The button calls the CancelWaitToJoin() method.
private void CancelWaitToJoin()
{
CancelInvoke("JoinOrCreateRoom");
// Hide waiting animation
waitingControls.SetActive(false);
}
Join the Game Node
As soon as a suitable Game Node is found by the Lobby, this is notified to the client by means of the CONNECTION_REQUIRED event, specific to the cluster environment. In our OnGameNodeConnectionRequired() listener we have to establish a new connection, precisely to the Game Node.
private void OnGameNodeConnectionRequired(BaseEvent evt)
{
TraceLobby("Connection to game node required");
// Retrieve connection settings
ConfigData cfg = (ConfigData)evt.Params["configData"];
// Retrieve and save login details
gameUsername = (string)evt.Params["userName"];
gamePassword = (string)evt.Params["password"];
// Initialize SmartFox client used to connect to the cluster game node
#if !UNITY_WEBGL
sfsGame = new SmartFox();
#else
sfsGame = new SmartFox(UseWebSocket.WS_BIN);
#endif
// Add event listeners
AddSmartFoxGameListeners();
// Establish a connection to the game node; a game room will be joined automatically after login
sfsGame.Connect(cfg);
}
private void AddSmartFoxGameListeners()
{
sfsGame.AddEventListener(SFSEvent.CONNECTION, OnGameNodeConnection);
sfsGame.AddEventListener(SFSEvent.CONNECTION_LOST, OnGameNodeConnectionLost);
sfsGame.AddEventListener(SFSEvent.LOGIN, OnGameNodeLogin);
sfsGame.AddEventListener(SFSEvent.LOGIN_ERROR, OnGameNodeLoginError);
sfsGame.AddEventListener(SFSEvent.ROOM_CREATION_ERROR, OnGameRoomCreationError);
sfsGame.AddEventListener(SFSEvent.ROOM_JOIN, OnGameRoomJoin);
sfsGame.AddEventListener(SFSEvent.ROOM_JOIN_ERROR, OnGameRoomJoinError);
sfsGame.AddEventListener(SFSEvent.USER_ENTER_ROOM, OnGameRoomUserEnter);
sfsGame.AddEventListener(SFSEvent.USER_EXIT_ROOM, OnGameRoomUserExit);
sfsGame.AddEventListener(SFSEvent.USER_VARIABLES_UPDATE, OnGameUserVarUpdate);
TraceGame("Game node event listeners added");
}
The listener is in charge of setting up the connection to the Game Node following these steps:
- Retrieve the
ConfigDataobject from the event parameters. It contains all the predefined settings needed to connect to the target Game Node. - Retrieve and save globally the username and password from the event parameters, to perform the login step later on. Please note that the password is not the same password of the user account possibly used to log into the Lobby (not in this example). It is a one-time password specifically required to give the user access to the target Game Node. The password can expire if the connection and login steps take too much time to complete.
- Initialize the
SmartFoxinstance for the Game Node: a reference to this instance is saved globally, to be able to interact with the Game Node at any given time. - Add all the listeners needed to manage the various connection states, in particular the ROOM_JOIN event signaling that the target Game Room was joined and the game logic can kick-in. Note that all the listeners are specific to the Game Node and not shared with the Lobby.
- Finally, connect to the Game Node.
If the connection is established successfully, we can proceed with the login step passing the user credentials returned by the CONNECTION_REQUIRED event to the LoginRequest class instance, as shown in the CONNECTION event listener for the Game Node.
private void OnGameNodeConnection(BaseEvent evt)
{
// Check if the connection was established or not
if ((bool)evt.Params["success"])
{
TraceGame("Connection to game node established; mode is: " + sfsGame.ConnectionMode);
// Enable lag monitor for the lobby node
sfsLobby.EnableLagMonitor(true);
// Login
sfsGame.Send(new LoginRequest(gameUsername, gamePassword));
}
else
{
TraceLobby("Connection to game node failed", true);
// Reset
RemoveSmartFoxGameListeners();
sfsGame = null;
// Schedule another request to join or create a game room
ScheduleJoinOrCreateRoom();
}
}
Note that at this stage it is good practice to enable the lag monitor on the Lobby connection. In conjunction with the Enable keepalive setting on the Zone (set to true by default in the Overcast Cluster), this feature helps avoiding the connection to the Lobby being closed as the user seems to be idle while playing, because now most of client-server communication occurs over the Game Node connection.
Just like for the Lobby before, a connection issue should never occur at this stage (an issue with the Game Nodes would probably trigger a LOAD_BALANCER_ERROR before). In any case it is good practice to clean the client state for the Game Node connection by removing all the listeners added previously and setting the SmartFox instance for the Game Node to null. A new attempt to join a Game Node can then be scheduled, as discussed previously.
The LOGIN event is not particularly meaningful here, because the client should move to the actual game screen or scene after the Game Room is joined successfully. In fact we just wait for the ROOM_JOIN event.
private void OnGameNodeLogin(BaseEvent evt)
{
TraceGame("Login to game node successful; waiting for Room auto-join event");
// Nothing to do; a Room-autojoin is triggered by the server
}
Instead, in the unlikely event of a login error (for example because the one-time password expired), a manual disconnection from the Game Node is executed, which in turn resets the client state (see Handling disconnections below) making it ready for a new attempt to join or create a game, as discussed before.
private void OnGameNodeLoginError(BaseEvent evt)
{
TraceGame("Login to game node failed due to the following error: " + (string)evt.Params["errorMessage"], true);
// Disconnect
sfsGame.Disconnect();
// Schedule another request to join or create a game room
ScheduleJoinOrCreateRoom();
}
Join the Game Room
If the Lobby's Match Making system can't find a suitable Room to join the user in, the creation of a new Room is attempted automatically by the server. Handling the ROOM_ADD event is not even needed, unless you want to provide a feedback to the user.
In case an error occurs (for example a name conflict, the maximum number of Rooms for the Zone being reached, etc), SmartFoxServer's common ROOM_CREATION_ERROR event is fired. Again, its OnGameRoomCreationError() listener executes a manual disconnection from the Game Node to reset the client state, and a new attempt to run the whole game join process is scheduled as described previously.
private void OnGameRoomCreationError(BaseEvent evt)
{
TraceGame("Game Room creation failed: " + (string)evt.Params["errorMessage"], true);
// Disconnect
sfsGame.Disconnect();
// Schedule another request to join or create a game room
ScheduleJoinOrCreateRoom();
}
Whether an existing Room was found, or a new Room was created, the join is then automatically attempted on the server side. If an error occurs, the ROOM_JOIN_ERROR event is fired. In such case, in the OnGameRoomJoinError() listener we need once more to manually disconnect from the Game Node, to reset the client state, and schedule a new attempt to run the whole process again from the beginning.
private void OnGameRoomJoinError(BaseEvent evt)
{
TraceGame("Game Room join failed: " + (string)evt.Params["errorMessage"], true);
// Disconnect from game node
sfsGame.Disconnect();
// Schedule another request to join or create a game room
ScheduleJoinOrCreateRoom();
}
If instead the Room was joined successfully, the ROOM_JOIN event is dispatched. It is now time to enable the controls in the Game portion of our user interface. This is accomplished in the OnGameRoomJoin() listener. In a real-case scenario we should now switch to the actual game screen or scene and transfer control to the game logic.
private void OnGameRoomJoin(BaseEvent evt)
{
Room room = (Room)evt.Params["room"];
TraceGame("Game Room '" + room.Name + "' joined successfully");
// Reset user interface
ResetUI();
// Hide wait state
CancelWaitToJoin();
}
Note that in the above method we also cancel the "waiting" state, which hides the related animation and Cancel button in the UI. In a real-case scenario, you'll probably want to keep showing the Searching for opponent view until the minimum number of players to start the game has been reached in the Room.
Our simplified example now simulates a basic interaction with the Game Room by means of the Interact button, which simply updates a User Variable. The USER_VARIABLES_UPDATE event notifies the update to all users in the Room, showing a message on the right side of the Game view.
private void OnGameUserVarUpdate(BaseEvent evt)
{
User user = (User)evt.Params["user"];
List<string> changedVars = (List<string>)evt.Params["changedVars"];
if (changedVars.Contains("cnt"))
{
int cnt = user.GetVariable("cnt").GetIntValue();
if (user.IsItMe)
TraceGame("You clicked the interact button " + cnt + " time" + (cnt != 1 ? "s" : ""));
else
TraceGame("User " + user.Name + " clicked the interact button " + cnt + " time" + (cnt != 1 ? "s" : ""));
}
}
Leave the Game Room
When the user wants to leave the game (or the game is over in a real-case scenario), the connection to the Game Node can be closed by clicking on the Leave button, which triggers a manual disconnection. This is important to free client resources, so that a new game can be launched from the Lobby view.
public void OnLeaveGameButtonClick()
{
sfsGame.Disconnect();
}
Handling disconnections
As described above, we have two connection active most of the time: the connection to the Lobby and the one to the Game Node. Both connection can be interrupted on purpose, by means of a manual disconnection, or abruptly, due to reasons independent from the user actions.
In our example the manual disconnection can be triggered by means of the Leave buttons, independently for each server type. This is not a real-case scenario of course, as usually you will always have one Leave button visible at a time, in particular to end the current game and go back to the Lobby view. Here we have buttons for both server types as it helps testing the various conditions.
Another reason for a manual disconnection from the Game Node is to reset the client state if an error occurs during the connect → login → join process, as we have seen multiple times above.
The unwanted disconnection instead can be simulated for both server types by clicking on the respective Kill Connection button. In an actual game such disconnection could be caused by a network error, or a kick/ban action executed by a moderator or administrator.
Lobby
Whatever the reason of the disconnection is, the CONNECTION_LOST event is fired by the SmartFox instance associated to the Lobby and the OnLobbyNodeConnectionLost() listener is called.
private void OnLobbyNodeConnectionLost(BaseEvent evt)
{
string connLostReason = (string)evt.Params["reason"];
bool isManual = connLostReason == ClientDisconnectionReason.MANUAL;
TraceLobby(ComposeConnLostMessage("An disconnection from lobby node occurred.", connLostReason), !isManual);
// On lobby non-manual disconnection, if a game is in progress (which means a connection to the game node is still active)
// we can ignore this event and let the user keep playing the game
if (sfsGame != null && sfsGame.IsConnected)
TraceLobby("Client still connected to game node: user can keep interacting with the game node");
// Cancel scheduled request to join a game
CancelWaitToJoin();
// Reset user interface
ResetUI();
// Reset
RemoveSmartFoxLobbyListeners();
sfsLobby = null;
}
This is how the listener handles the disconnection:
- The disconnection reason is extracted from the event's parameters.
- If the disconnection wasn't requested by the user, and the connection with the Game Node is still available, we can ignore this event: the user can keep interacting with the Game Node. We will have to deal with the Lobby being disconnected once the user decides to leave the Game Node, as described below.
If instead the connection with the Game Node isn't active, the UI is reset accordingly. - The now closed connection is cleaned by removing all the listeners and setting the
SmartFoxinstance for the Lobby tonull.
Game Node
Whatever the reason of the disconnection is, the CONNECTION_LOST event is fired by the SmartFox instance associated to the Game Node and the OnGameNodeConnectionLost() listener is called.
private void OnGameNodeConnectionLost(BaseEvent evt)
{
string connLostReason = (string)evt.Params["reason"];
bool isManual = connLostReason == ClientDisconnectionReason.MANUAL;
TraceGame(ComposeConnLostMessage("A disconnection from game node occurred.", connLostReason), !isManual);
// If lobby connection is available, disable lag monitor
if (sfsLobby != null && sfsLobby.IsConnected)
sfsLobby.EnableLagMonitor(false);
else
TraceGame("No active lobby connection available; new login required", true);
// Reset user interface
ResetUI();
// Reset
RemoveSmartFoxGameListeners();
sfsGame = null;
}
This is how the listener handles the disconnection:
- The disconnection reason is extracted from the event's parameters.
- When the disconnection occurs, the connection to the Lobby is usually still available. In this case we have to disable the lag monitor (which, remember, was used to keep the connection to the Lobby alive) and re-enable the Join or Create Room button in the Lobby section of the UI.
If instead the connection to the Lobby is not available anymore, the state of buttons and inputs is updated accordingly and a proper alert message is displayed on the right side. - The now closed connection is cleaned by removing all the listeners and setting the
SmartFoxinstance for the Game Node tonull.
You can now go back to the Unity examples series and check the next tutorial.
More resources
You can learn more about development in the Overcast Cluster environment by consulting the following resources:
