Faceți un joc multiplayer în Unity folosind PUN 2

V-ați întrebat vreodată ce este nevoie pentru a crea un joc multiplayer în Unity?

Spre deosebire de jocurile cu un singur jucător, jocurile multiplayer necesită un server la distanță care joacă rolul de punte, permițând clienților de joc să comunice între ei.

În prezent, numeroase servicii se ocupă de găzduirea serverului. Un astfel de serviciu este Photon Network, pe care îl vom folosi pentru acest tutorial.

PUN 2 este cea mai recentă versiune a API-ului lor, care a fost mult îmbunătățită în comparație cu versiunea moștenită.

În această postare, vom rula prin descărcarea fișierelor necesare, configurarea Photon AppID și programarea unui exemplu simplu de multiplayer.

Unity versiune folosită în acest tutorial: Unity 2018.3.0f2 (64 de biți)

Partea 1: Configurarea PUN 2

Primul pas este descărcarea unui pachet PUN 2 din Asset Store. Conține toate scripturile și fișierele necesare pentru integrarea multiplayer.

  • Deschideți proiectul Unity, apoi accesați Asset Store: (Fereastra -> General -> AssetStore) sau apăsați Ctrl+9
  • Căutați „PUN 2- Free” apoi faceți clic pe primul rezultat sau faceți clic aici
  • Importați pachetul PUN 2 după ce descărcarea este terminată

  • După ce pachetul este importat, trebuie să creați un ID de aplicație Photon, acest lucru se face pe site-ul lor: https://www.photonengine.com/
  • Creați un cont nou (sau conectați-vă la contul dvs. existent)
  • Accesați pagina Aplicații făcând clic pe pictograma profilului, apoi pe "Your Applications" sau urmați acest link: https://dashboard.photonengine.com/en-US/PublicCloud
  • În pagina Aplicații, faceți clic "Create new app"

  • Pe pagina de creare, pentru Photon Type selectați "Photon Realtime" și pentru Name, introduceți orice nume, apoi faceți clic "Create"

După cum puteți vedea, aplicația este implicită la planul gratuit. Puteți citi mai multe despre planurile de prețuri aici

  • Odată ce aplicația este creată, copiați ID-ul aplicației aflat sub numele aplicației

  • Reveniți la proiectul dvs. Unity, apoi accesați Window -> Photon Unity Networking -> PUN Wizard
  • În PUN Wizard, faceți clic pe "Setup Project", inserați ID-ul aplicației, apoi faceți clic "Setup Project"

  • PUN 2 este acum gata!

Partea 2: Crearea unui joc multiplayer

Acum să trecem la partea în care creăm de fapt un joc multiplayer.

Modul în care este gestionat multiplayerul în PUN 2 este:

  • În primul rând, ne conectăm la Regiunea Photon (ex. SUA Est, Europa, Asia etc.) care este cunoscută și sub numele de Lobby.
  • Odată ajuns în hol, solicităm toate camerele care sunt create în regiune și apoi ne putem alătura fie uneia dintre camere, fie să ne creăm propria cameră.
  • După ce ne alăturăm camerei, solicităm o listă a jucătorilor conectați la cameră și le instanțiem instanțele Player, care sunt apoi sincronizate cu instanțele locale prin PhotonView.
  • Când cineva părăsește Camera, instanța sa este distrusă și este eliminat din Lista de jucători.

1. Crearea unui hol

Să începem prin a crea o scenă Lobby care va conține logica Lobby-ului (Rasfoirea camerelor existente, crearea de noi camere etc.):

  • Creați un nou script C# și numiți-l PUN2_GameLobby
  • Creați o scenă nouă și apelați-o "GameLobby"
  • În scena GameLobby, creați un nou GameObject. Apelați-l "_GameLobby" și atribuiți-i scriptul PUN2_GameLobby

Acum deschideți scriptul PUN2_GameLobby:

Mai întâi, importăm spațiile de nume Photon adăugând liniile de mai jos la începutul scriptului:

using Photon.Pun;
using Photon.Realtime;

De asemenea, înainte de a continua, trebuie să înlocuim MonoBehaviour implicit cu MonoBehaviourPunCallbacks. Acest pas este necesar pentru a putea folosi apelurile inverse Photon:

public class PUN2_GameLobby : MonoBehaviourPunCallbacks

În continuare, creăm variabilele necesare:

    //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;

Apoi numim ConnectUsingSettings() în void Start(). Aceasta înseamnă că, de îndată ce jocul se deschide, acesta se conectează la Serverul 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();
        }
    }

Pentru a ști dacă o conexiune la Photon a avut succes, trebuie să implementăm aceste apeluri inverse: 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;
    }

Urmează partea interfață de utilizare, unde se fac navigarea camerei și crearea camerei:

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

Și, în sfârșit, implementăm alte 4 apeluri inverse: OnCreateRoomFailed(short returnCode, mesaj șir), OnJoinRoomFailed(short returnCode, mesaj șir), OnCreatedRoom() și OnJoinedRoom().

Aceste apeluri inverse sunt folosite pentru a determina dacă ne-am alăturat/creat camera sau dacă au existat probleme în timpul conexiunii.

Iată scriptul 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. Crearea unui prefabricat Player

În jocurile Multiplayer, instanța Player are 2 părți: Locală și La distanță

O instanță locală este controlată local (de noi).

O instanță la distanță, pe de altă parte, este o reprezentare locală a ceea ce face celălalt jucător. Ar trebui să fie neafectat de contribuția noastră.

Pentru a determina dacă instanța este locală sau la distanță, folosim o componentă PhotonView.

PhotonView acționează ca un mesager care primește și trimite valorile care trebuie sincronizate, de exemplu, poziție și rotație.

Deci, să începem prin a crea instanța de jucător (Dacă aveți deja instanța de jucător pregătită, puteți sări peste acest pas).

În cazul meu, instanța Player va fi un simplu cub care este mutat cu tastele W și S și rotit cu tastele A și D.

Iată un script simplu pentru controler:

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

Următorul pas este să adăugați o componentă PhotonView.

  • Adăugați o componentă PhotonView la Player Instance.
  • Creați un nou script C# și numiți-l PUN2_PlayerSync (acest script va fi folosit pentru a comunica prin PhotonView).

Deschideți scriptul PUN2_PlayerSync:

În PUN2_PlayerSync, primul lucru pe care trebuie să-l facem este să adăugăm un spațiu de nume Photon.Pun și să înlocuim MonoBehaviour cu MonoBehaviourPun și, de asemenea, să adăugăm interfața IPunObservable.

MonoBehaviourPun este necesar pentru a putea folosi variabila photonView stocată în cache, în loc să utilizați GetComponent<PhotonView>().

using UnityEngine;
using Photon.Pun;

public class PUN2_PlayerSync : MonoBehaviourPun, IPunObservable

După aceea, putem trece pentru a crea toate variabilele necesare:

    //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;

Apoi, în void Start(), verificăm dacă playerul este Local sau La distanță utilizând 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);
            }
        }
    }

Sincronizarea reală se face prin apelul invers al PhotonView: OnPhotonSerializeView(Flux PhotonStream, Info PhotonMessageInfo):

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

În acest caz, trimitem doar Poziția și Rotația jucătorului, dar puteți folosi exemplul de mai sus pentru a trimite orice valoare care este necesară pentru a fi sincronizată prin rețea, la o frecvență înaltă.

Valorile primite sunt apoi aplicate în 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);
        }
    }
}

Iată scriptul 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);
        }
    }
}

Acum să atribuim un script nou creat:

  • Atașați scriptul PUN2_PlayerSync la PlayerInstance.
  • Trageți și plasați PUN2_PlayerSync în Componentele observate PhotonView.
  • Atribuiți SimplePlayerController la "Local Scripts" și atribuiți GameObjects (pe care doriți să le dezactivați pentru jucătorii de la distanță) "Local Objects"

  • Salvați PlayerInstance în Prefab și mutați-l în folderul numit Resurse (Dacă nu există un astfel de folder, creați unul). Acest pas este necesar pentru a putea genera obiecte multiplayer prin rețea.

3. Crearea unui nivel de joc

GameLevel este o scenă care se încarcă după alăturarea camerei și este locul în care se întâmplă toată acțiunea.

  • Creați o nouă Scenă și numiți-o "GameLevel" (Sau dacă doriți să păstrați un alt nume, asigurați-vă că schimbați numele în această linie PhotonNetwork.LoadLevel ("GameLevel"); la PUN2_GameLobby.cs).

În cazul meu, voi folosi o scenă simplă cu un avion:

  • Acum creați un nou script și numiți-l PUN2_RoomController (Acest script se va ocupa de logica din interiorul camerei, cum ar fi generarea jucătorilor, afișarea listei de jucători etc.).

Deschideți scriptul PUN2_RoomController:

La fel ca PUN2_GameLobby, începem prin adăugarea spațiilor de nume Photon și înlocuirea MonoBehaviour cu MonoBehaviourPunCallbacks:

using UnityEngine;
using Photon.Pun;

public class PUN2_RoomController : MonoBehaviourPunCallbacks

Acum să adăugăm variabilele necesare:

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

Pentru a instanția prefabricatul Player, folosim 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);
    }

Și o interfață simplă cu un buton "Leave Room" și câteva elemente suplimentare, cum ar fi numele camerei și lista de jucători conectați:

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

În cele din urmă, implementăm un alt apel invers PhotonNetwork numit OnLeftRoom() care este apelat când părăsim camera:

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

Iată scriptul 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");
    }
}
  • Creați un nou GameObject în scena 'GameLevel' și apelați-l "_RoomController"
  • Atașați scriptul PUN2_RoomController la obiectul _RoomController
  • Atribuiți-i prefabricatul PlayerInstance și o transformare SpawnPoint, apoi salvați scena

  • Adăugați atât MainMenu, cât și GameLevel la setările de compilare.

4. Efectuarea unui test Build

Acum este timpul să faceți o construcție și să o testați:

Totul funcționează conform așteptărilor!

Primă

RPC

În PUN 2, RPC înseamnă Remote Procedure Call, este folosit pentru a apela o funcție pe clienții Remote care se află în aceeași cameră (puteți citi mai multe despre aceasta aici).

RPC-urile au multe utilizări, de exemplu, să presupunem că trebuie să trimiteți un mesaj de chat tuturor jucătorilor din cameră. Cu RPC-uri, este ușor să faceți acest lucru:

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

Observați [PunRPC] înainte de funcție. Acest atribut este necesar dacă intenționați să apelați funcția prin RPC-uri.

Pentru a apela funcțiile marcate ca RPC, aveți nevoie de un PhotonView. Exemplu de apel:

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

Sfat profesionist: dacă înlocuiți MonoBehaviour în scriptul dvs. cu MonoBehaviourPun sau MonoBehaviourPunCallbacks, puteți sări peste PhotonView.Get() și să utilizați direct photonView.RPC().

Proprietăți personalizate

În PUN 2, Proprietăți personalizate este un tabel Hash care poate fi atribuit unui jucător sau camerei.

Acest lucru este util atunci când trebuie să setați date persistente care nu trebuie modificate frecvent (de ex. Numele echipei jucătorului, Modul joc în cameră etc.).

Mai întâi, trebuie să definiți un Hashtable, care se face prin adăugarea liniei de mai jos la începutul scriptului:

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

Exemplul de mai jos setează proprietățile camerei numite "GameMode" și "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"]);

Proprietățile jucătorului sunt setate în mod similar:

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

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

Pentru a elimina o anumită proprietate, setați valoarea acesteia la null.

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

Tutoriale suplimentare:

Sincronizați corpuri rigide prin rețea folosind PUN 2

PUN 2 Adăugarea unui chat în cameră

Sursă
📁PUN2Guide.unitypackage14.00 MB
Articole sugerate
Sincronizați corpuri rigide prin rețea folosind PUN 2
Unity Adăugând chat multiplayer în camerele PUN 2
Comprimarea datelor multiplayer și manipularea biților
Creați un joc cu mașini multiplayer cu PUN 2
Crearea de jocuri multiplayer în rețea în Unity
Photon Network (Clasic) Ghid pentru începători
Introducere în Photon Fusion 2 în Unity