第十三章: 社交系统

1. 本章核心目标

本章节的核心目标是构建游戏内玩家之间进行交互的基础框架,增强社区感和多人协作体验。这些目标可分为显性和隐性两个层面。

  • 显性目标 (玩家体验层面)

    1. 文本聊天系统: 为玩家提供一个可以实时交流的渠道,包括面向服务器所有人的全局聊天和仅限部落成员可见的部落聊天

    2. 玩家名牌系统: 在玩家角色头部上方显示其名称部落名,方便身份识别,并根据玩家关系(是否为同部落成员)以不同颜色高亮。

    3. 近距离语音聊天: 实现一种基于地理位置的语音沟通方式,玩家按下特定按键后,只有附近的玩家才能听到其语音。

  • 隐性目标 (技术与战略层面)

    1. 网络架构验证: 建立并验证一套稳健的客户端-服务器(Client-Server)通信模型,用于处理实时的、非 gameplay 核心的数据交换,如聊天消息和状态更新。

    2. 系统解耦与模块化: 通过接口(Interfaces)和事件驱动,确保社交系统(聊天、语音)与玩家角色、游戏模式(Game Mode)等核心模块低耦合,易于扩展和维护。

    3. 第三方插件集成: 成功集成并使用 Advanced SessionsSteam Sessions 插件,为语音聊天等高级网络功能提供底层支持,为后续的在线服务打下基础。

    4. UI与游戏状态管理: 实现游戏输入模式的无缝切换(例如,从“仅游戏”模式切换到“仅UI”模式以进行聊天输入),确保玩家在进行社交互动时不会影响游戏主循环的稳定性。

2. 系统与功能实现

本章主要实现了三大核心功能系统,它们与前期系统紧密交互。

系统/功能 核心实现描述 与前期系统的交互/依赖
文本聊天系统 1. UI层: W_ChatWindow作为主聊天窗口,包含用于显示消息的ScrollBox和用于输入的EditableTextBoxW_ChatMessage作为单个消息的UI模板。
2. 逻辑层: 客户端通过PlayerController将消息RPC发送至服务器。服务器(GameMode)处理消息,判断是全局还是部落消息(通过/tribe前缀区分),然后将消息RPC分发给所有相关客户端。
3. 流程: 输入(Client) -> RPC(Server) -> GameMode处理 -> RPC(Target Clients) -> UI更新(Client)
依赖: 严重依赖玩家状态(PlayerState)来获取玩家名和部落信息。部落聊天功能直接依赖于前期构建的部落系统
交互: 新的聊天窗口W_ChatWindow被集成到主HUD布局DefaultHUDLayout中。
玩家名牌系统 1. UI层: W_PlayerName作为名牌UI模板,包含显示名称和部落的Text控件,以及一个用于语音提示的Image控件。
2. 组件: 在角色蓝图中添加一个Widget Component,设置其UI类为W_PlayerName,并将其附加到角色模型的头部骨骼上,空间模式设为Screen Space
3. 触发机制: 使用一个Sphere Collision球体碰撞器。当其他玩家进入范围时触发OnBeginOverlap显示名牌,离开时触发OnEndOverlap隐藏名牌。
4. 状态显示: 名牌颜色会根据是否与本地玩家同属一个部落而改变(友好为绿色,否则为黄色)。
依赖: 同样依赖玩家状态(PlayerState)来获取显示数据(玩家名、部落名)。颜色逻辑依赖部落系统进行关系判断。
交互: 作为Widget Component直接集成在玩家角色蓝图中。
近距离语音聊天 1. 输入: 通过新的输入动作IA_VoiceChat绑定到键盘按键(B)。
2. 核心逻辑: 按下按键时,在PlayerController中执行控制台命令Toggle Speaking 1开启语音发送;松开时执行Toggle Speaking 0关闭。
3. 技术实现: 依赖Advanced Sessions插件提供的VoipTalker。需在角色蓝图中初始化VoipTalker,将其注册到PlayerState,并设置衰减(Attenuation)来实现近距离效果。
4. UI反馈: 玩家说话时,本地HUD和其头顶名牌上都会显示一个麦克风图标。
5. 配置: 需要修改项目配置文件DefaultEngine.iniDefaultGame.ini以启用语音和按键说话功能。
依赖: 功能上完全依赖Advanced SessionsSteam Sessions插件。
交互: 语音状态的UI反馈与玩家名牌系统主HUD进行了集成。

3. 关键设计思想

本章的实现体现了多种优秀的设计思想和模式,确保了系统的可维护性和扩展性。

设计思想 具体应用体现
接口驱动开发 (Interface-Driven Development) - 广泛使用蓝图接口: 创建了BPI_SurvivalGameMode, BPI_SurvivalCharacter等多个接口,用于解耦不同模块间的通信。
- 示例: 聊天窗口W_ChatWindow不需要知道PlayerController的具体实现,只需调用SendChatMessage接口消息即可。这使得更换或扩展Controller变得容易,而无需修改UI代码。
关注点分离 (Separation of Concerns) - 客户端/服务器职责分离: 严格划分了客户端和服务器的职责。客户端负责UI呈现和输入捕捉(如按下聊天键),服务器负责核心逻辑处理和状态同步(如广播聊天消息)。
- UI与逻辑分离: Widget蓝图(如W_ChatWindow)主要处理UI布局和用户交互事件,而具体的网络通信和逻辑判断则委托给PlayerController和GameMode,遵循了MVVM或类似模式的思想。
单一职责原则 (Single Responsibility Principle) - W_ChatWindow负责管理聊天窗口的整体结构。
- W_ChatMessage仅负责展示一条消息。
- PlayerController负责处理玩家输入和网络消息的路由。
- GameMode负责服务器端游戏规则的执行,如谁应该接收聊天消息。
组件化设计 (Component-Based Architecture) - 玩家名牌系统被实现为一个可复用的Widget Component
- 近距离检测逻辑被封装在一个Sphere Collision Component中。
- 这种设计使得可以轻松地将这些功能附加到任何Actor上,而不仅仅是玩家角色。

4. 核心技术点与难点

本章的实现涉及多个关键技术点,并成功解决了一些开发中的常见难题。

技术类别 关键技术点/难点描述 解决方案
网络通信 不可靠的RPC调用:如何在客户端、服务器间建立一套清晰、解耦的通信流程。 使用**蓝图接口(Blueprint Interface)**作为消息契约,结合Run on Server, Run on Client, Multicast等不同类型的RPC事件,构建了从客户端到服务器再到目标客户端的完整通信链路。
UI 动态UI生成与管理:如何在运行时根据收到的消息动态创建并添加聊天条目到滚动列表中。 W_ChatWindow中,当接收到新消息时,调用Create Widget节点创建W_ChatMessage实例,然后使用Add Child函数将其添加到ScrollBox中。
UI 聊天框自动滚动:如何确保当新消息填满聊天框时,视图能自动滚动到最底部。 ScrollBoxScroll when focus changes属性设为Instant Scroll,并在添加新消息Widget后,对该新Widget调用Set Focus函数来触发自动滚动。
输入系统 输入模式冲突:玩家在打字时,如何阻止角色移动等游戏输入,并在打完字后恢复。 通过Set Input Mode Game OnlySet Input Mode UI Only节点来动态切换输入模式。当玩家聚焦到聊天输入框时,切换为UI模式;当发送消息或失去焦点时,切换回游戏模式。
集成与配置 第三方插件和引擎配置:如何正确配置和使用Advanced Sessions插件及虚幻引擎底层语音功能。 1. 通过蓝图节点(如Create Voip Talker)调用插件功能。
2. 通过修改DefaultEngine.iniDefaultGame.ini文件,添加如[Voice], bRequiresPushToTalk等配置项,来启用引擎级的语音聊天支持。

5. 自我批判与重构

在开发过程中遇到了一些问题,同时也识别出一些可优化的设计。

类别 问题/反思 当前解决方案/修正 若重构,可采用的优化方案
Bug修复 Widget焦点问题: 新创建的聊天消息Widget无法接收焦点,导致自动滚动失效。 W_ChatMessage的类默认设置中,勾选Is Focusable属性,使其可以被聚焦。 无需重构,此为正确修复。
Bug修复 文本不换行: 过长的聊天消息会超出UI边界,影响可读性。 W_ChatMessageText控件细节面板中,启用Auto Wrap Text属性。 无需重构,此为正确修复。
设计局限 名牌更新不及时: 玩家加入或退出部落后,其头顶名牌信息(部落名、颜色)不会立即更新,需要离开再进入对方的视野范围才能刷新。 当前设计依赖Overlap事件来触发名牌的刷新,没有实现主动推送更新。 采用事件驱动更新
1. 在部落系统中,当玩家部落状态变更时(加入、退出),应在服务器上广播一个事件。
2. 关心此事件的系统(如名牌系统)监听该事件。
3. 收到事件后,服务器可以主动找到相关玩家,通过RPC强制其客户端刷新名牌UI,实现数据的实时同步。
代码结构 逻辑分散: 社交系统的相关逻辑分散在PlayerController, Character, GameMode以及多个Widget中,当功能复杂化后,追溯和维护可能变得困难。 当前结构虽然分散,但职责相对清晰。 引入社交管理器(SocialManager)
创建一个独立的子系统或Actor(如SocialManagerSubsystem),专门负责处理所有社交相关的逻辑。无论是聊天消息、部落状态更新还是语音状态,都由这个管理器统一接收、处理和分发。这将极大地提高内聚性,降低模块间的耦合度。
性能 不必要的RPC: 名牌显示逻辑链条较长(Overlap -> RPC -> Client RPC -> Get Widget -> Call Interface),可能存在优化空间。 当前实现确保了逻辑在正确的客户端上执行。 利用数据复制优化: 由于PlayerState本身就是被复制到所有客户端的,因此触发重叠的客户端可以直接从其本地的、对端的PlayerState副本中读取所需信息(名字、部落ID),并在本地直接更新名牌UI。这可以减少为了获取数据显示信息而发起的RPC调用,降低网络开销。