第七章:护甲装备
1. 本章核心目标
本章的核心是构建一个完整且可扩展的护甲装备系统。这不仅包括玩家可见的功能实现,也包含了支撑未来开发的战略性技术决策。
1.1. 显性目标 (玩家导向)
为玩家实现了一套功能完整的护甲穿戴与交互系统,具体包括:
-
装备穿戴:玩家可以将护甲(头盔、上衣、裤子、靴子、背包)从物品栏拖拽至专用的护甲槽位进行装备。
-
视觉表现:装备护甲后,玩家角色模型会实时更新,显示相应的外观。
-
属性增益:穿戴护甲会为玩家提供伤害减免效果,提升生存能力。
-
耐久度系统:护甲拥有耐久度(生命值),在受到伤害时会损耗,最终会损坏并消失。
-
UI反馈:在物品栏UI中增加了专门的护甲槽位,并集成了一个3D角色预览窗口,让玩家可以直观地看到装备变化。
1.2. 隐性目标 (开发导向)
在技术和项目层面上,本章达成了以下几个关键的战略目标:
-
系统模块化:通过创建
BP_ArmorMaster
基类及各种子类,构建了一个模块化的护甲蓝图结构,极大增强了系统的可扩展性与可维护性。 -
深化游戏玩法:引入了伤害减免和装备损耗机制,丰富了战斗和生存策略,为后续更复杂的战斗系统(如部位伤害)和经济系统(如装备修理)奠定了基础。
-
增强用户体验 (UX):实现了实时的3D角色预览窗口,这是一种高级的UI/UX解决方案,显著提升了玩家的沉浸感和装备管理的便利性。
-
解耦通信架构:广泛使用蓝图接口(Interface)在UI、玩家控制器和角色之间进行通信,遵循了高内聚、低耦合的设计原则,使各模块可以独立开发和修改。
1.3. 目标关系图
下表清晰地展示了显性目标与隐性目标之间的支撑关系:
显性目标 (玩家看到什么) | 隐性/战略目标 (我们做了什么) |
---|---|
玩家可以穿上各种护甲,并看到外观变化。 | 系统模块化:建立了BP_ArmorMaster 继承体系,便于未来添加新护甲。 |
穿上护甲后更“抗揍”了。 | 深化游戏玩法:实现了基于护甲件数的伤害减免算法。 |
护甲被打多了会坏掉。 | 深化游戏玩法:设计并实现了护甲耐久度系统,将伤害事件与装备状态关联。 |
物品栏里可以直接看到角色穿上装备的样子。 | 增强用户体验:利用SceneCaptureComponent2D 实现了高级的UI内3D预览功能。 |
拖拽装备即可穿上/脱下,操作流畅。 | 解耦通信架构:通过接口驱动OnDrop 事件,实现了UI层和逻辑层的清晰分离。 |
2. 系统与功能实现
本章引入和扩展了多个系统,以支持完整的护甲功能。
2.1. 实现的系统列表
-
护甲数据系统:
-
使用主数据资产
DA_ItemInfo
为每件护甲(如DA_LeatherBoots
)创建了实例。 -
定义了护甲的核心数据属性,如物品ID、图标、护甲类型(
EArmorType
枚举)、耐久度(ItemDamage
字段复用为耐久度)等。
-
-
护甲Actor系统:
-
创建了
BP_ArmorMaster
作为所有护甲Actor的父类,其继承自BP_ItemMaster
。 -
BP_ArmorMaster
内含一个骨骼网格体组件和用于存储自身物品信息的S_ItemInfo
变量。 -
为每种护甲创建了对应的子蓝图(如
BP_ArmorBoots
),并为其分配了具体的骨骼网格体。
-
-
装备/卸装逻辑系统:
-
装备:在
W_InventorySlot
的OnDrop
事件中,增加了对"是否为护甲槽"的判断。若满足条件,则通过接口调用角色蓝图中的EquipArmor
事件。 该事件在服务器上执行,负责生成护甲Actor,将其附加到角色身上,并使用SetMasterPoseComponent
同步动画。 -
卸装:通过判断拖拽来源是否为护甲槽(
ContainerType == Armor
)来触发DequipArmor
事件。 该事件在服务器上执行,负责销毁护甲Actor,并将其物品信息添加回玩家物品栏。
-
-
UI扩展与3D预览系统:
-
护甲槽UI:复用并扩展了
W_InventorySlot
控件,在W_Inventory
中创建了五个专用的护甲槽位。通过公开IsArmorSlot
、ArmorType
等变量,使其具备了处理护甲装备的特殊逻辑。 -
3D预览:创建了一个独立的
BP_PlayerWindow
Actor,其包含一个骨骼网格体和一个SceneCaptureComponent2D
。 通过将场景捕捉组件的输出渲染到一个Render Target,再将该Render Target动态创建为UI材质实例,并应用到W_Inventory
中的一个Image控件上,从而实现了3D预览。
-
-
护甲属性系统:
-
伤害减免:在武器(如
BP_HatchetMaster
)的伤害计算流程中,增加了对目标玩家护甲件数的查询。根据护甲件数,应用一个分级的伤害减免系数。 -
耐久度损耗:在角色蓝图的
ApplyAnyDamage
事件流中,增加了对护甲造成耐久度伤害的逻辑。伤害值会平均分配给所有已装备的护甲件,并更新其CurrentHP
。当CurrentHP
小于等于0时,触发护甲破坏逻辑。
-
2.2. 系统交互流程图 (以“装备护甲”为例)
sequenceDiagram
participant Player as 玩家
participant UI_Slot as W_InventorySlot (护甲槽)
participant Player_Char as BP_PlayerCharacter
participant Server
Player->>UI_Slot: 拖拽护甲物品至护甲槽
UI_Slot->>UI_Slot: OnDrop() 触发
UI_Slot->>Player_Char: 调用接口 EquipArmor(ItemInfo)
Player_Char->>Server: RPC: EquipArmor_OnServer(ItemInfo)
Server->>Server: 检查是否可装备 (如槽位是否已占用)
Server->>Server: SpawnActor(BP_Armor_Child)
Server->>Server: SetOwner() & AttachToComponent()
Server->>Server: 调用 Multicast RPC 同步动画 (SetMasterPoseComponent)
Server->>Player_Char: 调用接口 RemoveItemFromInventory()
Server->>Player_Char: 调用接口 UpdateArmorUI()
Player_Char-->>UI_Slot: 更新UI显示 (图标、耐久条)
3. 关键设计思想
本章的实现体现了多种优秀的设计模式与原则,确保了系统的健壮性和可扩展性。
设计思想 | 具体应用实例 |
---|---|
设计模式:观察者模式 (Observer) | 通过蓝图接口和事件实现。UI(观察者)不直接修改角色(主题),而是发送EquipArmor 消息。 角色状态改变后,通过UpdateArmorUI 等事件通知控制器和UI进行更新。 这是一个典型的解耦通信模式。 |
设计模式:模板方法模式 (Template Method) | BP_ArmorMaster 作为抽象基类(模板),定义了所有护甲共有的组件(骨骼网格体)和变量(ItemInfo )。 子类如BP_ArmorBoots 只需重写或填充具体内容(如指定特定的网格体资源),而无需重复实现通用逻辑。 |
设计原则:单一职责原则 (SRP) | 职责划分清晰: - 数据资产 ( DA_ ):只负责存储静态数据。- Actor蓝图 ( BP_ ):负责游戏世界中的表现和交互。- UI控件 ( W_ ):负责显示和用户输入。- 角色蓝图:作为核心逻辑中枢,处理装备、卸装、伤害计算等。 - BP_PlayerWindow :专门负责3D预览的渲染。 |
设计原则:开闭原则 (OCP) | 系统对扩展开放,对修改关闭。要添加新护甲,只需创建新的数据资产和BP_ArmorMaster 的子类,并在UI上添加新槽位即可。核心的装备和伤害计算逻辑几乎无需修改。虽然部分Switch 节点 需要添加分支,但整体架构是高度可扩展的。 |
设计原则:接口隔离原则 (ISP) | 使用了多个目标明确的接口,如BPI_SurvivalCharacter 、BPI_ArmorInterface 、BPI_PlayerWindow ,而不是一个庞大的通用接口。 这使得类只需实现与其自身相关的方法,避免了实现不必要的功能。 |
设计原则:依赖倒置原则 (DIP) | 高层模块(如BP_PlayerCharacter )不直接依赖底层模块(如BP_ArmorBoots ),而是依赖于抽象(BP_ArmorMaster 和蓝图接口)。例如,UI通过BPI_SurvivalCharacter 接口与角色通信,实现了UI与角色逻辑的解耦。 |
4. 核心技术点与难点
本章涉及了多个关键技术点,并成功解决了实现过程中的一些难点。
技术/难点 | 描述与解决方案 |
---|---|
核心技术:模块化角色与动画同步 | 描述:如何让独立的护甲模型完美地跟随角色运动和动画。 解决方案:将护甲的 SkeletalMeshComponent 附加到角色的主网格体上后,关键是调用Set Master Pose Component 节点。 该节点强制护甲网格体(Slave)实时复制主角色网格体(Master)的骨骼姿态,从而实现无缝的动画同步。 |
核心技术:UI内嵌3D实时渲染 | 描述:如何在2D的UI界面中展示一个动态的、可交互的3D角色模型。 解决方案:在场景中一个玩家看不见的位置生成一个 BP_PlayerWindow Actor。使用SceneCaptureComponent2D 组件像摄像机一样“拍摄”这个Actor,并将画面实时输出到一个Render Target 纹理资源上。 接着,动态创建一个使用此纹理的UI材质实例,并将其应用到UMG的Image 控件上,从而在UI中“直播”3D场景。 通过在打开/关闭物品栏时开关Capture Every Frame 来进行性能优化。 |
核心技术:客户端-服务器数据同步 | 描述:如何确保装备状态在所有客户端上正确同步。 解决方案:采用服务器权威模型。装备/卸装的核心逻辑通过RPC(远程过程调用)在服务器上执行( Run on Server )。 视觉表现的更新,如生成Actor和同步动画,则通过Multicast 事件广播给所有客户端,确保所有玩家都能看到一致的外观变化。 UI的更新则只在所属客户端上执行(Run on Owning Client )。 |
难点解决:已装备物品的数据持久化 | 问题:护甲装备后会从物品栏数组中移除,那么它的耐久度等动态数据如何保存? 解决方案:当护甲被装备时,其完整的物品信息结构体( S_ItemInfo )被复制并存储在生成的护甲Actor(BP_ArmorMaster )的一个变量中。 当护甲受损时,直接修改这个Actor内部的变量。当护甲被卸下或损坏时,系统从该Actor中通过接口GetArmorItem 取回最新的物品信息,然后才销毁Actor。 |
5. 自我批判与重构
在开发过程中遇到了一些问题,同时也对现有设计进行了反思,并提出了未来的优化方向。
类别 | 内容描述与优化方案 |
---|---|
遇到的’坑’与Bug修复 | Bug 1: 无法卸下护甲 原因: UI中的护甲槽没有被正确设置为 Armor 容器类型,导致拖拽时逻辑判断失败。修复: 在 W_InventorySlot 中将ContainerType 变量设置为“公开并生成时可指定”,然后在W_Inventory 中为每个护甲槽手动设置为Armor 类型。 Bug 2: 3D预览窗口动画不播放 原因: 动画蓝图中使用 TryGetPawnOwner 获取所有者,但BP_PlayerWindow 是一个Actor而非Pawn。修复: 改为使用 GetOwningActor 来正确获取Actor引用。Bug 3: 忘记设置护甲蓝图的默认ItemInfo 原因: 生成的护甲Actor中没有预设物品信息,导致卸装时无法知道该往物品栏添加什么。 修复: 手动为每个护甲子蓝图的类默认值填充了正确的 S_ItemInfo 数据。 |
对前期设计的反思 | W_InventorySlot 控件的设计最初较为简单。本章通过增加布尔值IsArmorSlot 和枚举ArmorType 来扩展其功能。 这种方式虽然可行,但使基类变得臃肿。一个更优雅的重构方案是创建一个继承自W_InventorySlot 的W_ArmorSlot 子控件,将护甲相关的特殊逻辑封装在子类中,更好地遵循了开闭原则和单一职责原则。 |
如果重来一次 (重构与优化) | 1. 使用TMap优化Switch判断: 在角色蓝图中,装备/卸装逻辑使用了多个 Switch on EArmorType 节点来设置或获取对应的护甲槽位变量。 这可以重构为使用一个TMap<EArmorType, AActor*> (蓝图中的Map变量)。这样,整个Switch结构可以被一个Map.Find() 和Map.Add() 操作替代,代码更简洁,扩展新护甲类型时也无需修改节点,完全符合开闭原则。2. 抽象化伤害处理: 当前的伤害减免逻辑硬编码在武器( Hatchet )中。 更优的设计是为角色添加一个“可被伤害组件”(Damageable Component)。武器只负责调用该组件的“承受伤害”接口,而所有关于护甲减免、耐久度计算的逻辑都应由该组件内部处理。这能将伤害计算的职责从攻击方转移到被攻击方,是更稳健和可扩展的设计。3. 优化Actor生成: 当前设计中,每次装备/卸装都会 Spawn/Destroy Actor 。 对于频繁操作,这可能会带来性能开销。未来可以引入对象池(Object Pool)模式,预先生成一定数量的护甲Actor并隐藏,装备时从池中取出一个并激活,卸装时归还池中,从而避免运行时的动态内存分配和销毁。 |