前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >PUN☀️四、服务器大厅建房解析

PUN☀️四、服务器大厅建房解析

作者头像
星河造梦坊官方
发布2024-08-15 21:07:33
发布2024-08-15 21:07:33
8900
代码可运行
举报
运行总次数:0
代码可运行

🟥 实现目标

Demo:DemoAsteroids大厅的解析

🟧 大厅思路

代码语言:javascript
代码运行次数:0
复制
Awake:设置同步场景的方式
 
登陆按钮:同步本地昵称、连接到服务器
 
连接到服务器回调:关闭登陆界面,打开 创建房间 / 加入房间 / 显示房间列表 的面板选择界面
 
选择创建房间界面的Btn:打开创建房间界面
 
    创建房间界面:输入房间名、最大人数、拥有创建房间、返回的按钮
        返回按钮:返回到功能选择界面
 
    创建房间Btn:根据房间名、最大人数,创建服务器房间
 
        创建/加入房间回调:打开房间内面板、实例化当前所有玩家的条形信息预制体,并将(昵称、是否准备)信息初始化到该预制体上的脚本上↓
            条形信息物体:上有脚本:保存了该玩家的 ID、昵称、准备信息
                           根据初始化的ID==本地玩家ID?不等于则不显示该预制体的准备按钮(即咱们不显示别人电脑的ready,咱们只能控制咱们的ready)
                            若等于,即代表着这个预制体是我们自己的。则显示准备按钮。且将准备信息等做为自定义的同步信息
                            根据本房间内,该玩家的Number,决定这个预制体是什么颜色。(demo设定最多8人,因此这有8种对应的case)【用PUN的 Number更新回调实现】
                            注意,这儿要用到PUN自带的脚本:PlayerNumbering,要将其挂在场景中
                         上有准备按钮:
                            每次点击,改变自身状态(是否关闭等)、同步自身是否准备信息
                            若自己是主服务器,则还可根据当前玩家是否都已准备,显示开始游戏按钮(检查是否都已准备,就是foreach所有玩家的准备信息,进行判断)
                         开始游戏按钮:
                            设置当前房间状态:不可再加入、大厅列表不可见(隐身)
                            PUN同步加载场景
        
    
    加入随机房间按钮:加入随机房间,显示服务器房间界面
        返回按钮:退出服务器房间,返回到功能选择界面
 
    显示房间列表按钮:使用加入大厅API,使PUN调用 刷新大厅列表 回调,在该回调中完成相关逻辑(该回调会传入所有房间列表缓存):
        清空、删掉房间预制体、
        根据从网络获得的缓存列表,判断房间是否可加入、可见性、标记性,删除不需要的房间,将需要的房间添加到本地房间列表。
        更新实例化本地房间列表
    
 
 
 
 
开始游戏按钮状态:只有主客户端进行检测判断。(其他客户端没有开游戏的资格,自然不用检测)
    主客户端点击准备时、
    本地玩家进入房间时、(新玩家进来了,当然关闭按钮了)
    其他玩家进入房间时、(新玩家进来了,当然关闭按钮了)
    其他玩家离开房间时、
    主客户端切换给别人时、
    玩家属性更新时、(PUN回调)
 
 
房间列表更新时机:
    显示房间列表信息按钮、
    本地玩家退出大厅回调、
    本地玩家离开房间回调、
    其他玩家加入房间回调、
    其他玩家离开房间回调、

🟨 主要脚本

该场景主要由这两个脚本实现功能

PlayerNumbering作为PUN实用脚本,挂载到场景中,配合我们写的代码。

LobbyMainPanel

代码语言:javascript
代码运行次数:0
复制
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
    }
}

PlayerListEntry

代码语言:javascript
代码运行次数:0
复制
// --------------------------------------------------------------------------------------------------------------------
// <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;
        }
    }
}

大家还有什么问题,欢迎在下方留言!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-09-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 🟥 实现目标
  • 🟧 大厅思路
  • 🟨 主要脚本
    • LobbyMainPanel
    • PlayerListEntry
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档