Crea un juego multijugador en Unity usando PUN 2
¿Alguna vez te has preguntado qué se necesita para crear un juego multijugador dentro de Unity?
A diferencia de los juegos para un solo jugador, los juegos multijugador requieren un servidor remoto que desempeña el papel de puente, permitiendo que los clientes del juego se comuniquen entre sí.
Hoy en día numerosos servicios se encargan del alojamiento de servidores. Uno de esos servicios es Photon Network, que usaremos para este tutorial.
PUN 2 es la última versión de su API que se ha mejorado mucho en comparación con la versión anterior.
En esta publicación, descargaremos los archivos necesarios, configuraremos Photon AppID y programaremos un ejemplo multijugador simple.
Unity versión utilizada en este tutorial: Unity 2018.3.0f2 (64 bits)
Parte 1: Configuración de PUN 2
El primer paso es descargar un paquete PUN 2 desde el Asset Store. Contiene todos los scripts y archivos necesarios para la integración multijugador.
- Abra su proyecto Unity y luego vaya a Asset Store: (Ventana -> General -> AssetStore) o presione Ctrl+9
- Busque "PUN 2- Gratis" y luego haga clic en el primer resultado o haga clic aquí
- Importe el paquete PUN 2 una vez finalizada la descarga
- Después de importar el paquete, debe crear una ID de aplicación Photon, esto se hace en su sitio web: https://www.photonengine.com/
- Cree una nueva cuenta (o inicie sesión en su cuenta existente)
- Vaya a la página de Aplicaciones haciendo clic en el ícono de perfil y luego "Your Applications" o siga este enlace: https://dashboard.photonengine.com/en-US/PublicCloud
- En la página de Aplicaciones, haga clic en "Create new app"
- En la página de creación, para Tipo de fotón seleccione "Photon Realtime" y para Nombre, escriba cualquier nombre y luego haga clic "Create"
Como puede ver, la aplicación tiene por defecto el plan gratuito. Puedes leer más sobre los planes de precios aquí
- Una vez creada la aplicación, copie el ID de la aplicación ubicado debajo del nombre de la aplicación.
- Vuelva a su proyecto Unity y luego vaya a Ventana -> Redes Photon Unity -> Asistente PUN
- En PUN Wizard, haga clic en "Setup Project", pegue su ID de aplicación y luego haga clic en "Setup Project"
- ¡El PUN 2 ya está listo!
Parte 2: Crear un juego multijugador
Ahora pasemos a la parte en la que realmente creamos un juego multijugador.
La forma en que se maneja el modo multijugador en PUN 2 es:
- Primero, nos conectamos a la Región Photon (por ejemplo, EE. UU. Este, Europa, Asia, etc.), que también se conoce como Lobby.
- Una vez en el Lobby, solicitamos todas las Salas que se crean en la Región, y luego podemos unirnos a una de las Salas o crear nuestra propia Sala.
- Después de unirnos a la sala, solicitamos una lista de los jugadores conectados a la sala y creamos una instancia de sus instancias de jugador, que luego se sincronizan con sus instancias locales a través de PhotonView.
- Cuando alguien abandona la sala, su instancia se destruye y se elimina de la lista de jugadores.
1. Configurar un vestíbulo
Comencemos creando una escena del lobby que contendrá la lógica del lobby (explorar salas existentes, crear salas nuevas, etc.):
- Crea un nuevo script C# y llámalo PUN2_GameLobby
- Crea una nueva escena y llámala "GameLobby"
- En la escena GameLobby crea un nuevo GameObject. Llámalo "_GameLobby" y asígnale el script PUN2_GameLobby
Ahora abre el script PUN2_GameLobby:
Primero, importamos los espacios de nombres de Photon agregando las siguientes líneas al comienzo del script:
using Photon.Pun;
using Photon.Realtime;
Además, antes de continuar, debemos reemplazar el MonoBehaviour predeterminado con MonoBehaviourPunCallbacks. Este paso es necesario para poder utilizar las devoluciones de llamada de Photon:
public class PUN2_GameLobby : MonoBehaviourPunCallbacks
A continuación, creamos las variables necesarias:
//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;
Luego llamamos ConnectUsingSettings() en el vacío Start(). Esto significa que tan pronto como se abre el juego, se conecta al servidor Photon:
// Use this for initialization
void Start()
{
//This makes sure we can use PhotonNetwork.LoadLevel() on the master client and all clients in the same room sync their level automatically
PhotonNetwork.AutomaticallySyncScene = true;
if (!PhotonNetwork.IsConnected)
{
//Set the App version before connecting
PhotonNetwork.PhotonServerSettings.AppSettings.AppVersion = gameVersion;
// Connect to the photon master-server. We use the settings saved in PhotonServerSettings (a .asset file in this project)
PhotonNetwork.ConnectUsingSettings();
}
}
Para saber si una conexión a Photon fue exitosa, necesitamos implementar estas devoluciones de llamada: 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;
}
La siguiente es la parte de la interfaz de usuario, donde se realizan la navegación y la creación de la sala:
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...");
}
}
Y por último, implementamos otras 4 devoluciones de llamada: OnCreateRoomFailed(short returnCode, string message), OnJoinRoomFailed(short returnCode, string message), OnCreatedRoom(), y OnJoinedRoom().
Estas devoluciones de llamada se utilizan para determinar si nos unimos/creamos la sala o si hubo algún problema durante la conexión.
Aquí está el script PUN2_GameLobby.cs final:
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. Creando una casa prefabricada de reproductor
En los juegos multijugador, la instancia del jugador tiene 2 lados: local y remoto
Una instancia local es controlada localmente (por nosotros).
Una instancia remota, por otro lado, es una representación local de lo que está haciendo el otro jugador. Nuestras aportaciones no deberían afectarlo.
Para determinar si la instancia es local o remota utilizamos un componente PhotonView.
PhotonView actúa como un mensajero que recibe y envía los valores que deben sincronizarse, por ejemplo, posición y rotación.
Entonces, comencemos creando la instancia del reproductor (si ya tiene la instancia del reproductor lista, puede omitir este paso).
En mi caso, la instancia del Jugador será un Cubo simple que se mueve con las teclas W y S y se gira con las teclas A y D.
Aquí hay un script de controlador simple:
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);
}
}
}
El siguiente paso es agregar un componente PhotonView.
- Agregue un componente PhotonView a la instancia del reproductor.
- Cree un nuevo script C# y llámelo PUN2_PlayerSync (este script se utilizará para comunicarse a través de PhotonView).
Abra el script PUN2_PlayerSync:
En PUN2_PlayerSync lo primero que debemos hacer es agregar un espacio de nombres Photon.Pun y reemplazar MonoBehaviour con MonoBehaviourPun y también agregar la interfaz IPunObservable.
MonoBehaviourPun es necesario para poder utilizar la variable photonView almacenada en caché, en lugar de utilizar GetComponent<PhotonView>().
using UnityEngine;
using Photon.Pun;
public class PUN2_PlayerSync : MonoBehaviourPun, IPunObservable
Después de eso, podemos pasar a crear todas las variables necesarias:
//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;
Luego, en el vacío Start(), verificamos si el reproductor es Local o Remoto usando 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);
}
}
}
La sincronización real se realiza a través de la devolución de llamada de 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();
}
}
En este caso, solo enviamos la Posición y Rotación del reproductor, pero puedes usar el ejemplo anterior para enviar cualquier valor que sea necesario sincronizar a través de la red, a alta frecuencia.
Los valores recibidos luego se aplican en la actualización vacía():
// 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);
}
}
}
Aquí está el script PUN2_PlayerSync.cs final:
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);
}
}
}
Ahora asignemos un script recién creado:
- Adjunte el script PUN2_PlayerSync a PlayerInstance.
- Arrastre y suelte PUN2_PlayerSync en los componentes observados de PhotonView.
- Asigne SimplePlayerController a "Local Scripts" y asigne los GameObjects (que desea desactivar para jugadores remotos) al "Local Objects"
- Guarde PlayerInstance en Prefab y muévalo a la carpeta llamada Recursos (si no existe dicha carpeta, cree una). Este paso es necesario para poder generar Objetos multijugador a través de la Red.
3. Creando un nivel de juego
GameLevel es una escena que se carga después de unirse a la sala y es donde ocurre toda la acción.
- Cree una nueva escena y llámela "GameLevel" (O si desea mantener un nombre diferente, asegúrese de cambiar el nombre en esta línea PhotonNetwork.LoadLevel("GameLevel"); en PUN2_GameLobby.cs).
En mi caso, usaré una escena simple con un avión:
- Ahora cree un nuevo script y llámelo PUN2_RoomController (este script manejará la lógica dentro de la Sala, como generar jugadores, mostrar la lista de jugadores, etc.).
Abra el script PUN2_RoomController:
Al igual que PUN2_GameLobby, comenzamos agregando espacios de nombres Photon y reemplazando MonoBehaviour con MonoBehaviourPunCallbacks:
using UnityEngine;
using Photon.Pun;
public class PUN2_RoomController : MonoBehaviourPunCallbacks
Ahora agreguemos las variables necesarias:
//Player instance prefab, must be located in the Resources folder
public GameObject playerPrefab;
//Player spawn point
public Transform spawnPoint;
Para crear una instancia del prefabricado Player estamos usando 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);
}
Y una interfaz de usuario sencilla con un botón "Leave Room" y algunos elementos adicionales como el nombre de la sala y la lista de jugadores conectados:
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);
}
}
Finalmente, implementamos otra devolución de llamada de PhotonNetwork llamada OnLeftRoom() que se llama cuando salimos de la Sala:
public override void OnLeftRoom()
{
//We have left the Room, return back to the GameLobby
UnityEngine.SceneManagement.SceneManager.LoadScene("GameLobby");
}
Aquí está el script PUN2_RoomController.cs final:
using UnityEngine;
using Photon.Pun;
public class PUN2_RoomController : MonoBehaviourPunCallbacks
{
//Player instance prefab, must be located in the Resources folder
public GameObject playerPrefab;
//Player spawn point
public Transform spawnPoint;
// Use this for initialization
void Start()
{
//In case we started this demo with the wrong scene being active, simply load the menu scene
if (PhotonNetwork.CurrentRoom == null)
{
Debug.Log("Is not in the room, returning back to Lobby");
UnityEngine.SceneManagement.SceneManager.LoadScene("GameLobby");
return;
}
//We're in a room. spawn a character for the local player. it gets synced by using PhotonNetwork.Instantiate
PhotonNetwork.Instantiate(playerPrefab.name, spawnPoint.position, Quaternion.identity, 0);
}
void OnGUI()
{
if (PhotonNetwork.CurrentRoom == null)
return;
//Leave this Room
if (GUI.Button(new Rect(5, 5, 125, 25), "Leave Room"))
{
PhotonNetwork.LeaveRoom();
}
//Show the Room name
GUI.Label(new Rect(135, 5, 200, 25), PhotonNetwork.CurrentRoom.Name);
//Show the list of the players connected to this Room
for (int i = 0; i < PhotonNetwork.PlayerList.Length; i++)
{
//Show if this player is a Master Client. There can only be one Master Client per Room so use this to define the authoritative logic etc.)
string isMasterClient = (PhotonNetwork.PlayerList[i].IsMasterClient ? ": MasterClient" : "");
GUI.Label(new Rect(5, 35 + 30 * i, 200, 25), PhotonNetwork.PlayerList[i].NickName + isMasterClient);
}
}
public override void OnLeftRoom()
{
//We have left the Room, return back to the GameLobby
UnityEngine.SceneManagement.SceneManager.LoadScene("GameLobby");
}
}
- Crea un nuevo GameObject en la escena 'GameLevel' y llámalo "_RoomController"
- Adjunte el script PUN2_RoomController al objeto _RoomController
- Asigne la casa prefabricada PlayerInstance y una transformación SpawnPoint y luego guarde la escena.
- Agregue MainMenu y GameLevel a la configuración de compilación.
4. Hacer una compilación de prueba
Ahora es el momento de crear una compilación y probarla:
¡Todo funciona como se esperaba!
Prima
RPC
En PUN 2, RPC significa Llamada a procedimiento remoto, se usa para llamar a una función en clientes remotos que están en la misma habitación (puede leer más sobre esto aquí).
Los RPC tienen muchos usos, por ejemplo, digamos que necesitas enviar un mensaje de chat a todos los jugadores en la sala. Con los RPC, es fácil hacerlo:
[PunRPC]
void ChatMessage(string senderName, string messageText)
{
Debug.Log(string.Format("{0}: {1}", senderName, messageText));
}
Observe el [PunRPC] antes de la función. Este atributo es necesario si planea llamar a la función a través de RPC.
Para llamar a las funciones marcadas como RPC, necesita un PhotonView. Llamada de ejemplo:
PhotonView photonView = PhotonView.Get(this);
photonView.RPC("ChatMessage", RpcTarget.All, PhotonNetwork.playerName, "Some message");
Consejo profesional: si reemplaza MonoBehaviour en su script con MonoBehaviourPun o MonoBehaviourPunCallbacks puede omitir PhotonView.Get() y usar photonView.RPC() directamente.
Propiedades personalizadas
En PUN 2, las propiedades personalizadas son una tabla hash que se puede asignar a un jugador o a la sala.
Esto es útil cuando necesitas configurar datos persistentes que no necesitan cambiarse con frecuencia (por ejemplo, nombre del equipo del jugador, modo de juego en sala, etc.).
Primero, debe definir una Hashtable, lo cual se hace agregando la siguiente línea al comienzo del script:
//Replace default Hashtables with Photon hashtables
using Hashtable = ExitGames.Client.Photon.Hashtable;
El siguiente ejemplo establece las propiedades de la habitación denominadas "GameMode" y "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"]);
Las propiedades del reproductor se configuran de manera similar:
Hashtable setPlayerProperties = new Hashtable();
setPlayerProperties.Add("PlayerHP", (float)100);
PhotonNetwork.LocalPlayer.SetCustomProperties(setPlayerProperties);
print((float)PhotonNetwork.LocalPlayer.CustomProperties["PlayerHP"]);
Para eliminar una propiedad específica simplemente establezca su valor en nulo.
Hashtable setPlayerProperties = new Hashtable();
setPlayerProperties.Add("PlayerHP", null);
PhotonNetwork.LocalPlayer.SetCustomProperties(setPlayerProperties);
Tutoriales adicionales: