Demo:DemoAsteroids大厅的解析
Awake:设置同步场景的方式
登陆按钮:同步本地昵称、连接到服务器
连接到服务器回调:关闭登陆界面,打开 创建房间 / 加入房间 / 显示房间列表 的面板选择界面
选择创建房间界面的Btn:打开创建房间界面
创建房间界面:输入房间名、最大人数、拥有创建房间、返回的按钮
返回按钮:返回到功能选择界面
创建房间Btn:根据房间名、最大人数,创建服务器房间
创建/加入房间回调:打开房间内面板、实例化当前所有玩家的条形信息预制体,并将(昵称、是否准备)信息初始化到该预制体上的脚本上↓
条形信息物体:上有脚本:保存了该玩家的 ID、昵称、准备信息
根据初始化的ID==本地玩家ID?不等于则不显示该预制体的准备按钮(即咱们不显示别人电脑的ready,咱们只能控制咱们的ready)
若等于,即代表着这个预制体是我们自己的。则显示准备按钮。且将准备信息等做为自定义的同步信息
根据本房间内,该玩家的Number,决定这个预制体是什么颜色。(demo设定最多8人,因此这有8种对应的case)【用PUN的 Number更新回调实现】
注意,这儿要用到PUN自带的脚本:PlayerNumbering,要将其挂在场景中
上有准备按钮:
每次点击,改变自身状态(是否关闭等)、同步自身是否准备信息
若自己是主服务器,则还可根据当前玩家是否都已准备,显示开始游戏按钮(检查是否都已准备,就是foreach所有玩家的准备信息,进行判断)
开始游戏按钮:
设置当前房间状态:不可再加入、大厅列表不可见(隐身)
PUN同步加载场景
加入随机房间按钮:加入随机房间,显示服务器房间界面
返回按钮:退出服务器房间,返回到功能选择界面
显示房间列表按钮:使用加入大厅API,使PUN调用 刷新大厅列表 回调,在该回调中完成相关逻辑(该回调会传入所有房间列表缓存):
清空、删掉房间预制体、
根据从网络获得的缓存列表,判断房间是否可加入、可见性、标记性,删除不需要的房间,将需要的房间添加到本地房间列表。
更新实例化本地房间列表
开始游戏按钮状态:只有主客户端进行检测判断。(其他客户端没有开游戏的资格,自然不用检测)
主客户端点击准备时、
本地玩家进入房间时、(新玩家进来了,当然关闭按钮了)
其他玩家进入房间时、(新玩家进来了,当然关闭按钮了)
其他玩家离开房间时、
主客户端切换给别人时、
玩家属性更新时、(PUN回调)
房间列表更新时机:
显示房间列表信息按钮、
本地玩家退出大厅回调、
本地玩家离开房间回调、
其他玩家加入房间回调、
其他玩家离开房间回调、
该场景主要由这两个脚本实现功能
PlayerNumbering作为PUN实用脚本,挂载到场景中,配合我们写的代码。
using ExitGames.Client.Photon;
using Photon.Realtime;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace Photon.Pun.Demo.Asteroids
{
public class LobbyMainPanel : MonoBehaviourPunCallbacks
{
#region Public Parameters
[Header("Login Panel")]
public GameObject LoginPanel;
[Tooltip("玩家昵称输入框")]
public InputField PlayerNameInput;
[Header("房间操作界面")]
public GameObject SelectionPanel;
[Header("Create Room Panel")]
public GameObject CreateRoomPanel;
public InputField RoomNameInputField;
public InputField MaxPlayersInputField;
[Header("Join Random Room Panel")]
public GameObject JoinRandomRoomPanel;
[Header("Room List Panel")]
public GameObject RoomListPanel;
public GameObject RoomListContent;
public GameObject RoomListEntryPrefab;
[Header("Inside Room Panel")]
public GameObject InsideRoomPanel;
public Button StartGameButton;
public GameObject PlayerListEntryPrefab;
#endregion
#region Private Parameters
Dictionary<string, RoomInfo> cachedRoomList = new Dictionary<string, RoomInfo>();
Dictionary<string, GameObject> roomListEntries = new Dictionary<string, GameObject>();
Dictionary<int, GameObject> playerListEntries;
#endregion
#region Mono CallBacks
public void Awake()
{
PhotonNetwork.AutomaticallySyncScene = true;
PlayerNameInput.text = "Player " + Random.Range(1000, 10000);
}
#endregion
#region PUN CallBacks
public override void OnConnectedToMaster()
{
SetActivePanel(SelectionPanel.name);
}
//本地玩家进入房间时
public override void OnJoinedRoom()
{
print("OnJoinedRoom");
SetActivePanel(InsideRoomPanel.name);
if (playerListEntries == null)
playerListEntries = new Dictionary<int, GameObject>();
foreach (Player p in PhotonNetwork.PlayerList)
{
GameObject entry = Instantiate(PlayerListEntryPrefab);
entry.transform.SetParent(InsideRoomPanel.transform);
entry.transform.localScale = Vector3.one;
entry.GetComponent<PlayerListEntry>().Initialize(p.ActorNumber, p.NickName);
object isPlayerReady;
if (p.CustomProperties.TryGetValue(AsteroidsGame.PLAYER_READY, out isPlayerReady))
{
entry.GetComponent<PlayerListEntry>().SetPlayerReady((bool)isPlayerReady);
}
playerListEntries.Add(p.ActorNumber, entry);
}
StartGameButton.gameObject.SetActive(CheckPlayersReady());
Hashtable props = new Hashtable
{
{AsteroidsGame.PLAYER_LOADED_LEVEL, false}
};
PhotonNetwork.LocalPlayer.SetCustomProperties(props);
}
//刷新列表回调
public override void OnRoomListUpdate(List<RoomInfo> roomList)
{
ClearRoomListView();
UpdateCachedRoomList(roomList);
UpdateRoomListView();
}
public override void OnLeftLobby()
{
cachedRoomList.Clear();
ClearRoomListView();
}
public override void OnCreateRoomFailed(short returnCode, string message)
{
SetActivePanel(SelectionPanel.name);
}
public override void OnJoinRoomFailed(short returnCode, string message)
{
SetActivePanel(SelectionPanel.name);
}
public override void OnJoinRandomFailed(short returnCode, string message)
{
string roomName = "Room " + Random.Range(1000, 10000);
RoomOptions options = new RoomOptions { MaxPlayers = 8 };
PhotonNetwork.CreateRoom(roomName, options, null);
}
public override void OnLeftRoom()
{
SetActivePanel(SelectionPanel.name);
foreach (GameObject entry in playerListEntries.Values)
{
Destroy(entry.gameObject);
}
playerListEntries.Clear();
playerListEntries = null;
}
//其他玩家进入房间时
public override void OnPlayerEnteredRoom(Player newPlayer)
{
GameObject entry = Instantiate(PlayerListEntryPrefab);
entry.transform.SetParent(InsideRoomPanel.transform);
entry.transform.localScale = Vector3.one;
entry.GetComponent<PlayerListEntry>().Initialize(newPlayer.ActorNumber, newPlayer.NickName);
playerListEntries.Add(newPlayer.ActorNumber, entry);
StartGameButton.gameObject.SetActive(CheckPlayersReady());
}
public override void OnPlayerLeftRoom(Player otherPlayer)
{
Destroy(playerListEntries[otherPlayer.ActorNumber].gameObject);
playerListEntries.Remove(otherPlayer.ActorNumber);
StartGameButton.gameObject.SetActive(CheckPlayersReady());
}
public override void OnMasterClientSwitched(Player newMasterClient)
{
if (PhotonNetwork.LocalPlayer.ActorNumber == newMasterClient.ActorNumber)
{
StartGameButton.gameObject.SetActive(CheckPlayersReady());
}
}
public override void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
{
if (playerListEntries == null)
playerListEntries = new Dictionary<int, GameObject>();
GameObject entry;
if (playerListEntries.TryGetValue(targetPlayer.ActorNumber, out entry))
{
object isPlayerReady;
if (changedProps.TryGetValue(AsteroidsGame.PLAYER_READY, out isPlayerReady))
{
entry.GetComponent<PlayerListEntry>().SetPlayerReady((bool)isPlayerReady);
}
}
StartGameButton.gameObject.SetActive(CheckPlayersReady());
}
#endregion
#region Public Methods
//绑定到登陆按钮
public void OnLoginButtonClicked()
{
string playerName = PlayerNameInput.text;
if (!playerName.Equals(""))
{
PhotonNetwork.LocalPlayer.NickName = playerName;
PhotonNetwork.ConnectUsingSettings();
}
else
{
Debug.LogError("Player Name is invalid.");
}
}
//绑定到创建服务器房间按钮
public void OnCreateRoomButtonClicked()
{
string roomName = RoomNameInputField.text;
roomName = (roomName.Equals(string.Empty)) ? "Room " + Random.Range(1000, 10000) : roomName;
byte maxPlayers;
byte.TryParse(MaxPlayersInputField.text, out maxPlayers);
maxPlayers = (byte)Mathf.Clamp(maxPlayers, 2, 8);
RoomOptions options = new RoomOptions { MaxPlayers = maxPlayers };
PhotonNetwork.CreateRoom(roomName, options, null);
}
//给玩家信息条的准备按钮使用。当本地玩家是主客户端时,执行。
public void LocalPlayerPropertiesUpdated()
{
StartGameButton.gameObject.SetActive(CheckPlayersReady());
}
//绑定到开始游戏按钮
public void OnStartGameButtonClicked()
{
//当前房间不可再加入
PhotonNetwork.CurrentRoom.IsOpen = false;
//让当前房间不可见:在大厅的列表中搜不到。(并且当你创建这个房间时,也可设为隐身的房间)
PhotonNetwork.CurrentRoom.IsVisible = false;
PhotonNetwork.LoadLevel("DemoAsteroids-GameScene");
}
//绑定到加入随机房间按钮
public void OnJoinRandomRoomButtonClicked()
{
SetActivePanel(JoinRandomRoomPanel.name);
PhotonNetwork.JoinRandomRoom();
}
//绑定到显示列表按钮
public void OnRoomListButtonClicked()
{
if (!PhotonNetwork.InLobby)
{
//使用该API,使PUN调用 刷新大厅房间列表 回调,并在该回调完成相关逻辑。
PhotonNetwork.JoinLobby();
}
SetActivePanel(RoomListPanel.name);
}
//绑定到创建房间界面的返回按钮
public void OnBackButtonClicked()
{
//这并不会执行,因为没加入大厅
if (PhotonNetwork.InLobby)
{
PhotonNetwork.LeaveLobby();
}
SetActivePanel(SelectionPanel.name);
}
//绑定到加入随机房间界面的返回按钮上
public void OnLeaveGameButtonClicked()
{
PhotonNetwork.LeaveRoom();
}
#endregion
#region Private Methods
void SetActivePanel(string activePanel)
{
LoginPanel.SetActive(activePanel.Equals(LoginPanel.name));
SelectionPanel.SetActive(activePanel.Equals(SelectionPanel.name));
CreateRoomPanel.SetActive(activePanel.Equals(CreateRoomPanel.name));
JoinRandomRoomPanel.SetActive(activePanel.Equals(JoinRandomRoomPanel.name));
RoomListPanel.SetActive(activePanel.Equals(RoomListPanel.name)); // UI should call OnRoomListButtonClicked() to activate this
InsideRoomPanel.SetActive(activePanel.Equals(InsideRoomPanel.name));
}
//检查所有玩家是否已经准备
bool CheckPlayersReady()
{
if (!PhotonNetwork.IsMasterClient)
{
return false;
}
foreach (Player p in PhotonNetwork.PlayerList)
{
object isPlayerReady;
if (p.CustomProperties.TryGetValue(AsteroidsGame.PLAYER_READY, out isPlayerReady))
{
if (!(bool)isPlayerReady)
{
return false;
}
}
else
{
return false;
}
}
return true;
}
//清空本地房间数据列表、删除房间预制体
void ClearRoomListView()
{
foreach (GameObject entry in roomListEntries.Values)
{
Destroy(entry.gameObject);
}
roomListEntries.Clear();
}
//更新本地房间数据列表
void UpdateCachedRoomList(List<RoomInfo> roomList)
{
foreach (RoomInfo info in roomList)
{
// 如果缓存房间中该房间为关闭状态、不可见或被标记为已删除,则从缓存房间列表中删除该房间
if (!info.IsOpen || !info.IsVisible || info.RemovedFromList)
{
if (cachedRoomList.ContainsKey(info.Name))
{
cachedRoomList.Remove(info.Name);
}
continue;
}
// Update cached room info
if (cachedRoomList.ContainsKey(info.Name))
{
cachedRoomList[info.Name] = info;
}
// Add new room info to cache
else
{
cachedRoomList.Add(info.Name, info);
}
}
}
//实例化房间预制体
void UpdateRoomListView()
{
foreach (RoomInfo info in cachedRoomList.Values)
{
GameObject entry = Instantiate(RoomListEntryPrefab);
entry.transform.SetParent(RoomListContent.transform);
entry.transform.localScale = Vector3.one;
entry.GetComponent<RoomListEntry>().Initialize(info.Name, (byte)info.PlayerCount, info.MaxPlayers);
roomListEntries.Add(info.Name, entry);
}
}
#endregion
}
}
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PlayerListEntry.cs" company="Exit Games GmbH">
// Part of: Asteroid Demo,
// </copyright>
// <summary>
// Player List Entry
// </summary>
// <author>developer@exitgames.com</author>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
using UnityEngine.UI;
using ExitGames.Client.Photon;
using Photon.Realtime;
using Photon.Pun.UtilityScripts;
namespace Photon.Pun.Demo.Asteroids
{
public class PlayerListEntry : MonoBehaviour
{
[Tooltip("玩家昵称")]
public Text PlayerNameText;
public Image PlayerColorImage;
public Button PlayerReadyButton;
public Image PlayerReadyImage;
int ownerId;
bool isPlayerReady;
#region Mono CallBacks
public void OnEnable()
{
//每次房间索引更新时调用
PlayerNumbering.OnPlayerNumberingChanged += OnPlayerNumberingChanged;
}
public void Start()
{
if (PhotonNetwork.LocalPlayer.ActorNumber != ownerId)
{
PlayerReadyButton.gameObject.SetActive(false);
}
else
{
Hashtable initialProps = new Hashtable() {{AsteroidsGame.PLAYER_READY, isPlayerReady}, {AsteroidsGame.PLAYER_LIVES, AsteroidsGame.PLAYER_MAX_LIVES}};
PhotonNetwork.LocalPlayer.SetCustomProperties(initialProps);
//这将在本地设置分数,并将同步它在游戏中尽快。
PhotonNetwork.LocalPlayer.SetScore(0);
PlayerReadyButton.onClick.AddListener(() =>
{
isPlayerReady = !isPlayerReady;
SetPlayerReady(isPlayerReady);
Hashtable props = new Hashtable() {{AsteroidsGame.PLAYER_READY, isPlayerReady}};
PhotonNetwork.LocalPlayer.SetCustomProperties(props);
//检测是否全员准备,是则显示开始游戏按钮
if (PhotonNetwork.IsMasterClient)
{
FindObjectOfType<LobbyMainPanel>().LocalPlayerPropertiesUpdated();
}
});
}
}
public void OnDisable()
{
PlayerNumbering.OnPlayerNumberingChanged -= OnPlayerNumberingChanged;
}
#endregion
public void Initialize(int playerId, string playerName)
{
ownerId = playerId;
PlayerNameText.text = playerName;
}
private void OnPlayerNumberingChanged()
{
foreach (Player p in PhotonNetwork.PlayerList)
{
if (p.ActorNumber == ownerId)
{
PlayerColorImage.color = AsteroidsGame.GetColor(p.GetPlayerNumber());
}
}
}
public void SetPlayerReady(bool playerReady)
{
PlayerReadyButton.GetComponentInChildren<Text>().text = playerReady ? "Ready!" : "Ready?";
PlayerReadyImage.enabled = playerReady;
}
}
}
大家还有什么问题,欢迎在下方留言!