TH1/Unity/Assets/Scripts/Document/OnlineLobby_RequirementDoc.md
2026-02-14 22:57:12 +08:00

12 KiB
Raw Blame History

联机大厅 (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));
    }
}

要点:

  • 无命名空间(与 FriendRowMonoMemberRowMono 一致)
  • 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 中手动完成,代码层面无法自动化:

  1. 创建 LobbyRow 预制体

    • 新建 UI 预制体,挂载 UIOutsideMultiplayLobbyRowMono 组件
    • 包含 TextMeshProUGUIRoomNameText, OwnerNameText, PlayerCountText
    • 可选 TextMeshProUGUIGameStateText, VersionText
    • 包含 ButtonJoinButton
    • 参考 FriendRowPrefab 的布局风格
  2. 修改 UIOutsideMultiplay 主面板预制体

    • 新增 LobbyAreaGameObject 容器,包含标题 + ScrollView
    • 新增 RefreshLobbyButtonButton 组件)
    • 新增 LobbyListScrollView 的 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. 注意事项

  1. 异步时序SearchPublicLobbies() 是异步操作Steam 回调 OnLobbyMatchListCallback 会更新 _lobbyListInfos,需用 Timer 延迟刷新 UI
  2. 事件清理OnCloseView() 中需要取消所有事件订阅,避免内存泄漏
  3. 加入回调一次性:加入成功/失败后立即取消事件监听,避免重复触发
  4. 行对象池:沿用现有的动态 Instantiate + 隐藏复用模式(不销毁已创建的行)
  5. 无命名空间UIOutsideMultiplayLobbyRowMono.cs 不使用命名空间,与同系列 RowMono 保持一致
  6. 多语言:失败提示文本目前硬编码为中文,后续需替换为 MultilingualManager 多语言 key