Realtime Data Synchronization with Unity and PubNub

Some developers avoid realtime development in Unity due to the complexity, however, with the PubNub Unity SDK, multiplayer development is seamless and easy. PubNub is a globally distributed network with APIs for building realtime applications and multiplayer games. PubNub APIs are easy to use so you can iterate fast and get your game published faster than the competition. Additionally, PubNub powers the backend for you, allowing you to focus on innovating your application instead of building a complicated global network.

With that, we want to showcase the Unity SDK with PubNub. Data synchronization, which ensures that two or more clients are synced, is essential for multiplayer games specifically. I built a simple image switcher app that allows one user to take an action, and that action is synced across all other connected clients.

Setting Up Unity

To start, check out my GitHub and clone/download the repo. The repo contains an Assets folder which contains all of the Project Files. Open up Unity, and go to File -> Open Project and select the folder. It will take some time to load the project in Unity. When it completes, open up the Assets folder in your Project tab, and double-click on the SplashScreen Unity Scene.

When you click on the SplashScreen scene, you should see:

unity-ide-screenshot-pubnub

To try out the demo, click the Play button at the top.

If you get the following error, follow the steps below:

Scene ‘MainScene’ couldn’t be loaded because it has not been added to the build settings or the AssetBundle has not been loaded.

To fix this, go to File -> Build Settings. Click the Add Open Scenes button. Then go back to your Project tab, and double-click on the MainScene, go back to your Build Settings, and click Add Open Scenes once again. Then in your Project Tab, double-click on the SplashScreen scene again and run the project.

Note: running the MainScene alone will result in an error since it requires UUID information from the SplashScreen. Make sure to always run SplashScreen first.

Running the Project

Now that your project is all set up, click the Run button, enter your username, and you should see the below screen upon submitting.

Unity-MainScene-PubNub

When you click one of the Picture buttons, you will notice that the cube texture changes. The texture changes only when the client receives a PubNub publish message which means you need an internet connection for the demo to work.

The UUID text at the bottom of the screen is your unique ID that PubNub uses to identify your user. In a real use case, you would want the UUID to be randomly generated and not shown to the end user. However, for the sake of this demo, your UUID is what you entered on the SplashScreen.

Unity-PubNub-ide-screenshot3

To check out the realtime functionality of the application, go to File -> Build Settings, and select the platform you want to build for. I recommend standalone, or WebGL. For WebGL, you will have to run a python server by typing (Mac instructions):

python -m SimpleHTTPServer 8000

Then navigate to your index.html file when you type in http://localhost:8000/ in your web browser.

When you run the windows side by side, you can see that each of the UUID’s are different, however, each screen shows who changed the cube texture last. This is PubNub in it’s simplest form. Try it out for yourself to see how realtime PubNub really is!

PubNub-GitHub-Demo-Screenshot

Open up two separate windows for the demo

How Does it Work?

SplashScreen Scene

In the SplashScreen scene, I created the script called InputScript which is attached to the GameObject InputObject.  This script takes the string entered in a textbox, and saves it as a public variable so other scenes can access it.

The MainScene has to be able to read the information that was input in the field which is why we created a DontDestroyOnLoad object. DontDestroyOnLoad stays alive even if the scene is changed or deleted.

As you can see in the inspector images below, the string in the Input Script called UUIDinput is blank, however upon submitting the form and changing scenes, the UUIDinput string is now “Jordan“.

Inspector-Screenshot1 Inspector-Screenshot2 Inspector-Screenshot3
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class InputScript : MonoBehaviour
{
  public InputField Field;
  private bool wasFocused;
  public string UUIDinput;
  public GameObject InputObject;

  void Start(){
    DontDestroyOnLoad(InputObject);
  }
  private void Update()
  {
    if (wasFocused && Input.GetKeyDown(KeyCode.Return)) {

      Submit(Field.text);
    }

    wasFocused = Field.isFocused;
  }

  private void Submit(string text)
  {
    Debug.Log("Submit=" + text);
    UUIDinput = text;
    SceneManager.LoadScene("MainScene");
  }
}

The code above is the Input Script, and listens to every frame for an input event from the keyboard. When it receives a Return response, it runs the Submit function which assigns the UUIDinput string to whatever text was input.  It then changes scenes to the MainScene.

MainScene

The MainScene consists of three Canvas buttons, a rotating cube, and some text objects.

The below code assigns each button to a variable and then associates each button with the canvas GameObjects. The function TaskOnClick is called when the listener detects a click. We then pass an int as a message through PubNub. The message is then sent to the PubNub servers which distributes the message to anyone currently subscribed to “my_channel“.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using PubNubAPI;

public class mouseButton : MonoBehaviour {
  // Use this for initisoualization
  public Button Button1;
  public Button Button2;
  public Button Button3;
  void Start () {
    Button btn1 = Button1.GetComponent<Button>();
    Button btn2 = Button2.GetComponent<Button>();
    Button btn3 = Button3.GetComponent<Button>();
    btn1.onClick.AddListener(delegate{TaskOnClick(1);});
    btn2.onClick.AddListener(delegate{TaskOnClick(2);});
    btn3.onClick.AddListener(delegate{TaskOnClick(3);});
  }
  void TaskOnClick(int btnnumber){
    if (btnnumber == 1) {
      Debug.Log ("Pressed Button 1");
    }else if (btnnumber == 2) {
      Debug.Log ("Pressed Button 2");
    }
    PublicPubNub.pubnub.Publish ()
      .Channel ("my_channel")
      .Message(btnnumber)
      .Async((result, status) => {
        if (!status.Error) {
          Debug.Log(string.Format("Publish Timetoken: {0}", result.Timetoken));
        } else {
          Debug.Log(status.Error);
          Debug.Log(status.ErrorData.Info);
        }
      });
  }
}

Now that we have code that publishes messages to a channel, let’s write code that allows clients to subscribe to the data stream.

Below we initiate PubNub and assign the UUID to be the inputResponse (which finds the DontDestroyOnLoad gameObject and grabs the UUIDinput string).

Near the bottom of the code, we call pubnub.Subscribe () which subscribes the client to the channel “my_channel“. In the Update() function we rotate the cube based on the delta time.

The pubnub.SusbcribeCallback is the function where you receive any inbound messages that are being published on “my_channel“. In the callback, we have some if statements to see what int‘s we are receiving, and then we changed the cube’s material based on that data.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using PubNubAPI;
using UnityEngine.UI;


public class PublicPubNub : MonoBehaviour {
  public static PubNub pubnub;
  public GameObject CubeObject;
  public Material Meme1;
  public Material Meme2;
  public Material Meme3;
  public float speed = 10f;
  public Text UUIDText;
  public Text lastClickText;
  public string inputResponse;

  // Use this for initialization
  void Start () {
    // Use this for initialization
    PNConfiguration pnConfiguration = new PNConfiguration ();
    pnConfiguration.PublishKey = "pub-c-efb4a8fe-605a-42e8-866a-facc7271a49d";
    pnConfiguration.SubscribeKey = "sub-c-1bb9d4ac-52f4-11e8-85c6-a6b0a876dba1";

    pnConfiguration.LogVerbosity = PNLogVerbosity.BODY;

    inputResponse= GameObject.Find("InputObject").GetComponent<InputScript>().UUIDinput.ToString();
    Debug.Log (inputResponse);
    pnConfiguration.UUID = inputResponse;

    pubnub = new PubNub(pnConfiguration);
    Debug.Log (pnConfiguration.UUID);
    UUIDText.text = "UUID: " + pnConfiguration.UUID;
    
    pubnub.SusbcribeCallback += (sender, e) => { 
      SusbcribeEventEventArgs mea = e as SusbcribeEventEventArgs;
      if (mea.MessageResult != null) {
        lastClickText.text = "Last Publish Sent by: " + mea.MessageResult.IssuingClientId.ToString();
        if((int)mea.MessageResult.Payload == 1){
          CubeObject.GetComponent<Renderer>().material = Meme1;
        } else if((int)mea.MessageResult.Payload == 2){
          CubeObject.GetComponent<Renderer>().material = Meme2;
        } else if((int)mea.MessageResult.Payload == 3){
          CubeObject.GetComponent<Renderer>().material = Meme3;
        }
      }
      if (mea.PresenceEventResult != null) {
        Debug.Log("In Example, SusbcribeCallback in presence" + mea.PresenceEventResult.Channel + mea.PresenceEventResult.Occupancy + mea.PresenceEventResult.Event);
      }
    };
    pubnub.Subscribe ()
      .Channels (new List<string> () {
      "my_channel"
    })
      .WithPresence()
      .Execute();
  }
  
  // Update is called once per frame
  void Update () {
    CubeObject.transform.Rotate(Vector3.up, speed * Time.deltaTime);
    CubeObject.transform.Rotate(0f, 0f, -3f * Time.deltaTime);
  }
}

Wrapping Up

Setting up PubNub with Unity is straightforward and seamless. To download the Unity SDK, visit our Unity documentation. You can download my demo project on my GitHub here. If you have any questions, reach out to me at schuetz@pubnub.com.

Try PubNub Today