Unreal Engine

UE4.27迁移UE5.8

本文总结 project09 从 UE4.27 迁移到 UE5.8 后完成的主要修复工作,覆盖场景光照、模型与武器资产、Windows Dedicated Server、武器物理、多人同步、晚加入恢复、非房主拾取/丢弃以及 HUD 更新。

project09 UE5.8 迁移与多人游戏修复总结

1. 文档目的

本文总结 project09 从 UE4.27 迁移到 UE5.8 后完成的主要修复工作,覆盖场景光照、模型与武器资产、Windows Dedicated Server、武器物理、多人同步、晚加入恢复、非房主拾取/丢弃以及 HUD 更新。

本文用于保存当前阶段的技术结论、问题根因和验证结果,方便后续回归测试、Dedicated Server 验证以及服务端权威化改造。它不是最终验收报告;未完成的压力测试和 C++ 权威化工作会在文末单独列出。

2. 项目与环境基线

项目当前信息
工作目录C:\personal\UE_Project\project10
Unreal 项目project09.uproject
当前目标引擎UE5.8
UE5 Launcher 路径C:\Program Files\Epic Games\UE_5.8
UE5 源码引擎路径C:\UESource\UnrealEngine5.8
UE5 迁移基线提交9b508bf
默认地图/Game/Season7/Main
多人测试地图/Game/Maps/csgo
默认 GameMode/Game/All/BP_MyGamemode.BP_MyGamemode_C
GameInstance/Game/All/BP_MyGameInstance.BP_MyGameInstance_C

当前项目仍以蓝图玩法为主。FppShooter 负责输入与 Server/Multicast RPC 转发,Shooter 负责角色和武器库存状态,Weapon 负责武器基础行为、表现和掉落物理。

3. UE4.27 到 UE5.8 的迁移工作

3.1 项目升级

项目已完成从 UE4.27 到 UE5.8 的基础迁移,并建立迁移基线提交 9b508bf。迁移涉及:

  • 更新项目与 UE5.8 的引擎关联。
  • 重新编译项目 C++ 模块和 Target。
  • 更新客户端与服务器构建脚本中的引擎路径。
  • 适配 UE5.8 的 Windows 客户端 Stage/Archive 目录结构。
  • 重新保存受版本升级影响的蓝图、地图和资源。
  • 保留旧 UE4 源码引擎作为迁移对照,待问题完全稳定后再决定是否清理。

3.2 构建脚本更新

以下脚本已经切换到 UE5.8 源码引擎:

  • build_client.bat
  • build_server.bat
  • start_packaged_server.bat

客户端归档目录由旧的 WindowsNoEditor 调整为 UE5.8 使用的 Windows,并增加了构建失败、Cook 缺失和最终可执行文件缺失检查。

服务器脚本可以构建并归档 Windows Dedicated Server,服务器测试地图显式使用 /Game/Maps/csgo,避免默认地图 /Game/Season7/Main 与测试地图不一致。

4. 场景光照与地图修复

迁移后对场景显示和光照进行了调整,并重新保存相关地图与光照构建数据。当前仓库中可确认涉及的资产包括:

  • /Game/Maps/csgo
  • /Game/Maps/csgo_BuiltData
  • /Game/Season7/Main
  • /Game/Season7/Main_BuiltData

本阶段处理的目标是恢复 UE5.8 下可用、稳定的场景表现,避免迁移后出现光照缺失、亮度异常、构建数据失配或地图使用旧版本 BuiltData 的问题。

当前结果:

  • csgo 多人测试地图可以在 PIE、Listen Server 和打包 Dedicated Server 流程中加载。
  • Main 默认地图仍保留为项目入口地图。
  • 地图和 BuiltData 已随 UE5.8 迁移重新保存。
  • 场景光照问题已经完成当前阶段的人工验证。

由于光源的逐项参数修改没有单独形成变更记录,本文不虚构具体亮度、曝光、阴影或烘焙参数。后续若继续调整光照,建议记录光源 Actor、修改前后参数、目标平台和截图,避免 BuiltData 变化无法追溯。

5. 模型与武器资产修复

UE5.8 迁移后重新检查并调整了角色/武器相关模型表现。当前工作区中可确认修改过的武器资产包括:

  • /Game/All/Weapon/AK47
  • /Game/All/Weapon/AUG
  • /Game/All/Weapon/AWP
  • /Game/All/Weapon/Deagle
  • /Game/All/Weapon/Knife
  • /Game/All/Weapon/Shotgun
  • /Game/All/Weapon/Weapon

修复重点包括:

  • UE5.8 下武器模型和基础蓝图的兼容性。
  • 第一人称与第三人称武器显示。
  • 武器在角色手部 Socket 上的挂接表现。
  • 武器掉落后的物理根组件状态。
  • 不同客户端看到的武器模型、可见性和位置一致性。

角色选择武器挂接组件的现有规则保持不变:

本地玩家 -> 第一人称 Mesh
其他玩家 -> 第三人称 TPP
挂接 Socket -> b_RightWeaponSocket
变换规则 -> SnapToTarget

排查证明,晚加入时武器位置错误不是 getMesh 或 Socket 选择错误,而是武器在复制初始化期间只 Attach 一次,随后被网络 Transform 或物理状态覆盖。

6. Dedicated Server 支持

6.1 已完成内容

  • 创建并使用 project09Server.Target.cs
  • 完成 UE5.8 Windows 客户端构建、Cook、Stage 和 Archive。
  • 完成 UE5.8 Windows Dedicated Server 构建、Stage 和 Archive。
  • 增加打包服务器启动脚本。
  • 使用 /Game/Maps/csgo 进行独立服务器测试。
  • 验证两个本地客户端可以加入服务器。

6.2 已完成的多人流程验证

当前阶段已经人工验证:

  • 客户端加入 Dedicated Server。
  • 两客户端并发进入游戏。
  • 玩家死亡与复活。
  • 比赛结束。
  • 空房间重置。
  • 服务器地图跳转。
  • 服务器进程可以正常退出。

已检查的 Dedicated Server 日志中没有发现 Fatal、Assertion、Blueprint Runtime Error、崩溃或异常网络失败。服务器曾连续运行超过 13 分钟并正常退出。

需要注意:已有两个客户端的会话不是一条完整连续十分钟记录,因此严格的 M1 十分钟双客户端验收证据仍应重新采集。

6.3 性能观察

本机同时运行服务器和多个客户端时,画面流畅度差异部分来自本地 CPU、GPU 和内存资源竞争。服务器日志中曾观察到最大 Tick Rate 为 30。该现象不能直接视为网络同步错误,后续应在独立机器或受控环境中区分服务器帧率、客户端渲染性能与网络复制成本。

7. 武器穿墙和消失问题

7.1 现象

武器被丢出后可能高速穿过墙体或地面,看起来像武器被销毁或消失。

7.2 根因

排查确认:

  • 武器没有调用 DestroyActor
  • 武器 InitialLifeSpan = 0,不存在自动销毁。
  • 武器物理 Root 使用 /Game/All/smallcube
  • smallcube 边界约为 -1..1 cm,物理代理很小。
  • 投掷/冲量速度较高,小型物理体容易在离散碰撞中发生 tunneling。
  • 原武器 Root 的 CCD 未启用。

7.3 修复

在保留现有 Root 和组件层级的前提下,为武器物理 Root 启用 Use CCD

该修改避免了不必要的组件替换和资产层级重构,用户测试确认武器穿墙消失问题已修复。

8. 晚加入客户端武器同步

8.1 原始问题

比赛进行中加入的客户端无法得到此前发生的 Multicast 历史。表现包括:

  • 对方当前武器缺失或位置错误。
  • 备用武器仍显示在地面。
  • 切一次武器后显示恢复。
  • 备用武器的 WeaponOwner 为空。
  • 后续切换时触发 onEquipPlayMontage 空引用错误。

根因是原逻辑主要依赖 all-pickWeaponall_attachWeapon Multicast。Multicast 只发送给事件发生时已经连接的客户端,不会为晚加入客户端重放历史。

8.2 状态模型

最终采用以下状态分工:

状态用途
CreatedWeapon初始创建武器,不作为长期当前武器真相
WeaponArrayRepNotify,复制完整库存快照
EquippedWeaponRepNotify,复制当前装备武器引用
CurrentWeaponindex客户端表现和现有切枪逻辑使用的索引

没有复用 CreatedWeapon 作为当前武器,因为它的原始语义是“新创建的武器”,且 CreateWeaponOnRep_CreatedWeapon 和现有初始化流程都依赖该语义。

8.3 WeaponArray 重建

OnRep_WeaponArray 在远端客户端遍历库存:

ForEach WeaponArray
  -> IsValid Weapon
  -> Weapon.onPick(Self)
  -> 恢复 WeaponOwner
  -> 关闭碰撞
  -> 关闭物理模拟
  -> 非当前武器 SetVisibility(false)

这解决了晚加入客户端上备用武器仍留在地面、备用武器没有 Owner、切换备用武器后空引用等问题。

8.4 EquippedWeapon 重建

由于 RepNotify 自动函数不能被普通蓝图直接调用,新增普通函数:

ApplyReplicatedEquippedWeapon

OnRep_EquippedWeaponOnRep_WeaponArray 完成后都调用该普通函数,统一应用当前装备状态。

应用状态前增加:

IsValid(EquippedWeapon)
WeaponArray Contains EquippedWeapon

只有当前武器仍然属于库存时才执行:

onPick(Self)
-> all_attachWeapon(EquippedWeapon)
-> SetVisibility(true)
-> CurrentWeaponindex = EquippedWeapon.getType
-> onEquip

如果武器无效或已不在数组中,则设置:

CurrentWeaponindex = 3

Contains 检查避免了丢枪时 WeaponArrayEquippedWeapon 到达顺序不同,导致客户端把刚丢出的武器重新捡回。

8.5 Attach 初始化时序

正常换枪路径中的 all_attachWeapon 在 Remote 端使用 0.2 秒间隔进行有限次数重试。晚加入状态恢复改为复用这一挂接路径,而不是只执行一次直接 Attach。

同时增加终止条件:只有武器自定义 WeaponOwner 仍等于当前 Shooter 时才继续 Attach。不能使用 Actor 自带的 GetOwner,因为现有 onPick 设置的是武器蓝图中的 WeaponOwner 变量。

该检查解决了枪已经 onDrop 后,残留 Attach 重试又把枪拉回手中的问题。

9. 非房主拾取与丢弃修复

9.1 原始问题

Listen Server 房主丢枪基本正常,但非房主拾枪后丢弃可能出现:

  • 枪没有正确抛出。
  • 枪短暂落地后又回到手上。
  • 客户端找不到要丢弃的当前武器。
  • Dedicated Server 风险高于 Listen Server。

9.2 根因

原流程为:

客户端按 G
-> Server-dropWeapon
-> all-dropWeapon
-> Delay 0.2
-> getCurrentWeapon
-> DropWeapon

延迟期间,服务器复制的 WeaponArrayEquippedWeaponCurrentWeaponindex 可能已经变化。客户端延迟结束后重新查询“当前武器”时可能得到 None 或索引 3,从而跳过丢枪。

此外,Attach 重试在非房主客户端仍可能继续执行,与 onDrop 的物理状态冲突。

9.3 修复后的流程

服务器在状态变化前捕获明确武器引用:

Server-dropWeapon
-> getCurrentWeapon
-> IsValid
-> all-dropWeapon(WeaponToDrop)

all-dropWeapon 将同一个 WeaponToDrop 传递给后续流程:

WeaponToDrop.OnShootButtonUp
-> Delay 0.2
-> WeaponToDrop.middlestep_AUG_AWP
-> DropWeapon(WeaponToDrop)

DropWeapon 增加 WeaponToDrop 输入,并立即缓存为局部变量。函数不再依赖延迟后的 getCurrentWeaponCurrentWeaponindex != 3 判断。

关键处理包括:

  • 使用 WeaponToDrop 执行 Detach、SetActorLocation 和 onDrop
  • 使用 WeaponToDrop.getType 清空正确库存槽位。
  • getNearByWeapon.exceptthis 明确排除刚丢出的武器。
  • 只有 Authority 且 EquippedWeapon == WeaponToDrop 时才清空 EquippedWeapon
  • onDrop 清空 WeaponOwner 后,远端 Attach 重试停止。

用户已在 Listen Server 下验证非房主拾取、丢弃和武器切换恢复正常。

10. WeaponOwner 空引用防护

新增复制恢复后,原先假设 WeaponOwner 永远有效的函数可能在初始化、切换、丢弃或网络状态过渡时被调用。

已经在以下函数读取 WeaponOwner 前增加执行式 IsValid

  • Weapon.onEquip
  • Weapon.PlayMontage
  • Weapon.StopMontage

无效分支直接结束,不再调用:

  • WeaponOwner.isLocalPlayer
  • PlayAnimMontage
  • StopAnimMontage

日志曾出现的 WeaponOwner Accessed None 错误已经通过状态重建和判空保护处理。

11. HUD 重复与残留修复

11.1 现象

  • 丢弃枪并切到刀后,枪的 HUD 仍留在视口。
  • 捡起枪后,刀的 HUD 仍留在视口。
  • 主武器、副武器和刀之间切换时可能出现多个 HUD 重叠。

11.2 根因

一次装备现在可能由以下路径多次调用 onEquip

  • 正常 Equip Weapon
  • OnRep_EquippedWeapon
  • OnRep_WeaponArray 完成后的统一状态应用。

onEquip 只检查 HUD Class 是否存在,每次调用都可能重新 CreateWidgetAndAddToViewportCrossHairUI 变量只保存最后创建的实例,onUnEquip 因此只能移除最后一个 Widget,之前创建的实例会成为视口中的残留 HUD。

11.3 修复

onEquip 改为幂等逻辑:

IsDedicatedServer
-> IsValid WeaponOwner
-> WeaponOwner.isLocalPlayer
-> hideshadow
-> IsValid CrossHairUI
    Valid     -> 结束,不重复创建
    Not Valid -> 获取 HUD Class
               -> CreateWidgetAndAddToViewport
               -> Set CrossHairUI

onUnEquip 保持:

IsValid CrossHairUI
-> RemoveFromParent
-> CrossHairUI = None

由于旧 PIE 会话中已经失去引用的 Widget 无法再通过 CrossHairUI 删除,修改后必须完全停止并重新启动 PIE 才能验证。

用户测试确认 HUD 残留问题已经修复。

12. 当前验证结果汇总

项目当前结果
UE5.8 项目迁移已完成基础迁移
UE5.8 C++ 编译已完成
Windows 客户端打包已完成
Windows Dedicated Server 打包已完成
csgo 地图服务器启动已验证
两客户端加入已验证
死亡与复活已验证
比赛结束/空房间重置/跳图已验证
场景光照当前表现已人工确认修复
武器模型与挂接已人工确认修复
高速掉落武器穿墙CCD 修复后通过
晚加入当前武器已修复并验证
晚加入备用武器地面残留已修复并验证
晚加入后换枪已修复并验证
非房主拾取/丢弃Listen Server 下已验证
WeaponOwner 空引用已增加状态恢复与判空
武器 HUD 重复/残留已修复并验证

13. 主要设计结论

  1. Multicast 不能代替持久复制状态,因为晚加入客户端不会收到历史事件。
  2. WeaponArray 应表达库存快照,EquippedWeapon 应表达当前装备快照,二者不能继续由 CreatedWeapon 混合承担。
  3. RepNotify 到达顺序不能假定固定;状态应用必须验证武器仍属于库存。
  4. 延迟 RPC 或 Multicast 流程不能在延迟结束后重新查询易变的“当前对象”,应在 Authority 端提前捕获明确引用。
  5. 重试 Attach 必须有终止条件,不能只判断 Actor 是否有效。
  6. UI 和表现函数可能被正常流程与 RepNotify 重复调用,因此必须具备幂等性。
  7. WeaponOwner 是当前武器蓝图自定义状态,不等同于 Unreal Actor 内置 Owner。
  8. 小型高速物理体需要考虑 CCD,不能把穿墙误判成 Actor 被销毁。

14. 当前资产和工作区状态

本次总结生成时,Git 工作区中可见以下未提交资产变化:

  • Content/All/Shooter.uasset
  • Content/All/Weapon/Weapon.uasset
  • Content/All/Weapon/AK47.uasset
  • Content/All/Weapon/AUG.uasset
  • Content/All/Weapon/AWP.uasset
  • Content/All/Weapon/Deagle.uasset
  • Content/All/Weapon/Knife.uasset
  • Content/All/Weapon/Shotgun.uasset
  • Content/Maps/csgo.umap
  • Content/Maps/csgo_BuiltData.uasset
  • Content/Season7/Main.umap
  • build_client.bat
  • build_server.bat

此外,以下网络文档尚未提交:

  • Docs/MultiplayerArchitecture.md
  • Docs/NetworkTestMatrix.md
  • Docs/ReplicationProfiling.md

这些是状态记录,不代表应立即提交。提交前仍需在编辑器中 Compile、Save All,并检查实际 Diff 范围是否全部属于本阶段修改。

15. 后续建议

15.1 必做回归

在 Dedicated Server 下重新执行:

  • 主武器、副武器、刀的拾取、切换和丢弃。
  • 非房主在拾枪后两秒内立即丢弃。
  • 多次连续捡起、丢弃同一武器。
  • 玩家持有三类武器后让新客户端晚加入。
  • 晚加入后继续换枪、丢枪和再拾取。
  • 死亡时持枪、复活后默认武器恢复。
  • HUD 在所有上述流程中始终只显示当前武器的一份实例。

15.2 日志验收

每轮测试结束后检查:

Fatal
Ensure
Blueprint Runtime Error
Accessed None
WeaponOwner
Infinite Loop
Assertion

15.3 网络环境

当前主要结果来自 LAN、PIE、Listen Server 和本机 Dedicated Server。后续仍需执行:

  • 完整十分钟双客户端 Dedicated Server 会话。
  • WAN 延迟测试。
  • 2% 丢包测试。
  • 高延迟和 5% 丢包 Stress 测试。
  • 三十分钟服务器帧时间、内存和网络复制稳定性采集。

15.4 架构演进

当前修复优先恢复 UE5.8 蓝图项目的现有行为,没有完成全面的 C++ 服务端权威战斗改造。后续仍应逐步确保:

  • 服务器决定武器归属、库存和当前装备状态。
  • 服务器决定弹药、射速、命中、伤害、死亡和得分。
  • Multicast 只承担动画、声音、特效和必要的表现刷新。
  • HUD 只读取本地玩家的复制状态,不参与玩法真相。
  • 掉落武器的物理和 Transform 由服务器保持最终一致。

16. 相关文档

项目内已有以下配套文档:

  • Docs/MultiplayerArchitecture.md
  • Docs/NetworkTestMatrix.md
  • Docs/ReplicationProfiling.md
  • Docs/Week5_网络职责表.md
  • Docs/Week5_回归测试记录.md

本文是当前阶段的总结快照;后续修改网络状态模型、RPC 边界或 HUD 生命周期时,应同步更新相关架构与测试文档。