发布时间:2026/9/4 0:31:02
UE5游戏开发实战:构建RTS资源采集闭环系统 很多刚开始研究 UE5 RTS 的同学通常会把第一个 Demo 放在“单位移动”和“兵种战斗”上结果做到中期才发现真正把整个玩法串起来的其实是资源采集这一条非常基础又非常容易被小看的链路。单位走到矿脉、采集、带回基地、更新 HUD、节点资源扣减、满载调度……任何一个环节没想清楚后面做大系统时都会回来返工。本文将围绕“虚幻引擎5 游戏开发实战打造 RTS 资源采集系统”这条主线拆开一个最小可用但结构清晰的资源采集原型。文章会覆盖 UE5 的 Gameplay 框架、Actor 数据设计、C 类与蓝图配合方式、右键命令与自动提交资源等关键内容。适合有一定 UE 基础、想从零搭建 RTS 单局玩法的开发者阅读如果你已经有 Unity 或 Godot 项目经验也可以当作跨引擎思路对照。1. 为什么先把 RTS 资源采集系统单独拿出来做1.1 RTS 资源采集到底在做什么RTS 游戏的资源采集可以抽象成一句话让某个单位把场景中的资源点数据搬运到属于玩家的存储容器中。看起来只有“采集”和“提交”两个动作但实际展开之后会涉及资源种类划分例如 Gold、Mineral、Food资源节点的当前余量与总量显示采集单位的状态切换单位寻路到达资源点到达后按节奏抽取资源采集单位携带容量满后寻找提交点提交后把资源加到玩家或队伍的总量中HUD 实时刷新资源数值。如果只是临时写死一串数值Demo 当然很快。但一旦加入第二个资源类型、多个阵营、多个采集单位你会发现代码结构中“资源数值存在哪里”“谁有权添加资源”“节点剩余量由谁扣减”会成为主要矛盾。这也是 UE5 游戏开发中资源管理话题一直讨论度很高的原因。很多人会把大量精力放在资产和打包资源上但从玩法设计角度看运行时资源数值的归属与流转同样重要。1.2 UE5 落地时容易卡住的点使用 UE5 实现 RTS 资源采集经常遇到的困惑包括选中单位后如何右键让单位去采集单位到达资源点后以什么频率扣减资源玩家持有的资源应该保存在 GameMode、PlayerState 还是某个全局单例中资源节点余量变了UI 怎么感知单位满载后是立刻返回基地还是继续占用资源节点C 写的数值变更蓝图界面如何订阅。这些问题如果从一开始就明确了后面再接多单位框选、行为树 AI、联网同步都会顺很多。1.3 本文实现的功能边界本文不会强行实现完整的 RTS 框架而是聚焦下面这条可以跑通的闭环左键选中采集单位 → 右键点击资源节点 → 单位寻路到节点附近 → 开始按时间间隔采集 → 背包满或节点枯竭后回到最近提交点 → 资源写入玩家 PlayerState → HUD 刷新这套结构足够支撑一个可演示的 UE5 RTS 原型同时也方便后续扩展。2. 技术方案与演示环境2.1 引擎与工程本文使用的核心引擎为 Unreal Engine 5 系列。UE5.0 之后 Gameplay 框架整体变化不大后续给的代码在 UE5.1、UE5.2、UE5.3 或更高版本中思路通用。由于不同版本默认输入系统差异较大文章在输入响应部分会同时说明传统 BindKey 与 Enhanced Input 的对接思路。你可以根据自己工程创建时的模板选择合适方式。2.2 准备一个第三人称或空白工程如果你已经有一个 UE5 C 工程可以直接在现有 Module 中添加代码。如果是从零开始建议在启动器中选择“游戏”分类下的 Blank 或 Third Person 模板并勾选“包含初学者内容包”。初学者内容包里的基础几何体和材质可以用来快速搭场景。2.3 Gameplay 框架的类划分在这个资源采集 Demo 中我建议按 UE5 的标准 Gameplay 框架划分职责类职责ARTSResourceNode场景中的资源节点负责扣减自身余量ARTSHarvester玩家控制的采集单位负责移动、采集、提交ARTSResourceDropOffPoint基地提交点提交后资源进入玩家账户ARTSPlayerState保存当前玩家的资源数量ARTSPlayerController处理鼠标点击选中与右键命令HUD/UMG显示玩家资源数值这里有一个很重要的设计观点不要把所有逻辑都塞进 GameMode。GameMode 更适合控制规则流程不适合保存某个玩家的个人资源数据。多人模式下每个客户端对应不同 PlayerState把资源数量放在 PlayerState 中是最符合 UE 习惯的做法。3. 第一步先设计资源数据而不是先摆场景3.1 创建基础资源类型在 UE5 中资源类型最简单的表达方式是枚举。为了能暴露给蓝图我们使用 UENUM。文件路径Source/RTSSystem/RTSResourceTypes.h#pragma once #include CoreMinimal.h #include RTSResourceTypes.generated.h UENUM(BlueprintType) enum class ERTSResourceType : uint8 { None UMETA(DisplayName None), Gold UMETA(DisplayName Gold), Mineral UMETA(DisplayName Mineral), Energy UMETA(DisplayName Energy) };这样设计的好处是枚举名称可读蓝图下拉框能直接选择后续需要扩展资源类型时只需要增加枚举项可以像普通整数一样用 TMap 做映射存储。3.2 把资源数值放在哪一层很多 RTS 新手会把“资源数量”直接写死在资源节点 Actor 上同时在 HUD 中通过 GetActorLocation 之类的接口拿数值。这样在单人演示中没有明显问题但当资源节点由服务端管理、玩家资源由客户端的玩家存档管理时就会混淆。推荐的做法是场景中的ARTSResourceNode只关心“这个节点的剩余量”玩家持有量统一放到ARTSPlayerState采集单位不直接调用 PlayerState 修改资源而是把资源送到提交点后由提交逻辑统一结算。在这个模型下资源节点的数值与玩家的资源数值是两套数据中间通过采集单位的“携带量”作为临时传递而不是一边采集一边直接加到玩家总资源里。这样实现自动施法、科技加速、资源减半等玩法改动时会更灵活。3.3 采集阶段的定义我们把单位要做的事情定义成枚举状态。虽然在小型 Demo 中可以用 bool 标记代替但状态枚举在调试时更有价值。UENUM(BlueprintType) enum class EHarvesterPhase : uint8 { Idle, MovingToNode, Harvesting, MovingToDropOff, Depositing };有了这个枚举单位在 Tick 中只需要根据当前状态做对应判断逻辑会清晰很多后续接入调试菜单时也能一眼看出单位正在做什么。4. 实现资源节点 Actor4.1 创建 ARTSResourceNode 类资源节点是整个资源采集的源头。它需要暴露资源类型初始总量当前余量被采集时可以扣减的接口资源变化的事件回调。文件路径Source/RTSSystem/RTSResourceNode.h#pragma once #include CoreMinimal.h #include GameFramework/Actor.h #include RTSResourceTypes.h #include RTSResourceNode.generated.h class UStaticMeshComponent; class UWidgetComponent; DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FOnResourceAmountChanged, ARTSResourceNode*, Node, float, NewAmount ); UCLASS(Blueprintable) class ARTSResourceNode : public AActor { GENERATED_BODY() public: ARTSResourceNode(); virtual void BeginPlay() override; UPROPERTY(VisibleAnywhere, Category Components) TObjectPtrUStaticMeshComponent MeshComponent; UPROPERTY(VisibleAnywhere, Category Components) TObjectPtrUWidgetComponent InfoWidget; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category RTS|Resource) ERTSResourceType ResourceType ERTSResourceType::Gold; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category RTS|Resource) float TotalAmount 1000.0f; UPROPERTY(BlueprintReadOnly, Category RTS|Resource) float CurrentAmount 0.0f; UPROPERTY(BlueprintAssignable, Category RTS|Event) FOnResourceAmountChanged OnResourceAmountChanged; UFUNCTION(BlueprintCallable, Category RTS|Resource) float Extract(float RequestedAmount); UFUNCTION(BlueprintPure, Category RTS|Resource) bool IsDepleted() const; protected: void UpdateWidget(); UPROPERTY(EditDefaultsOnly, Category RTS|UI) TSubclassOfUUserWidget InfoWidgetClass; };有一个细节要提前说清楚CurrentAmount不能在构造函数里直接复制TotalAmount因为蓝图放置到场景后设计者可能修改TotalAmount的默认值。更稳妥的方式是在BeginPlay中重置。对应实现文件文件路径Source/RTSSystem/RTSResourceNode.cpp#include RTSResourceNode.h #include Blueprint/UserWidget.h #include Components/StaticMeshComponent.h #include Components/WidgetComponent.h ARTSResourceNode::ARTSResourceNode() { PrimaryActorTick.bCanEverTick false; MeshComponent CreateDefaultSubobjectUStaticMeshComponent(TEXT(MeshComponent)); SetRootComponent(MeshComponent); InfoWidget CreateDefaultSubobjectUWidgetComponent(TEXT(InfoWidget)); InfoWidget-SetupAttachment(MeshComponent); InfoWidget-SetWidgetSpace(EWidgetSpace::World); InfoWidget-SetDrawSize(FVector2D(220.0f, 80.0f)); InfoWidget-SetRelativeLocation(FVector(0.0f, 0.0f, 160.0f)); } void ARTSResourceNode::BeginPlay() { Super::BeginPlay(); CurrentAmount TotalAmount; UpdateWidget(); } float ARTSResourceNode::Extract(float RequestedAmount) { if (IsDepleted() || RequestedAmount 0.0f) { return 0.0f; } const float ActualAmount FMath::Min(RequestedAmount, CurrentAmount); CurrentAmount - ActualAmount; UpdateWidget(); OnResourceAmountChanged.Broadcast(this, CurrentAmount); return ActualAmount; } bool ARTSResourceNode::IsDepleted() const { return CurrentAmount 0.0f; } void ARTSResourceNode::UpdateWidget() { if (!InfoWidget || !InfoWidgetClass) { return; } if (!InfoWidget-GetWidget()) { InfoWidget-SetWidgetClass(InfoWidgetClass); } }4.2 给资源节点添加可见 Mesh代码中只创建了UStaticMeshComponent并没有指定具体的 Static Mesh因为引擎的 C 构造函数不推荐直接硬编码资源路径。正确做法是基于ARTSResourceNode创建蓝图子类在蓝图里把 MeshComponent 的 Static Mesh 指定为立方体或自定义矿脉模型给 MeshComponent 设置合适的碰撞预设默认 BlockAll 即可调整 InfoWidget 的渲染位置。我建议使用 Cube 并缩放成长条形矿脉颜色上用暖色与普通地形区分。这样右键点击时碰撞检测更容易命中。4.3 节点 UI 的更新思路InfoWidget使用的是 WidgetComponent它是一个世界空间 UI。若要显示剩余量可以在ARTSResourceNode蓝图子类中创建一个 UMG Widget让 UI 里的进度条监听OnResourceAmountChanged事件并刷新显示。由于数据变更事件是动态多播蓝图里可以把事件绑定到 UI 的函数节点不需要频繁轮询 Tick。5. 实现采集单位 ARTSHarvester5.1 Harvester 类声明采集单位继承 ACharacter 比较方便因为不需要自己实现角色移动组件可以直接使用 CharacterMovement 在 NavMesh 上移动。文件路径Source/RTSSystem/RTSHarvester.h#pragma once #include CoreMinimal.h #include GameFramework/Character.h #include RTSResourceTypes.h #include RTSHarvester.generated.h class ARTSResourceNode; class ARTSResourceDropOffPoint; class UStaticMeshComponent; UENUM(Blueprint

相关新闻

2026/9/4 0:31:02

Claude API工程化:从Prompt Eval到稳定运行的关键链路

Claude Certified Architect 这套前置课程里,最容易被低估的其实不是 API 怎么调,而是 Part 5 这条从 Prompt Eval 到 Running 的完整链路。很多人拿到 Claude API 之后,第一件事就是复制一个示例请求跑通,然后以为结束了。但真正…

2026/9/4 0:31:02

会话级动态表单渲染与实时校验联动

会话级动态表单渲染与实时校验联动 在复杂 Agent 交互中,纯文本对话往往无法胜任高精度、多字段的业务参数录入。比如订机票、填写运维工单或配置数据看板时,如果大模型一段一段地追问用户“您的出发时间是哪天?”、“您需要几张票&#xff1…

2026/9/4 0:31:02

伊顿数据中心固态变压器:AI算力时代供配电架构的范式跃迁

数据中心正经历一场前所未有的电力挑战。GPU集群的功耗密度以每两年翻番的速度攀升,单机架功率从传统的5-10kW飙升至40kW甚至100kW以上,传统供配电架构的多级变换损耗、庞大的占地空间以及毫秒级响应能力的不足,已成为制约AI算力部署的物理瓶…

2026/9/4 1:11:04

解析split与mph:用Python构建运动速度曲线分析模型

先跑一段速度数据,看看自己到底能榨出多少信息。最近我在处理运动表现数据时,拿到一组类似「2.88 split / 31.21 mph」的记录,一开始只觉得是一个分段计时和瞬时速度,真正动手做转换、建模、可视化之后,才发现里面的门…

2026/9/4 1:11:04

Qt常用控件学习路线:理解对象树与信号槽,手写界面代码

重学 Qt 的第二站,几乎都会落在常用控件上。Qt 自带的控件种类远不止 QPushButton、QLabel 这几样,但如果只是逐个控件试属性,学完两周后很容易发现自己只是记住了 API 名,遇到一个带输入框、下拉框和表格的窗口仍然不知道代码该怎…

2026/9/4 1:11:04

Matlab仿真椭圆振动铣削:从轨迹规划到超声加工应用

简介:本资源面向机械制造、超声加工及先进切削工艺领域的研究生、工程师与科研人员,聚焦椭圆振动铣削这一融合超声技术与传统铣削的高精度加工方法,解决难加工材料切削力大、表面质量差、刀具磨损快等工程痛点。压缩包共2个文件(1…

2026/9/4 1:11:04

MATLAB森林火灾检测实战:构建抗干扰鲁棒工作流

简介:本资源是一套基于图像处理技术的森林火灾检测Matlab实现方案,面向计算机、电子信息工程、数学等专业的本科生,适用于课程设计、期末大作业及毕业设计等实践教学场景,解决真实环境下的火焰与烟雾识别问题。压缩包共18个文件&a…

2026/9/4 1:06:03

AI电话外呼工具有哪些?AI初筛+真人坐席如何守住B24合规

【数据截止日期:2026年9月3日】【作者资质说明:本文作者为通信行业独立观察者,拥有5年以上企业通信服务研究经验(作者自陈述,未附第三方资质证明),本文为第三方自媒体平台发布的行业科普内容&am…

2026/9/3 18:28:26

vSound小提琴数字处理器实操指南:从接线到演出的完整配置

电小提琴或者原声小提琴插电演出,第一个绕不开的坎就是声音难听。原声琴的共鸣和空气感一旦进了拾音器,出来的往往是一坨干瘪、发尖、带着奇怪塑料味的信号。我当初第一次把琴接上乐队调音台,直接被主唱吐槽"你这声音像在锯钢丝"。…

2026/9/3 14:29:47

传感器接口IC如何攻克生物化学传感的微弱信号难题?

1. 从电极到比特流:为什么生物化学传感必须依赖专用接口IC 做生物化学传感的人都有过类似的经历:明明传感器本身性能很好,信号输出却一塌糊涂——噪声大、漂移明显、重复性差,怎么调都达不到预期。很多时候问题并不在传感器&#…

2026/9/3 14:30:35

STM32F411CEU6多通道ADC采集:扫描模式+DMA实现详解

1. 多通道 ADC 的用武之地把“Multichannel ADC”和“STM32F411CEU6”这两个关键字放在一起,其实就是嵌入式开发里最常遇到的一类需求:用一块不算贵的 MCU,同时采集多路模拟信号。STM32F411CEU6 是 48 引脚的 Cortex-M4F 主控,主频…

2026/9/4 0:00:58

STM32H743 SPI从机DMA双缓冲通信实战

简介:本资源是面向嵌入式开发工程师与STM32进阶学习者的SPI DMA双机通信从机端完整实现方案,聚焦STM32H743高性能Cortex-M7单片机在工业控制与高速数据交互场景下的从机通信开发痛点。压缩包含1355个文件,主体为599个C源码与321个头文件&…

2026/9/4 0:00:58

CPU开盖降温教程:20元成本让温度直降30度的原理与实践

最近很多朋友都在抱怨,自己的电脑一到夏天就变成"烤箱",玩游戏时CPU温度动不动就飙到90度以上,风扇噪音堪比直升机。更让人头疼的是,明明配置不错,却因为高温降频导致性能大打折扣。如果你也遇到了类似问题&…

2026/9/4 0:00:58

ArkTS 表单工程:场地预约页的三态场次 Grid 与校验

ArkTS 表单工程:场地预约页的三态场次 Grid 与校验 App 14「运动场地预约」场地 Tab(Func1Tab),是整 App 交互最丰富的页面——场地横向切换 三色图例 渐变预约预览卡 快捷模板 今日场次 Grid(可选/已选/已满三态&…

2026/9/3 20:43:36

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/3 17:51:43

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/3 21:06:57

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…