Lag et flerspillerspill i Unity med PUN 2

Har du noen gang lurt på hva som skal til for å lage et flerspillerspill i Unity?

I motsetning til enkeltspillerspill krever flerspillerspill en ekstern server som spiller rollen som broen, slik at spillklienter kan kommunisere med hverandre.

I dag tar mange tjenester seg av serverhosting. En slik tjeneste er Photon Network, som vi skal bruke for denne opplæringen.

PUN 2 er den siste utgivelsen av deres API som har blitt kraftig forbedret sammenlignet med den eldre versjonen.

I dette innlegget vil vi kjøre gjennom å laste ned de nødvendige filene, sette opp Photon AppID og programmere et enkelt flerspillereksempel.

Unity versjon brukt i denne opplæringen: Unity 2018.3.0f2 (64-bit)

Del 1: Sette opp PUN 2

Det første trinnet er å laste ned en PUN 2-pakke fra Asset Store. Den inneholder alle skriptene og filene som kreves for flerspillerintegrasjon.

  • Åpne Unity-prosjektet og gå til Asset Store: (Vindu -> Generelt -> AssetStore) eller trykk Ctrl+9
  • Søk etter "PUN 2- Free" og klikk deretter på det første resultatet eller klikk her
  • Importer PUN 2-pakken etter at nedlastingen er fullført

  • På opprettelsessiden, for Photon Type, velg "Photon Realtime" og for Navn, skriv inn et navn og klikk "Create"

Som du kan se, bruker applikasjonen gratisplanen som standard. Du kan lese mer om prisplaner her

  • Når applikasjonen er opprettet, kopierer du app-ID-en som ligger under app-navnet

  • Gå tilbake til Unity-prosjektet, og gå deretter til Vindu -> Foton Unity Nettverk -> PUN-veiviser
  • I PUN Wizard klikker du på "Setup Project", limer inn app-ID-en din og klikker "Setup Project"

  • PUN 2 er nå klar!

Del 2: Lage et flerspillerspill

La oss nå gå til delen der vi faktisk lager et flerspillerspill.

Måten flerspiller håndteres i PUN 2 er:

  • Først kobler vi til Photon Region (f.eks. USA Øst, Europa, Asia, etc.) som også er kjent som lobbyen.
  • En gang i lobbyen ber vi om alle rommene som er opprettet i regionen, og så kan vi enten bli med i et av rommene eller lage vårt eget rom.
  • Etter å ha blitt med i rommet ber vi om en liste over spillerne som er koblet til rommet og instansierer deres spillerforekomster, som deretter synkroniseres med deres lokale forekomster gjennom PhotonView.
  • Når noen forlater rommet, blir instansen deres ødelagt og de fjernes fra spillerlisten.

1. Sette opp en lobby

La oss starte med å lage en Lobby-scene som vil inneholde Lobby-logikk (Sur gjennom eksisterende rom, opprette nye rom osv.):

  • Lag et nytt C#-skript og kall det PUN2_GameLobby
  • Lag en ny scene og ring den "GameLobby"
  • Lag et nytt GameObject i GameLobby-scenen. Kall det "_GameLobby" og tilordne PUN2_GameLobby-skriptet til det

Åpne nå PUN2_GameLobby-skriptet:

Først importerer vi Photon-navnerommene ved å legge til linjene nedenfor i begynnelsen av skriptet:

using Photon.Pun;
using Photon.Realtime;

Før vi fortsetter, må vi også erstatte standard MonoBehaviour med MonoBehaviourPunCallbacks. Dette trinnet er nødvendig for å kunne bruke Photon callbacks:

public class PUN2_GameLobby : MonoBehaviourPunCallbacks

Deretter lager vi de nødvendige variablene:

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

Deretter kaller vi ConnectUsingSettings() i tomrommet Start(). Dette betyr at så snart spillet åpner, kobles det til 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();
        }
    }

For å vite om en tilkobling til Photon var vellykket, må vi implementere disse tilbakeringingene: 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;
    }

Neste er UI-delen, der romsurfing og romoppretting gjøres:

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

Og til slutt implementerer vi ytterligere 4 tilbakeringinger: OnCreateRoomFailed(short returnCode, string message), OnJoinRoomFailed(short returnCode, string message), OnCreatedRoom(), og OnJoinedRoom().

Disse tilbakeringingene brukes til å avgjøre om vi ble med/opprettet rommet eller om det var noen problemer under forbindelsen.

Her er det endelige PUN2_GameLobby.cs-skriptet:

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. Opprette en prefabrikkert spiller

I flerspillerspill har Player-forekomsten 2 sider: lokal og ekstern

En lokal instans kontrolleres lokalt (av oss).

En ekstern instans er derimot en lokal representasjon av hva den andre spilleren gjør. Det skal være upåvirket av våre innspill.

For å finne ut om forekomsten er lokal eller ekstern bruker vi en PhotonView-komponent.

PhotonView fungerer som en messenger som mottar og sender verdiene som må synkroniseres, for eksempel posisjon og rotasjon.

Så la oss begynne med å lage spillerforekomsten (Hvis du allerede har spillerforekomsten klar, kan du hoppe over dette trinnet).

I mitt tilfelle vil Player-forekomsten være en enkel kube som flyttes med W- og S-tastene og roteres med A- og D-tastene.

Her er et enkelt kontrollerskript:

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

Det neste trinnet er å legge til en PhotonView-komponent.

  • Legg til en PhotonView-komponent til Player Instance.
  • Lag et nytt C#-skript, og kall det PUN2_PlayerSync (dette skriptet vil bli brukt til å kommunisere gjennom PhotonView).

Åpne PUN2_PlayerSync-skript:

I PUN2_PlayerSync er det første vi må gjøre å legge til et Photon.Pun navneområde og erstatte MonoBehaviour med MonoBehaviourPun og også legge til IPunObservable grensesnitt.

MonoBehaviourPun er nødvendig for å kunne bruke den bufrede photonView-variabelen, i stedet for å bruke GetComponent<PhotonView>().

using UnityEngine;
using Photon.Pun;

public class PUN2_PlayerSync : MonoBehaviourPun, IPunObservable

Etter det kan vi flytte for å lage alle nødvendige variabler:

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

Så i den tomme Start(), sjekker vi om spilleren er lokal eller ekstern ved å bruke 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);
            }
        }
    }

Selve synkroniseringen gjøres gjennom PhotonViews tilbakeringing: OnPhotonSerializeView(PhotonStream-strøm, 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();
        }
    }

I dette tilfellet sender vi bare spillerens posisjon og rotasjon, men du kan bruke eksemplet ovenfor til å sende en hvilken som helst verdi som er nødvendig for å bli synkronisert over nettverket, med høy frekvens.

Mottatte verdier blir deretter brukt i 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);
        }
    }
}

Her er det endelige PUN2_PlayerSync.cs-skriptet:

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

La oss nå tilordne et nyopprettet skript:

  • Fest PUN2_PlayerSync-skriptet til PlayerInstance.
  • Dra og slipp PUN2_PlayerSync inn i PhotonView Observed Components.
  • Tilordne SimplePlayerController til "Local Scripts" og tilordne GameObjects (som du ønsker skal deaktiveres for eksterne spillere) til "Local Objects"

  • Lagre PlayerInstance til Prefab og flytt den til mappen som heter Resources (Hvis det ikke finnes en slik mappe, lag en). Dette trinnet er nødvendig for å kunne skape flerspillerobjekter over nettverket.

3. Opprette et spillnivå

GameLevel er en scene som lastes inn etter å ha blitt med i rommet, og det er der all handlingen skjer.

  • Lag en ny scene og kall den "GameLevel" (Eller hvis du vil beholde et annet navn, sørg for å endre navnet på denne linjen PhotonNetwork.LoadLevel("GameLevel"); på PUN2_GameLobby.cs).

I mitt tilfelle vil jeg bruke en enkel scene med et fly:

  • Lag nå et nytt skript og kall det PUN2_RoomController (Dette skriptet vil håndtere logikken inne i rommet, som å skape spillere, vise spillerlisten osv.).

Åpne PUN2_RoomController-skript:

På samme måte som PUN2_GameLobby begynner vi med å legge til Photon-navneområder og erstatte MonoBehaviour med MonoBehaviourPunCallbacks:

using UnityEngine;
using Photon.Pun;

public class PUN2_RoomController : MonoBehaviourPunCallbacks

La oss nå legge til de nødvendige variablene:

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

For å instansiere Player prefab bruker vi 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);
    }

Og et enkelt brukergrensesnitt med en "Leave Room"-knapp og noen tilleggselementer som romnavn og listen over tilkoblede spillere:

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

Til slutt implementerer vi en annen PhotonNetwork callback kalt OnLeftRoom() som kalles når vi forlater rommet:

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

Her er det endelige PUN2_RoomController.cs-skriptet:

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");
    }
}
  • Lag et nytt GameObject i 'GameLevel'-scenen og kall det "_RoomController"
  • Fest PUN2_RoomController-skriptet til _RoomController-objektet
  • Tilordne PlayerInstance-prefabrikken og en SpawnPoint-transformasjon til den, og lagre deretter scenen

  • Legg til både MainMenu og GameLevel i byggeinnstillingene.

4. Lage et testbygg

Nå er det på tide å lage en build og teste den:

Alt fungerer som forventet!

Bonus

RPC

I PUN 2 står RPC for Remote Procedure Call, det brukes til å kalle opp en funksjon på Remote-klienter som er i samme rom (Du kan lese mer om det her).

RPC-er har mange bruksområder, for eksempel, la oss si at du må sende en chatmelding til alle spillerne i rommet. Med RPC-er er det enkelt å gjøre det:

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

Legg merke til [PunRPC] før funksjonen. Dette attributtet er nødvendig hvis du planlegger å kalle funksjonen via RPCer.

For å kalle opp funksjonene merket som RPC, trenger du en PhotonView. Eksempelanrop:

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

Profftips: Hvis du erstatter MonoBehaviour i skriptet ditt med MonoBehaviourPun eller MonoBehaviourPunCallbacks, kan du hoppe over PhotonView.Get() og bruke photonView.RPC() direkte.

Egenskaper

I PUN 2 er Custom Properties en hashtabell som kan tilordnes en spiller eller rommet.

Dette er nyttig når du trenger å angi vedvarende data som ikke trenger å endres ofte (f.eks. spillerlagsnavn, romspillmodus osv.).

Først må du definere en hashtabell, som gjøres ved å legge til linjen nedenfor i begynnelsen av skriptet:

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

Eksemplet nedenfor setter romegenskapene kalt "GameMode" og "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"]);

Spilleregenskapene er satt på samme måte:

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

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

For å fjerne en spesifikk egenskap må du sette verdien til null.

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

Ytterligere opplæringsprogrammer:

Synkroniser rigidbodies over nettverk ved hjelp av PUN 2

PUN 2 Legge til romchat