Erstellen Sie mit PUN 2 ein Multiplayer-Spiel in Unity

Haben Sie sich jemals gefragt, was nötig ist, um ein Multiplayer-Spiel innerhalb von Unity zu erstellen?

Im Gegensatz zu Einzelspieler-Spielen erfordern Mehrspieler-Spiele einen Remote-Server, der die Rolle der Brücke übernimmt und es den Spiel-Clients ermöglicht, miteinander zu kommunizieren.

Heutzutage kümmern sich zahlreiche Dienste um das Server-Hosting. Ein solcher Dienst ist Photon Network, den wir für dieses Tutorial verwenden werden.

PUN 2 ist die neueste Version ihrer API, die im Vergleich zur Vorgängerversion erheblich verbessert wurde.

In diesem Beitrag werden wir die erforderlichen Dateien herunterladen, Photon AppID einrichten und ein einfaches Multiplayer-Beispiel programmieren.

Unity In diesem Tutorial verwendete Version: Unity 2018.3.0f2 (64-Bit)

Teil 1: Einrichten von PUN 2

Der erste Schritt besteht darin, ein PUN 2-Paket vom Asset Store herunterzuladen. Es enthält alle für die Multiplayer-Integration erforderlichen Skripte und Dateien.

  • Öffnen Sie Ihr Unity-Projekt und gehen Sie dann zu Asset Store: (Fenster -> Allgemein -> AssetStore) oder drücken Sie Strg+9
  • Suchen Sie nach „PUN 2- Free“ und klicken Sie dann auf das erste Ergebnis oder klicken Sie hier
  • Importieren Sie das PUN 2-Paket, nachdem der Download abgeschlossen ist

  • Nachdem das Paket importiert wurde, müssen Sie eine Photon-App-ID erstellen. Dies geschieht auf deren Website: https://www.photonengine.com/
  • Erstellen Sie ein neues Konto (oder melden Sie sich bei Ihrem bestehenden Konto an)
  • Gehen Sie zur Seite „Anwendungen“, indem Sie auf das Profilsymbol und dann auf "Your Applications" klicken oder diesem Link folgen: https://dashboard.photonengine.com/en-US/PublicCloud
  • Klicken Sie auf der Seite „Anwendungen“ auf "Create new app"

  • Wählen Sie auf der Erstellungsseite als Photonentyp "Photon Realtime" aus, geben Sie als Name einen beliebigen Namen ein und klicken Sie dann "Create"

Wie Sie sehen können, verwendet die Anwendung standardmäßig den kostenlosen Plan. Weitere Informationen zu den Preisplänen finden Sie hier

  • Sobald die Anwendung erstellt ist, kopieren Sie die App-ID, die sich unter dem App-Namen befindet

  • Gehen Sie zurück zu Ihrem Unity-Projekt und gehen Sie dann zu Fenster -> Photon Unity-Netzwerk -> PUN-Assistent
  • Klicken Sie im PUN-Assistenten auf "Setup Project", fügen Sie Ihre App-ID ein und klicken Sie dann "Setup Project"

  • Das PUN 2 ist jetzt fertig!

Teil 2: Erstellen eines Multiplayer-Spiels

Kommen wir nun zu dem Teil, in dem wir tatsächlich ein Multiplayer-Spiel erstellen.

Der Mehrspielermodus in PUN 2 wird wie folgt gehandhabt:

  • Zuerst stellen wir eine Verbindung zur Photon-Region her (z. B. USA Ost, Europa, Asien usw.), die auch als Lobby bekannt ist.
  • Sobald wir in der Lobby sind, fordern wir alle Räume an, die in der Region erstellt werden, und können dann entweder einem der Räume beitreten oder unseren eigenen Raum erstellen.
  • Nachdem wir dem Raum beigetreten sind, fordern wir eine Liste der mit dem Raum verbundenen Spieler an und instanziieren ihre Player-Instanzen, die dann über PhotonView mit ihren lokalen Instanzen synchronisiert werden.
  • Wenn jemand den Raum verlässt, wird seine Instanz zerstört und er wird aus der Spielerliste entfernt.

1. Eine Lobby einrichten

Beginnen wir mit der Erstellung einer Lobby-Szene, die Lobby-Logik enthält (Vorhandene Räume durchsuchen, neue Räume erstellen usw.):

  • Erstellen ein neues C#-Skript und nennen Sie es PUN2_GameLobby
  • Erstellen Sie eine neue Szene und nennen Sie sie "GameLobby"
  • Erstellen Sie in der GameLobby-Szene ein neues GameObject. Nennen Sie es "_GameLobby" und weisen Sie ihm das Skript PUN2_GameLobby zu

Öffnen Sie nun das PUN2_GameLobby-Skript:

Zuerst importieren wir die Photon-Namespaces, indem wir die folgenden Zeilen am Anfang des Skripts hinzufügen:

using Photon.Pun;
using Photon.Realtime;

Bevor wir fortfahren, müssen wir außerdem das Standard-MonoBehaviour durch MonoBehaviourPunCallbacks ersetzen. Dieser Schritt ist notwendig, um Photon-Callbacks nutzen zu können:

public class PUN2_GameLobby : MonoBehaviourPunCallbacks

Als nächstes erstellen wir die notwendigen Variablen:

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

Dann rufen wir ConnectUsingSettings() in der Leere Start() auf. Das heißt, sobald das Spiel geöffnet wird, verbindet es sich mit dem 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();
        }
    }

Um zu wissen, ob eine Verbindung zu Photon erfolgreich war, müssen wir diese Rückrufe implementieren: 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;
    }

Als nächstes folgt der UI-Teil, in dem das Durchsuchen und Erstellen von Räumen durchgeführt wird:

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

Und schließlich implementieren wir weitere 4 Rückrufe: OnCreateRoomFailed(short returnCode, string message), OnJoinRoomFailed(short returnCode, string message), OnCreatedRoom() und OnJoinedRoom().

Diese Rückrufe werden verwendet, um festzustellen, ob wir dem Raum beigetreten/erstellt sind oder ob es während der Verbindung Probleme gab.

Hier ist das endgültige PUN2_GameLobby.cs-Skript:

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. Erstellen eines Player-Fertigteils

In Multiplayer-Spielen hat die Player-Instanz zwei Seiten: Lokal und Remote

Eine lokale Instanz wird lokal (von uns) gesteuert.

Eine Remote-Instanz hingegen ist eine lokale Darstellung dessen, was der andere Spieler tut. Es sollte von unserer Eingabe unberührt bleiben.

Um festzustellen, ob die Instanz lokal oder remote ist, verwenden wir eine PhotonView-Komponente.

PhotonView fungiert als Messenger, der die zu synchronisierenden Werte empfängt und sendet, beispielsweise Position und Rotation.

Beginnen wir also mit der Erstellung der Player-Instanz (Wenn Sie Ihre Player-Instanz bereits bereit haben, können Sie diesen Schritt überspringen).

In meinem Fall ist die Player-Instanz ein einfacher Würfel, der mit den Tasten W und S bewegt und mit den Tasten A und D gedreht wird.

Hier ist ein einfaches Controller-Skript:

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

Der nächste Schritt besteht darin, eine PhotonView-Komponente hinzuzufügen.

  • Fügen Sie der Player-Instanz eine PhotonView-Komponente hinzu.
  • Erstellen Sie ein neues C#-Skript und nennen Sie es PUN2_PlayerSync (dieses Skript wird für die Kommunikation über PhotonView verwendet).

Öffnen Sie das PUN2_PlayerSync-Skript:

In PUN2_PlayerSync müssen wir zunächst einen Photon.Pun-Namespace hinzufügen und MonoBehaviour durch MonoBehaviourPun ersetzen und außerdem die Schnittstelle IPunObservable hinzufügen.

MonoBehaviourPun ist erforderlich, um die zwischengespeicherte photonView-Variable anstelle von GetComponent<PhotonView>() verwenden zu können.

using UnityEngine;
using Photon.Pun;

public class PUN2_PlayerSync : MonoBehaviourPun, IPunObservable

Danach können wir alle notwendigen Variablen erstellen:

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

Dann prüfen wir im void Start(), ob der Player lokal oder remote ist, indem wir photonView.IsMine verwenden:

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

Die eigentliche Synchronisierung erfolgt über den PhotonView-Callback: 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 diesem Fall senden wir nur die Position und Rotation des Spielers, aber Sie können das obige Beispiel verwenden, um jeden Wert, der synchronisiert werden muss, mit hoher Frequenz über das Netzwerk zu senden.

Empfangene Werte werden dann im void Update() übernommen:

    // 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 ist das endgültige PUN2_PlayerSync.cs-Skript:

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

Nun weisen wir ein neu erstelltes Skript zu:

  • Hängen Sie das PUN2_PlayerSync-Skript an die PlayerInstance an.
  • Ziehen Sie PUN2_PlayerSync per Drag & Drop in die PhotonView Observed Components.
  • Weisen Sie "Local Scripts" den SimplePlayerController zu und weisen Sie dem die GameObjects zu (die für Remote-Spieler deaktiviert werden sollen). "Local Objects"

  • Speichern Sie die PlayerInstance in Prefab und verschieben Sie sie in den Ordner „Ressourcen“ (Falls kein solcher Ordner vorhanden ist, erstellen Sie einen). Dieser Schritt ist notwendig, um Multiplayer-Objekte über das Netzwerk erzeugen zu können.

3. Erstellen eines Spiellevels

GameLevel ist eine Szene, die nach dem Betreten des Raums geladen wird und in der die gesamte Aktion stattfindet.

  • Erstellen Sie eine neue Szene und nennen Sie sie "GameLevel" (Oder wenn Sie einen anderen Namen behalten möchten, stellen Sie sicher, dass Sie den Namen in dieser Zeile PhotonNetwork.LoadLevel("GameLevel"); in PUN2_GameLobby.cs ändern).

In meinem Fall verwende ich eine einfache Szene mit einem Flugzeug:

  • Erstellen Sie nun ein neues Skript und nennen Sie es PUN2_RoomController (Dieses Skript verwaltet die Logik im Raum, z. B. das Spawnen der Spieler, das Anzeigen der Spielerliste usw.).

Öffnen Sie das Skript PUN2_RoomController:

Wie bei PUN2_GameLobby beginnen wir mit dem Hinzufügen von Photon-Namespaces und dem Ersetzen von MonoBehaviour durch MonoBehaviourPunCallbacks:

using UnityEngine;
using Photon.Pun;

public class PUN2_RoomController : MonoBehaviourPunCallbacks

Fügen wir nun die notwendigen Variablen hinzu:

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

Um das Player-Prefab zu instanziieren, verwenden wir 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);
    }

Und eine einfache Benutzeroberfläche mit einer "Leave Room"-Schaltfläche und einigen zusätzlichen Elementen wie dem Raumnamen und der Liste der verbundenen Spieler:

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

Schließlich implementieren wir einen weiteren PhotonNetwork-Rückruf namens OnLeftRoom(), der aufgerufen wird, wenn wir den Raum verlassen:

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

Hier ist das endgültige PUN2_RoomController.cs-Skript:

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");
    }
}
  • Erstellen Sie ein neues GameObject in der 'GameLevel'-Szene und nennen Sie es "_RoomController"
  • Hängen Sie das PUN2_RoomController-Skript an das _RoomController-Objekt an
  • Weisen Sie ihm das PlayerInstance-Prefab und eine SpawnPoint-Transformation zu und speichern Sie dann die Szene

  • Fügen Sie sowohl MainMenu als auch GameLevel zu den Build-Einstellungen hinzu.

4. Erstellen eines Test-Builds

Jetzt ist es an der Zeit, einen Build zu erstellen und ihn zu testen:

Alles funktioniert wie erwartet!

Bonus

RPC

In PUN 2 steht RPC für Remote Procedure Call und wird verwendet, um eine Funktion auf Remote-Clients aufzurufen, die sich im selben Raum befinden (mehr darüber können Sie hier lesen).

RPCs haben viele Verwendungsmöglichkeiten. Nehmen wir zum Beispiel an, Sie müssen eine Chat-Nachricht an alle Spieler im Raum senden. Mit RPCs ist das ganz einfach:

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

Beachten Sie [PunRPC] vor der Funktion. Dieses Attribut ist erforderlich, wenn Sie planen, die Funktion über RPCs aufzurufen.

Um die als RPC gekennzeichneten Funktionen aufzurufen, benötigen Sie ein PhotonView. Beispielaufruf:

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

Profi-Tipp: Wenn Sie MonoBehaviour in Ihrem Skript durch MonoBehaviourPun oder MonoBehaviourPunCallbacks ersetzen, können Sie PhotonView.Get() überspringen und photonView.RPC() direkt verwenden.

Benutzerdefinierte Eigenschaften

In PUN 2 handelt es sich bei „Benutzerdefinierte Eigenschaften“ um eine Hashtabelle, die einem Spieler oder dem Raum zugewiesen werden kann.

Dies ist nützlich, wenn Sie dauerhafte Daten festlegen müssen, die nicht häufig geändert werden müssen (z. B. Name des Spielerteams, Raumspielmodus usw.).

Zuerst müssen Sie eine Hashtabelle definieren, was durch das Hinzufügen der folgenden Zeile am Anfang des Skripts erreicht wird:

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

Das folgende Beispiel legt die Raumeigenschaften mit den Namen "GameMode" und "AnotherProperty" fest:

        //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"]);

Spielereigenschaften werden auf ähnliche Weise festgelegt:

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

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

Um eine bestimmte Eigenschaft zu entfernen, setzen Sie einfach ihren Wert auf null.

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

Zusätzliche Tutorials:

Synchronisieren Sie Rigidbodies über das Netzwerk mit PUN 2

WORTSPIEL 2 Raumchat hinzufügen