很多新手打开 UE5 之后,第一反应就是去做开放世界、大逃杀或者 MMORPG。角色动画还没调顺,就开始堆植被、搭装备栏、写 NPC 对话。两三个星期过去,项目膨胀到打不开,或者在编译阶段就被 MSB3073、Linker 错误反复教育。
跑酷(Endless Runner)是大多数人低估的一类项目。它看上去只是“让角色一直往前跑、跳过障碍物”,但真正动手做起来,你要同时处理移动输入、碰撞反馈、程序化生成、分数统计、失败重开和移动端触摸操作。这一套东西,恰好是 UE5 游戏开发最常用机制的浓缩版。
我的判断是:如果你想在 UE5 里建立完整的游戏工程思维,又不想一上来就被开放世界的规模压垮,跑酷是最合适的“第一个能通关的项目”。它不要求你懂复杂的动画蓝图,也不需要先学会 GameplayAbilitySystem,但它会逼你把游戏循环从头到尾闭环一次。
读完这篇文章,你会得到一个可在 PC 和移动端运行的三线跑酷原型:角色自动前进,左右切道、跳跃躲避障碍,障碍物随机生成,撞击即失败,失败后可重开。整个过程以蓝图为主,并附带切换到 C++ 混合实现的具体方法。文章还会顺带解决几个 UE5 新手高频问题:LowLevelFatalError 崩溃、VS 编译 MSB3073、3D UI 文字模糊、双指触摸蓝图没反应,以及“蓝图设置中文”的真实边界。
1. 为什么用 UE5 做跑酷:一个被低估的入门项目
1.1 新手最常见的失败路径
UE5 的功能密度非常高。一个角色身上同时挂着网格体、动画蓝图、运动组件、弹簧臂、相机、音效、特效,这还只是“一个角色”。如果一上来就做大型项目,你很快就会发现自己陷入一种状态:每个系统都了解一点,但没有一个系统能完整跑通。
跑酷把问题空间压缩到最小:地图是无限延伸的跑道,敌人是静态障碍物,玩家唯一要做的是在“跳、切道、减速”之间做选择。正因为规则足够少,你才有余力去关注真正重要的工程问题,比如对象什么时候生成、什么时候销毁、碰撞用 Overlap 还是 Block、UI 数据从哪里读、GameMode 和 GameState 的职责边界在哪里。
1.2 跑酷是一个“小而完整”的游戏循环
一个完整游戏循环至少包含:玩家输入、角色运动、世界反馈、失败条件、重新开始。跑酷恰好把这五个环节全部覆盖了,而且每个环节都不需要额外造复杂系统。
具体来说,跑酷会逼你回答这些问题:
- 角色“自动前进”应该在 Tick 里加移动输入,还是直接用动画驱动根骨骼?
- 障碍物应该预先摆放在地图里,还是在玩家前方实时生成、在身后销毁?
- 角色碰到障碍物时,物理层的表现是“被挡住”还是“判定死亡”?
- 分数应该存在 GameMode、GameState 还是角色身上?
- 玩家失败后,是直接 RestartLevel,还是先播放死亡动画再重开?
这些问题的答案不是唯一的,但你必须给每个问题一个答案,游戏才能玩起来。这就是跑酷作为入门项目最大的价值:它逼迫你建立“游戏是用规则堆出来的”这一认知。
1.3 这篇文章能带你跑到哪里
完成本文后,你会拥有一套可以继续扩展的原型。它不是一个“看一眼就会”的演示工程,而是一个能让你后续往任何方向加内容的骨架:加道具、加滑铲、加连击判定、加关卡配置、加网络同步,都可以在这条循环上做增量。
适合阅读本文的读者包括:刚接触 UE5 想找第一个完整项目的开发者、用虚幻做课程设计的在校学生、从 Unity 转 Unreal 想快速熟悉蓝图工作流的工程师,以及想评估 UE5 开发效率的独立游戏爱好者。
2. 跑酷游戏的核心概念与 UE5 对应机制
2.1 先画游戏循环,再写代码
做跑酷最容易犯的错误是“先做角色,再做地图”。实际上应该先画一条循环逻辑线,再决定用什么 UE5 机制去实现。
跑酷的基本循环可以写成:
角色自动前进 → 玩家切换车道或起跳 → 判断是否碰到障碍 → 没碰到则继续加分 → 碰到则进入失败状态 → 重新开始。
每次循环都会经过“输入层、运动层、世界层、规则层、表现层”。想清楚这条线之后,UE5 里的每个系统才有落点。
2.2 UE5 中负责这些事的组件
下面这张表把游戏需求映射到 UE5 的常用机制:
| 游戏需求 | UE5 对应机制 | 本文用法 |
|---|---|---|
| 场景里有一个可控制角色 | Pawn / Character | 创建基于 Character 类的蓝图 |
| 重力、跳跃、碰撞移动 | CharacterMovementComponent | 调 MaxWalkSpeed、JumpZVelocity |
| 三线切换 | Actor 位置插值 | 保存目标车道索引,每帧 FInterp 到目标 X 坐标 |
| 障碍物 | Actor + StaticMesh + 碰撞体 | 运行时随机生成,离开玩家视野后销毁 |
| 碰撞判定 | Overlap 事件 | 角色 Capsule 与障碍物 Box 产生 Overlap 时触发死亡 |
| 计分 | GameState 或 PlayerController | 根据存活时间或前进距离累加 |
| 失败重开 | GameMode | 延迟后 RestartLevel 或重新生成角色 |
| UI 显示 | UMG Widget | 分数文本、暂停按钮、结算面板 |
这几个概念是跑酷的“最小代码量”版本。你不需要在第一个项目里引入 BehaviorTree、GAS、Niagara、Lumen 等高级系统,先把上面表格里的机制跑通,比任何炫技都重要。
2.3 蓝图还是 C++:跑酷项目怎么选
很多人在动手前会纠结:跑酷用蓝图写还是用 C++ 写?
更稳妥的判断是:以蓝图为主,C++ 做少量补充。跑酷的绝大多数逻辑属于“规则简单、迭代频繁”的类型,用蓝图改起来最快,尤其是移动端输入和 UI 反馈部分。只有当项目开始出现大量批量生成、需要热路径性能优化的对象时,才值得把 Spawner 和角色核心逻辑迁移到 C++。
本文后面会给出一个常见的混合路线:蓝图负责场景搭建和 UI,C++ 负责角色类的核心移动逻辑。这样既能体验 UE5 蓝图工作流,又能看到 C++ 代码接入蓝图的完整路径。
3. 环境准备与项目初始化
3.1 安装 UE5 与创建项目
本文以 UE5.x 版本为例,不绑定某个具体小版本。安装步骤很简单:通过 Epic Games Launcher 安装对应版本的 Unreal Engine,然后打开引擎,在 Projects 里新建项目。
选择模板时,推荐用 Blank,而不是第三人称模板。原因是第三人称模板自带一套相机和动画蓝图,很多初学者会直接依赖它,反而不清楚哪些部分是自己的。Blank 项目更干净,角色、相机、输入全部从零搭起,理解更完整。
项目名称和项目路径建议全部使用英文。UE5 对中文路径的兼容性比早期版本好,但 Visual Studio、打包工具链仍可能在中文路径下出现奇怪问题。命名上建议直观一些,比如ParkourDemo、EndlessRunnerDemo。
3.2 项目设置清单
项目创建后,先不要急着搭场景,按下表检查设置:
| 设置项 | 建议值 | 说明 |
|---|---|---|
| 项目名称 | ParkourDemo | 英文、语义明确 |
| Target Hardware | Mobile / Desktop | 只做 PC 选 Desktop,做移动端选 Mobile |
| Default Map | 自己新建的 RunnerMap | 避免每次运行进到一个默认空地图 |
| Input | Classic Input 或 Enhanced Input | 新手先用传统输入理解映射关系 |
| 阴影与渲染 | 移动端关闭不必要的动态阴影 | 降低设备压力 |
| 碰撞预设 | 地面 WorldStatic,障碍 OverlapPawn | 保证死亡判定可控 |
这里特别提醒一个点:先配置好默认地图,否则每次按 Play 进入的都是编辑器里当前打开的那张地图,容易出现“逻辑写在关卡蓝图里,但启动时加载的地图不对”的困惑。
3.3 编辑器中文问题:期望管理
很多新手搜索“ue5 蓝图设置中文”,以为把编辑器界面改成中文后,蓝图节点名称也会变成中文。实际上,UE 的官方本地化主要覆盖编辑器主界面、菜单和部分右键菜单,蓝图节点名称仍然以英文为主。
如果你想使用中文编辑器界面,可以在安装引擎时确认语言包完整,然后在编辑器偏好设置中寻找语言相关选项。如果找不到,不要强行修改配置文件,因为不同版本的配置路径差异较大,改错会导致界面异常。
更务实的做法是:保留英文节点名,但项目里的资源、变量、注释全部使用中文。比如把蓝图节点用注释节点写上“跳跃处理”“切左道”,后续维护的人一眼就能看懂。这样既避开本地化不完整的问题,又保留中文阅读习惯。
4. 基础设施:跑道生成、障碍物与碰撞设计
4.1 无限跑道不是“多摆几块地砖”
如果只是把几十块地面模型在编辑器里排成一条长路,看起来很快,但有两个问题:一是道路长度固定,玩家跑完就结束了;二是如果你把整条路做成一个超长 StaticMesh,加载和碰撞计算都不合理。
常见的做法是分段 Tile 设计:地面由若干节长度相同的 Tile 拼接而成,角色跑过某节后,把这节 Tile 移到远处循环使用。这样在场景里始终只有少量地面网格体在复用。
更简单的原型版本是:角色沿地图的某个固定方向前进,地面用一段足够长的模型来承载,后续再改成 tile 循环。关键点在于,先保证玩法能跑起来,再优化地面复用的细节。
4.2 障碍物的随机生成与回收
障碍物不应该一开始就全部摆放好,应该由 Spawner 在玩家前方一定距离处生成,在玩家通过后销毁或回收。原因很简单:无限跑道意味着障碍物的数量和位置是动态的,你不可能在编辑器里预置“无限多个箱子”。
Spawner 的核心逻辑是:
- 记录角色当前所在位置;
- 每隔一定时间或距离,在角色前方 N 米处选择一个车道生成障碍物;
- 障碍物与角色的距离超过阈值后,销毁或放入对象池。
障碍物的配置建议用 DataTable,而不是在蓝图里硬编码每种障碍的生成概率和位置。DataTable 的好处是策划或你自己调参数时不需要打开蓝图。下面是一个示意结构:
Name,ObstacleMesh,Lane,ScoreValue Barrier,StaticMesh'/Game/Props/SM_Barrier.SM_Barrier',0,10 Crate,StaticMesh'/Game/Props/SM_Crate.SM_Crate',1,15 TallBlock,StaticMesh'/Game/Props/SM_TallBlock.SM_TallBlock',2,20在蓝图中读取 DataTable 时,只需要随机选一行数据,再根据 Lane 字段决定生成在哪条车道。这样难度曲线可以完全用数据控制,代码逻辑不用改。
4.3 碰撞判定:为什么角色被挡住而不是死亡
这是跑酷新手最容易踩的坑:障碍物明明碰到了角色,结果角色被物理挡住,而不是触发死亡。
原因是默认碰撞预设的问题。如果障碍物的碰撞预设是 BlockPawn,CharacterMovementComponent 的碰撞会把角色挡在障碍物前面。跑酷游戏的障碍物应该“穿透”死亡判定,而不是像一堵墙一样把角色卡住。
更好的做法是:障碍物配置为 OverlapPawn 或 OverlapAll,角色 Capsule 组件监听 OnComponentBeginOverlap,在 Overlap 事件里判断是不是 PlayerPawn。这样角色碰到障碍物会直接进入失败流程,不会被物理阻挡。
同时要注意地面的碰撞。地面应该保持 BlockPawn 或 WorldStatic,否则角色会直接掉下去。一个很常见的错误是:把地面也设置成了 Overlap,结果角色一边跑一边往下沉,还找不到原因。
5. 核心角色:自动前进、跳跃与三线切换
5.1 自动前进
跑酷角色的核心特征是“你不操作它也会自己往前走”。实现方案有很多种,最简单的是在 Tick 里不断给角色一个朝前方向的移动输入。
更准确的说法是,使用 CharacterMovementComponent 的移动输入机制:
- 确定跑道的前进方向,例如世界坐标系中 Y 轴正方向;
- 在角色 Tick 或定时事件中,调用 AddMovementInput;
- 把 MaxWalkSpeed 设为固定值,比如 600。
这里容易混淆的是“朝前”的定义。如果角色开启了 OrientRotationToMovement,它的 Actor 朝向会跟随移动方向改变;如果同时使用 GetActorForwardVector,方向可能来回抖动。更稳妥的做法是锁定一个世界方向常量,或者锁定角色 Yaw,不让旋转影响前进逻辑。
5.2 三线切换
三线切换是移动端跑酷最常见的操作方式:屏幕左滑切到左道、右滑切到右道,角色只在三条固定车道之间移动。
可以这样理解:三线切换本质上是把二维避障降维成一道选择题。玩家不需要精确控制角色位置,只需要决定“当前障碍在中间车道,我该去左边还是右边”。这种交互对移动端非常友好。
实现思路如下:
- 在角色蓝图中定义三个车道偏移量,例如 X 方向 -300、0、+300;
- 保存一个整数变量 TargetLane,范围 0 到 2;
- 收到左滑或右滑输入时,TargetLane 加一或减一,并做 Clamp 限制;
- 在 Tick 里把角色当前位置的 X 坐标向目标车道的 X 坐标插值。
插值不要用直接 SetActorLocation 一步到位,否则角色像瞬移一样没有过渡。应该在每帧里使用 FInterp,让切换过程有平滑的手感。
这里的隐藏细节是:角色已经有自动前进的 Y 方向位移,而三线切换只改 X 坐标。两者互不干扰,这也是三线跑酷比自由移动跑酷更容易做好手感的原因。
5.3 跳跃设置
跳跃本质上不是“做一个向上位移”,而是让 CharacterMovementComponent 进入跳跃运动模式。你需要做的只是在输入触发时调用角色的 Jump 方法,并调整运动组件的两个参数:
- JumpZVelocity:决定起跳速度,值越大跳得越高;
- AirControl:决定在空中能否微调横向位置,跑酷里建议给一定数值,让玩家在空中能切道,手感更好。
跳跃的重力可以保持默认。如果后面需要二段跳、滑铲,再去修改对应运动组件的模式。第一个版本不要让跳跃太复杂,先把“按一下就跳起来”这个反馈做稳。
5.4 移动端触摸与双指触摸蓝图
如果目标平台包含移动端,输入就不能只绑定键盘。UE5 支持触摸输入,但蓝图中直接处理“双指”这类组合手势时,没有一个现成的节点叫“双指滑动”。你需要自己组合触摸事件。
常见思路是:在 PlayerController 或 UMG 的触摸事件中,分别记录两个触摸点的起始位置和当前状态。当第二个触摸点出现时,说明进入双指状态。两个手指同时向上滑动时,可以触发跳跃或暂停。
例如,想实现“双指点击暂停”:
- 记录 TouchIndex 为 0 的触摸按下位置;
- 记录 TouchIndex 为 1 的触摸按下位置;
- 如果两个触摸点在极短时间内先后触发,并且随后几乎同时抬起,判定为双指点击;
- 在双指点击事件里调用暂停逻辑。
核心在于“状态机”:单指按下、双指按下、单指抬起、双指抬起,四种状态要分别处理,避免手指松开顺序不一样导致误判。移动端真机和模拟器的触摸事件表现不同,一定要在真机上测试后再调整判定阈值。
6. 游戏规则与 UI 反馈:计分、暂停与失败重开
6.1 分数从哪来
跑酷分数最简单可靠的来源是“距离”或“存活时间”。你不需要计算玩家跳过多少个障碍,只需要每帧累加一个基于距离的分数值,然后更新 UI。
分数不建议放在角色蓝图里。角色只是被控制的 Pawn,它不应该知道“游戏规则”。更合理的归属是 GameState 或 PlayerController。原型阶段可以放在 GameMode 里,但要注意 GameMode 在网络游戏里只存在于服务器端;如果将来要加多人,需要把分数迁移到 GameState。
UI 更新频率也不需要每帧刷新。每帧更新文本会浪费 CPU,通常利用 BindWidget 或定时器,在分数变化达到整数时刷新一次即可。
6.2 失败与重开
角色碰撞到障碍物后,最直观的表现是“画面定住一瞬间,然后出现游戏结束面板,最后回到重新开始状态”。
在 UE5 中,可以用这样的流程:
- 角色蓝图触发死亡事件;
- 调用 GameMode 里的自定义事件,例如 OnPlayerDied;
- GameMode 暂停游戏或播放失败表现;
- 玩家点击“重新开始”按钮时,调用 RestartLevel 或重新生成角色。
RestartLevel 的好处是干净,所有动态生成的障碍物都会被清空,不需要手动做场景重置。缺点是它会重置整个关卡,如果玩家没有存档,进度就没了。跑酷游戏本身没有保存进度的需求,所以 RestartLevel 是最简单可靠的重开方案。
6.3 3D UI 模糊问题的处理
跑酷项目里,如果你尝试把分数或“角色头顶血条”做成 3D 空间中的 Widget(World Space Widget),很容易遇到文字模糊的问题。
模糊的主要原因通常是三个:
- WidgetComponent 的 Draw Size 太小,文字被缩放过;
- 动态分辨率或屏幕百分比被降低;
- Widget 使用的字体没有开启可缩放渲染。
处理思路是:能放在屏幕空间(Screen Space)的 UI 就尽量放屏幕空间,不要在 3D 场景里渲染文字。只有当 UI 必须附着在某个 Actor 上并且需要相对场景移动时,才使用 World Space Widget。此时把 Draw Size 调大,并确认屏幕百分比处于合理范围,避免文字发虚。
在移动端,还要注意 Widget 的安全区问题。刘海屏或挖孔屏会遮挡 UI,需要留出安全边距,不要把分数、暂停按钮放到屏幕边缘。
7. 完整示例:蓝图为主、C++ 混合的最小实现
为了让思路更落地,这里给出一个 C++ 与蓝图混合的最小实现。如果你完全不想写 C++,可以跳过这段,直接根据第 5 章的蓝图思路实现;如果后续要扩充功能,这套 C++ 骨架可以直接作为起点。
7.1 最简单的输入绑定
在 C++ 里绑定输入,需要先在项目设置中定义 Action Mapping。最简单的方案是使用传统输入绑定,在 PlayerInputComponent 中绑定名字对应的 Action。
角色类头文件可以这样定义:
// 文件路径:Source/ParkourDemo/Public/RunnerCharacter.h #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "RunnerCharacter.generated.h" UCLASS() class PARKOURDEMO_API ARunnerCharacter : public ACharacter { GENERATED_BODY() public: ARunnerCharacter(); virtual void Tick(float DeltaTime) override; virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override; // 三条车道的 X 偏移 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Lane") float LaneOffset = 300.0f; // 切道插值速度 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Lane") float LaneInterpSpeed = 10.0f; private: // 当前目标车道:0 左,1 中,2 右 int32 TargetLane = 1; void SwitchLaneLeft(); void SwitchLaneRight(); };对应的实现:
// 文件路径:Source/ParkourDemo/Private/RunnerCharacter.cpp #include "RunnerCharacter.h" ARunnerCharacter::ARunnerCharacter() { PrimaryActorTick.bCanEverTick = true; } void ARunnerCharacter::Tick(float DeltaTime) { Super::Tick(DeltaTime); // 自动前进:跑道正向设为 Y 轴 AddMovementInput(FVector(0.0f, 1.0f, 0.0f), 1.0f); // 三线切换:只插值 X 坐标 FVector CurrentLocation = GetActorLocation(); float TargetX = (TargetLane - 1) * LaneOffset; float NewX = FMath::FInterpTo(CurrentLocation.X, TargetX, DeltaTime, LaneInterpSpeed); SetActorLocation(FVector(NewX, CurrentLocation.Y, CurrentLocation.Z)); } void ARunnerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump); PlayerInputComponent->BindAction("LaneLeft", IE_Pressed, this, &ARunnerCharacter::SwitchLaneLeft); PlayerInputComponent->BindAction("LaneRight", IE_Pressed, this, &ARunnerCharacter::SwitchLaneRight); } void ARunnerCharacter::SwitchLaneLeft() { TargetLane = FMath::Clamp(TargetLane - 1, 0, 2); } void ARunnerCharacter::SwitchLaneRight() { TargetLane = FMath::Clamp(TargetLane + 1, 0, 2); }这段代码有几个关键点:
AddMovementInput只提供“前进意图”,实际移动速度由 CharacterMovementComponent 的 MaxWalkSpeed 控制;TargetLane - 1让中间车道对应目标 X 为 0,左边为负,右边为正;FInterpTo的第四个参数越大,切道越快,可以在蓝图里随时调整;- Clamp 保证玩家不会从左侧直接切到右侧。
如果你完全用蓝图实现,就把上面这些逻辑对应到蓝图节点上,效果是一样的。C++ 版本的好处是逻辑可读性更强,后续加碰撞死亡判定、计分接口时更容易维护。
7.2 编译与运行
用 C++ 开发 UE 项目,必须先为当前引擎版本生成 Visual Studio 工程文件。操作方式很简单:右键项目 .uproject 文件,选择 Generate Visual Studio project files,然后用 Visual Studio 打开生成的 .sln 文件。
编译时选择 DebugGame Editor 配置即可。第一次编译会持续一段时间,因为引擎需要生成模块目标。编译通过后,返回编辑器,点击 Play 测试。
如果不想经过 IDE,也可以直接从命令行运行项目:
# 把 UE_5.x 替换成你本机实际安装的引擎版本,路径改成实际路径 & "C:\Program Files\Epic Games\UE_5.x\Engine\Binaries\Win64\UnrealEditor.exe" "D:\Projects\ParkourDemo\ParkourDemo.uproject" -game -log这里加-game表示以游戏模式启动,-log会在启动时打开日志窗口,方便看到报错信息。
7.3 如何验证效果
运行后,你应该按以下步骤验证:
- 角色是否自动向前移动;
- 按左方向键或左滑是否切到左侧车道;
- 按右方向键或右滑是否切到右侧车道;
- 按空格键是否起跳;
- 角色碰到障碍物后是否进入失败状态;
- 点击重开按钮后,场景障碍物是否被清除,角色是否回到起点。
如果某一步没有反应,先从输入绑定查起:确认项目设置里确实存在名为LaneLeft、LaneRight的 Action Mapping,并且绑定了正确的按键。蓝图和 C++ 混合项目里,输入名拼写不一致是最常见的静默失败原因。
8. 常见问题与排查方法
8.1 问题速查表
下面整理的是 UE5 跑酷项目里最常见的问题,按“现象、可能原因、排查方式、解决方案”四列归档:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 启动项目时报 LowLevelFatalError,堆栈路径指向 RenderCore | 渲染模块初始化失败,常见为显卡驱动、RHI 选择、着色器缓存损坏 | 查看完整日志中的 LogRHI、LogD3D12 行;确认显卡驱动版本 | 更新显卡驱动;在项目设置中切换 DX11/DX12;删除 Saved 和 DerivedDataCache 缓存后重试 |
| VS 编译时报 error MSB3073,返回非零退出码 | UnrealBuildTool 构建步骤失败,常见原因是编辑器未关闭、VS 版本不匹配、工程文件过期 | 看 MSB3073 下方具体错误;确认是否同时开着 UE 编辑器 | 关闭编辑器;右键 .uproject 重新生成 VS 工程;清理 Binaries 和 Intermediate 目录后重新编译 |
| 角色碰到障碍物被挡住而不是死亡 | 障碍物碰撞预设是 BlockPawn | 查看障碍物的 Collision Preset | 改为 Overlap 类型,用 OnComponentBeginOverlap 触发死亡事件 |
| 移动端触摸没有反应 | 输入映射未创建,或 PlayerController 未接收触摸输入 | 检查项目设置的 Action Mapping;在真机日志中查看输入事件 | 确认触摸输入绑定;用增强输入或传统输入中的 Touch 事件重新绑定 |
| 3D Widget 文字模糊 | Draw Size 过小、屏幕百分比降低、字体渲染分辨率不足 | 调整 WidgetComponent 的 Draw Size;查看视口屏幕百分比 | 调大 Draw Size;尽量改用屏幕空间 UI;替换支持缩放的字体 |
| 双指触摸蓝图没有触发 | 没有区分单指和双指状态,手指顺序导致误判 | 打印两个触摸点的索引与坐标 | 用两个变量记录 TouchIndex 0 和 1 的状态,在完整按压周期内判断双指同时动作 |
| 蓝图里中文无法显示 | UMG 的字体不支持中文字符 | 查看 UI 字体设置 | 更换支持中文的字体,或修改字体 CharacterSet 包含中文范围 |
8.2 重点问题展开
LowLevelFatalError 是 UE5 新手最常看到的崩溃信息,尤其当错误日志里出现Engine\Source\Runtime\RenderCore这类路径时,会给人一种“引擎源码坏了”的错觉。其实这只是崩溃发生在渲染模块内部,通常是显卡驱动、渲染 API 或着色器缓存出了问题。优先更新驱动、切换 RHI,并清理缓存,绝大多数情况下能解决。
MSB3073 则是 C++ 项目编译期的老熟人。它本身不是一个具体错误,而是构建脚本返回了非零退出码。真正的原因藏在它上面的若干行日志里。常见诱因有三个:开着 UE 编辑器同时编译、VS 版本和引擎预期不一致、上次编译的中间文件损坏。处理顺序是:先关 UE,再重新生成 VS 工程文件,最后清理 Binaries 与 Intermediate 目录。
双指触摸的调试建议是:不要把逻辑一次性写完再测试。先打印每个触摸点的 TouchIndex 和位置,确认事件链路通不通,再叠加手势判定。移动端模拟器通常不准确,一定要在真机上验证。
9. 最佳实践与后续学习方向
9.1 工程规范
跑酷项目的代码量不大,但依然要养成好习惯。资源命名建议遵循 UE 社区惯例:蓝图类用BP_前缀,Widget 用WBP_前缀,StaticMesh 用SM_前缀,材质用M_前缀,Actor 子类用A_前缀。这样项目规模变大时,搜索文件不会靠记忆。
另一个容易被忽略的规范是“逻辑分层”:角色只负责输入和自身运动,GameMode 负责规则,Spawner 负责生成,UI 负责显示。跑酷原型里角色直接操作分数、直接调用重开,看起来省事,但后续每加一个功能都会互相纠缠。
9.2 性能与移动端建议
如果目标是移动端,有几个地方值得提前注意。障碍物大量生成时,优先用对象池而不是反复 Spawn 和 Destroy,避免瞬时 GC 卡顿。不要在每一帧里更新 UI 文本。Tick 函数里尽量减少不必要的 GetActorLocation 和 SetActorLocation 频率。
移动端上三线切换的判定要留出手势容错:玩家滑动手指的方向不是完全水平的,建议用斜率或水平位移比例判断“左滑还是右滑”,而不是死等横轴绝对值超过某阈值。
9.3 下一步做什么
跑酷原型的“最小循环”跑通之后,不要急着加几十种障碍物。先把手感调好:跳跃高度、切道速度、碰撞反馈、失败节奏。手感是一个跑酷游戏成立的前提,也是 UE5 里最容易拉开人和人差距的地方。
之后可以按顺序扩展:加滑铲躲避高障碍、加速度条与道具、动画状态机、音效与飘字反馈、难度曲线配置、Android 打包。每加一个功能,都要回到“游戏循环是否依然完整”这个基本问题上。
当你发现蓝图节点开始变得难以维护时,就是把纯蓝图项目迁移到 C++ 的时机。UE5 允许在一个项目里同时使用蓝图和 C++,你完全可以把 Spawner、角色移动、分数计算逐步搬到 C++,只保留关卡美术和 UI 在蓝图中。
跑酷这个品类,表面上是让角色一直跑下去,实际上它训练的是你对游戏循环、状态管理和工程边界的判断力。在 UE5 里把这套基本功练扎实,以后无论做平台跳跃、塔防、甚至 RTS 里的单位移动与生成逻辑,你都会发现自己走的是同一条思维路径。