1. 项目概述:从蓝图到生存法则
如果你已经跟着UE4的官方文档或者一些入门教程,磕磕绊绊地搭出了一个能跑能跳的角色,甚至做了一个简单的场景,那么恭喜你,你已经成功推开了虚幻引擎世界的大门。但紧接着,很多朋友会陷入一个迷茫期:接下来该做什么?那些酷炫的生存游戏、开放世界里的复杂系统,比如饥饿值、建造、昼夜交替、敌人AI,到底是怎么从一个个孤立的“Hello World”节点,变成一套环环相扣、能稳定运行的游戏的?这正是我们这个“生存游戏案例”要解决的问题。它不是一个从零开始的“安装UE4”教程,而是一个面向“已经会走,但还不会跑”的初学者的“开发进阶实战篇”。我们的目标很明确:将你学过的零散知识点(如变量、事件、蓝图类),通过一个具体的、有吸引力的“生存游戏”项目,串联成一套可复用的开发思维和实战能力。
为什么是生存游戏?因为它几乎囊括了UE4中高级蓝图应用的所有核心模块:角色状态管理(生命、饥饿、耐力)、动态交互系统(采集、建造)、基础AI行为(巡逻、追击)、以及游戏流程控制(昼夜循环、存档读档)。通过亲手实现这些系统,你不仅能巩固蓝图编程,更能深刻理解游戏逻辑是如何被组织、数据是如何流动的。这远比单纯学习一个“如何做第三人称角色”要更有挑战,也更有成就感。你会发现,之前那些看似独立的节点,现在需要你像导演一样,去思考它们何时触发、如何通信、怎样避免冲突。
2. 核心系统设计与架构思路
在动手写第一行蓝图之前,我们必须先想清楚整个游戏的骨架。一个粗糙的生存游戏Demo,其核心架构可以围绕几个关键的游戏模式(GameMode)和玩家控制器(PlayerController)来展开。这里我们不追求大而全的商业级架构,而是采用一种清晰、易于理解和扩展的模块化思路。
2.1 游戏框架与核心类规划
首先,我们需要规划几个核心的蓝图类:
BP_SurvivalGameMode(游戏模式):这是游戏规则的“大脑”。它不关心具体的角色怎么移动,而是负责管理游戏的整体状态。在我们的生存游戏中,它需要掌管:
- 游戏时间系统:实现一个独立的游戏内时钟,用于驱动昼夜循环。这个时钟的速度可以调节(比如现实1秒=游戏1分钟)。
- 全局事件广播:当时间到达白天、黑夜、或某个特定时刻(如怪物活跃的午夜),它需要能通知游戏中的所有相关对象。
- 存档/读档管理器:虽然具体存档逻辑可能由其他对象执行,但GameMode是发起存档/读档命令的入口,并负责协调各个系统的数据保存与加载。
BP_SurvivalCharacter(玩家角色):这是玩家直接控制的实体。它需要扩展基础的角色移动功能,集成复杂的生存状态。
- 生存属性组件:强烈建议为生命值(Health)、饥饿值(Hunger)、耐力值(Stamina)创建一个独立的“生存属性组件”(比如叫
BP_AttributeComp)。这样做的好处是逻辑分离,未来如果你想为NPC也添加属性,或者添加新的属性(如口渴度、体温),直接复用或扩展这个组件即可,不会把角色蓝图搞得一团糟。 - 交互系统:负责处理玩家按下“E”键时,如何检测面前的物体(树木、石头、建造点)并触发相应的行为(采集、建造)。
- 装备与建造系统:管理玩家当前手持的工具(斧头、镐子),并在满足条件时,在指定位置生成建筑蓝图(如墙壁、篝火)。
- 生存属性组件:强烈建议为生命值(Health)、饥饿值(Hunger)、耐力值(Stamina)创建一个独立的“生存属性组件”(比如叫
BP_AI_Enemy(敌人AI):使用UE4强大的行为树(Behavior Tree)和黑板(Blackboard)系统来构建。一个基础的敌人AI应该包含:
- 感知系统:通过
PawnSensingComponent组件,让敌人拥有视觉和听觉,能发现玩家。 - 行为状态:至少包含“巡逻”(Patrol)、“追击”(Chase)、“攻击”(Attack)和“返回”(Return)几种状态。
- 生命与伤害:敌人也需要有生命值,并能对玩家的攻击做出反应(播放受击动画、死亡)。
- 感知系统:通过
BP_Interactable_Resource(可交互资源):所有可以被玩家采集的物体,如树木、矿石的基类。它定义了一个标准的交互接口(比如叫
Interact),当被玩家调用时,执行采集逻辑(播放动画、减少自身“耐久度”、生成掉落物)。
注意:在项目初期就花时间规划好这些核心类的职责和通信方式,能极大避免后期蓝图之间相互引用混乱、逻辑纠缠不清的“蜘蛛网”问题。记住一个原则:数据向下流动,事件向上广播。比如,角色饿了(数据变化),可以触发一个“OnHungerChanged”事件,UI去监听这个事件并更新显示;而UI不需要知道角色内部具体怎么计算饥饿值。
2.2 数据驱动与配置化思维
作为进阶实战,我们必须摒弃在蓝图中硬编码数值的做法。比如,一棵树被砍伐后掉落多少木材,这个数值不应该直接写在砍伐事件的节点里。正确的做法是:
- 为
BP_Interactable_Tree创建一个数据资产(Data Asset)或结构体(Struct),比如叫TreeData。 - 在这个数据资产里定义属性:
WoodYield(木材产量)、ToolRequired(所需工具类型,如Axe)、HitPoints(砍伐所需次数)。 - 在蓝图中,通过“获取数据资产”节点来读取这些数值。
这样做的好处是,策划(或者未来的你)想要调整游戏平衡时,不需要打开复杂的蓝图,只需要在数据资产表格里修改几个数字。这就是数据驱动开发的核心思想,也是专业项目必备的素养。
3. 核心模块实现详解
有了清晰的架构,我们就可以开始动手实现一个个具体的功能模块了。这里我会挑几个最具代表性、也最容易踩坑的模块,深入讲解实现细节和背后的逻辑。
3.1 动态生存属性系统的构建
生存游戏的核心是角色的状态管理。我们之前提到用组件来实现,现在来看看具体怎么做。
首先,创建一个名为BP_AttributeComp的Actor组件。它内部主要维护几个关键变量:
CurrentHealth,MaxHealthCurrentHunger,MaxHunger(饥饿值会随时间缓慢下降)CurrentStamina,MaxStamina(奔跑、攻击时消耗,站立不动时恢复)
核心逻辑1:属性变化与事件驱动。属性值的变化不应该静默发生。每次数值变动(无论是增加还是减少),都应该触发一个多播事件(Multicast Delegate)。例如,当饥饿值变化时:
- 在组件内设置一个浮点型变量
Hunger。 - 创建一个自定义事件
OnHungerChanged,带一个浮点数参数(新的饥饿值)。 - 任何修改
Hunger的地方(比如每秒钟定时减少),在设置新值后,立即调用OnHungerChanged事件,并将新值传递出去。 - 角色的UI组件(如
BP_HUD_Widget)会监听这个事件。一旦事件被触发,UI就更新屏幕上饥饿条的显示。
这样,属性管理(BP_AttributeComp)和属性显示(BP_HUD_Widget)就完全解耦了。UI不需要每帧去查询“角色现在饿不饿”,它只需要在事件发生时被动更新,效率更高,逻辑更清晰。
核心逻辑2:耐力系统的实现技巧。耐力(Stamina)的实现比想象中要精细。常见的需求是:奔跑时持续消耗耐力,停止奔跑后开始恢复,但如果耐力耗尽,则强制角色进入行走状态。
- 消耗:在角色的移动逻辑中(通常是
InputAxis MoveForward等事件后),判断如果玩家正在奔跑(比如按住了Shift键),并且当前耐力>0,则每帧减少一定量的耐力。这里的关键是使用“每帧(Event Tick)”要谨慎,最好在角色进入奔跑状态时,设置一个布尔变量bIsSprinting为true,并开启一个自定义的定时器(Custom Event by Function)来循环减少耐力,退出奔跑时关闭这个定时器。这比直接挂在Tick上更可控。 - 恢复:当
bIsSprinting为false且当前耐力小于最大值时,开启另一个恢复定时器,缓慢增加耐力。 - 耗尽惩罚:当耐力减少到0时,除了强制设置
bIsSprinting为false,还可以触发一个事件,比如通知角色播放一个“喘气”的动画蒙太奇,并在短时间内禁止再次奔跑,增加生存的紧张感。
3.2 基于接口的通用交互系统
交互系统是连接玩家与世界的关键。我们希望玩家面对树木、石头、工作台时,都按同一个“E”键,但能触发不同的行为。UE4的接口(Interface)就是为此而生的。
- 创建接口:在蓝图里创建一个接口,命名为
BPI_Interactable。在里面定义一个函数Interact,它需要一个参数,比如InstigatorPawn(交互的发起者,通常是玩家角色)。 - 实现接口:让
BP_Interactable_Tree(树木)、BP_Interactable_Stone(石头)、BP_CraftingTable(工作台)等蓝图都实现这个BPI_Interactable接口。 - 在角色中实现检测:在玩家角色蓝图中,编写“按下E键”的事件。
- 使用
LineTraceByChannel(射线检测)从玩家摄像机向前方发射一条短线。 - 检测命中的物体(Hit Actor)。
- 使用
Does Implement Interface节点,判断命中的物体是否实现了BPI_Interactable接口。 - 如果实现了,就通过
Get Interface节点获取接口,然后调用其Interact函数,并将玩家自身(Self)作为InstigatorPawn参数传入。
- 使用
- 在不同物体中响应:在树木的蓝图中,它实现的
Interact函数里,就写砍伐逻辑:检查玩家手里是不是斧头,播放砍树动画,减少树木生命值,生命值为0时生成木材掉落物并销毁自身。在工作台的蓝图中,它的Interact函数则可能是打开一个制作物品的UI界面。
这个设计的精妙之处在于,玩家角色完全不需要知道它面前具体是一棵树还是一个工作台。它只认“可交互”这个标签。未来你想增加一个新的可交互物品,比如一口水井,只需要让水井的蓝图实现同一个接口,玩家的“E”键就能自动适配,无需修改任何角色蓝图的代码。这是面向对象编程中“依赖倒置”原则的经典应用。
3.3 行为树AI入门与实战配置
很多初学者对行为树望而却步,觉得它复杂。其实对于生存游戏里一个简单的“巡逻-追击-攻击”敌人,它的行为树可以非常直观。
创建黑板(Blackboard):先创建一个
BB_Enemy。在里面定义几个关键键值(Key):HomeLocation(Vector):敌人的老家位置,巡逻的起点,也是追击丢失目标后返回的位置。PatrolLocation(Vector):当前要去的巡逻点。TargetActor(Object):当前锁定的目标(玩家)。HasLineOfSight(Bool):是否有视线看到目标。
创建行为树(Behavior Tree):创建一个
BT_Enemy,并将BB_Enemy分配给它。构建逻辑主干:行为树的根节点(Root)通常是一个
Selector(选择器)节点。它的意思是:从左到右执行子节点,直到有一个子节点运行成功(Succeeded)或正在运行(Running)。- 第一个子节点(攻击):可以是一个
Sequence(序列)节点,里面检查HasLineOfSight是否为真且与TargetActor距离是否小于攻击范围。如果条件满足,则执行攻击任务(播放攻击动画、对玩家造成伤害)。 - 第二个子节点(追击):如果攻击条件不满足(比如距离太远),但
TargetActor有效,则执行追击任务。这个任务会使用“Move To”节点,让AI向TargetActor的位置移动。 - 第三个子节点(巡逻):如果
TargetActor无效(没有发现玩家),则执行巡逻任务。这里需要一个服务(Service)来定期为PatrolLocation设置一个新的、在HomeLocation附近随机的位置,然后让AI“Move To”那个位置。
- 第一个子节点(攻击):可以是一个
在AI控制器中链接:在
BP_AI_EnemyController中,BeginPlay事件里,使用Run Behavior Tree节点,运行我们创建好的BT_Enemy。感知的接入:在敌人蓝图
BP_AI_Enemy身上添加PawnSensingComponent。在其OnSeePawn事件中,判断看到的Pawn是否是玩家,如果是,就将该玩家的引用设置到黑板(Blackboard)的TargetActor键中,并将HasLineOfSight设为True。在OnHearNoise事件中,可以处理听到声音的逻辑,比如将声音源位置设置为一个临时的追击目标。
实操心得:行为树调试是门艺术。一定要多用UE4编辑器中的“行为树调试器”(在运行游戏时,选择AI控制器,然后打开行为树资源)。你可以清晰地看到当前运行到哪个节点(节点会高亮),黑板变量的值是什么。这对于排查AI“发呆”或行为异常的问题至关重要。另外,为巡逻任务设置一个“等待”(Wait)节点,让敌人在到达巡逻点后发呆几秒钟,会让AI的行为看起来更自然,而不是像无头苍蝇一样不停地走。
4. 游戏流程与高级功能集成
当核心系统都搭建完毕后,我们需要将它们编织成一个完整的、有节奏的游戏体验。这涉及到游戏内的时间流逝、世界的动态变化,以及游戏进度的保存。
4.1 游戏内时间与昼夜循环系统
一个独立的游戏时间系统能让世界“活”起来。我们在BP_SurvivalGameMode中实现它。
- 变量:定义
GameTime(游戏内总分钟数,0-1440代表一天)、TimeScale(时间流速,比如1.0表示现实1秒=游戏1分钟)。 - 逻辑:在
Event Tick中,每帧执行:GameTime += (DeltaSeconds * TimeScale)。然后对GameTime取模1440,得到当前一天中的分钟数。 - 昼夜计算:根据当前分钟数,计算出一个
TimeOfDay(0.0到1.0的小数,0.0是午夜,0.5是正午)。这个值可以直接用来驱动:- 定向光源(Directional Light)的旋转:将
TimeOfDay乘以360°,设置光源的Yaw旋转,就能模拟太阳东升西落。 - 天空球(Sky Sphere)的颜色和亮度:可以通过一个时间-颜色的曲线资源(Curve Linear Color)来映射,让天空在清晨和黄昏呈现不同的色彩。
- 全局事件:当
GameTime经过特定时间点(如1380,代表晚上11点)时,广播一个OnNightStart事件。这个事件可以被敌人AI监听,触发它们进入“夜间活跃”模式(比如巡逻范围增大、移动速度加快)。
- 定向光源(Directional Light)的旋转:将
一个提升沉浸感的小技巧:不要只让光源旋转。可以创建两个指数高度雾(Exponential Height Fog)组件,一个用于白天(颜色偏蓝,密度低),一个用于夜晚(颜色偏深蓝/黑,密度高)。然后根据TimeOfDay,用Lerp(线性插值)节点动态混合这两个雾效的参数(颜色、密度、起始距离),这样能实现非常平滑的昼夜环境过渡。
4.2 可扩展的建造与放置系统
生存游戏的乐趣很大一部分来自建造。一个基础的建造系统需要解决几个问题:如何预览建筑?如何检测放置位置是否合法?如何消耗资源并生成最终建筑?
建筑预览(Ghost Mesh):
- 当玩家进入建造模式(比如按下B键),从数据资产中读取想要建造的物体(如一面木墙)的静态网格体(Static Mesh)。
- 将这个网格体生成一个Actor(如
BP_BuildGhost),但将其材质替换为一个半透明的、绿色的“幽灵”材质。 - 这个幽灵Actor会跟随玩家的准星(通过射线检测命中地面或其它表面)。
- 在
BP_BuildGhost中,需要编写复杂的碰撞检测逻辑。通常使用“盒体追踪”(Box Trace)来检查预览物体与周围环境的碰撞。如果与不可建造物(如地形、其他建筑)发生碰撞,则将幽灵材质切换为红色,并设置一个布尔变量bCanPlace为false。
放置与资源检查:
- 当玩家点击鼠标左键确认放置时,首先检查
bCanPlace是否为true。 - 然后,检查玩家背包中是否有足够的资源(如木材、石头)。这需要查询玩家的库存系统(
BP_InventoryComponent)。 - 如果资源充足,则执行消耗资源的逻辑,并在幽灵Actor的位置,生成真正的建筑Actor(如
BP_Wall_Wooden)。 - 最后,销毁幽灵Actor。
- 当玩家点击鼠标左键确认放置时,首先检查
数据驱动设计:同样,所有建筑的数据(所需资源类型和数量、网格体、建造时间等)都应该放在一个数据表格(Data Table)或数据资产中。建造系统根据玩家选择的建筑ID去读取这些数据,从而实现完全配置化。未来增加一个新建筑,只需要在表格里加一行。
4.3 存档与读档功能实现
存档是让游戏拥有持久生命力的关键。UE4提供了GameplayStatics里的SaveGameToSlot和LoadGameFromSlot函数,使用起来并不复杂,但关键在于要保存哪些数据,以及如何组织这些数据。
创建存档类:首先创建一个继承自
SaveGame的蓝图,比如叫BP_SurvivalSaveGame。在这个蓝图中,定义所有需要保存的变量:PlayerTransform(Transform):玩家的位置和旋转。PlayerAttributes(Struct):一个自定义结构体,包含生命、饥饿、耐力等所有属性值。InventoryItems(Array of Structs):一个结构体数组,每个元素记录背包里一个物品的ID和数量。WorldTime(Float):游戏内的时间。BuiltStructures(Array of Structs):一个结构体数组,记录所有玩家建造的建筑的类型ID和位置(Transform)。
存档过程:当玩家触发存档(如进入睡觉点或打开菜单存档)时:
- 创建一个
BP_SurvivalSaveGame对象。 - 从游戏模式、玩家角色、玩家背包等各个系统中,收集当前的数据,并赋值给这个存档对象的对应变量。这是一个“收集数据”的过程。
- 调用
SaveGameToSlot,指定一个存档槽位名称(如“SaveSlot01”),将对象保存到硬盘。
- 创建一个
读档过程:在游戏开始时(或玩家选择读档时):
- 调用
LoadGameFromSlot,尝试加载指定槽位的存档。 - 如果加载成功,就获得了之前保存的那个
BP_SurvivalSaveGame对象。 - 然后,将对象中的数据“分发”给各个系统:把玩家位置设置给角色,把属性值设置给属性组件,遍历
BuiltStructures数组并在对应位置重新生成每一个建筑Actor。最后,把WorldTime设置给游戏模式。
- 调用
避坑指南:存档读档最常见的坑是“引用丢失”。你绝对不能直接保存某个Actor的引用(比如把
BP_Tree_01这个Actor对象本身存进去)。因为读档时,内存中根本没有这个Actor,这个引用会变成null。正确的做法是保存“标识符”和“状态”。比如,对于一棵树,如果你希望它被砍伐后读档依然是砍伐状态,你需要在存档中记录这棵树的唯一ID(可以用一个Tag或生成时赋予的GUID)以及它的“是否被砍伐”状态。读档时,根据ID找到世界中的那棵树(这需要你提前管理好所有可交互物体的ID),然后根据状态去设置它的表现(比如替换成树桩网格体)。对于动态生成的物体(如建造的建筑),则必须保存其类型和位置,读档时重新生成。
5. 性能优化与调试技巧实录
当你的生存游戏世界越来越丰富,树木、敌人、建筑越来越多时,性能问题就会悄然而至。对于初学者而言,掌握几个立竿见影的优化技巧和调试方法,能让你在开发后期少走很多弯路。
5.1 蓝图性能瓶颈排查
蓝图虽然方便,但滥用会导致严重的性能问题。你需要像一个侦探一样,使用UE4提供的工具来找出瓶颈。
Stat Unit 与 Stat FPS:在游戏运行时按“~”键打开控制台,输入
stat unit。这是你最重要的性能仪表盘。它会显示三行时间:- Frame:一帧的总时间。
- Game:游戏线程(包括蓝图逻辑、动画更新等)耗时。
- Draw:渲染线程耗时。 如果
Game的时间很高(比如超过10ms),那很可能就是蓝图逻辑太复杂了。输入stat fps可以查看实时帧率。
蓝图分析器(Blueprint Profiler):这是定位具体是哪个蓝图、哪个事件拖慢游戏的利器。在编辑器菜单栏选择“窗口(Window) -> 开发者工具(Developer Tools) -> 蓝图分析器(Blueprint Profiler)”。启动它并运行游戏,它会记录所有蓝图函数的执行时间和调用次数。你可以清晰地看到,是角色Tick事件里的某个复杂计算,还是敌人AI行为树里某个服务(Service)执行过于频繁,导致了性能问题。
Tick的滥用:这是蓝图性能的头号杀手。务必检查所有Actor和组件的“事件Tick”。问自己:这个逻辑真的需要每帧都执行吗?
- 优化案例:你的
BP_AttributeComp里有一个“饥饿值随时间减少”的逻辑。如果你把它放在Tick里,每帧减少0.001,那完全没有必要。应该改用定时器(Timer)。在组件BeginPlay时,设置一个每1.0秒循环一次的定时器,在定时器回调事件里减少1点饥饿值。这样就从每秒执行60次(假设60帧)降低到了每秒执行1次,性能开销天差地别。 - 通用规则:对于状态监控、缓慢的资源恢复/消耗、非紧急的检测,优先考虑使用定时器,而不是Tick。
- 优化案例:你的
5.2 资源管理与内存优化
对于生存游戏,场景中可能会有大量相同的物体,比如成千上万的草、石头。直接放置这么多静态网格体,Draw Call(绘制调用)会爆炸。
实例化静态网格体(Instanced Static Mesh):对于大量重复的、简单的环境物体(草、小石子、散落的树枝),不要放置成独立的
StaticMeshActor。使用“实例化静态网格体组件”(Instanced Static Mesh Component)。你可以在一个“管理器”Actor下添加这个组件,然后通过蓝图或代码,向这个组件中添加无数个实例(指定位置、旋转、缩放)。渲染引擎会将这些实例合并批次处理,极大地减少Draw Call。UE4的植被系统(Foliage)底层就是基于此原理。关卡流送(Level Streaming):如果你的地图很大,不要把所有内容都加载到内存里。将世界分割成多个子关卡(Level),然后通过关卡流送动态加载和卸载。当玩家移动到一个区域时,加载该区域的子关卡;离开时,卸载它。这在UE4编辑器中可以很方便地设置。对于生存游戏,你可以按地形区块或功能区域(森林区、矿区、基地)来划分子关卡。
LOD(细节层次)与裁剪距离(Cull Distance):为你自定义的模型(尤其是高面数的资源)设置LOD。在静态网格体编辑器中,可以生成LOD,让模型在远处自动切换成面数更少的版本。同时,合理设置“裁剪距离体积”(Cull Distance Volume),让极远处的、很小的物体(比如远处的草)根本不被渲染,进一步减轻GPU负担。
5.3 常见问题与快速排查表
在开发过程中,你一定会遇到各种光怪陆离的问题。下面这个表格整理了一些典型问题及其排查思路,希望能帮你快速定位:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 按下按键无反应 | 1. 输入映射(Input Mapping Context)未正确添加到角色或控制器。 2. 蓝图中的输入事件未正确绑定或节点被禁用。 3. 玩家控制器(Player Controller)被其他逻辑覆盖。 | 1. 检查角色或控制器的BeginPlay事件中,是否用Enable Input启用了输入,并是否将正确的输入映射上下文(通过Enhanced Input子系统)添加给了玩家。2. 在角色蓝图中,右键搜索输入事件(如“Jump”),检查节点是否连通,且没有被“Branch”等节点错误阻断。 3. 检查游戏模式(GameMode)中设置的玩家控制器类是否正确。 |
| AI敌人原地发呆或不移动 | 1. 行为树(Behavior Tree)未运行。 2. 导航网格体(NavMesh)未生成或生成不完整。 3. 黑板(Blackboard)中的关键变量(如 TargetActor)未设置。4. AI控制器的 Possess未成功。 | 1. 在AI控制器蓝图中,检查BeginPlay时是否执行了Run Behavior Tree节点。2. 在编辑器视口中按“P”键,查看绿色的导航网格体是否覆盖了AI需要移动的区域。在复杂地形或有移动组件的物体上,可能需要手动设置 NavModifierVolume。3. 使用行为树调试器,查看黑板中 TargetActor等变量的值是否为有效对象。4. 确保AI控制器在 BeginPlay时成功Possess了对应的Pawn(敌人角色)。 |
| 建造预览(幽灵物体)穿墙或浮空 | 1. 射线检测(Line Trace)的通道(Channel)设置错误,未能检测到所有障碍物。 2. 盒体追踪(Box Trace)的大小或位置与预览网格体不匹配。 3. 检测逻辑忽略了某些复杂碰撞的物体。 | 1. 确保射线检测和盒体追踪使用的碰撞通道(如Visibility或自定义的BuildTrace通道)能与环境中的静态物体、其他建筑等正确碰撞。 2. 根据预览网格体的实际大小,精确调整盒体追踪的“Extent”(范围)参数,可以稍微比网格体大一点以确保检测准确。 3. 对于有复杂碰撞的物体(如斜坡、不规则岩石),可能需要使用多重检测或更复杂的物理检测逻辑。一个简单的调试方法是,在检测失败时,在屏幕上打印(Print String)命中的物体名称,看看是否漏掉了关键障碍物。 |
| 存档后读档,物体状态重置 | 1. 只保存了动态生成物体的数据,未保存场景中原有物体的状态(如被砍伐的树)。 2. 读档时生成物体的逻辑有误,未正确应用保存的状态。 3. 存档数据序列化失败,部分变量未正确保存。 | 1. 为场景中所有需要保存状态的可交互物体(如树木、矿石)赋予唯一ID,并在存档中记录ID和状态(是否被采集)。读档时根据ID查找并恢复状态。 2. 在生成建筑或恢复物体状态的循环中,加入调试打印,确认每个物体的ID、位置、状态是否与存档数据一致。 3. 检查 BP_SurvivalSaveGame中定义的变量类型是否都是可序列化的(基本类型、数组、结构体等)。避免保存对UObject的直接引用。 |
开发到这个阶段,你的生存游戏已经具备了核心的骨架和血肉。回顾整个过程,从最初规划几个孤立的蓝图类,到后来它们通过事件、接口、数据资产相互交织,共同驱动起一个动态的世界,这其中的思维转变是最宝贵的收获。你会发现,学习UE4(或任何游戏引擎)的进阶之路,其实就是学习如何将复杂的需求分解为模块,并设计清晰的数据流动和通信协议的过程。这个案例中的每一个系统——属性、交互、AI、建造、存档——都不是孤立的,它们是你构建更宏大、更复杂游戏的基石。接下来,你可以尝试为敌人添加更复杂的行为(如呼叫同伴、躲避攻击),为建造系统加入更多样化的建筑和升级链,甚至引入简单的科技树。记住,在动手编码之前,多花5分钟在纸上画一画模块之间的关系图,这能为你节省下未来5个小时的调试时间。