Posted on

PUN 2 in Unity: Make a Game Multiplayer Lesson 3

For this lesson, we will teach you how to set up your paddle prefab so it is networked and synchronized with all connected clients. Now the first thing that we want to do is convert are paddle prefab into an object that can be instantiated across the network. Go to the prefabs folder that has are paddle prefab saved and we’re going to double click on are prefab which will open it up. The first thing that we need to do to network this object is to attach the Photon View component. With your prefab selected click on Add Component in the Inspector then search Photon and click on Photon View.

The next thing that we need to do is move this prefab to the Resources folder and you should already have the resources folder from importing are matchmaking add on. Drag the Paddle prefab from its current location to the PhotonPrefabs folder inside the Resources folder. This will make it so this object can now be spawned across the network.

Now let us change some code so that the paddle prefabs will be instantiated instead of the capsule object. To do this we will need to open the GameSetupController script. Once opened, find the line were we are instantiating the PhotonPlayer prefab. Change the string parameter so it will instantiate the paddle prefab instead. At this point both players’ paddles will be spawned in the same location to fix this we will need to make it so one of the paddles spawn at the rotation of 180 in the z-axis. We will do this based on if the player is the master client or not.

Unlock Code and Member Content

GameSetupController.cs

using Photon.Pun;
using System.IO;
using UnityEngine;
public class GameSetupController : MonoBehaviour
{
    // This script will be added to any multiplayer scene
    void Start()
    {
        CreatePlayer(); //Create a networked player object for each player that loads into the multiplayer scenes.
    }
    private void CreatePlayer()
    {
        Debug.Log("Creating Player");
        if (PhotonNetwork.IsMasterClient)
        {
            PhotonNetwork.Instantiate(Path.Combine("PhotonPrefabs", "Paddle"), Vector3.zero, Quaternion.identity);
        }
        else
        {
            PhotonNetwork.Instantiate(Path.Combine("PhotonPrefabs", "Paddle"), Vector3.zero, Quaternion.Euler(new Vector3(0,0,180)));
        }
    }
}
using Photon.Chat;
using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class PhotonChatManager : MonoBehaviour, IChatClientListener
{
    #region Setup

    [SerializeField] GameObject joinChatButton;
    ChatClient chatClient;
    bool isConnected;
    [SerializeField] string username;

    public void UsernameOnValueChange(string valueIn)
    {
        username = valueIn;
    }

    public void ChatConnectOnClick()
    {
        isConnected = true;
        chatClient = new ChatClient(this);
        //chatClient.ChatRegion = "US";
        chatClient.Connect(PhotonNetwork.PhotonServerSettings.AppSettings.AppIdChat, PhotonNetwork.AppVersion, new AuthenticationValues(username));
        Debug.Log("Connenting");
    }

    #endregion Setup

    #region General

    [SerializeField] GameObject chatPanel;
    string privateReceiver = "";
    string currentChat;
    [SerializeField] InputField chatField;
    [SerializeField] Text chatDisplay;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        if (isConnected)
        {
            chatClient.Service();
        }

        if (chatField.text != "" && Input.GetKey(KeyCode.Return))
        {
            SubmitPublicChatOnClick();
            SubmitPrivateChatOnClick();
        }
    }

    #endregion General

    #region PublicChat

    public void SubmitPublicChatOnClick()
    {
        if (privateReceiver == "")
        {
            chatClient.PublishMessage("RegionChannel", currentChat);
            chatField.text = "";
            currentChat = "";
        }
    }

    public void TypeChatOnValueChange(string valueIn)
    {
        currentChat = valueIn;
    }

    #endregion PublicChat

    #region PrivateChat

    public void ReceiverOnValueChange(string valueIn)
    {
        privateReceiver = valueIn;
    }

    public void SubmitPrivateChatOnClick()
    {
        if (privateReceiver != "")
        {
            chatClient.SendPrivateMessage(privateReceiver, currentChat);
            chatField.text = "";
            currentChat = "";
        }
    }

    #endregion PrivateChat

    #region Callbacks

    public void DebugReturn(DebugLevel level, string message)
    {
        //throw new System.NotImplementedException();
    }

    public void OnChatStateChange(ChatState state)
    {
        if(state == ChatState.Uninitialized)
        {
            isConnected = false;
            joinChatButton.SetActive(true);
            chatPanel.SetActive(false);
        }
    }

    public void OnConnected()
    {
        Debug.Log("Connected");
        joinChatButton.SetActive(false);
        chatClient.Subscribe(new string[] { "RegionChannel" });
    }

    public void OnDisconnected()
    {
        isConnected = false;
        joinChatButton.SetActive(true);
        chatPanel.SetActive(false);
    }

    public void OnGetMessages(string channelName, string[] senders, object[] messages)
    {
        string msgs = "";
        for (int i = 0; i < senders.Length; i++)
        {
            msgs = string.Format("{0}: {1}", senders[i], messages[i]);

            chatDisplay.text += "\n" + msgs;

            Debug.Log(msgs);
        }

    }

    public void OnPrivateMessage(string sender, object message, string channelName)
    {
        string msgs = "";

        msgs = string.Format("(Private) {0}: {1}", sender, message);

        chatDisplay.text += "\n " + msgs;

        Debug.Log(msgs);
        
    }

    public void OnStatusUpdate(string user, int status, bool gotMessage, object message)
    {
        throw new System.NotImplementedException();
    }

    public void OnSubscribed(string[] channels, bool[] results)
    {
        chatPanel.SetActive(true);
    }

    public void OnUnsubscribed(string[] channels)
    {
        throw new System.NotImplementedException();
    }

    public void OnUserSubscribed(string channel, string user)
    {
        throw new System.NotImplementedException();
    }

    public void OnUserUnsubscribed(string channel, string user)
    {
        throw new System.NotImplementedException();
    }

    #endregion Callbacks
}

Now that we have the spawning of our paddles figured out we need to synchronize the movement of the paddles across the network. To do this we first need to add the Photon Transform View component to our paddle prefab. With the prefab selected click Add Component, then with “photon” in the search field select Photon Transform View. You will want to make sure you are only syncing the Position of the paddle and then drag the Photon Transform View component into the Observable Components field of the Photon View.

  • – Add Photon View and Photon Transform View components
    • Sprite – No changes.

Now we need to make some changes to our PaddleController script so we are only controlling the paddle object if we are the owner. To do this add a PhotonView variable then use the IsMine value to check if you are the owner before calling the PaddleMovement function.

PaddleController.cs

using UnityEngine;
using Photon.Pun;
public class PaddleController : MonoBehaviour
{
    private PhotonView myPV;
    public string leftKey, rightKey;
    public float speed;
    private void Start()
    {
        myPV = GetComponent<PhotonView>();
    }
    void Update()
    {
        if (myPV.IsMine)
        {
            PaddleMovement();
        }
    }
    void PaddleMovement()
    {
        if (Input.GetKey(leftKey) && transform.position.x > -4)
        {
            transform.Translate(Vector3.left * Time.deltaTime * speed, Space.World);
        }
        if (Input.GetKey(rightKey) && transform.position.x < 4)
        {
            transform.Translate(Vector3.right * Time.deltaTime * speed, Space.World);
        }
    }
}
using Photon.Chat;
using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class PhotonChatManager : MonoBehaviour, IChatClientListener
{
    #region Setup

    [SerializeField] GameObject joinChatButton;
    ChatClient chatClient;
    bool isConnected;
    [SerializeField] string username;

    public void UsernameOnValueChange(string valueIn)
    {
        username = valueIn;
    }

    public void ChatConnectOnClick()
    {
        isConnected = true;
        chatClient = new ChatClient(this);
        //chatClient.ChatRegion = "US";
        chatClient.Connect(PhotonNetwork.PhotonServerSettings.AppSettings.AppIdChat, PhotonNetwork.AppVersion, new AuthenticationValues(username));
        Debug.Log("Connenting");
    }

    #endregion Setup

    #region General

    [SerializeField] GameObject chatPanel;
    string privateReceiver = "";
    string currentChat;
    [SerializeField] InputField chatField;
    [SerializeField] Text chatDisplay;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        if (isConnected)
        {
            chatClient.Service();
        }

        if (chatField.text != "" &amp;&amp; Input.GetKey(KeyCode.Return))
        {
            SubmitPublicChatOnClick();
            SubmitPrivateChatOnClick();
        }
    }

    #endregion General

    #region PublicChat

    public void SubmitPublicChatOnClick()
    {
        if (privateReceiver == "")
        {
            chatClient.PublishMessage("RegionChannel", currentChat);
            chatField.text = "";
            currentChat = "";
        }
    }

    public void TypeChatOnValueChange(string valueIn)
    {
        currentChat = valueIn;
    }

    #endregion PublicChat

    #region PrivateChat

    public void ReceiverOnValueChange(string valueIn)
    {
        privateReceiver = valueIn;
    }

    public void SubmitPrivateChatOnClick()
    {
        if (privateReceiver != "")
        {
            chatClient.SendPrivateMessage(privateReceiver, currentChat);
            chatField.text = "";
            currentChat = "";
        }
    }

    #endregion PrivateChat

    #region Callbacks

    public void DebugReturn(DebugLevel level, string message)
    {
        //throw new System.NotImplementedException();
    }

    public void OnChatStateChange(ChatState state)
    {
        if(state == ChatState.Uninitialized)
        {
            isConnected = false;
            joinChatButton.SetActive(true);
            chatPanel.SetActive(false);
        }
    }

    public void OnConnected()
    {
        Debug.Log("Connected");
        joinChatButton.SetActive(false);
        chatClient.Subscribe(new string[] { "RegionChannel" });
    }

    public void OnDisconnected()
    {
        isConnected = false;
        joinChatButton.SetActive(true);
        chatPanel.SetActive(false);
    }

    public void OnGetMessages(string channelName, string[] senders, object[] messages)
    {
        string msgs = "";
        for (int i = 0; i &lt; senders.Length; i++)
        {
            msgs = string.Format("{0}: {1}", senders[i], messages[i]);

            chatDisplay.text += "\n" + msgs;

            Debug.Log(msgs);
        }

    }

    public void OnPrivateMessage(string sender, object message, string channelName)
    {
        string msgs = "";

        msgs = string.Format("(Private) {0}: {1}", sender, message);

        chatDisplay.text += "\n " + msgs;

        Debug.Log(msgs);
        
    }

    public void OnStatusUpdate(string user, int status, bool gotMessage, object message)
    {
        throw new System.NotImplementedException();
    }

    public void OnSubscribed(string[] channels, bool[] results)
    {
        chatPanel.SetActive(true);
    }

    public void OnUnsubscribed(string[] channels)
    {
        throw new System.NotImplementedException();
    }

    public void OnUserSubscribed(string channel, string user)
    {
        throw new System.NotImplementedException();
    }

    public void OnUserUnsubscribed(string channel, string user)
    {
        throw new System.NotImplementedException();
    }

    #endregion Callbacks
}
Posted on

PUN 2 in Unity: Make a Game Multiplayer Lesson 1

Welcome to this tutorial series on how to convert your games into online multiplayers. We will be using our Pong Unity project for this series and we will turn it into a networked multiplayer game. This series will teach you general principles that can be applied to any game you want to convert from single-player into a networked multiplayer game.

If you are not familiar with Pong and would to play it first, you can play our version here.

Preparation for making Pong Multiplayer

Setting up your project

I will already assume you have the basics (Computer, Internet, Unity…) however you will also need your own version of Pong for Unity. You can either create your own by following along with our basic Pong tutorial series or you can purchase it already premade from our asset store here. (If you are a member this product is free)

If you already have your own version of Pong you can go ahead and open it up in Unity but if you are purchasing our version from the store then you will need to import the Unity package into a new project. After importing, you will need to set up the build order for your scenes. Go to File>Build Setting then add the MainMenu scene to the Scene in Build so it has the scene index of 0. Then add the GameScene so it has the scene index of 1.

Setting up Photon PUN

Once you have your Unity project set up we then need to import the Photon PUN 2 plugin. To do this you will want to open up the Unity Asset Store and search PUN 2. You can then select the free version and you will need to click Import. After it is finished loading there should be an import window that pops up. To avoid getting any errors, you can just click Import with everything selected (This may take a second).

After you have imported the Photon PUN 2 plugin, it is time to connect your Unity project to a Photon project. To do this you will need to go to https://www.photonengine.com/. Here you will need to create a new account if you do not have one already (they are free), or you can log in if you do have an account. In the Photon Dashboard, you will need to create a new app. For Photon Type select Photon PUN, give your app a name and fill in the rest of the information, after which click Create. You will then need to copy the App ID of your newly created project and go back to Unity.

Inside Unity go to Window > Photon Unity Network > setup which will open a new window. Inside this window paste in your App ID and then click Setup Project. This will connect your Unity Project to your Photon App. Now we are ready to start networking and converting our Pong game into a networked multiplayer game.

Posted on

How to make Pong in Unity Lesson 6

Play Classic Pong

Now we need to build our main menu scene to keep things simple I will cover only the most necessary objects but if you want your menu scene to look a little more interesting you should copy most of the visual elements from the game scene only remove they scripts.

To get started let us create a new C# script. Add it to your Scripts folder and call MMController, then open it up. Inside this script, we need to add the SceneManagement namespace to the top. The add one public function of type void called Play. Inside this function load to scene 1. This function will be paired with the play button of our menu scene. The last thing you could do for this script is to add a way for players to quit your application. This will be done in the Update function and should be triggered with the press of the escape key.

MMController.cs

using UnityEngine;
using UnityEngine.SceneManagement;


public class MMController : MonoBehaviour {

    
	public void Play()
	{
		SceneManager.LoadScene(1);

	}
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            Application.Quit();
        }
    }
}
using Photon.Chat;
using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class PhotonChatManager : MonoBehaviour, IChatClientListener
{
    #region Setup

    [SerializeField] GameObject joinChatButton;
    ChatClient chatClient;
    bool isConnected;
    [SerializeField] string username;

    public void UsernameOnValueChange(string valueIn)
    {
        username = valueIn;
    }

    public void ChatConnectOnClick()
    {
        isConnected = true;
        chatClient = new ChatClient(this);
        //chatClient.ChatRegion = "US";
        chatClient.Connect(PhotonNetwork.PhotonServerSettings.AppSettings.AppIdChat, PhotonNetwork.AppVersion, new AuthenticationValues(username));
        Debug.Log("Connenting");
    }

    #endregion Setup

    #region General

    [SerializeField] GameObject chatPanel;
    string privateReceiver = "";
    string currentChat;
    [SerializeField] InputField chatField;
    [SerializeField] Text chatDisplay;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        if (isConnected)
        {
            chatClient.Service();
        }

        if (chatField.text != "" &amp;&amp; Input.GetKey(KeyCode.Return))
        {
            SubmitPublicChatOnClick();
            SubmitPrivateChatOnClick();
        }
    }

    #endregion General

    #region PublicChat

    public void SubmitPublicChatOnClick()
    {
        if (privateReceiver == "")
        {
            chatClient.PublishMessage("RegionChannel", currentChat);
            chatField.text = "";
            currentChat = "";
        }
    }

    public void TypeChatOnValueChange(string valueIn)
    {
        currentChat = valueIn;
    }

    #endregion PublicChat

    #region PrivateChat

    public void ReceiverOnValueChange(string valueIn)
    {
        privateReceiver = valueIn;
    }

    public void SubmitPrivateChatOnClick()
    {
        if (privateReceiver != "")
        {
            chatClient.SendPrivateMessage(privateReceiver, currentChat);
            chatField.text = "";
            currentChat = "";
        }
    }

    #endregion PrivateChat

    #region Callbacks

    public void DebugReturn(DebugLevel level, string message)
    {
        //throw new System.NotImplementedException();
    }

    public void OnChatStateChange(ChatState state)
    {
        if(state == ChatState.Uninitialized)
        {
            isConnected = false;
            joinChatButton.SetActive(true);
            chatPanel.SetActive(false);
        }
    }

    public void OnConnected()
    {
        Debug.Log("Connected");
        joinChatButton.SetActive(false);
        chatClient.Subscribe(new string[] { "RegionChannel" });
    }

    public void OnDisconnected()
    {
        isConnected = false;
        joinChatButton.SetActive(true);
        chatPanel.SetActive(false);
    }

    public void OnGetMessages(string channelName, string[] senders, object[] messages)
    {
        string msgs = "";
        for (int i = 0; i &lt; senders.Length; i++)
        {
            msgs = string.Format("{0}: {1}", senders[i], messages[i]);

            chatDisplay.text += "\n" + msgs;

            Debug.Log(msgs);
        }

    }

    public void OnPrivateMessage(string sender, object message, string channelName)
    {
        string msgs = "";

        msgs = string.Format("(Private) {0}: {1}", sender, message);

        chatDisplay.text += "\n " + msgs;

        Debug.Log(msgs);
        
    }

    public void OnStatusUpdate(string user, int status, bool gotMessage, object message)
    {
        throw new System.NotImplementedException();
    }

    public void OnSubscribed(string[] channels, bool[] results)
    {
        chatPanel.SetActive(true);
    }

    public void OnUnsubscribed(string[] channels)
    {
        throw new System.NotImplementedException();
    }

    public void OnUserSubscribed(string channel, string user)
    {
        throw new System.NotImplementedException();
    }

    public void OnUserUnsubscribed(string channel, string user)
    {
        throw new System.NotImplementedException();
    }

    #endregion Callbacks
}

Now save this script and go back to Unity.

Add the following objects to your menu scene hierarchy.

  • MainMenuController: This is an empty object with the new MMController attached to it.
  • : This is just the default canvas object with one small change to its Canvas Scaler.
    • MainPanel: This is the default UI panel object with a transparent image.
    • : A large text object with value “Pong”
    • : A UI button with the Play function for its Onclick event
      • Text: The default text that comes with the UI button. The Text value says “Play” and size the text to fit the button.

This is what your game view should look like

Now when you test your project you should be able to make a complete gameplay circle.

This concludes this short series on how to make the bear bones of Pong.