第七章:护甲装备

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_InventorySlotOnDrop事件中,增加了对"是否为护甲槽"的判断。若满足条件,则通过接口调用角色蓝图中的EquipArmor事件。 该事件在服务器上执行,负责生成护甲Actor,将其附加到角色身上,并使用SetMasterPoseComponent同步动画。

    • 卸装:通过判断拖拽来源是否为护甲槽(ContainerType == Armor)来触发DequipArmor事件。 该事件在服务器上执行,负责销毁护甲Actor,并将其物品信息添加回玩家物品栏。

  • UI扩展与3D预览系统

    • 护甲槽UI:复用并扩展了 W_InventorySlot 控件,在 W_Inventory 中创建了五个专用的护甲槽位。通过公开IsArmorSlotArmorType等变量,使其具备了处理护甲装备的特殊逻辑。

    • 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_SurvivalCharacterBPI_ArmorInterfaceBPI_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_InventorySlotW_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并隐藏,装备时从池中取出一个并激活,卸装时归还池中,从而避免运行时的动态内存分配和销毁。