Maak een multiplayergame in Unity met PUN 2

Heb je je ooit afgevraagd wat er nodig is om een ​​multiplayergame te maken in Unity?

In tegenstelling tot games voor één speler vereisen multiplayer-games een externe server die de rol van brug speelt, waardoor gameclients met elkaar kunnen communiceren.

Tegenwoordig verzorgen tal van diensten de serverhosting. Eén zo'n service is Photon Network, die we voor deze tutorial zullen gebruiken.

PUN 2 is de nieuwste release van hun API die aanzienlijk is verbeterd in vergelijking met de oudere versie.

In dit bericht bespreken we het downloaden van de benodigde bestanden, het instellen van Photon AppID en het programmeren van een eenvoudig multiplayer-voorbeeld.

Unity versie gebruikt in deze tutorial: Unity 2018.3.0f2 (64-bit)

Deel 1: PUN 2 instellen

De eerste stap is het downloaden van een PUN 2-pakket van de Asset Store. Het bevat alle scripts en bestanden die nodig zijn voor multiplayer-integratie.

  • Open uw Unity-project en ga vervolgens naar Asset Store: (Venster -> Algemeen -> AssetStore) of druk op Ctrl+9
  • Zoek naar "PUN 2- Gratis" en klik vervolgens op het eerste resultaat of klik hier
  • Importeer het PUN 2-pakket nadat het downloaden is voltooid

  • Nadat het pakket is geïmporteerd, moet u een Photon App ID aanmaken, dit doet u op hun website: https://www.photonengine.com/
  • Maak een nieuw account aan (of log in op uw bestaande account)
  • Ga naar de pagina Toepassingen door op het profielpictogram en vervolgens op "Your Applications" te klikken of volg deze link: https://dashboard.photonengine.com/en-US/PublicCloud
  • Klik op de pagina Toepassingen "Create new app"

  • Op de aanmaakpagina selecteert u bij Fotontype "Photon Realtime" en bij Naam typt u een willekeurige naam en klikt u vervolgens op "Create"

Zoals u kunt zien, is de applicatie standaard ingesteld op het gratis abonnement. U kunt hier meer lezen over prijsplannen

  • Zodra de applicatie is gemaakt, kopieert u de app-ID onder de app-naam

  • Ga terug naar uw Unity-project en ga vervolgens naar Venster -> Photon Unity Networking -> PUN Wizard
  • Klik in de PUN-wizard op "Setup Project", plak uw app-ID en klik vervolgens op "Setup Project"

  • De PUN 2 is nu klaar!

Deel 2: Een multiplayerspel maken

Laten we nu naar het gedeelte gaan waar we daadwerkelijk een multiplayer-game maken.

De manier waarop multiplayer wordt afgehandeld in PUN 2 is:

  • Ten eerste maken we verbinding met de fotonregio (bijvoorbeeld VS-Oost, Europa, Azië, enz.), ook wel bekend als de lobby.
  • Eenmaal in de lobby vragen we alle kamers op die in de regio zijn gemaakt, en dan kunnen we ons aansluiten bij een van de kamers of onze eigen kamer creëren.
  • Nadat we ons bij de kamer hebben aangesloten, vragen we een lijst op van de spelers die met de kamer zijn verbonden en instantiëren we hun spelersinstanties, die vervolgens via PhotonView worden gesynchroniseerd met hun lokale instanties.
  • Wanneer iemand de kamer verlaat, wordt zijn exemplaar vernietigd en wordt hij van de spelerslijst verwijderd.

1. Een lobby opzetten

Laten we beginnen met het maken van een lobbyscène die lobbylogica bevat (door bestaande kamers bladeren, nieuwe kamers maken, enz.):

  • Maak een nieuw C#-script en noem het PUN2_GameLobby
  • Maak een nieuwe scène en roep deze aan "GameLobby"
  • Maak in de GameLobby-scène een nieuw GameObject. Noem het "_GameLobby" en wijs het PUN2_GameLobby-script eraan toe

Open nu het PUN2_GameLobby-script:

Eerst importeren we de Photon-naamruimten door de onderstaande regels aan het begin van het script toe te voegen:

using Photon.Pun;
using Photon.Realtime;

Voordat we verder gaan, moeten we ook het standaard MonoBehaviour vervangen door MonoBehaviourPunCallbacks. Deze stap is nodig om Photon callbacks te kunnen gebruiken:

public class PUN2_GameLobby : MonoBehaviourPunCallbacks

Vervolgens creëren we de benodigde variabelen:

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

Vervolgens roepen we ConnectUsingSettings() aan in de lege Start(). Dit betekent dat zodra het spel wordt geopend, het verbinding maakt met de Photon Server:

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

Om te weten of een verbinding met Photon succesvol was, moeten we deze callbacks implementeren: 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;
    }

Het volgende is het UI-gedeelte, waar het bladeren door kamers en het maken van kamers worden gedaan:

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

En ten slotte implementeren we nog eens 4 callbacks: OnCreateRoomFailed(short returnCode, string message), OnJoinRoomFailed(short returnCode, string message), OnCreatedRoom(), en Aanaangeslotenruimte().

Deze terugbelverzoeken worden gebruikt om te bepalen of we lid zijn geworden van de ruimte of dat er problemen zijn opgetreden tijdens de verbinding.

Hier is het definitieve PUN2_GameLobby.cs-script:

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. Een Player-prefab maken

In multiplayer-spellen heeft de Player-instantie twee kanten: lokaal en extern

Een lokale instantie wordt lokaal (door ons) beheerd.

Een externe instantie is daarentegen een lokale weergave van wat de andere speler doet. Het mag niet worden beïnvloed door onze inbreng.

Om te bepalen of de instance lokaal of extern is, gebruiken we een PhotonView component.

PhotonView fungeert als een messenger die de waarden ontvangt en verzendt die moeten worden gesynchroniseerd, bijvoorbeeld positie en rotatie.

Laten we dus beginnen met het maken van de spelerinstantie (als u uw spelerinstantie al gereed heeft, kunt u deze stap overslaan).

In mijn geval zal de Player-instantie een eenvoudige kubus zijn die wordt verplaatst met de W- en S-toetsen en wordt geroteerd met de A- en D-toetsen.

Hier is een eenvoudig controllerscript:

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

De volgende stap is het toevoegen van een PhotonView-component.

  • Voeg een PhotonView-component toe aan Player Instance.
  • Maak een nieuw C#-script en noem het PUN2_PlayerSync (dit script wordt gebruikt om via PhotonView te communiceren).

Open PUN2_PlayerSync-script:

In PUN2_PlayerSync moeten we eerst een Photon.Pun-naamruimte toevoegen en MonoBehaviour vervangen door MonoBehaviourPun en ook de IPunObservable-interface toevoegen.

MonoBehaviourPun is nodig om de in de cache opgeslagen photonView-variabele te kunnen gebruiken, in plaats van GetComponent<PhotonView>().

using UnityEngine;
using Photon.Pun;

public class PUN2_PlayerSync : MonoBehaviourPun, IPunObservable

Daarna kunnen we doorgaan met het maken van alle benodigde variabelen:

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

Vervolgens controleren we in de lege Start() of de speler lokaal of extern is met behulp van 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);
            }
        }
    }

De daadwerkelijke synchronisatie wordt uitgevoerd via de callback van 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();
        }
    }

In dit geval sturen we alleen de positie en rotatie van de speler, maar u kunt het bovenstaande voorbeeld gebruiken om elke waarde die nodig is om te worden gesynchroniseerd via het netwerk, met een hoge frequentie te verzenden.

Ontvangen waarden worden vervolgens toegepast in de 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);
        }
    }
}

Hier is het laatste PUN2_PlayerSync.cs-script:

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

Laten we nu een nieuw gemaakt script toewijzen:

  • Voeg het PUN2_PlayerSync-script toe aan de PlayerInstance.
  • Sleep PUN2_PlayerSync naar de geobserveerde componenten van PhotonView.
  • Wijs de SimplePlayerController toe aan "Local Scripts" en wijs de GameObjects (die u wilt deactiveren voor externe spelers) toe aan de "Local Objects"

  • Sla de PlayerInstance op in Prefab en verplaats deze naar de map met de naam Resources (als zo'n map niet bestaat, maak er dan een). Deze stap is nodig om multiplayer-objecten via het netwerk te kunnen spawnen.

3. Een spelniveau creëren

GameLevel is een scène die wordt geladen nadat je de kamer hebt betreden en hier vindt alle actie plaats.

  • Maak een nieuwe scène en noem deze "GameLevel" (of als je een andere naam wilt behouden, zorg er dan voor dat je de naam wijzigt in deze regel PhotonNetwork.LoadLevel("GameLevel"); in PUN2_GameLobby.cs).

In mijn geval gebruik ik een eenvoudige scène met een vliegtuig:

  • Maak nu een nieuw script en noem het PUN2_RoomController (dit script zal de logica in de kamer afhandelen, zoals het spawnen van de spelers, het tonen van de spelerslijst, enz.).

Open het PUN2_RoomController-script:

Hetzelfde als PUN2_GameLobby, we beginnen met het toevoegen van Photon-naamruimten en het vervangen van MonoBehaviour door MonoBehaviourPunCallbacks:

using UnityEngine;
using Photon.Pun;

public class PUN2_RoomController : MonoBehaviourPunCallbacks

Laten we nu de benodigde variabelen toevoegen:

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

Om de prefab Player te instantiëren gebruiken we 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);
    }

En een eenvoudige gebruikersinterface met een "Leave Room"-knop en enkele extra elementen, zoals de kamernaam en de lijst met verbonden spelers:

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

Ten slotte implementeren we nog een PhotonNetwork-callback genaamd OnLeftRoom(), die wordt aangeroepen wanneer we de kamer verlaten:

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

Hier is het definitieve PUN2_RoomController.cs-script:

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");
    }
}
  • Maak een nieuw GameObject in de 'GameLevel'-scène en roep het aan "_RoomController"
  • Voeg het PUN2_RoomController-script toe aan het _RoomController-object
  • Wijs de PlayerInstance-prefab en een SpawnPoint-transformatie eraan toe en sla vervolgens de scène op

  • Voeg zowel MainMenu als GameLevel toe aan de Build-instellingen.

4. Een proefbuild maken

Nu is het tijd om een ​​build te maken en deze te testen:

Alles werkt zoals verwacht!

Bonus

RPC

In PUN 2 staat RPC voor Remote Procedure Call, het wordt gebruikt om een ​​functie aan te roepen op externe clients die zich in dezelfde kamer bevinden (je kunt er hier meer over lezen).

RPC's hebben veel toepassingen. Laten we bijvoorbeeld zeggen dat je een chatbericht naar alle spelers in de kamer moet sturen. Met RPC's is dit eenvoudig te doen:

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

Let op de [PunRPC] vóór de functie. Dit kenmerk is nodig als u van plan bent de functie via RPC's aan te roepen.

Om de functies gemarkeerd als RPC op te roepen, hebt u een PhotonView nodig. Voorbeeld oproep:

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

Pro tip: Als u MonoBehaviour in uw script vervangt door MonoBehaviourPun of MonoBehaviourPunCallbacks, kunt u PhotonView.Get() overslaan en photonView.RPC() rechtstreeks gebruiken.

Aangepaste eigenschappen

In PUN 2 is Aangepaste eigenschappen een hashtabel die kan worden toegewezen aan een speler of de kamer.

Dit is handig als u permanente gegevens moet instellen die niet vaak hoeven te worden gewijzigd (bijvoorbeeld de teamnaam van de speler, de kamerspelmodus, enz.).

Eerst moet je een hashtabel definiëren, wat je doet door de onderstaande regel aan het begin van het script toe te voegen:

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

In het onderstaande voorbeeld worden de kamereigenschappen ingesteld, genaamd "GameMode" en "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"]);

Spelereigenschappen worden op dezelfde manier ingesteld:

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

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

Als u een specifieke eigenschap wilt verwijderen, stelt u de waarde ervan in op null.

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

Aanvullende tutorials:

Synchroniseer starre lichamen via netwerk met behulp van PUN 2

PUN 2 Kamerchat toevoegen

Bron
📁PUN2Guide.unitypackage14.00 MB
Voorgestelde artikelen
Synchroniseer starre lichamen via netwerk met PUN 2
Unity voegt multiplayer-chat toe aan de PUN 2-kamers
Multiplayer-datacompressie en bitmanipulatie
Maak een multiplayer-autospel met PUN 2
Unity Login-systeem met PHP en MySQL
Inleiding tot Photon Fusion 2 in Unity
Samen multiplayer-netwerkgames bouwen