第六章:玩家属性系统
一、本章核心目标
本章的核心任务是构建一个全面且与游戏核心循环紧密相连的玩家属性系统。此目标可分解为对玩家可见的显性目标和项目层面的隐性战略目标。
-
显性目标 (Player-Facing Goals):
为玩家实现一个完整的、可视化的角色属性与成长体系。具体包括:
-
基础生存属性: 建立并展示核心生存指标,如生命、体力、食物和水分等,让玩家能够直观地了解角色状态。
-
动态交互: 玩家能够通过行为(如冲刺)消耗属性(体力),并通过消耗品(如浆果)恢复属性。
-
环境影响: 引入被动消耗机制,如食物和水分会随时间自动减少,强化了生存压力和资源管理的重要性。
-
角色成长: 建立经验与升级系统,玩家通过游戏行为(如采集)获得经验,升级后获得技能点,并可自由分配技能点以增强特定属性,实现个性化角色构建。
-
-
隐性/战略目标 (Technical & Project Goals):
在技术和项目管理层面,本章旨在为后续开发奠定坚实的数据和逻辑基础。
-
系统解耦: 设计并实现一套基于接口(Interfaces)和事件(Events)的通信框架,将核心属性逻辑(服务器端)与UI表现(客户端)分离,确保系统的可维护性和多人游戏下的稳定性。
-
数据驱动设计: 引入数据资产(Data Assets)和数据表(Data Tables)来管理消耗品效果和经验曲线,将策划数值与程序逻辑分离,便于后期进行游戏平衡性调整,而无需修改核心代码。
-
模块化与可复用性: 创建独立的、功能专一的UI控件(Widgets)和功能函数,如独立的属性条、通知模块等,确保了代码的可复用性和未来扩展的便捷性。
-
网络同步基础: 严格区分客户端与服务器逻辑,通过RPCs(远程过程调用)确保所有关键状态(如伤害计算、属性消耗)在服务器上权威执行,并同步到客户端,为稳定的多人游戏体验打下基础。
-
二、系统与功能实现
本章成功实现了一系列相互关联的系统与功能模块,构成了玩家属性的核心框架。
-
实现系统/功能列表:
-
核心属性结构 (
S_PlayerStatStruct
,S_PlayerCoreStats
): 定义了玩家所有健康、生存及成长相关的数值结构体,作为数据的中心模型。 -
属性UI系统:
-
HUD属性条 (
W_PlayerStatsWindow
): 在主界面实时显示生命、体力、食物、水分等关键属性的圆形进度条。 -
库存界面属性详情 (
W_PlayerInfoWindow
): 在玩家库存界面提供更详细的属性数值展示、经验条、技能点,并包含用于升级属性的交互按钮。
-
-
伤害系统:
-
通过为玩家角色添加
Damageable
标签,实现了可被伤害的逻辑。 -
实现了
Event AnyDamage
事件,在服务器上权威处理伤害计算并更新生命值。
-
-
被动消耗系统:
-
通过服务器端的计时器 (
Set Timer by Event
),实现了食物和水分随时间被动减少的生存机制。 -
实现了“饥饿”与“脱水”状态,当相应属性降为零时触发,并显示UI通知。当玩家处于这些负面状态时,会持续扣减生命值。
-
-
体力与冲刺系统:
-
玩家移动(特别是冲刺)时会消耗体力。
-
当体力低于特定阈值时,玩家移动速度会降低。
-
停止移动后,体力会自动再生。
-
-
消耗品系统:
-
创建了可定义消耗效果的数据资产 (
PrimaryDataAsset_ConsumableEffect
),允许策划轻松配置物品(如浆果)能恢复哪些属性、恢复量(即时/持续)。 -
实现了物品消耗逻辑,玩家使用快捷栏中的消耗品会减少物品数量并触发属性恢复效果。
-
-
经验与升级系统:
-
玩家通过特定行为(如采集)可获得经验值。
-
通过数据表 (
DT_ExperienceTable
) 定义了各等级所需的经验值。 -
经验值满后,玩家自动升级,获得技能点,并触发UI通知。
-
-
技能点分配系统:
- 玩家可以在库存界面点击“+”按钮,消耗技能点来永久提升某个属性的最大值(如最大生命值)。
-
-
与前期系统依赖交互:
本章构建的系统与前期已有的物品和库存系统深度集成。
-
依赖: 消耗品系统依赖于物品系统的
PrimaryDataAsset_ItemInfo
,通过在其内部新增一个对ConsumableEffect
数据资产的引用来判定一个物品是否可被消耗及其效果。 -
交互: 当玩家在快捷栏使用一个被标记为
Consumable
的物品时,会触发本章实现的F_ConsumeItem
函数,该函数会与库存组件 (BPC_ItemsContainer
) 交互,减少物品数量,并应用属性恢复效果。
-
三、关键设计思想
本章的实现体现了多种优秀的软件设计思想和原则,确保了系统的健壮性与可扩展性。
设计思想/原则 | 具体应用实例 | 优势分析 |
---|---|---|
数据驱动设计 (Data-Driven) | - 使用数据表 (DT_ExperienceTable ) 管理角色升级所需的经验值。 - 使用数据资产 ( PDA_ConsumableEffect ) 定义消耗品的效果,如恢复量和持续时间。 |
将易变的策划数值(平衡性)与稳定的程序逻辑分离,允许在不重新编译代码的情况下调整游戏平衡,极大提升了开发和迭代效率。 |
观察者模式 (Observer Pattern) | 通过蓝图接口 (BPI_SurvivalController ) 和事件实现。角色(被观察者)在属性变化后,通过控制器调用接口消息(如UpdateStatBar )。 UI控件(观察者)在控制器中监听这些消息并更新自身状态。 |
实现了核心逻辑与UI表现的松耦合。角色不需要知道具体的UI实现,UI也可以独立于角色逻辑进行修改,符合高内聚、低耦合的原则。 |
单一职责原则 (SRP) | - 创建了多个功能专一的UI控件,如W_PlayerStat (单个属性条)、W_StatNotification (状态通知)。 - 将不同逻辑封装在独立的函数中,如 F_FoodWaterDrain 、F_AddExperience 。 |
每个模块或函数只负责一项任务,使得代码更易于理解、测试和维护。 |
关注点分离 (SoC) | - 严格使用Switch Has Authority 节点和RPCs(Run on Server , Run on Client )来区分服务器端权威逻辑和客户端表现逻辑。 |
这是构建稳定、安全的多人游戏的基础。保证了所有重要计算都在服务器上进行,防止客户端作弊,同时确保了流畅的客户端体验。 |
开闭原则 (OCP) | - 消耗品系统:要添加一个新的消耗品,只需创建一个新的物品数据资产和对应的消耗效果数据资产即可,无需修改任何现有的消耗逻辑代码。 | 系统对扩展开放(可以添加新物品),对修改关闭(核心消耗逻辑稳定不变),增强了系统的灵活性和可扩展性。 |
四、核心技术点与难点
本章的实现涉及多个Unreal Engine的核心技术,并解决了多人游戏开发中的一些经典难题。
-
关键技术点:
-
UMG高级应用: 深度使用UMG创建了模块化、数据驱动的UI。包括控件的动态创建(
Create Widget
)、数据绑定、以及使用UI动画(Animation Track
)来增强视觉反馈(如经验获得时的弹出效果)。 -
异步资源加载 (
Async Load Asset
): 在需要获取物品详细信息(特别是消耗品效果)时,采用异步加载的方式,避免了因同步加载磁盘资源而可能导致的瞬间卡顿,保证了游戏运行的流畅性。 -
蓝图通信框架: 综合运用蓝图接口、事件分发器和RPCs,构建了一个高效、解耦的系统间通信网络,是本章实现多人游戏功能的核心。
-
计时器管理 (
Timer
): 熟练运用计时器实现被动属性消耗、持续性效果(如消耗品随时间恢复)和冷却时间等逻辑。
-
-
难点与解决方案:
难点 | 解决方案 |
---|---|
多人环境下UI状态的精确同步 | 采用了一套严谨的服务器权威更新流程: 1. 服务器执行权威操作(如应用伤害)。 2. 服务器更新权威数据(如 PlayerStats 结构体)。 3. 服务器通过接口调用玩家控制器上的事件。 4. 玩家控制器再调用一个 Run on Owning Client 的RPC事件。 5. 客户端在该事件中,安全地访问并更新自己的UI控件。 这确保了数据的一致性和UI的及时响应。 |
设计可扩展的经验和消耗品系统 | 通过数据驱动的方式解决: - 经验系统:将复杂的等级经验曲线抽象为一个数据表 ( DT_ExperienceTable ),策划可直接在表格中配置每一级的数值,无需程序员介入。 - 消耗品系统:将物品效果抽象为独立的数据资产,新物品只需引用或创建新的效果资产即可,扩展性极强。 |
处理复杂的状态逻辑(如多级升级) | 在F_AddExperience 函数中,通过一个循环检查的逻辑来处理一次性获得大量经验可能导致的多级提升。 该函数使用局部变量来追踪剩余待处理的经验值,循环判断是否足够升下一级,直到所有经验值被正确分配。 这保证了等级计算的准确性。 |
五、自我批判与重构
本章在实现过程中遇到了一些问题,同时也暴露出一些可优化的设计,为未来的重构提供了方向。
-
遇到的“坑”与关键问题:
-
网络同步Bug: 在开发过程中,曾出现一个因蓝图节点连线错误导致的多人模式下属性更新失败的问题。这暴露出在网络编程中,微小的疏忽都可能导致严重的同步问题,强调了细心和充分测试的重要性。
-
测试数值不当: 在测试“饥饿”状态时,初始设定的消耗速率过高,导致属性瞬间清零,无法观察到渐变过程。这说明了迭代开发中,对测试用例和平衡数值进行细致调节的必要性。
-
-
对前期设计的反思与修正:
- 本章开发中,对前期的库存UI (
W_Inventory
) 进行了重构。为了集成新的属性详情面板,原有的布局被拆分并替换为更灵活的垂直框和水平框组合,并删除了不再需要的占位符按钮。这是项目迭代过程中的正常修正,反映了系统设计随功能增加而演进的客观规律。
- 本章开发中,对前期的库存UI (
-
重构优化建议:
待优化点 | 当前实现 | 建议优化方案 |
---|---|---|
技能点升级逻辑重复 | 在F_LevelUpSkillPoint 函数中,为生命、食物、体力等每个属性升级的逻辑几乎完全相同(获取当前/最大值、最大值+10、更新结构体)。 |
将此段逻辑抽象成一个独立的辅助函数,如F_IncreaseMaxStat(InStatType, InAmount) 。原函数只需根据传入的属性类型调用这个新函数即可,从而大幅减少代码冗余,提高可维护性。 |
被动消耗逻辑的扩展性 | F_FoodWaterDrain 函数中,减少食物和水分的逻辑是分开处理的,存在轻微的重复。 |
创建一个更通用的函数F_ApplyPassiveDrain(StatType, DrainPercentage) 。这样,未来若要增加新的被动消耗(如寒冷环境下的体温消耗),只需调用此通用函数,而无需编写新的独立逻辑。 |
“魔法数字”硬编码 | 冲刺速度(800 )、体力消耗率(0.8 )、技能点提升量(10 )等数值直接写在蓝图中。 |
将这些易于变动的平衡性数值统一提取到一个全局的数据资产或游戏实例变量中进行管理。这样策划就可以在一个地方集中调整所有相关数值,而不用深入到复杂的蓝图逻辑中。 |