Créez un jeu multijoueur dans Unity en utilisant PUN 2

Vous êtes-vous déjà demandé ce qu'il fallait pour créer un jeu multijoueur dans Unity ?

Contrairement aux jeux solo, les jeux multijoueurs nécessitent un serveur distant qui joue le rôle de pont, permettant aux clients du jeu de communiquer entre eux.

De nos jours, de nombreux services s'occupent de l'hébergement des serveurs. L'un de ces services est Photon Network, que nous utiliserons pour ce didacticiel.

PUN 2 est la dernière version de leur API qui a été considérablement améliorée par rapport à la version existante.

Dans cet article, nous allons télécharger les fichiers nécessaires, configurer Photon AppID et programmer un exemple multijoueur simple.

Unity version utilisée dans ce tutoriel: Unity 2018.3.0f2 (64 bits)

Partie 1: Configuration du PUN 2

La première étape consiste à télécharger un package PUN 2 à partir du Asset Store. Il contient tous les scripts et fichiers requis pour l'intégration multijoueur.

  • Ouvrez votre projet Unity puis accédez au Asset Store: (Fenêtre -> Général -> AssetStore) ou appuyez sur Ctrl+9
  • Recherchez "PUN 2- Free" puis cliquez sur le premier résultat ou cliquez ici
  • Importez le package PUN 2 une fois le téléchargement terminé

  • Une fois le package importé, vous devez créer un identifiant Photon App, cela se fait sur leur site Web: https://www.photonengine.com/
  • Créez un nouveau compte (ou connectez-vous à votre compte existant)
  • Accédez à la page Applications en cliquant sur l'icône de profil puis "Your Applications" ou suivez ce lien: https://dashboard.photonengine.com/en-US/PublicCloud
  • Sur la page Applications, cliquez sur "Create new app"

  • Sur la page de création, pour Type de photon sélectionnez "Photon Realtime" et pour Nom, tapez n'importe quel nom puis cliquez "Create"

Comme vous pouvez le constater, l'application utilise par défaut le forfait gratuit. Vous pouvez en savoir plus sur les plans tarifaires ici

  • Une fois l'application créée, copiez l'ID de l'application situé sous le nom de l'application.

  • Revenez à votre projet Unity puis allez dans Fenêtre -> Photon Unity Réseau -> Assistant PUN
  • Dans l'assistant PUN, cliquez sur "Setup Project", collez votre identifiant d'application puis cliquez sur "Setup Project"

  • Le PUN 2 est maintenant prêt !

Partie 2: Créer un jeu multijoueur

Passons maintenant à la partie où nous créons réellement un jeu multijoueur.

La façon dont le multijoueur est géré dans PUN 2 est la suivante:

  • Tout d’abord, nous nous connectons à la région Photon (ex. USA Est, Europe, Asie, etc.) qui est également connue sous le nom de Lobby.
  • Une fois dans le Lobby, nous demandons toutes les salles créées dans la région, puis nous pouvons soit rejoindre l'une des salles, soit créer notre propre salle.
  • Après avoir rejoint la salle, nous demandons une liste des joueurs connectés à la salle et instancions leurs instances de joueur, qui sont ensuite synchronisées avec leurs instances locales via PhotonView.
  • Lorsque quelqu'un quitte la salle, son instance est détruite et il est supprimé de la liste des joueurs.

1. Mise en place d'un lobby

Commençons par créer une scène Lobby qui contiendra la logique du Lobby (Parcourir les salles existantes, créer de nouvelles salles, etc.):

  • Créez un nouveau script C# et appelez-le PUN2_GameLobby
  • Créez une nouvelle scène et appelez-la "GameLobby"
  • Dans la scène GameLobby, créez un nouveau GameObject. Appelez-le "_GameLobby" et attribuez-lui le script PUN2_GameLobby

Ouvrez maintenant le script PUN2_GameLobby:

Tout d'abord, nous importons les espaces de noms Photon en ajoutant les lignes ci-dessous au début du script:

using Photon.Pun;
using Photon.Realtime;

Également avant de continuer, nous devons remplacer le MonoBehaviour par défaut par MonoBehaviourPunCallbacks. Cette étape est nécessaire pour pouvoir utiliser les rappels Photon:

public class PUN2_GameLobby : MonoBehaviourPunCallbacks

Ensuite, nous créons les variables nécessaires:

    //Our player name
    string playerName = "Player 1";
    //Users are separated from each other by gameversion (which allows you to make breaking changes).
    string gameVersion = "0.9";
    //The list of created rooms
    List<RoomInfo> createdRooms = new List<RoomInfo>();
    //Use this name when creating a Room
    string roomName = "Room 1";
    Vector2 roomListScroll = Vector2.zero;
    bool joiningRoom = false;

Ensuite, nous appelons ConnectUsingSettings() dans le void Start(). Cela signifie que dès que le jeu s'ouvre, il se connecte au serveur Photon:

    // Use this for initialization
    void Start()
    {
        //This makes sure we can use PhotonNetwork.LoadLevel() on the master client and all clients in the same room sync their level automatically
        PhotonNetwork.AutomaticallySyncScene = true;

        if (!PhotonNetwork.IsConnected)
        {
            //Set the App version before connecting
            PhotonNetwork.PhotonServerSettings.AppSettings.AppVersion = gameVersion;
            // Connect to the photon master-server. We use the settings saved in PhotonServerSettings (a .asset file in this project)
            PhotonNetwork.ConnectUsingSettings();
        }
    }

Pour savoir si une connexion à Photon a réussi, nous devons implémenter ces rappels: OnDisconnected(DisconnectCause cause), OnConnectedToMaster(), OnRoomListUpdate(List<RoomInfo> roomList)

    public override void OnDisconnected(DisconnectCause cause)
    {
        Debug.Log("OnFailedToConnectToPhoton. StatusCode: " + cause.ToString() + " ServerAddress: " + PhotonNetwork.ServerAddress);
    }

    public override void OnConnectedToMaster()
    {
        Debug.Log("OnConnectedToMaster");
        //After we connected to Master server, join the Lobby
        PhotonNetwork.JoinLobby(TypedLobby.Default);
    }

    public override void OnRoomListUpdate(List<RoomInfo> roomList)
    {
        Debug.Log("We have received the Room list");
        //After this callback, update the room list
        createdRooms = roomList;
    }

Vient ensuite la partie UI, où la navigation dans la salle et la création de la salle sont effectuées:

    void OnGUI()
    {
        GUI.Window(0, new Rect(Screen.width / 2 - 450, Screen.height / 2 - 200, 900, 400), LobbyWindow, "Lobby");
    }

    void LobbyWindow(int index)
    {
        //Connection Status and Room creation Button
        GUILayout.BeginHorizontal();

        GUILayout.Label("Status: " + PhotonNetwork.NetworkClientState);

        if (joiningRoom || !PhotonNetwork.IsConnected || PhotonNetwork.NetworkClientState != ClientState.JoinedLobby)
        {
            GUI.enabled = false;
        }

        GUILayout.FlexibleSpace();

        //Room name text field
        roomName = GUILayout.TextField(roomName, GUILayout.Width(250));

        if (GUILayout.Button("Create Room", GUILayout.Width(125)))
        {
            if (roomName != "")
            {
                joiningRoom = true;

                RoomOptions roomOptions = new RoomOptions();
                roomOptions.IsOpen = true;
                roomOptions.IsVisible = true;
                roomOptions.MaxPlayers = (byte)10; //Set any number

                PhotonNetwork.JoinOrCreateRoom(roomName, roomOptions, TypedLobby.Default);
            }
        }

        GUILayout.EndHorizontal();

        //Scroll through available rooms
        roomListScroll = GUILayout.BeginScrollView(roomListScroll, true, true);

        if (createdRooms.Count == 0)
        {
            GUILayout.Label("No Rooms were created yet...");
        }
        else
        {
            for (int i = 0; i < createdRooms.Count; i++)
            {
                GUILayout.BeginHorizontal("box");
                GUILayout.Label(createdRooms[i].Name, GUILayout.Width(400));
                GUILayout.Label(createdRooms[i].PlayerCount + "/" + createdRooms[i].MaxPlayers);

                GUILayout.FlexibleSpace();

                if (GUILayout.Button("Join Room"))
                {
                    joiningRoom = true;

                    //Set our Player name
                    PhotonNetwork.NickName = playerName;

                    //Join the Room
                    PhotonNetwork.JoinRoom(createdRooms[i].Name);
                }
                GUILayout.EndHorizontal();
            }
        }

        GUILayout.EndScrollView();

        //Set player name and Refresh Room button
        GUILayout.BeginHorizontal();

        GUILayout.Label("Player Name: ", GUILayout.Width(85));
        //Player name text field
        playerName = GUILayout.TextField(playerName, GUILayout.Width(250));

        GUILayout.FlexibleSpace();

        GUI.enabled = (PhotonNetwork.NetworkClientState == ClientState.JoinedLobby || PhotonNetwork.NetworkClientState == ClientState.Disconnected) && !joiningRoom;
        if (GUILayout.Button("Refresh", GUILayout.Width(100)))
        {
            if (PhotonNetwork.IsConnected)
            {
                //Re-join Lobby to get the latest Room list
                PhotonNetwork.JoinLobby(TypedLobby.Default);
            }
            else
            {
                //We are not connected, estabilish a new connection
                PhotonNetwork.ConnectUsingSettings();
            }
        }

        GUILayout.EndHorizontal();

        if (joiningRoom)
        {
            GUI.enabled = true;
            GUI.Label(new Rect(900 / 2 - 50, 400 / 2 - 10, 100, 20), "Connecting...");
        }
    }

Et enfin, nous implémentons 4 autres rappels: OnCreateRoomFailed(short returnCode, string message), OnJoinRoomFailed(short returnCode, string message), OnCreatedRoom(), et OnJoinedRoom().

Ces rappels sont utilisés pour déterminer si nous avons rejoint/créé la salle ou s'il y a eu des problèmes lors de la connexion.

Voici le script final PUN2_GameLobby.cs:

using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;

public class PUN2_GameLobby : MonoBehaviourPunCallbacks
{

    //Our player name
    string playerName = "Player 1";
    //Users are separated from each other by gameversion (which allows you to make breaking changes).
    string gameVersion = "0.9";
    //The list of created rooms
    List<RoomInfo> createdRooms = new List<RoomInfo>();
    //Use this name when creating a Room
    string roomName = "Room 1";
    Vector2 roomListScroll = Vector2.zero;
    bool joiningRoom = false;

    // Use this for initialization
    void Start()
    {
        //This makes sure we can use PhotonNetwork.LoadLevel() on the master client and all clients in the same room sync their level automatically
        PhotonNetwork.AutomaticallySyncScene = true;

        if (!PhotonNetwork.IsConnected)
        {
            //Set the App version before connecting
            PhotonNetwork.PhotonServerSettings.AppSettings.AppVersion = gameVersion;
            // Connect to the photon master-server. We use the settings saved in PhotonServerSettings (a .asset file in this project)
            PhotonNetwork.ConnectUsingSettings();
        }
    }

    public override void OnDisconnected(DisconnectCause cause)
    {
        Debug.Log("OnFailedToConnectToPhoton. StatusCode: " + cause.ToString() + " ServerAddress: " + PhotonNetwork.ServerAddress);
    }

    public override void OnConnectedToMaster()
    {
        Debug.Log("OnConnectedToMaster");
        //After we connected to Master server, join the Lobby
        PhotonNetwork.JoinLobby(TypedLobby.Default);
    }

    public override void OnRoomListUpdate(List<RoomInfo> roomList)
    {
        Debug.Log("We have received the Room list");
        //After this callback, update the room list
        createdRooms = roomList;
    }

    void OnGUI()
    {
        GUI.Window(0, new Rect(Screen.width / 2 - 450, Screen.height / 2 - 200, 900, 400), LobbyWindow, "Lobby");
    }

    void LobbyWindow(int index)
    {
        //Connection Status and Room creation Button
        GUILayout.BeginHorizontal();

        GUILayout.Label("Status: " + PhotonNetwork.NetworkClientState);

        if (joiningRoom || !PhotonNetwork.IsConnected || PhotonNetwork.NetworkClientState != ClientState.JoinedLobby)
        {
            GUI.enabled = false;
        }

        GUILayout.FlexibleSpace();

        //Room name text field
        roomName = GUILayout.TextField(roomName, GUILayout.Width(250));

        if (GUILayout.Button("Create Room", GUILayout.Width(125)))
        {
            if (roomName != "")
            {
                joiningRoom = true;

                RoomOptions roomOptions = new RoomOptions();
                roomOptions.IsOpen = true;
                roomOptions.IsVisible = true;
                roomOptions.MaxPlayers = (byte)10; //Set any number

                PhotonNetwork.JoinOrCreateRoom(roomName, roomOptions, TypedLobby.Default);
            }
        }

        GUILayout.EndHorizontal();

        //Scroll through available rooms
        roomListScroll = GUILayout.BeginScrollView(roomListScroll, true, true);

        if (createdRooms.Count == 0)
        {
            GUILayout.Label("No Rooms were created yet...");
        }
        else
        {
            for (int i = 0; i < createdRooms.Count; i++)
            {
                GUILayout.BeginHorizontal("box");
                GUILayout.Label(createdRooms[i].Name, GUILayout.Width(400));
                GUILayout.Label(createdRooms[i].PlayerCount + "/" + createdRooms[i].MaxPlayers);

                GUILayout.FlexibleSpace();

                if (GUILayout.Button("Join Room"))
                {
                    joiningRoom = true;

                    //Set our Player name
                    PhotonNetwork.NickName = playerName;

                    //Join the Room
                    PhotonNetwork.JoinRoom(createdRooms[i].Name);
                }
                GUILayout.EndHorizontal();
            }
        }

        GUILayout.EndScrollView();

        //Set player name and Refresh Room button
        GUILayout.BeginHorizontal();

        GUILayout.Label("Player Name: ", GUILayout.Width(85));
        //Player name text field
        playerName = GUILayout.TextField(playerName, GUILayout.Width(250));

        GUILayout.FlexibleSpace();

        GUI.enabled = (PhotonNetwork.NetworkClientState == ClientState.JoinedLobby || PhotonNetwork.NetworkClientState == ClientState.Disconnected) && !joiningRoom;
        if (GUILayout.Button("Refresh", GUILayout.Width(100)))
        {
            if (PhotonNetwork.IsConnected)
            {
                //Re-join Lobby to get the latest Room list
                PhotonNetwork.JoinLobby(TypedLobby.Default);
            }
            else
            {
                //We are not connected, estabilish a new connection
                PhotonNetwork.ConnectUsingSettings();
            }
        }

        GUILayout.EndHorizontal();

        if (joiningRoom)
        {
            GUI.enabled = true;
            GUI.Label(new Rect(900 / 2 - 50, 400 / 2 - 10, 100, 20), "Connecting...");
        }
    }

    public override void OnCreateRoomFailed(short returnCode, string message)
    {
        Debug.Log("OnCreateRoomFailed got called. This can happen if the room exists (even if not visible). Try another room name.");
        joiningRoom = false;
    }

    public override void OnJoinRoomFailed(short returnCode, string message)
    {
        Debug.Log("OnJoinRoomFailed got called. This can happen if the room is not existing or full or closed.");
        joiningRoom = false;
    }

    public override void OnJoinRandomFailed(short returnCode, string message)
    {
        Debug.Log("OnJoinRandomFailed got called. This can happen if the room is not existing or full or closed.");
        joiningRoom = false;
    }

    public override void OnCreatedRoom()
    {
        Debug.Log("OnCreatedRoom");
        //Set our player name
        PhotonNetwork.NickName = playerName;
        //Load the Scene called GameLevel (Make sure it's added to build settings)
        PhotonNetwork.LoadLevel("GameLevel");
    }

    public override void OnJoinedRoom()
    {
        Debug.Log("OnJoinedRoom");
    }
}

2. Création d'un préfabriqué Player

Dans les jeux multijoueurs, l'instance Player a 2 faces: locale et distante.

Une instance locale est contrôlée localement (par nous).

Une instance distante, en revanche, est une représentation locale de ce que fait l’autre joueur. Cela ne devrait pas être affecté par notre contribution.

Pour déterminer si l'instance est locale ou distante, nous utilisons un composant PhotonView.

PhotonView agit comme un messager qui reçoit et envoie les valeurs qui doivent être synchronisées, par exemple la position et la rotation.

Commençons donc par créer l'instance de lecteur (si votre instance de lecteur est déjà prête, vous pouvez ignorer cette étape).

Dans mon cas, l'instance Player sera un simple Cube qui est déplacé avec les touches W et S et tourné avec les touches A et D.

Voici un script de contrôleur simple:

SimplePlayerController.cs

using UnityEngine;

public class SimplePlayerController : MonoBehaviour
{

    // Update is called once per frame
    void Update()
    {
        //Move Front/Back
        if (Input.GetKey(KeyCode.W))
        {
            transform.Translate(transform.forward * Time.deltaTime * 2.45f, Space.World);
        }
        else if (Input.GetKey(KeyCode.S))
        {
            transform.Translate(-transform.forward * Time.deltaTime * 2.45f, Space.World);
        }

        //Rotate Left/Right
        if (Input.GetKey(KeyCode.A))
        {
            transform.Rotate(new Vector3(0, -14, 0) * Time.deltaTime * 4.5f, Space.Self);
        }
        else if (Input.GetKey(KeyCode.D))
        {
            transform.Rotate(new Vector3(0, 14, 0) * Time.deltaTime * 4.5f, Space.Self);
        }
    }
}

L'étape suivante consiste à ajouter un composant PhotonView.

  • Ajoutez un composant PhotonView à l'instance du joueur.
  • Créez un nouveau script C# et appelez-le PUN2_PlayerSync (ce script sera utilisé pour communiquer via PhotonView).

Ouvrez le script PUN2_PlayerSync:

Dans PUN2_PlayerSync, la première chose que nous devons faire est d'ajouter un espace de noms Photon.Pun et de remplacer MonoBehaviour par MonoBehaviourPun et d'ajouter également l'interface IPunObservable.

MonoBehaviourPun est nécessaire pour pouvoir utiliser la variable photonView mise en cache, au lieu d'utiliser GetComponent<PhotonView>().

using UnityEngine;
using Photon.Pun;

public class PUN2_PlayerSync : MonoBehaviourPun, IPunObservable

Après cela, nous pouvons créer toutes les variables nécessaires:

    //List of the scripts that should only be active for the local player (ex. PlayerController, MouseLook etc.)
    public MonoBehaviour[] localScripts;
    //List of the GameObjects that should only be active for the local player (ex. Camera, AudioListener etc.)
    public GameObject[] localObjects;
    //Values that will be synced over network
    Vector3 latestPos;
    Quaternion latestRot;

Puis dans le void Start(), nous vérifions si le lecteur est Local ou Distant en utilisant photonView.IsMine:

    // Use this for initialization
    void Start()
    {
        if (photonView.IsMine)
        {
            //Player is local
        }
        else
        {
            //Player is Remote, deactivate the scripts and object that should only be enabled for the local player
            for (int i = 0; i < localScripts.Length; i++)
            {
                localScripts[i].enabled = false;
            }
            for (int i = 0; i < localObjects.Length; i++)
            {
                localObjects[i].SetActive(false);
            }
        }
    }

La synchronisation réelle se fait via le rappel de PhotonView: OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info):

    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if (stream.IsWriting)
        {
            //We own this player: send the others our data
            stream.SendNext(transform.position);
            stream.SendNext(transform.rotation);
        }
        else
        {
            //Network player, receive data
            latestPos = (Vector3)stream.ReceiveNext();
            latestRot = (Quaternion)stream.ReceiveNext();
        }
    }

Dans ce cas, nous envoyons uniquement la position et la rotation du joueur, mais vous pouvez utiliser l'exemple ci-dessus pour envoyer toute valeur nécessaire à la synchronisation sur le réseau, à haute fréquence.

Les valeurs reçues sont ensuite appliquées dans le void Update():

    // Update is called once per frame
    void Update()
    {
        if (!photonView.IsMine)
        {
            //Update remote player (smooth this, this looks good, at the cost of some accuracy)
            transform.position = Vector3.Lerp(transform.position, latestPos, Time.deltaTime * 5);
            transform.rotation = Quaternion.Lerp(transform.rotation, latestRot, Time.deltaTime * 5);
        }
    }
}

Voici le script final PUN2_PlayerSync.cs:

using UnityEngine;
using Photon.Pun;

public class PUN2_PlayerSync : MonoBehaviourPun, IPunObservable
{

    //List of the scripts that should only be active for the local player (ex. PlayerController, MouseLook etc.)
    public MonoBehaviour[] localScripts;
    //List of the GameObjects that should only be active for the local player (ex. Camera, AudioListener etc.)
    public GameObject[] localObjects;
    //Values that will be synced over network
    Vector3 latestPos;
    Quaternion latestRot;

    // Use this for initialization
    void Start()
    {
        if (photonView.IsMine)
        {
            //Player is local
        }
        else
        {
            //Player is Remote, deactivate the scripts and object that should only be enabled for the local player
            for (int i = 0; i < localScripts.Length; i++)
            {
                localScripts[i].enabled = false;
            }
            for (int i = 0; i < localObjects.Length; i++)
            {
                localObjects[i].SetActive(false);
            }
        }
    }

    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if (stream.IsWriting)
        {
            //We own this player: send the others our data
            stream.SendNext(transform.position);
            stream.SendNext(transform.rotation);
        }
        else
        {
            //Network player, receive data
            latestPos = (Vector3)stream.ReceiveNext();
            latestRot = (Quaternion)stream.ReceiveNext();
        }
    }

    // Update is called once per frame
    void Update()
    {
        if (!photonView.IsMine)
        {
            //Update remote player (smooth this, this looks good, at the cost of some accuracy)
            transform.position = Vector3.Lerp(transform.position, latestPos, Time.deltaTime * 5);
            transform.rotation = Quaternion.Lerp(transform.rotation, latestRot, Time.deltaTime * 5);
        }
    }
}

Attribuons maintenant un script nouvellement créé:

  • Attachez le script PUN2_PlayerSync à PlayerInstance.
  • Faites glisser et déposez PUN2_PlayerSync dans les composants observés de PhotonView.
  • Attribuez le SimplePlayerController à "Local Scripts" et attribuez les GameObjects (que vous souhaitez désactiver pour les joueurs distants) au "Local Objects"

  • Enregistrez l'instance de lecteur dans Prefab et déplacez-la vers le dossier appelé Ressources (s'il n'existe pas de dossier de ce type, créez-en un). Cette étape est nécessaire pour pouvoir générer des objets multijoueurs sur le réseau.

3. Créer un niveau de jeu

GameLevel est une scène qui est chargée après avoir rejoint la salle et c'est là que se déroule toute l'action.

  • Créez une nouvelle scène et appelez-la "GameLevel" (Ou si vous souhaitez conserver un nom différent, assurez-vous de changer le nom dans cette ligne PhotonNetwork.LoadLevel("GameLevel"); dans PUN2_GameLobby.cs).

Dans mon cas, j'utiliserai une simple scène avec un avion:

  • Créez maintenant un nouveau script et appelez-le PUN2_RoomController (ce script gérera la logique à l'intérieur de la salle, comme la génération des joueurs, l'affichage de la liste des joueurs, etc.).

Ouvrez le script PUN2_RoomController:

Comme pour PUN2_GameLobby, nous commençons par ajouter des espaces de noms Photon et remplacer MonoBehaviour par MonoBehaviourPunCallbacks:

using UnityEngine;
using Photon.Pun;

public class PUN2_RoomController : MonoBehaviourPunCallbacks

Ajoutons maintenant les variables nécessaires:

    //Player instance prefab, must be located in the Resources folder
    public GameObject playerPrefab;
    //Player spawn point
    public Transform spawnPoint;

Pour instancier le préfabriqué Player, nous utilisons PhotonNetwork.Instantiate:

    // Use this for initialization
    void Start()
    {
        //In case we started this demo with the wrong scene being active, simply load the menu scene
        if (PhotonNetwork.CurrentRoom == null)
        {
            Debug.Log("Is not in the room, returning back to Lobby");
            UnityEngine.SceneManagement.SceneManager.LoadScene("GameLobby");
            return;
        }

        //We're in a room. spawn a character for the local player. it gets synced by using PhotonNetwork.Instantiate
        PhotonNetwork.Instantiate(playerPrefab.name, spawnPoint.position, Quaternion.identity, 0);
    }

Et une UI simple avec un bouton "Leave Room" et quelques éléments supplémentaires tels que le nom de la salle et la liste des joueurs connectés:

    void OnGUI()
    {
        if (PhotonNetwork.CurrentRoom == null)
            return;

        //Leave this Room
        if (GUI.Button(new Rect(5, 5, 125, 25), "Leave Room"))
        {
            PhotonNetwork.LeaveRoom();
        }

        //Show the Room name
        GUI.Label(new Rect(135, 5, 200, 25), PhotonNetwork.CurrentRoom.Name);

        //Show the list of the players connected to this Room
        for (int i = 0; i < PhotonNetwork.PlayerList.Length; i++)
        {
            //Show if this player is a Master Client. There can only be one Master Client per Room so use this to define the authoritative logic etc.)
            string isMasterClient = (PhotonNetwork.PlayerList[i].IsMasterClient ? ": MasterClient" : "");
            GUI.Label(new Rect(5, 35 + 30 * i, 200, 25), PhotonNetwork.PlayerList[i].NickName + isMasterClient);
        }
    }

Enfin, nous implémentons un autre rappel PhotonNetwork appelé OnLeftRoom() qui est appelé lorsque nous quittons la salle:

    public override void OnLeftRoom()
    {
        //We have left the Room, return back to the GameLobby
        UnityEngine.SceneManagement.SceneManager.LoadScene("GameLobby");
    }

Voici le script final PUN2_RoomController.cs:

using UnityEngine;
using Photon.Pun;

public class PUN2_RoomController : MonoBehaviourPunCallbacks
{

    //Player instance prefab, must be located in the Resources folder
    public GameObject playerPrefab;
    //Player spawn point
    public Transform spawnPoint;

    // Use this for initialization
    void Start()
    {
        //In case we started this demo with the wrong scene being active, simply load the menu scene
        if (PhotonNetwork.CurrentRoom == null)
        {
            Debug.Log("Is not in the room, returning back to Lobby");
            UnityEngine.SceneManagement.SceneManager.LoadScene("GameLobby");
            return;
        }

        //We're in a room. spawn a character for the local player. it gets synced by using PhotonNetwork.Instantiate
        PhotonNetwork.Instantiate(playerPrefab.name, spawnPoint.position, Quaternion.identity, 0);
    }

    void OnGUI()
    {
        if (PhotonNetwork.CurrentRoom == null)
            return;

        //Leave this Room
        if (GUI.Button(new Rect(5, 5, 125, 25), "Leave Room"))
        {
            PhotonNetwork.LeaveRoom();
        }

        //Show the Room name
        GUI.Label(new Rect(135, 5, 200, 25), PhotonNetwork.CurrentRoom.Name);

        //Show the list of the players connected to this Room
        for (int i = 0; i < PhotonNetwork.PlayerList.Length; i++)
        {
            //Show if this player is a Master Client. There can only be one Master Client per Room so use this to define the authoritative logic etc.)
            string isMasterClient = (PhotonNetwork.PlayerList[i].IsMasterClient ? ": MasterClient" : "");
            GUI.Label(new Rect(5, 35 + 30 * i, 200, 25), PhotonNetwork.PlayerList[i].NickName + isMasterClient);
        }
    }

    public override void OnLeftRoom()
    {
        //We have left the Room, return back to the GameLobby
        UnityEngine.SceneManagement.SceneManager.LoadScene("GameLobby");
    }
}
  • Créez un nouveau GameObject dans la scène 'GameLevel' et appelez-le "_RoomController"
  • Attachez le script PUN2_RoomController à l'objet _RoomController
  • Attribuez-lui le préfabriqué PlayerInstance et une transformation SpawnPoint, puis enregistrez la scène.

  • Ajoutez à la fois MainMenu et GameLevel aux paramètres de construction.

4. Faire un test

Il est maintenant temps de créer un build et de le tester:

Tout fonctionne comme prévu !

Prime

RPC

Dans PUN 2, RPC signifie Remote Procedure Call, il est utilisé pour appeler une fonction sur des clients distants qui se trouvent dans la même pièce (vous pouvez en savoir plus à ce sujet ici).

Les RPC ont de nombreuses utilisations, par exemple, disons que vous devez envoyer un message de discussion à tous les joueurs de la salle. Avec les RPC, c'est facile à faire:

[PunRPC]
void ChatMessage(string senderName, string messageText)
{
    Debug.Log(string.Format("{0}: {1}", senderName, messageText));
}

Notez le [PunRPC] avant la fonction. Cet attribut est nécessaire si vous prévoyez d'appeler la fonction via des RPC.

Pour appeler les fonctions marquées comme RPC, vous avez besoin d'un PhotonView. Exemple d'appel:

PhotonView photonView = PhotonView.Get(this);
photonView.RPC("ChatMessage", RpcTarget.All, PhotonNetwork.playerName, "Some message");

Conseil de pro: si vous remplacez MonoBehaviour dans votre script par MonoBehaviourPun ou MonoBehaviourPunCallbacks, vous pouvez ignorer PhotonView.Get() et utiliser directement photonView.RPC().

Propriétés personnalisées

Dans PUN 2, les propriétés personnalisées sont une table de hachage qui peut être attribuée à un joueur ou à la salle.

Ceci est utile lorsque vous devez définir des données persistantes qui n'ont pas besoin d'être modifiées fréquemment (par exemple, nom de l'équipe du joueur, mode de jeu de la salle, etc.).

Tout d'abord, vous devez définir une table de hachage, ce qui se fait en ajoutant la ligne ci-dessous au début du script:

//Replace default Hashtables with Photon hashtables
using Hashtable = ExitGames.Client.Photon.Hashtable;

L'exemple ci-dessous définit les propriétés de la pièce appelées "GameMode" et "AnotherProperty":

        //Set Room properties (Only Master Client is allowed to set Room properties)
        if (PhotonNetwork.IsMasterClient)
        {
            Hashtable setRoomProperties = new Hashtable();
            setRoomProperties.Add("GameMode", "FFA");
            setRoomProperties.Add("AnotherProperty", "Test");
            PhotonNetwork.CurrentRoom.SetCustomProperties(setRoomProperties);
        }

        //Will print "FFA"
        print((string)PhotonNetwork.CurrentRoom.CustomProperties["GameMode"]);
        //Will print "Test"
        print((string)PhotonNetwork.CurrentRoom.CustomProperties["AnotherProperty"]);

Les propriétés du joueur sont définies de la même manière:

            Hashtable setPlayerProperties = new Hashtable();
            setPlayerProperties.Add("PlayerHP", (float)100);
            PhotonNetwork.LocalPlayer.SetCustomProperties(setPlayerProperties);

            print((float)PhotonNetwork.LocalPlayer.CustomProperties["PlayerHP"]);

Pour supprimer une propriété spécifique, définissez simplement sa valeur sur null.

            Hashtable setPlayerProperties = new Hashtable();
            setPlayerProperties.Add("PlayerHP", null);
            PhotonNetwork.LocalPlayer.SetCustomProperties(setPlayerProperties);

Tutoriels supplémentaires:

Synchroniser les corps rigides sur le réseau à l'aide de PUN 2

PUN 2 Ajout d'un chat en salle

Source
📁PUN2Guide.unitypackage14.00 MB
Articles suggérés
Synchroniser les corps rigides sur le réseau à l'aide de PUN 2
Unity ajoute un chat multijoueur aux salles PUN 2
Compression de données multijoueur et manipulation de bits
Créez un jeu de voiture multijoueur avec PUN 2
Créer des jeux multijoueurs en réseau dans Unity
Guide du débutant Photon Network (classique)
Introduction à Photon Fusion 2 dans Unity