The Infinity Game Table ("IGT" for short) server supports multiplayer. By integrating our multiplayer API you can easily leverage the user's friends list and our IGT dashboard's game invitation system, and add multiplayer support to your game.
This project is a basic sample project demonstrating how to establish multiplayer connection using our IGT multiplayer API. Follow the instructions in this repo to learn how to work with the IGT's multiplayer API.
- You need an Infinity Game Table, either 24" or 32"
- The webscoket links of the game you are developing on. Contact us to get set up.
- Clone this project
Replace links at ConnectedPlay.cs
public static readonly string[] multiplayTestingURL = {
"<IGT_WS_API_URL>/?hash=xxxxx",
"<IGT_WS_API_URL>/?hash=xxxxx",
};
Setup number of players to invite at ConnectedPlay.cs
private const int MAX_INVITES = 1;
Add connectedplay-1.3.3-release.arr to Assets/Plugins/Android/
Here are the host/client invitations for development (with hardcoded wss links) at MenuController.cs.
public void HostConnect()
{
ConnectedPlay.Instance.ForceChannel(ConnectedPlay.multiplayTestingURL[0], true);
}
public void ClientConnect()
{
ConnectedPlay.Instance.ForceChannel(ConnectedPlay.multiplayTestingURL[1], false);
}
The friends list page will launch on the IGT after calling InvitePlayers()
public void InvitePlayer()
{
ConnectedPlay.Instance.InvitePlayers();
}
When client accept invitation on IGT, the game will launch and client will go through Start() function of ConnectedPlay.cs. The websocket link can be retrieved from Android Intent.
private void Start()
{
NetworkController.Instance.OnDataRecieve += OnSocketData;
NetworkController.Instance.OnDisconnected += OnDisconnect;
#if UNITY_EDITOR
Debug.Log("Running in Editor. Connected Play disabled.");
return;
#endif
try
{
// When launched from dashboard the game channel information can be retrieved from the UnityPlayer activity
AndroidJavaClass UnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject currentActivity = UnityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
AndroidJavaObject intent = currentActivity.Call<AndroidJavaObject>("getIntent");
string channel = intent.Call<string>("getStringExtra", "channel");
Debug.Log($"Channel (from intent): {channel}");
if (string.IsNullOrEmpty(channel) == false)
{
ConnectedPlay.IsIGTConnectedPlay = true;
ForceChannel(channel);
OnPlayerLaunch?.Invoke(channel);
}
}
catch (System.Exception e)
{
Debug.Log(e.ToString());
}
}
Host player info will pass through callback OnJoinSession via function HandleConnection at ConnectedPlay.cs
private void HandleConnection(ConnectedPlayData.ConnectResult res)
{
if (res.Status != "OK")
{
Debug.LogError(res.Message);
return;
}
if (string.IsNullOrEmpty(res.Payload) == false)
{
_initData = JsonUtility.FromJson<ConnectedPlayData.InitialPayload>(Regex.Unescape(res.Payload));
GameStateManager.Instance.GameSetup(NetworkController.Instance.Channel, _isHosting);
Debug.Log(string.Format("CONNECTED: {0}, id={1} (Host: {2})", _initData.handle, UserID, GameStateManager.Instance.ActiveGameHost == UserID));
// SendDataPing(-1);
OnJoinSession?.Invoke(_initData.handle, UserID);
}
}
Client players info will pass through callback OnJoinSession via function UpdatePlayer at ConnectedPlay.cs
private void UpdatePlayer(string pUser, string id)
{
...
if (newPlayer && (_initData == null || pUser != _initData.handle))
{
Debug.Log("NEW USER " + pUser);
OnJoinSession?.Invoke(pUser, id);
}
}
And the callback listener located at MenuController.cs
private void OnJoinSession(string pUser, string pUserId)
{
if (ConnectedPlay.Instance.IsHost())
{
serverResponse.text += $"{pUser}({pUserId}) joined\n";
startGameButton.gameObject.SetActive(true);
}else{
serverResponse.text += $"{pUser}({pUserId}) joined\n";
}
}
Callback function for websocket error or cancellation at ConnectedPlay.cs
private void OnError(string error)
{
ForceClose();
OnPlayerCancel?.Invoke(error);
}
// Callback when cancel invitation
private void OnCancel(string message)
{
ForceClose();
OnPlayerCancel?.Invoke(message);
}
Configure any notification type at IGTNotification.cs
Add IGTListener to class for receiving notification
public class MenuController : MonoBehaviour, IGTListener
IGTNotificationManager.AddListeners(this, IGTNotification.START_GAME);
Send data using function IGTNotificationManager.Broadcast
IGTNotificationManager.Broadcast(IGTNotification.START_GAME, new ConnectedPlayData.GamePayload(new object[] {}));
Receive notification response at function NotificationReceived
public void NotificationReceived(IGTNotification pNotification, object pBody)
{
if (this == null)
return;
object[] data = null;
switch (pNotification)
{
case IGTNotification.START_GAME:
{
//response callback
}
}
}
The web socket system was developed to echo whatever you send to every user connected to the channel. We recommend you send JSON messages, however it’s totally up to you. Each message terminated with the newline is sent as a single message to all other users.
It is also recommended that you enable the PING/PONG protocol on your websocket channel from your side to help maintain channel stability. The server will also have it enabled.
The Websocket protocol implements so called PING/PONG messages to keep Websockets alive, even behind proxies, firewalls and load-balancers. The server sends a PING message to the client through the Websocket, which then replies with PONG. If the client does not reply, the server closes the connection.
When sending a message, a success response would look like:
Responses for connections/commands
{
“Status”:”OK” // or ErrorCode
“Message”:”” // verbose message of error
“Payload”:”” // optional payload
}
The message is then relayed to all users on the channel (including yourself) in this format:
{
“ID”: int // Unique ID for each message
“Timestamp”: int // Time since Jan 1, 1970,
“FromHandle”:”” // from which user (blank for admin message)
“Payload: string // message payload -- see below
}
ID will be an ever increasing (however not sequential) integer.
Timestamp is the time the message was sent FromHandle will be a string representation of the sender’s Handle
Payload is a string representation of the payload you wrote to the channel. It will not be JSON until you decode it.
On both game and admin channels you can replay the messages starting at the last one you received, or 0 for all.
CMD:REPLAY:0
If the last message you received was 9 and you disconnected, you can get ID > 9 by passing in
CMD:REPLAY:9
Anything not in the format /^CMD:/ will just be relayed to the entire game channel wrapped in the Outbound Payload Wrapper below in the Payload field.
A muted message is a message that will be relayed realtime to all connected parties, however won’t be saved for future REPLAY command. These can be used for things like connect messages, PING’s etc.
CMD:SENDMUTED:some payload here
Example:
CMD:SENDMUTED:{“pingFrom”: “Bill”}