12 KiB
12 KiB
联机大厅 (Online Lobby) 需求开发文档
1. 功能概述
在现有的 UIOutsideMultiplayView 面板中新增「联机大厅」模块,展示当前 Steam 上所有公开待机房间列表。玩家可以浏览、刷新、点击加入房间。加入成功后自动切换到「当前游戏房间信息」视图;加入失败则弹出失败提示。
2. 功能需求
2.1 联机大厅列表
- 调用
SteamLobbyManager.SearchPublicLobbies()获取公开房间列表 - 使用
SteamLobbyManager.LobbyListInfos属性获取搜索结果 - 每一行数据使用
UIOutsideMultiplayLobbyRowMono组件展示 - 每行显示的信息(来自
LobbyListInfo):- 房间名 (
RoomName) - 房主名 (
OwnerName) - 当前人数/最大人数 (
CurrentPlayers/MaxPlayers) - 游戏状态 (
GameState) - 版本号 (
Version)
- 房间名 (
- 每行有一个「加入」按钮
2.2 加入房间
- 点击行中的「加入」按钮,调用
LobbyManager.Instance.Lobby.JoinLobbyById(lobbyInfo.LobbyId) - 监听
SteamLobbyManager.OnLobbyEnteredEvent判断加入成功 - 监听
SteamLobbyManager.OnLobbyErrorEvent判断加入失败 - 加入成功:自动刷新界面(触发
RefreshRoomInfo()切换到房间信息视图) - 加入失败:在界面上显示「加入失败」提示(使用现有的
CantStartHint+CantStartHintAnimancer淡入淡出模式)
2.3 刷新按钮
- 新增「刷新大厅」按钮,点击后重新调用
SearchPublicLobbies()并刷新列表 - 搜索结果通过
OnLobbyMatchListCallback异步返回后更新UI
2.4 显示/隐藏逻辑
- 联机大厅区域在「未加入房间」时可见(与 CreateRoomButton/NoRoomHint 同时显示)
- 玩家加入房间后(
_lobby.IsInLobby() == true),隐藏联机大厅区域,显示房间成员信息 - 玩家离开房间后,重新显示联机大厅区域
3. 技术设计
3.1 新增文件
| 文件 | 路径 | 说明 |
|---|---|---|
UIOutsideMultiplayLobbyRowMono.cs |
TH1_UI/View/Outside/ |
大厅列表行组件(MonoBehaviour) |
3.2 修改文件
| 文件 | 修改内容 |
|---|---|
UIOutsideMultiplayView.cs |
新增大厅区域的字段引用、刷新逻辑、加入逻辑、事件监听 |
3.3 不需要修改的文件
UIOutsideMultiplayController.cs— 无需改动,Controller 已通过现有委托和RefreshAll机制足以支撑ViewControllerManager.cs— 无需改动,不是新增独立面板UIEvents.cs/UIEventManagerBinder.cs— 无需新增事件
4. 详细实现规范
4.1 UIOutsideMultiplayLobbyRowMono.cs(新增)
遵循 UIOutsideMultiplayFriendRowMono.cs 的代码模式:
using TH1_Logic.Steam;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
// 不使用 namespace,与 FriendRowMono/MemberRowMono 保持一致
public class UIOutsideMultiplayLobbyRowMono : MonoBehaviour
{
[Header("UI引用")]
public TextMeshProUGUI RoomNameText; // 房间名
public TextMeshProUGUI OwnerNameText; // 房主名
public TextMeshProUGUI PlayerCountText; // "2/4" 格式的人数显示
public TextMeshProUGUI GameStateText; // 游戏状态
public TextMeshProUGUI VersionText; // 版本号
public Button JoinButton; // 加入按钮
private LobbyListInfo _lobbyInfo;
public bool CheckParam()
{
return RoomNameText != null && OwnerNameText != null
&& PlayerCountText != null && JoinButton != null;
}
/// <summary>
/// 设置行内容
/// </summary>
/// <param name="lobbyInfo">房间信息</param>
/// <param name="onJoinClicked">加入按钮回调(由View统一处理)</param>
public void SetContent(LobbyListInfo lobbyInfo, System.Action<LobbyListInfo> onJoinClicked)
{
if (!CheckParam()) return;
_lobbyInfo = lobbyInfo;
RoomNameText.text = string.IsNullOrEmpty(lobbyInfo.RoomName)
? $"Room {lobbyInfo.LobbyId}" : lobbyInfo.RoomName;
OwnerNameText.text = lobbyInfo.OwnerName ?? "Unknown";
PlayerCountText.text = $"{lobbyInfo.CurrentPlayers}/{lobbyInfo.MaxPlayers}";
if (GameStateText != null)
GameStateText.text = lobbyInfo.GameState.ToString();
if (VersionText != null)
VersionText.text = lobbyInfo.Version ?? "";
JoinButton.onClick.RemoveAllListeners();
JoinButton.onClick.AddListener(() => onJoinClicked?.Invoke(_lobbyInfo));
}
}
要点:
- 无命名空间(与
FriendRowMono、MemberRowMono一致) CheckParam()校验 Inspector 引用SetContent()方法接收LobbyListInfo数据 + 加入按钮回调委托- 加入按钮回调由 View 层统一提供,RowMono 不直接调用 Steam API
4.2 UIOutsideMultiplayView.cs(修改)
4.2.1 新增字段
在 [Header("视觉区块及预制体")] 区域下新增:
[Header("联机大厅")]
public GameObject LobbyArea; // 大厅区域根节点
public Button RefreshLobbyButton; // 刷新大厅按钮
public GameObject LobbyRowPrefab; // 大厅行预制体
public GameObject LobbyList; // 大厅行的父容器(ScrollView Content)
新增私有字段:
private List<UIOutsideMultiplayLobbyRowMono> _lobbyRowList;
4.2.2 OnInit() 修改
在 OnInit() 中新增:
_lobbyRowList = new List<UIOutsideMultiplayLobbyRowMono>();
// 刷新大厅按钮
RefreshLobbyButton.onClick.RemoveAllListeners();
RefreshLobbyButton.onClick.AddListener(OnRefreshLobbyClicked);
4.2.3 新增方法
/// <summary>
/// 点击刷新大厅按钮
/// </summary>
public void OnRefreshLobbyClicked()
{
_lobby.SearchPublicLobbies();
// 搜索结果通过回调异步返回,需要延迟刷新
// 使用Timer延迟刷新,等待Steam回调完成
Timer.Instance.TimerRegister(this, RefreshLobbyList, 1f, "RefreshLobbyList");
}
/// <summary>
/// 刷新联机大厅列表
/// </summary>
public void RefreshLobbyList()
{
if (!_lobby.IsInitialized()) return;
var lobbyInfos = _lobby.LobbyListInfos;
int lobbyCount = lobbyInfos?.Count ?? 0;
// 动态补足行对象(遵循现有 FriendRow 模式)
while (_lobbyRowList.Count < lobbyCount)
{
var item = Instantiate(LobbyRowPrefab, LobbyList.transform);
var cpn = item.GetComponent<UIOutsideMultiplayLobbyRowMono>();
if (cpn != null)
_lobbyRowList.Add(cpn);
}
// 先全部隐藏
for (int i = 0; i < _lobbyRowList.Count; i++)
_lobbyRowList[i].gameObject.SetActive(false);
// 填充数据
for (int i = 0; i < lobbyCount; i++)
{
_lobbyRowList[i].gameObject.SetActive(true);
_lobbyRowList[i].SetContent(lobbyInfos[i], OnJoinLobbyClicked);
}
}
/// <summary>
/// 点击加入某个大厅房间
/// </summary>
private void OnJoinLobbyClicked(LobbyListInfo lobbyInfo)
{
if (_lobby.IsInLobby())
{
// 已在房间中,忽略
return;
}
// 注册一次性加入结果监听
_lobby.OnLobbyEnteredEvent += OnLobbyJoinSuccess;
_lobby.OnLobbyErrorEvent += OnLobbyJoinFailed;
LobbyManager.Instance.Lobby.JoinLobbyById(lobbyInfo.LobbyId);
}
/// <summary>
/// 加入成功回调
/// </summary>
private void OnLobbyJoinSuccess(CSteamID lobbyId)
{
_lobby.OnLobbyEnteredEvent -= OnLobbyJoinSuccess;
_lobby.OnLobbyErrorEvent -= OnLobbyJoinFailed;
// 加入成功,刷新界面 => 自动切换到房间信息视图
RefreshRoomInfo();
}
/// <summary>
/// 加入失败回调
/// </summary>
private void OnLobbyJoinFailed(string error)
{
_lobby.OnLobbyEnteredEvent -= OnLobbyJoinSuccess;
_lobby.OnLobbyErrorEvent -= OnLobbyJoinFailed;
// 显示加入失败提示(复用 CantStartHint 模式)
CantStartHint.SetActive(true);
CantStartHintText.text = "加入房间失败"; // 后续可替换为多语言文本
CantStartHintAnimancer.Play(ResourceCache.Instance.AnimCache.UICommonPanelFadeIn);
Timer.Instance.TimerRegister(this, () =>
{
CantStartHintAnimancer.Play(ResourceCache.Instance.AnimCache.UICommonPanelFadeOut);
}, 1f, "LobbyJoinFailed");
Timer.Instance.TimerRegister(this, () =>
{
CantStartHint.SetActive(false);
}, 1f + ResourceCache.Instance.AnimCache.UICommonPanelFadeOut.length, "LobbyJoinFailed");
}
4.2.4 SetNoRoom() 修改
在现有的 SetNoRoom() 中追加大厅区域显示 + 自动搜索:
public void SetNoRoom()
{
// ... 现有代码保持不变 ...
// 新增:显示联机大厅区域
LobbyArea?.SetActive(true);
OnRefreshLobbyClicked(); // 自动搜索一次
}
4.2.5 SetRoomInfoMember() 修改
在现有的 SetRoomInfoMember() 开头追加隐藏大厅:
private void SetRoomInfoMember()
{
// 新增:隐藏联机大厅区域
LobbyArea?.SetActive(false);
// ... 现有代码保持不变 ...
}
4.2.6 SetContent() 修改
在 SetContent() 中追加自动搜索大厅:
public void SetContent(ShowUIOutsideMultiplay evt)
{
//Step #1 设置朋友列表
RefreshFriendList();
//Step #2 设置房间列表
RefreshRoomInfo();
// 新增:Step #3 初始搜索联机大厅
OnRefreshLobbyClicked();
//Step #4 绑定房间数据更新的委托 (原Step #3)
_lobby.OnMembersChangedEvent += RefreshAll;
_lobby.OnLobbyLeftEvent += RefreshAll;
}
4.2.7 OnCloseView() 修改
在 OnCloseView() 中清理加入回调:
public void OnCloseView()
{
_lobby.OnMembersChangedEvent -= RefreshAll;
_lobby.OnLobbyLeftEvent -= RefreshAll;
// 新增:清理加入回调,防止内存泄漏
_lobby.OnLobbyEnteredEvent -= OnLobbyJoinSuccess;
_lobby.OnLobbyErrorEvent -= OnLobbyJoinFailed;
}
5. Unity Prefab 配置要求(手动操作)
以下步骤需在 Unity Editor 中手动完成,代码层面无法自动化:
-
创建 LobbyRow 预制体:
- 新建 UI 预制体,挂载
UIOutsideMultiplayLobbyRowMono组件 - 包含
TextMeshProUGUI:RoomNameText, OwnerNameText, PlayerCountText - 可选
TextMeshProUGUI:GameStateText, VersionText - 包含
Button:JoinButton - 参考
FriendRowPrefab的布局风格
- 新建 UI 预制体,挂载
-
修改 UIOutsideMultiplay 主面板预制体:
- 新增
LobbyArea(GameObject 容器,包含标题 + ScrollView) - 新增
RefreshLobbyButton(Button 组件) - 新增
LobbyList(ScrollView 的 Content 节点,挂载 VerticalLayoutGroup) - 将 LobbyRow 预制体拖拽赋值给 View 的
LobbyRowPrefab字段
- 新增
6. 数据流图
[用户点击刷新]
→ View.OnRefreshLobbyClicked()
→ SteamLobbyManager.SearchPublicLobbies()
→ Steam SDK 异步回调 → OnLobbyMatchListCallback()
→ _lobbyListInfos 更新
→ Timer延迟 → View.RefreshLobbyList()
→ 遍历 _lobbyListInfos → 填充 LobbyRowMono.SetContent()
[用户点击加入]
→ LobbyRowMono JoinButton → View.OnJoinLobbyClicked()
→ SteamLobbyManager.JoinLobbyById()
→ Steam SDK 异步回调
→ 成功: OnLobbyEnterCallback → OnLobbyEnteredEvent
→ View.OnLobbyJoinSuccess() → RefreshRoomInfo() → 显示房间信息
→ 失败: OnLobbyEnterCallback → OnLobbyErrorEvent
→ View.OnLobbyJoinFailed() → 显示失败提示
7. 注意事项
- 异步时序:
SearchPublicLobbies()是异步操作,Steam 回调OnLobbyMatchListCallback会更新_lobbyListInfos,需用 Timer 延迟刷新 UI - 事件清理:
OnCloseView()中需要取消所有事件订阅,避免内存泄漏 - 加入回调一次性:加入成功/失败后立即取消事件监听,避免重复触发
- 行对象池:沿用现有的动态 Instantiate + 隐藏复用模式(不销毁已创建的行)
- 无命名空间:
UIOutsideMultiplayLobbyRowMono.cs不使用命名空间,与同系列 RowMono 保持一致 - 多语言:失败提示文本目前硬编码为中文,后续需替换为
MultilingualManager多语言 key