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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: PUN☀️四、服务器大厅建房解析

🟥 实现目标

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实用脚本,挂载到场景中,配合我们写的代码。

LobbyMainPanel

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

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


相关文章
|
4天前
|
机器学习/深度学习 人工智能 弹性计算
阿里云GPU服务器全解析_GPU价格收费标准_GPU优势和使用说明
阿里云GPU云服务器提供强大的GPU算力,适用于深度学习、科学计算、图形可视化和视频处理等场景。作为亚太领先的云服务商,阿里云GPU云服务器具备高灵活性、易用性、容灾备份、安全性和成本效益,支持多种实例规格,满足不同业务需求。
|
17天前
|
存储 弹性计算 NoSQL
"从入门到实践,全方位解析云服务器ECS的秘密——手把手教你轻松驾驭阿里云的强大计算力!"
【10月更文挑战第23天】云服务器ECS(Elastic Compute Service)是阿里云提供的基础云计算服务,允许用户在云端租用和管理虚拟服务器。ECS具有弹性伸缩、按需付费、简单易用等特点,适用于网站托管、数据库部署、大数据分析等多种场景。本文介绍ECS的基本概念、使用场景及快速上手指南。
57 3
|
21天前
|
监控 网络协议 安全
DNS服务器故障不容小觑,从应急视角谈DNS架构
DNS服务器故障不容小觑,从应急视角谈DNS架构
44 4
|
28天前
|
存储 固态存储 安全
阿里云服务器X86计算架构解析与X86计算架构云服务器收费价格参考
阿里云服务器架构分为X86计算、Arm计算、高性能计算等多种架构,其中X86计算是用户选择最多的一种架构,本文将深入探讨阿里云X86计算架构的云服务器,包括其技术特性、适用场景、性能优势以及最新价格情况。
|
29天前
|
域名解析 网络协议 CDN
阿里云服务器购买后如何解析域名,三步操作即可解析绑定
阿里云服务器购买后如何解析域名,三步操作即可解析绑定
|
29天前
|
编解码 弹性计算 应用服务中间件
阿里云服务器Arm计算架构解析:Arm计算架构云服务器租用收费标准价格参考
阿里云服务器架构分为X86计算、Arm计算、高性能计算等多种架构,其中Arm计算架构以其低功耗、高效率的特点受到广泛关注。本文将深入解析阿里云Arm计算架构云服务器的技术特点、适用场景以及包年包月与按量付费的收费标准与最新活动价格情况,以供选择参考。
|
1月前
|
弹性计算 网络协议 Ubuntu
如何在阿里云国际版Linux云服务器中自定义配置DNS
如何在阿里云国际版Linux云服务器中自定义配置DNS
|
21天前
|
人工智能 关系型数据库 双11
2024年阿里双十一活动解析:助力大家优惠上云!云服务器79元1年起
2024年阿里云双十一活动已启动,提供云服务器79元1年起等特惠,涵盖云数据库、对象存储、无影云电脑等140余款产品免费试用,企业用户还可申请百万补贴金及5亿算力补贴,助力优惠上云。
|
28天前
|
域名解析 缓存 网络协议
Windows系统云服务器自定义域名解析导致网站无法访问怎么解决?
Windows系统云服务器自定义域名解析导致网站无法访问怎么解决?
|
1月前
|
网络安全 Docker 容器
【Bug修复】秒杀服务器异常,轻松恢复网站访问--从防火墙到Docker服务的全面解析
【Bug修复】秒杀服务器异常,轻松恢复网站访问--从防火墙到Docker服务的全面解析
24 0

推荐镜像

更多