Skip to content

Arcade1Up/igt-multiplayer-sample-unity

Repository files navigation

THE INFINITY GAME TABLE Multiplayer Setup (Unity)

Multiplayer

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.

Getting Started

  1. You need an Infinity Game Table, either 24" or 32"
  2. The webscoket links of the game you are developing on. Contact us to get set up.
  3. Clone this project

The Code

1. Configure websocket links for development

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;

2. Add library to plugin folder

Add connectedplay-1.3.3-release.arr to Assets/Plugins/Android/

3. Setup invitation for development

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);
}

4. Setup invitation for production (only available on IGT/IGB)

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());
	}
}

5. Callback response when host invite or client joined

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);
}

6. Data communication after connection established

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
		}
	}
}

Protocol Details

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.

Game Connection Details

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
}

Outbound Payload Wrapper (Server to Game/Admin)

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.

Channel Commands

Replay Conversation

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.

Send a “Muted” Message

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”}

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages