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: