尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
UE5第一人称射击实战:从项目设置到射击与AI完整指南
简介这是一份针对UE5第一人称射击游戏开发的实战PDF教程适合具备一定UE5基础、希望系统掌握FPS玩法实现的初、中级开发者。教程以完整项目为主线包含项目概述、环境搭建、项目设置、第一人称视角、玩家移动与交互、射击机制、敌人AI行为及UI界面展示等章节逐步讲解从创建项目到构建可运行游戏原型的全过程并提供对应的C示例代码与参数设置思路。作者将每个环节的技术要点拆解为可操作步骤读者边读边练即可直观看到功能效果的变化与联动。资源为单个PDF文档压缩包仅779KB便于携带和随时查阅目前已有2202人学习下载。跟随教程完整复现既能理清角色控制器、摄像机、移动映射、射击判定、AI对手等核心模块的实现逻辑也能获得独立开发3D动作游戏从框架搭建到功能落地的宝贵经验适合作为课程设计或个人项目的起步参考。1. 从模板到能玩的UE5第一人称射击这个实战案例到底在讲什么UE5 自带的 First Person 模板只能给你一个能走能跳的空场景真正的 FPS 工作量全在视角手感、射击反馈、敌人 AI 和 HUD 上。这套「UE5实战案例教程」正好把这条链路拆成可复现的步骤环境搭建、项目设置、第一人称视角、移动交互、射击与敌人 AI、UI 界面展示一路覆盖到能跑出原型的程度。它适合有点 UE5 基础、想系统做 FPS 而不是零散看文档的人。我按它的源码完整过了一遍中间至少有四个地方属于“编译能过、运行就翻车”的典型陷阱后面我会逐个点出来。2. 环境搭建与项目设置安装、模板选择与三个必改参数2.1 安装UE5并创建First Person项目安装环节本身没有太多悬念。通过 Epic Launcher 的 Library 页面选择要装的 UE5 版本点击 Install 后等待下载。这里容易被忽略的是版本选择教程按 UE5 标准版编写如果你装的是带 Early Access 或预览后缀的版本部分模块路径和默认设置会不一样等会儿编译时容易卡在头文件找不到上。所以建议固定使用正式发布版别图新去追预览版。创建项目时关键点是模板的选择。教程要求走 Games 分类下的 First Person 模板这个模板自带一套可运行的第一人称蓝图以及对应的角色模型但它默认是蓝图驱动和我们后续要用 C 重写控制器、角色的目标并不完全吻合。项目名称建议直接叫 FirstPersonShooter保存路径也别用中文或带空格的目录避免生成中间文件时出现编码问题。创建完成后先别急着进编辑器。检查一下 Source 目录下是否生成了 FirstPersonShooter 模块后续所有 C 类都要挂在这个模块下。如果发现项目生成的是纯蓝图工程可以在编辑器的 File 菜单里新建 C 类来激活编译环境这一步很容易被漏掉结果代码文件放不进去。模板生成的内容里地图和人物模型都齐了但玩法是空的教程从这里切入正好是从一个可以运行的空壳开始逐步替换成自己的逻辑。2.2 Project Settings先确认输入方案再谈渲染教程在项目设置这一步只写了 General 标签页设置项目名称和描述、Engine 标签页设置渲染质量。实际操作时顺序应该反过来先确认 Input 标签页的输入方案再动渲染。原因很简单输入方案决定后面所有代码怎么写。UE5 提供旧版 Input 系统和 Enhanced Input 两套教程代码里两套混在一起这里先给结论如果你要完全按教程的 C 代码走建议在项目设置的 Input 里把 Axis Mappings 逐个加上名字和代码里的 MoveForward、MoveRight、Turn、LookUp 严格一致如果你打算走 Enhanced Input在项目设置里创建一个输入映射上下文并关联对应的 InputAction后面代码里才能拿到非空引用。Engine 标签页的渲染参数优先看 RHI 和后端设置。教程这里提得比较泛我给一份可落地的参数范围阴影质量建议先降到 Medium反射方法选 Lumen 或 Screen Space 任一即可后期处理体积的 Bloom 强度降到 0.5 以下抗锯齿选 TSR。第一人称射击原型阶段稳定 60 帧的价值远大于画面不要在这个阶段追求高画质。另外动画相关的细节层级如果开了建议暂时关掉防止角色网格体在近距离切换 LOD 时出现视觉跳动。其他值得在项目设置阶段就确认的还有四项第一Default Player Controller Class 是否指向你新建的 C 控制器类第二Default Pawn Class 是否指向玩家角色类第三GameMode 是否配置了默认 Pawn 和 HUD第四Startup Map 是否指向你测试用的关卡。这四项只要有一项没配好运行时就会出现视角不动、角色不生成、HUD 不显示这类问题而且不报任何错误。这类问题最容易在“从模板升级到自定义类”的过程中出现因为模板本身已经配了一套类你新建类后如果不主动覆盖编辑器仍然会用旧的模板类。2.3 资源导入的目录规范与命名约定教程建议把模型、纹理和音效放进 Content 下的对应子文件夹这是正确的但只说了一半。另一半是导入参数和命名。我一般会把资源按模块分目录比如 FPS/Characters、FPS/Weapons、FPS/Props、FPS/Audio、FPS/UI。导入 FBX 模型时碰撞体选项建议选择 Use Complex Collision As Simple避免后续用默认胶囊体碰撞导致子弹模型穿墙。纹理导入时贴图类型选对应类型UI 图片选 UserInterface 压缩格式否则 HUD 上的准星会变糊。音效文件则要注意采样率UE5 对部分不同采样率的音频在导入时会重新编码音质会有可见差异。命名这块教程没提但实际上非常影响效率。我用的是“资产类型缩写_模块_名称”的格式SM_Gun_M4 表示静态网格体、武器模块、M4 枪T_HUD_Crosshair 表示纹理、HUD 模块、准星。这套命名在 C 里用 ConstructorHelpers 加载资源路径时不容易拼错。顺便说一句这个阶段最好把 Content 目录结构固定下来因为后面所有 C 代码里的资源引用路径都是根据这个目录写的中途改目录等于全局改路径很容易漏改。2.4 验证项目结构编译环境与模板资源的取舍创建完项目和 C 类后我习惯先编译一次再继续往下走。编译的作用不只是确认环境通还能提前暴露模块依赖、头文件路径、API 版本差异这些问题。UE5 的编译流程和普通 C 项目不同编辑器内部的编译日志有时会吞掉部分错误所以建议直接看 Output Log 的完整输出搜 Error 关键字逐个排查。模板资源在这一步也要做取舍。First Person 模板自带一个第一人称角色蓝图、一张测试地图、一套准星 UI 和几个输入映射。教程后面要自己建 C 类模板自带的角色蓝图如果不删运行时会和你新建的 C Pawn 同时存在导致 GameMode 里两个可生成角色互相冲突。我的做法是保留测试地图和 UI 样式参考把模板自带的角色蓝图从 GameMode 默认 Pawn 里摘掉换成自己的 C 类。这一步不做后面很容易出现“改了代码但运行时看到的还是模板角色”的怪象。3. 第一人称视角与玩家移动控制器、角色与输入绑定的正确写法3.1 玩家控制器旋转速率、DeltaTime 与输入来源区分看教程里 AFirstPersonPlayerController 的头文件TurnRate 和 LookUpRate 都定义为 EditAnywhere float初值 45。实现代码是标准的void AFirstPersonPlayerController::TurnAtRate(float Rate) { AddYawInput(Rate * TurnRate * GetWorld()-GetDeltaSeconds()); }这段代码的核心是乘了 GetWorld()-GetDeltaSeconds()把“每帧增量”变成“每秒角速度”。如果去掉这个因子高刷新率显示器上的角色转向速度会比低刷新率高出一截手感完全不同。但这个写法是给手柄摇杆准备的摇杆输入本身是一个持续的轴值配合 DeltaTime 得到角速度是对的。鼠标输入则不同鼠标的移动量本身已经是一次性的事件值如果再用乘 DeltaTime 的曲线视角会显得“发飘”也就是常说的鼠标加速问题。基于这个区别实际项目里我一般会区分处理鼠标的 Turn 和 LookUp 直接走 AddControllerYawInput / AddControllerPitchInput手柄摇杆才走 TurnAtRate 和 LookUpAtRate。教程把这套摇杆改法直接用在了鼠标场景新手照抄会发现视角慢半拍这不是代码错了是输入源选错了。3.2 玩家角色SpringArm、FollowCamera 与移动方向的矩阵运算AFirstPersonCharacter 的代码里创建了 CameraBoom 和 FollowCamera 两个默认子对象TargetArmLength 为 300.fbUsePawnControlRotation 打开。在第一人称场景下弹簧臂带来的问题是它默认参与碰撞检测角色贴墙时弹簧臂会收缩视野会突然被拉近产生很不舒服的“呼吸感”。常见做法是关闭弹簧臂的碰撞测试或者干脆去掉弹簧臂把 FollowCamera 挂在角色根组件上旋转完全跟随控制器。接着看移动相关的参数GetCharacterMovement()-bOrientRotationToMovement true; GetCharacterMovement()-RotationRate FRotator(0.0f, 540.0f, 0.0f);bOrientRotationToMovement 让角色面朝移动方向RotationRate 控制每秒转向速度。这段配置更适合第三人称。第一人称下屏幕中央的视角由控制器旋转决定角色网格体只是视觉替身如果开启朝向移动玩家向左跑时模型会整体转向镜头却仍然朝前出现“模型歪头走路”的违和感。我建议把 bOrientRotationToMovement 设为 false让角色的旋转完全由控制器的控制旋转驱动。RotationRate 保持每秒 540 度问题不大因为它在第一人称下基本用不到。移动的代码逻辑值得展开看const FRotator YawRotation(0, Rotation.Yaw, 0); const FVector Direction FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X); AddMovementInput(Direction, Value);这段代码把控制器的旋转取出来后只保留 Yaw 分量然后通过 FRotationMatrix 得到当前朝向的 X 轴单位向量作为前方向。这么做的意图是防止玩家抬头或低头时移动方向跟着俯仰角变化飞上天只沿水平面移动是大多数 FPS 的标准做法。需要注意的是Direction 是单位向量Value 是输入轴值两者相乘后再传给 AddMovementInput。由于 AddMovementInput 内部还会做归一化处理这里即使不手动归一化也不会出大问题但保留单位向量的习惯会避免输入叠加时出现速度异常。3.3 输入绑定拆开 Enhanced Input 和旧版 BindAxis 的矛盾点这是整个教程最需要警惕的一处。控制器的 BeginPlay 里写了Subsystem-AddMappingContext(DefaultMappingContext, 0);但 SetupInputComponent 里又是InputComponent-BindAxis(MoveForward, this, AFirstPersonPlayerController::MoveForward);这两套机制在同一个类里共存实际会产生两个问题。第一DefaultMappingContext 在代码里被使用但项目里没有创建这个资源也没在项目设置里关联所以 AddMappingContext 会因为拿到空引用而静默失败。第二旧版的 BindAxis 需要项目设置里配好 Axis Mappings如果按教程创建项目时模板自带了对应轴映射旧系统能用但 Enhanced Input 的子系统接入就完全没意义。正确做法是二选一。如果你保留旧版 BindAxis就把 BeginPlay 里的 Enhanced Input 相关代码全部删掉如果你要走 Enhanced Input则要补三样东西一个 UInputMappingContext 数据资产、一个或几个 UInputAction 数据资产、以及对应的绑定代码。以移动为例创建一个名为 IA_Move 的 InputAction在映射上下文里绑定“W/A/S/D”键位然后在 SetupInputComponent 里改成if (UEnhancedInputComponent* EIC CastUEnhancedInputComponent(InputComponent)) { EIC-BindAction(MoveAction, ETriggerEvent::Triggered, this, AFirstPersonPlayerController::MoveForward); }这里的 MoveForward 函数签名需要改成带 FInputActionValue 参数的形式和旧版的 float Value 不一样。这类签名差异正是“编译过了但运行时不触发”的最常见原因。我的建议是直接走 Enhanced InputUE5 后续版本的输入方案都会向它收敛老接口迟早要被淘汰。3.4 鼠标灵敏度调节与视角边界限制教程里没有涉及灵敏度调节和视角边界但这两个参数对 FPS 手感影响极大。灵敏度可以暴露在玩家控制器的 BlueprintReadWrite 变量里在 SetupInputComponent 或 BeginPlay 里读取并应用到旋转倍率。我的经验是灵敏度分开成“水平”和“垂直”两个值玩家对横向和纵向的适应度差异通常很大。视角边界指的是俯仰角限制。正常 FPS 会把向下看的角度限制在 -90 到 90 之间更精细的做法是限制在 -85 到 85避免玩家视角完全垂直后方向感丢失。UE 里可以直接用 Controller 的 SetControlRotation 来 Clamp也可以在 PlayerCameraManager 的 UpdateCamera 里做二次修正。这个参数和弹簧臂问题一样都属于“默认值能跑、但不加就手感奇怪”的范畴。4. 射击系统武器类、子弹类与开火节奏控制4.1 武器类的组件结构与枪口世界变换AWeapon 的构造函数创建了 SceneRoot、Mesh 和 MuzzleFlash 三个组件。SceneRoot 是根组件Mesh 挂载到根上MuzzleFlash 挂到 Mesh 上。这个结构本身合理但用第一人称枪械模型时会遇到两个问题。第一第一人称枪械通常会放在单独的第一人称渲染层避免角色模型遮挡视线教程里没有涉及渲染层分区直接把枪挂在角色根下也可以看但视角会略有违和。第二MuzzleFlash 是粒子组件它的位置和旋转在游戏运行时才会确定如果用它作为子弹出生点最好把粒子组件对齐到枪口模型上的一个专用插槽。教程的 Fire 实现核心是这个冷却判断if (GetWorld()-GetTimeSeconds() NextFireTime) { NextFireTime GetWorld()-GetTimeSeconds() FireRate; // 生成子弹并播放特效 }这个写法逻辑上是对的但要注意 GetWorld()-GetTimeSeconds() 在暂停或时间膨胀时会受影响。如果游戏中做了慢动作特效子弹射速会跟着变慢冷却判断也会被拉长。对 FPS 原型来说可接受但对要求手感稳定的项目建议改用引擎的 Tick 计时或专门的射击冷却组件。生成子弹时有一处关键参数SpawnParams.SpawnCollisionHandlingOverride ESpawnActorCollisionHandlingMethod::AlwaysSpawn;AlwaysSpawn 的含义是忽略生成位置的碰撞强制生成。好处是子弹不会因为枪口紧贴墙壁而生成失败坏处是子弹可能生成在墙体内部一出生就和墙碰撞视觉上弹孔直接出现在镜头前。更合理的做法是把枪口位置向视线方向偏移几个单位或者用 Ignore 选项生成后再做一次修正。我一般会在枪口位置基础上加一段 forward 方向的偏移量偏移距离取 10 到 30 个单位。4.2 子弹类ProjectileMovement 与命中回调的细节子弹类里 InitialSpeed 和 MaxSpeed 都是 1000bRotationFollowsVelocity 为 truebShouldBounce 为 true。这个组合有几个问题要单独拎出来。第一bShouldBounce 打开后子弹碰到任何表面都会反弹弹道会变成台球而不是射击游戏的子弹。教程可能是为了演示物理效果才这么写但如果是正经 FPS应该关掉反弹并把子弹设为 Overlap 或 Block 对应碰撞通道后销毁。第二InitialSpeed 1000cm/s相当于每秒移动 10 米人形角色的身高约 180 厘米子弹横跨整个房间大概要多过半秒视觉上会有明显的“水枪延迟”。如果做 hitscan 类武器根本没有子弹 Actor直接做射线检测更合适如果做发射物弹速通常 20000 到 30000足够快到肉眼无感。第三生命周期用了SetLifeSpan(MaxDistance / InitialSpeed);MaxDistance 是 10000InitialSpeed 是 1000寿命 10 秒。10 秒对于大多数 FPS 地图来说太长了一颗子弹可能在地图上飞行 100 米才销毁如果场景里同时存在多个发射物会产生无谓的物理开销。更常见的做法是只给 1 到 2 秒的生命周期或者命中后立即销毁并生成命中特效。参数教程值建议值原因InitialSpeed1000 cm/s20000~30000 cm/s弹速过慢会带来明显视觉延迟bShouldBouncetruefalse射击游戏子弹应命中即结算反弹会破坏弹道SetLifeSpan 时长10 秒1~2 秒控制发射物存活数量与物理开销Damage 事件类型FDamageEvent()FPointDamageEvent空事件不携带基础伤害数命中方读不到数值再看命中逻辑void AProjectile::OnHit(...) { if (OtherActor OtherActor ! this OtherComp) { OtherActor-TakeDamage(Damage, FDamageEvent(), nullptr, this); } }这里有一个隐藏问题FDamageEvent() 是空参数结构不携带 BaseDamage 数据。目标角色在 TakeDamage 里如果依赖 DamageEvent 的 Damage 字段会拿到 0 或空值。教程里伤害数值直接放在 Damage 变量并通过 SetDamage 传进子弹但调用目标 TakeDamage 时没有把数值装进事件。标准做法是构造 FPointDamageEvent把 BaseDamage、HitInfo、ShotDirection 都填上目标侧才能完整读到命中信息。4.3 开火节奏与输入边沿检测FireRate 是 0.15也就是每秒约 6.7 发这是典型的原型参数。但冷却判断本身不区分单发和连发只要调用 Fire且时间满足条件就会射出一发。要实现“按住连射”输入侧需要持续触发要实现“单发半自动”输入侧需要边沿检测。UE5 里的 Enhanced Input 提供了 ETriggerEvent::Started 和 Completed 来区分按下与抬起旧输入系统下则要在角色的 Tick 里记录上一次按键状态只有从 false 变 true 时才调用 Fire。另一个值得说的点是射速和子弹生成的耦合。教程把 Fire 写在武器类里由外部输入调用。如果通过参数把 FireRate 调成 0会在同一帧连续生成大量子弹造成瞬时性能尖刺。更稳的做法是在武器里维护一个累积时间轴由 Tick 驱动发射队列输入只标记是否开火。原型阶段教程的做法够用但要提前知道这个限制防止后面对射速做动态调整时出问题。4.4 武器蓝图中的资源挂接与音效绑定代码里创建的 MuzzleFlash 组件只是一个发射器容器真正的特效资产要到蓝图里指定。打开武器蓝图后选中 MuzzleFlash 组件在 Details 面板的 Template 里拖入一个 Niagara 系统或粒子系统资源。记得把 Auto Activate 关掉改由 Fire 逻辑里的 MuzzleFlash-Activate() 手动触发否则编辑器打开场景时就会播特效调射击手感时根本看不出是哪一颗子弹触发的。枪声音效方面教程正文没提但实际必做。给武器加一个 UAudioComponent或在 Fire 里用 UGamplayStatics::PlaySoundAtLocation 播放。注意两点不要在构造函数里加载音效因为这时资产路径还没有被安全解析建议在 BeginPlay 或蓝图初始化时异步加载。另外枪声资源如果是从音效包导入的优先选单发枪声不要选循环音效循环音效挂到枪上很容易造成“开火后一直响停不下来”的尴尬情况。5. 敌人AI与健康组件行为树、伤害回调和三个典型的翻车点5.1 敌人角色与AI控制器的关联时机AICharacter 的构造函数里有几处代码单独看都像那么回事组合到一起就是坑。最典型的是AIController CastAAIController(GetController());Actor 的 Controller 是由 SpawnActor 的参数或者游戏模式在生成流程中赋值的构造函数执行时 Pawn 还没被任何人 PossessGetController() 拿到的必然是 nullptr。所以这行代码不但取不到控制器还会让后面的if (BehaviorTree AIController)直接短路行为树永远不会被运行。正确的做法是等 Pawn 被生成并 Possess 之后再获取控制器。通常会在敌人的 AIController 子类里处理或者在 AICharacter 里复写 PossessedBy拿到控制器后立即运行行为树。如果一定要在角色类里做BeginPlay 里动态获取也行AAIController* EnemyAIController GetControllerAAIController(); if (EnemyAIController BehaviorTree) { EnemyAIController-RunBehaviorTree(BehaviorTree); }教程里将 BehaviorTreeComponent 直接创建为角色的默认子对象这点也不太标准。常规架构是让 AIController 拥有并运行行为树组件Pawn 只负责提供感知数据。行为树的黑板变量通常也由 AIController 持有角色和 AI 之间靠 PossessedBy 和感知组件通信。如果你坑在“行为树没运行”上先看是不是 AIController 类没有指定再看行为树资源有没有挂到控制器上最后再看是不是没有 Possess。这个顺序能定位九成的问题。5.2 健康组件初始值、广播参数与重复伤害过滤UHealthComponent 的 TakeDamage / Heal 逻辑整体干净三个要点值得展开。第一CurrentHealth 初始化依赖构造函数里的CurrentHealth MaxHealth;但 MaxHealth 是 EditAnywhere 属性在蓝图中可以被覆盖。只做一次初始化没问题但如果做过 SetMaxHealth 动态调整运行时不会自动重置 CurrentHealth。建议在 BeginPlay 里强制把 CurrentHealth 拉回 MaxHealth保证每次运行都是满血开局。第二广播和回调的签名问题。OnHealthChanged 是动态多播委托AddDynamic 绑定的回调必须与委托签名完全一致。教程里 AAICharacter 中声明了一个带完整伤害事件的 TakeDamage 函数又用 AddDynamic 把它绑定到 OnHealthChanged(float) 上这两个签名对不上所以后面编译会反复报错。正确做法是单独写一个专门处理血量变化回调的 UFUNCTIONUFUNCTION() void OnHealthChanged(float NewHealth);然后在这个函数里判断 NewHealth 是否小于等于 0再触发死亡逻辑。不要把不同类型的事件塞进同一个函数名里这是一个很典型的“名字冲突”问题重命名回调函数比手动改签名省事得多。第三TakeDamage 里的重复伤害过滤。代码用if (DamageAmount 0.0f || CurrentHealth 0.0f) return;拦截了无效伤害这个很好。但多攻击者场景下可能出现两颗子弹同帧命中第二次伤害依然作用在小于 0 的 CurrentHealth 上。由于 Clamp 已经存在这不会产生负数血量却会额外触发一次广播。对原型阶段不是问题如果后面要做伤害数字或击杀音效建议加一层死亡状态判断死亡后直接忽略所有后续伤害。5.3 避坑排查编译不过、行为树不跑、子弹打不出伤害现象 1AICharacter 编译报错提示函数重定义或签名不匹配。 原因AAICharacter 声明了带 FDamageEvent 的重载 TakeDamage随后又用同一个函数名绑定到 HealthComponent 的 OnHealthChanged 委托两者签名冲突。 解决拆开两个回调伤害入口用 TakeDamage血量变化监听用单独的 BindHealthChanged(float NewHealth)不要在同一个类里用一个名字承载两种职责。现象 2敌人站在场景里不动行为树没有任何运行迹象。 原因构造函数里获取到的 AIController 永远为空BeginPlay 的判断条件不成立RunBehaviorTree 没被调用。另一个常见附加原因是敌人的 Pawn 没有被任何 AIController Possess行为树组件存在但无人驱动。 解决先在 AI 角色蓝图上确认 Pawn 的 AIController 类是否指定了自定义子类然后在 BeginPlay 或 PossessedBy 里获取控制器并调用 RunBehaviorTree最后确认行为树资源的根节点和黑板都已正确配置。顺序错了任何一步都会表现为“剧情 NPC 罚站”。现象 3子弹命中敌人后不掉血。 原因敌人角色没有重写 TakeDamage或者重写了但没有把伤害数值转给 UHealthComponent。教程里子弹 OnHit 调用 OtherActor-TakeDamage 时默认实现不会处理自定义血量伤害被静默消耗。 解决在敌人角色的 TakeDamage 重写里调用 UHealthComponent 的 TakeDamage 方法并更新血量为状态。同时把子弹侧的 FDamageEvent 改成 FPointDamageEvent 并填入 BaseDamage防止伤害数值在事件层丢失。现象 4开火时没有枪口特效。 原因UParticleSystemComponent 的 Template 没有在蓝图里赋值代码里只是调用了 Activate系统认为没有特效资源什么都不播。 解决在武器蓝图里给 MuzzleFlash 组件指定一个 Niagara 或粒子系统资产并确认 Auto Activate 为关闭由 Fire 逻辑手动激活。这样每次开火都能看到明确的枪口提示调射程和弹速时也更直观。6. 收尾HUD、调试技巧与把原型推进到可玩状态6.1 用 UMG 搭最小可用的 HUD教程只留了“创建 HUD 类”的入口。最省事的方案是新建一个 Widget Blueprint拖一个 Image 做准星再放一个 ProgressBar 显示血量百分比存为 WBP_FPS_HUD。在玩家角色或控制器的 BeginPlay 里加载并显示if (APlayerController* PC GetWorld()-GetFirstPlayerController()) { UUserWidget* HUD CreateWidgetUUserWidget(PC, HUDBPClass); if (HUD) { HUD-AddToViewport(0); } }HUDBPClass 需要是 TSubclassOf 在蓝图里指定刚才保存的 WBP_FPS_HUD。AddToViewport 的参数是层级0 在最底层数值越大越靠前。血量进度条不要在 Tick 里每帧刷新应该把 HealthComponent 的 OnHealthChanged 广播接到 Widget 蓝图事件里更新性能更稳逻辑也更符合事件驱动。6.2 三个调试习惯第一个是画调试线。在子弹生成点和碰撞点调用 DrawDebugLine能直接看出子弹出生位置和运动方向排查“子弹穿模”和“子弹贴脸”这类问题比打日志快得多。第二个是屏幕调试消息。在 Fire、TakeDamage、Die 三个关键函数里加 AddOnScreenDebugMessage先验证调用链是否走通再做数值调优。第三个是性能观察。跑起来按 打开控制台输入 Stat FPS 看帧数输入 ShowDebug 看玩家状态如果帧数明显下降先查粒子特效的激活数量和物理碰撞的生成量这两处最容易在 FPS 里爆 DrawCall。6.3 我的收尾习惯每做一次 UE5 的 FPS 原型我都会强制自己按顺序复查一遍先跑 C 类确认视角能转再关掉弹簧臂碰撞确认贴墙不拉镜头然后把 FireRate 调到 0.05 验证连射节奏和落点最后才接行为树和 HUD。这四步做完大多数“能跑但手感奇怪”的问题都提前暴露了。UE5 的 FPS 开发里很多坑藏在看似正常的默认值里比如 bOrientRotationToMovement、bShouldBounce、GetController() 的获取时机——它们不是玄学靠断点和日志就能把原因翻出来。这套教程的工程源码我都整理在下载资源里建议对照源码跑一遍再改参数改动后手感差异一眼就能看出来。希望这篇能让你少走一两个弯路。本文还有配套的精品资源点击获取
RELATED

相关推荐

双节锂电主动均衡原理与MP2642GR-Z工程实践

双节锂电主动均衡原理与MP2642GR-Z工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/17 1:03:52
嵌入式网络开发:lwIP协议栈深度实践指南

嵌入式网络开发:lwIP协议栈深度实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/17 1:03:52
Python Challenge第19关:PIL+NumPy破解LSB隐写

Python Challenge第19关:PIL+NumPy破解LSB隐写

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/17 1:03:52
MORE NEWS

更多资讯

📰

02-MySQL数据库服务安装部署全流程

文章目录1. 数据库服务程序下载2. 数据库服务安装2.1 软件环境准备2.2 数据目录初始化2.3 初始化数据库2.4 启动数据库服务2.5 验证是否启动成功2.6 配置远程3. 安装使用常见问题和处理3.1 常见问题问题1:初始化报错libaio问题2: 初始化报错数据文件目录不为空问题3…

📰

微信AI隐私边界剖析:从权限模型到最小权限原则的工程实践

微信AI隐私这次上热搜,员工回应里那句“能接触信息不超过本人可查看权限”,我反复看了好几遍。作为一个常年跟权限系统打交道的开发者,这句话在我眼里不是一句公关辞令,而是一个可以拆出完整技术方案的边界声明:AI接触…

📰

Python Web攻击检测系统:基于字节熵与多模型联动的真实流量实战

简介:本资源是一套基于Python与机器学习的Web攻击检测系统完整源码实现,面向网络安全初学者、渗透测试人员及AI安全方向开发者,旨在解决SQL注入、XSS等常见Web攻击的自动化识别与防御问题。压缩包共66个文件,涵盖17个核心Python脚…

📰

令牌环IEEE 802.5深度解析:从确定性介质访问控制到工程启示

看到“令牌环网IEEE 802.5”这个题目,估计不少人第一反应是:这不是早就被以太网淘汰的技术吗,教材为什么还要花一整节讲它,考试还要考?我当年第一遍学计算机网络时也是这个心态,觉得知道个“环形拓扑令牌传…

📰

DeepSeek-R1 1.5B本地部署与轻量RLHF实战指南

简介:本资源是一份面向AI初学者与本地部署实践者的DeepSeek大模型技术详解PDF,聚焦大语言模型原理、本地化部署全流程及强化学习驱动的R1推理模型训练方法。内容覆盖本地部署动因(隐私保护、离线可用、零成本适配)、Transformer架…

📰

储能电池被动均衡方案:MP2642GR-Z与STM32L073RZ硬件设计与固件实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬