UnrealEngine5游戏引擎实践(C++)

发布于:2025-07-11 ⋅ 阅读:(15) ⋅ 点赞:(0)

目录

目录

目录

Unreal Engine 是什么?

Unreal Engine 5 简介

核心技术特性

应用场景扩展

兼容性与生态系统

Unreal Engine安装

下载 Epic Games Launcher

启动 Unreal Engine

选择安装版本和路径

选择组件

开始安装

验证安装

配置项目模板(可选)

更新和插件管理

UE游戏引擎

动作捕捉与动画系统

程序化生成与AI技术

物理与破坏系统

音频与本地化技术

性能优化

导入静态网格体

材质实例创建与批量调整

材质参数动态控制技巧

性能优化策略

基础框架与对象管理

游戏逻辑系统

物理与碰撞

UI与交互

高渲染技术

网络与多人游戏

性能优化

C++与Unreal Engine 5结合

基础框架与对象管理

游戏逻辑系统

物理与碰撞

UI与交互

高级渲染技术

网络与多人游戏

性能优化

动态光源布置基础

批量生成动态光源

光源动态更新与控制

性能优化策略

调试与验证

参考资源

C++ 角色移动控制实例

基础2D角色移动

使用键盘输入控制移动

带边界检测的移动

基于时间的平滑移动

使用SDL库的2D角色控制

基于网格的移动系统

带加速度的运动

使用状态模式的移动控制

基于组件的移动系统

基于路径点的移动

Unreal Engine 5

基础门类实现

开关门实例

自动门实例

密码门实例

定时门实例

多重验证门实例

声控门实例

网络控制门实例

生物识别门实例

物品拾取系统核心实现

物体交互逻辑实现

完整工作流程示例

性能优化技巧

库存UI展示方法

物品类型枚举

事件驱动拾取方案

数据持久化实现

网络同步方案

基于C++设计简易UI血量条

使用ASCII字符绘制血量条(终端)

使用Windows API(GDI)

使用SFML库

使用OpenGL(现代)

使用Qt框架

使用Unreal Engine(UMG)

使用ImGUI库

使用SDL2库

使用文本文件持久化

使用Curses库(跨平台终端)

敌人AI巡逻逻辑实例

基础巡逻逻辑

带随机路径的巡逻

带视觉检测的巡逻

带休息时间的巡逻

带环境交互的巡逻

动态调整路径的巡逻

带速度变化的巡逻

带警戒状态的巡逻

带时间限制的巡逻

带分组协调的巡逻

基于C++动态水面材质

基础正弦波水面

多正弦波叠加

基于距离的波浪衰减

法线扰动增强

顶点着色器动态计算

基于C++开发玻璃折射材质

光线追踪基础玻璃材质

OpenGL着色器实现

DirectX 12玻璃材质管线

基于物理的渲染(PBR)玻璃

Unreal Engine玻璃材质蓝图

Unity HDRP玻璃着色器

Vulkan光线追踪玻璃

C++ 玻璃折射材质开发基础

光线追踪中的玻璃材质实现

实时渲染中的玻璃效果(OpenGL/DirectX)

基于物理的玻璃材质优化

常见引擎集成

溶解效果材质基础原理

噪声生成算法实现

材质着色器中的Clip操作

动态控制溶解进度

进阶溶解效果实现

多噪声层混合

对象空间溶解效果

溶解方向控制

粒子系统配合

溶解动画曲线

材质实例管理

实现Parallax Occlusion Mapping (POM)的基础概念

基础代码框架

关键参数优化

实例的批量渲染优化

动态LOD控制

性能对比数据

高级变体:陡峭视差映射

错误处理与边界情况

混合材质支持

跨平台兼容性

配置材质实例参数动态调整

创建材质实例动态对象

批量设置ScalarParameter

优化性能

动态更新参数

参数名称管理

异步加载材质

在C++中控制Metahuman角色的面部表情

控制面部骨骼旋转

使用变形目标(Morph Target)

动画蓝图控制

表情混合系统

实时面部捕捉集成

情绪系统驱动

基于C++实现角色八方向移动的混合空间

基础八方向键盘输入

四向与八向混合模式

角度阈值分段

动画状态机控制

物理引擎驱动

移动预测与插值

网络同步实现

触摸屏虚拟摇杆

基于行为的AI移动

3D空间八方向投影

动画蒙太奇与攻击连招系统基础

基础三连击实现(C++)

动态动画切换(根据敌人距离)

连招分支系统

连招伤害判定集成

取消连招后摇

扩展设计思路

C++中模拟布料动态

基础质点弹簧模型

使用Bullet Physics引擎

性能优化技巧

高级效果实现方向

可视化工具集成

自定义骨骼控制器基础

创建基础骨骼控制器

基于曲线的动态缩放

IK目标跟随

物理驱动的摆动效果

进阶开发建议

开放世界技术

基于C++的World Partition系统自动流送大场景

启用World Partition系统

创建World Partition配置

实现自动流送逻辑

动态加载和卸载实例

优化性能

调试和测试

处理边界情况

异步加载支持

实例管理

多线程支持

动态调整流送参数

实例分组管理

加载优先级设置

内存管理

实例状态查询

事件通知

场景保存和恢复

网络同步

动态实例生成

实例卸载策略

基于C++的PCG植被分布规则

使用Perlin噪声生成植被密度

基于生物群落的植被分布

植被生长条件模拟

基于距离的植被分布

随机植被分布

基于高度的植被分布

植被种群扩散算法

基于资源竞争的植被分布

季节性植被变化

植被年龄模拟

C++与Houdini引擎结合创建动态地形侵蚀的常用方法

基于Hydra的实时侵蚀模拟实现

程序化侵蚀掩模生成技术

多通道侵蚀效果混合方案

动态侵蚀缓存与迭代优化

实现C++ Runtime Virtual Texture混合地面细节

基础设置与初始化

动态纹理混合技术

实例化渲染优化

性能优化技巧

高级混合效果

多通道渲染技术

动态更新策略

调试与可视化

跨平台兼容性

扩展与定制

性能监控与调整

C++ Data Layer管理光照配置基础框架

JSON配置文件读写实现

区域分组管理策略

光照配置动态切换机制

性能优化方案

多线程安全访问控制

配置差异分析工具

光照配置验证系统

配置版本控制系统

混合光照配置方案

模拟破碎建筑物的C++ Chaos物理系统实现

建筑物网格划分算法

物理材质参数设置

碰撞检测与响应处理

性能优化策略

视觉效果增强

使用C++和Niagara制作火焰粒子轨迹(GPU Sprite)

创建Niagara系统

添加GPU Sprite渲染器

设置粒子生成参数

控制粒子运动

调整粒子外观

添加噪声和扰动

粒子颜色渐变

粒子数量控制

动态调整参数

优化性能

粒子碰撞

粒子事件

材质设置

粒子缩放

粒子旋转

粒子子UV

粒子拖尾

粒子光照

基于Navier-Stokes方程的流体模拟

粒子系统与SPH方法

着色器渲染技术

开源库快速实现方案

性能优化技巧

实现树叶动态摇摆

更新树叶摇摆角度

渲染摇摆树叶

风力变化模拟

物理引擎集成

性能优化技巧

着色器实现

3D树叶摇摆扩展

基于C++实现的破坏性武器冲击波效果

基础冲击波实现

 圆形冲击波基础计算

带衰减的二次方冲击波

3D空间球状冲击波

脉冲式冲击波(随时间衰减)

多段伤害区域划分

冲击波物理材质响应

随机扰动增强真实感

 基于距离的伤害曲线

冲击波传播延迟模拟

能量守恒计算

高级效果实现

 冲击波空气扰动(2D伪流体)

地形破坏检测

冲击波音效动态计算

物体撕裂效果

 冲击波视觉后效(屏幕扭曲)

冲击波热力学模拟

 多层冲击波叠加

基于物理材质的穿透力

冲击波反射模拟

 冲击波衍射计算

动态阻力模拟

冲击波压力波模拟

精确能量传播

 冲击波粒子系统

物体层级破坏系统

电磁脉冲模拟

冲击波视觉残影

冲击波气浪效果

动态破坏阈值计算

冲击波AI感知系统

性能优化方案

音频设计

平台特性开发

性能优化

网络与多人游戏


Unreal Engine 是什么?

Unreal Engine 5 简介

Unreal Engine 5(UE5)是由Epic Games开发的实时3D创作工具,专为游戏开发、影视制作、建筑可视化等领域设计。它通过突破性的图形渲染技术和工作流程优化,降低了高保真内容创作的门槛。

核心技术特性

Nanite虚拟几何体

  • 允许直接导入电影级高模资产(如数百万多边形模型),无需手动优化。
  • 动态调整细节级别(LOD),实现无缝缩放和实时渲染。

Lumen全局动态光照

  • 全动态光照系统,实时响应场景变化(如光源移动、材质调整)。
  • 支持间接光照反射和漫反射,减少烘焙时间。

World Partition大场景管理

  • 自动分区加载大型开放世界,支持多开发者协同编辑。
  • 消除传统流送技术的加载卡顿问题。

MetaHuman创作者工具

  • 提供高保真数字人类创建系统,包含预设库和自定义功能。
  • 结合动画工具链(如Control Rig),快速生成逼真角色。

应用场景扩展

  • 游戏开发:支持次世代主机与PC平台,《黑客帝国:觉醒》Demo展示其潜力。
  • 虚拟制片:LED墙实时渲染技术被用于《曼达洛人》等影视作品。
  • 工业设计:汽车、建筑行业用于原型可视化和交互式演示。

兼容性与生态系统

  • 向后兼容UE4项目,提供迁移工具。
  • 集成Quixel Megascans资产库,免费使用数千种扫描资源。
  • 支持Python脚本和蓝图可视化编程,兼顾程序化与美术工作流。

Unreal Engine安装

下载 Epic Games Launcher

访问 Epic Games 官方网站(https://www.epicgames.com/store/),下载并安装 Epic Games Launcher。注册或登录 Epic Games 账户。

启动 Unreal Engine

打开 Epic Games Launcher,在左侧导航栏中选择“Unreal Engine”选项卡。进入 Unreal Engine 页面后,点击“安装引擎”按钮。

选择安装版本和路径

在安装界面中,选择 Unreal Engine 5 的最新版本(如 5.3 或其他稳定版本)。自定义安装路径或使用默认路径,确保磁盘空间充足(至少需 30GB 以上)。

选择组件

根据项目需求勾选额外组件,例如平台支持(Android、iOS)、示例内容或模板。初学者建议保留默认选项。

开始安装

点击“安装”按钮,等待下载和安装完成。安装时间取决于网络速度和硬件性能。

验证安装

安装完成后,在 Epic Games Launcher 的“Unreal Engine”选项卡中会显示已安装的版本。点击“启动”按钮打开 Unreal Engine 编辑器,确认运行正常。

配置项目模板(可选)

首次启动时,可选择创建新项目或学习模板。建议初学者从“Games”类别中选择“Third Person”模板进行测试。

更新和插件管理

定期通过 Epic Games Launcher 检查更新,确保使用最新版本。在编辑器内可通过“Edit” > “Plugins”管理插件。

UE游戏引擎

《黑神话:悟空》采用虚幻引擎5(Unreal Engine 5)开发。该引擎的Nanite虚拟几何技术和Lumen全局动态光照技术显著提升了游戏的画面表现力,尤其是高精度模型和实时光影效果。

动作捕捉与动画系统

游戏通过高精度动作捕捉技术(如惯性动捕和光学动捕)记录真实演员的动作数据,结合虚幻引擎的动画蓝图系统实现流畅的角色动作。战斗中“棍势”系统和精准的打击感反馈依赖物理引擎与动画状态机的深度协作。

程序化生成与AI技术

部分场景和植被可能使用程序化生成工具(如PCG)快速构建,提升开发效率。敌人AI采用行为树和状态机设计,Boss战的多阶段行为变化体现了动态难度调整机制。

物理与破坏系统

虚幻引擎5的Chaos物理系统用于实现武器碰撞、布料模拟和场景破坏效果。游戏中金箍棒的伸缩变形、建筑坍塌等交互效果均依赖物理引擎的实时计算。

音频与本地化技术

3D空间音频技术(如虚幻引擎的MetaSounds)增强环境沉浸感。方言配音和动态配乐系统通过音频中间件(如Wwise)实现,根据战斗节奏切换音乐段落。

性能优化

采用虚拟纹理、层级细节(LOD)和动态分辨率技术平衡画面与性能。光线追踪反射和阴影效果通过硬件加速(如DLSS/FSR)实现高效渲染。

导入静态网格体

将FBX或OBJ文件导入UE时,需确保文件路径无中文,网格体拓扑结构合理。在内容浏览器中右键选择“导入”,指定文件后勾选“生成光照UV”和“自动生成碰撞”。导入后检查材质和纹理是否自动关联,若无需手动指定。

静态网格体导入后,可在细节面板调整缩放比例、旋转方向。启用“合并材质”选项可减少材质数量,优化性能。复杂模型建议分批次导入,避免因顶点数过多导致崩溃。

材质实例创建与批量调整

材质实例基于父材质创建,允许非程序员快速调整参数。在内容浏览器中右键父材质,选择“创建材质实例”。命名规则建议使用“MI_”前缀,便于搜索管理。

批量修改材质实例可通过内容浏览器筛选“MaterialInstanceConstant”,全选后右键“批量编辑”。调整基础颜色、金属度、粗糙度等参数,所有选中实例同步更新。使用“参数集合”可实现跨实例联动控制。

通过蓝图或Python脚本可自动化处理实例。以下代码片段批量设置 emissive 强度:

import unreal
instances = unreal.EditorAssetLibrary.list_assets('/Game/Materials/Instances')
for path in instances:
    mi = unreal.load_asset(path)
    if mi.is_a(unreal.MaterialInstanceConstant):
        mi.set_scalar_parameter_value('Emissive_Strength', 2.0)

材质参数动态控制技巧

在实例中暴露“动态参数”可实现运行时调整。常见参数包括:

  • 向量参数(VectorParameter):控制颜色变化
  • 标量参数(ScalarParameter):调节数值型属性
  • 纹理参数(TextureParameter):动态替换纹理

动态参数通过MaterialParameterCollection或蓝图控制。创建参数集合后,在材质图表中引用CollectionParameter节点。以下蓝图节点序列实现渐变动画:

Timeline -> Lerp (0-1) -> Set Scalar Parameter Value

材质函数库可封装复用逻辑。将常用效果如“湿表面”、“积雪”制作成函数,拖拽到不同父材质中。实例化时仅需调整函数输入参数,保持效果一致性。

性能优化策略

实例化材质比动态材质节省资源。检查Shader复杂度视图(快捷键Ctrl+Shift+.),过度复杂的材质应拆分或简化。静态批量处理规则:

  • 相同父材质的实例自动合并绘制调用
  • 避免单个材质超过100个纹理采样
  • 使用材质质量开关(Quality Switch)适配不同平台

纹理流送池大小需匹配项目需求。4K纹理建议压缩为BC7格式,蒙版类纹理使用BC4/BC5。启用虚拟纹理(VT)时,需在材质中勾选“Runtime Virtual Texture Output”选项。

通过材质统计视图(Material Statistics)分析内存占用。典型优化手段包括:

  • 共享纹理采样器
  • 移除未使用的材质插槽
  • 烘焙静态属性到纹理

以下是Unreal Engine 5与C++结合的实用实例分类整理,涵盖核心功能、游戏机制、优化技巧等方向:

基础框架与对象管理

  1. 创建自定义Actor类并添加组件
    AMyActor::AMyActor() { PrimaryActorTick.bCanEverTick = true; }
    
  2. 实现UObject派生类的垃圾回收
    UCLASS() class UMyObject : public UObject { GENERATED_BODY() };
    
  3. 动态加载资源使用FSoftObjectPath
    FSoftObjectPath AssetRef(TEXT("/Game/Path/To/Asset.Asset"));
    

游戏逻辑系统

  1. 角色控制器输入绑定
    PlayerInputComponent->BindAxis("MoveForward", this, &AMyCharacter::MoveForward);
    
  2. 基于时间轴的动态材质参数控制
    Timeline.AddInterpFloat(CurveFloat, FOnTimelineFloat::CreateUObject(this, &AMyActor::UpdateMaterialParam));
    
  3. 伤害计算系统实现
    UGameplayStatics::ApplyDamage(TargetActor, BaseDamage, GetInstigatorController(), this, UDamageType::StaticClass());
    

物理与碰撞

  1. 自定义碰撞通道响应设置
    CollisionComponent->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECR_Block);
    
  2. 射线检测实现武器射击
    GetWorld()->LineTraceSingleByChannel(HitResult, StartLoc, EndLoc, ECC_Visibility);
    
  3. 物理约束组件创建
    UPhysicsConstraintComponent* Constraint = CreateDefaultSubobject<UPhysicsConstraintComponent>(TEXT("PhysConstraint"));
    

UI与交互

  1. UMG Widget动态创建
    UUserWidget* Widget = CreateWidget<UMyWidget>(GetWorld(), WidgetClass);
    
  2. 鼠标悬停交互事件绑定
    ButtonWidget->OnHovered.AddDynamic(this, &AMyController::HandleButtonHover);
    
  3. 多语言本地化系统集成
    FText LocalizedText = NSLOCTEXT("MyNamespace", "Key", "DefaultText");
    

高渲染技术

  • Lumen动态光照调整
  • Nanite网格体编程控制
  • 自定义渲染管线扩展

网络与多人游戏

  • 复制变量同步实现
  • RPC远程调用示例
  • 网络预测补偿系统

性能优化

  • 异步资源加载策略
  • 对象池技术实现
  • 事件驱动架构设计

完整代码实例可参考Unreal官方文档中的C++编程指南,或访问GitHub仓库"UnrealCppExamples"获取结构化项目文件。每个实例建议结合对应引擎版本(UE5.3+)的API参考手册使用。

C++与Unreal Engine 5结合

以下是Unreal Engine 5与C++结合的实用实例分类整理,涵盖核心功能、游戏机制、优化技巧等方向:

基础框架与对象管理

  1. 创建自定义Actor类并添加组件
    AMyActor::AMyActor() { PrimaryActorTick.bCanEverTick = true; }
    
  2. 实现UObject派生类的垃圾回收
    UCLASS() class UMyObject : public UObject { GENERATED_BODY() };
    
  3. 动态加载资源使用FSoftObjectPath
    FSoftObjectPath AssetRef(TEXT("/Game/Path/To/Asset.Asset"));
    

游戏逻辑系统

  1. 角色控制器输入绑定
    PlayerInputComponent->BindAxis("MoveForward", this, &AMyCharacter::MoveForward);
    
  2. 基于时间轴的动态材质参数控制
    Timeline.AddInterpFloat(CurveFloat, FOnTimelineFloat::CreateUObject(this, &AMyActor::UpdateMaterialParam));
    
  3. 伤害计算系统实现
    UGameplayStatics::ApplyDamage(TargetActor, BaseDamage, GetInstigatorController(), this, UDamageType::StaticClass());
    

物理与碰撞

  1. 自定义碰撞通道响应设置
    CollisionComponent->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECR_Block);
    
  2. 射线检测实现武器射击
    GetWorld()->LineTraceSingleByChannel(HitResult, StartLoc, EndLoc, ECC_Visibility);
    
  3. 物理约束组件创建
    UPhysicsConstraintComponent* Constraint = CreateDefaultSubobject<UPhysicsConstraintComponent>(TEXT("PhysConstraint"));
    

UI与交互

  1. UMG Widget动态创建
    UUserWidget* Widget = CreateWidget<UMyWidget>(GetWorld(), WidgetClass);
    
  2. 鼠标悬停交互事件绑定
    ButtonWidget->OnHovered.AddDynamic(this, &AMyController::HandleButtonHover);
    
  3. 多语言本地化系统集成
    FText LocalizedText = NSLOCTEXT("MyNamespace", "Key", "DefaultText");
    

高级渲染技术

  • Lumen动态光照调整
  • Nanite网格体编程控制
  • 自定义渲染管线扩展

网络与多人游戏

  • 复制变量同步实现
  • RPC远程调用示例
  • 网络预测补偿系统

性能优化

  • 异步资源加载策略
  • 对象池技术实现
  • 事件驱动架构设计

完整代码实例可参考Unreal官方文档中的C++编程指南,或访问GitHub仓库"UnrealCppExamples"获取结构化项目文件。每个实例建议结合对应引擎版本(UE5.3+)的API参考手册使用。

动态光源布置基础

在C++中实现Lumen全局光照系统的动态光源布置,需结合引擎API与光照数据结构。UE5的Lumen系统支持动态光源实时影响全局光照,核心在于正确初始化光源参数并管理其生命周期。

// 示例:创建单个动态点光源
ALight* Light = GetWorld()->SpawnActor<APointLight>();
Light->SetMobility(EComponentMobility::Movable);
Light->SetIntensity(5000.0f);
Light->SetLightColor(FLinearColor::White);

批量生成动态光源

通过循环结构批量生成光源时,需考虑性能优化。使用对象池或分帧加载避免单帧卡顿,同时设置合理的光照范围衰减参数。

// 批量生成100个动态光源
for (int i = 0; i < 100; ++i) {
    FVector Location = FVector(FMath::RandRange(-1000, 1000), FMath::RandRange(-1000, 1000), 300);
    APointLight* NewLight = GetWorld()->SpawnActor<APointLight>(Location, FRotator::ZeroRotator);
    NewLight->SetAttenuationRadius(1000.0f);
    NewLight->SetSourceRadius(FMath::RandRange(10.0f, 50.0f));
}

光源动态更新与控制

动态光源需实时响应场景变化。通过Tick事件或事件驱动机制调整光源属性,如位置、颜色或强度。Lumen会自动处理这些变化对全局光照的影响。

// 动态调整光源属性
void ADynamicLightActor::Tick(float DeltaTime) {
    Super::Tick(DeltaTime);
    LightComponent->SetIntensity(FMath::Sin(GetWorld()->GetTimeSeconds()) * 5000 + 5000);
}

性能优化策略

大量动态光源会显著增加计算负担。采用以下方法优化:

  • Lumen光追降噪设置:在项目设置中调整r.Lumen.Denoiser参数。
  • 光源重要性裁剪:通过FLightSceneInfobAffectDynamicIndirectLighting控制次要光源影响。
  • 实例化渲染:对同类型光源使用Instanced Static Mesh组件。
// 光源重要性判断
if (LightComponent->GetLightType() == LightType_Point && LightComponent->Intensity < Threshold) {
    LightComponent->SetAffectGlobalIllumination(false);
}

调试与验证

使用UE5的Visualize Lighting工具检查光源是否被Lumen正确捕获。命令行工具stat lumen可实时监控光照计算开销。

参考资源

  • UE5官方文档:Lumen Technical Details
  • Epic Games示例项目:Lyra Starter Game中的动态光照实现
  • 源码参考:Engine/Source/Runtime/Renderer/Private/Lumen/LumenScene.cpp

通过合理配置光源属性和优化策略,可在C++中高效实现Lumen支持的动态全局光照效果。

C++ 角色移动控制实例

以下是一些基于C++实现角色移动控制的示例代码片段,涵盖不同场景和需求。

基础2D角色移动

#include <iostream>
using namespace std;

class Character {
public:
    float x, y;
    void move(float dx, float dy) {
        x += dx;
        y += dy;
    }
};

int main() {
    Character player;
    player.x = 0;
    player.y = 0;
    player.move(1.5f, 2.0f);
    cout << "New position: (" << player.x << ", " << player.y << ")" << endl;
    return 0;
}

使用键盘输入控制移动

#include <iostream>
#include <conio.h>

class Player {
public:
    int x, y;
    Player() : x(0), y(0) {}
    void update(char input) {
        switch(input) {
            case 'w': y++; break;
            case 's': y--; break;
            case 'a': x--; break;
            case 'd': x++; break;
        }
    }
};

int main() {
    Player p;
    while(true) {
        cout << "Position: (" << p.x << "," << p.y << ")" << endl;
        cout << "Move with WASD: ";
        char input = _getch();
        p.update(input);
    }
}

带边界检测的移动

#include <iostream>
using namespace std;

class GameCharacter {
    int x, y;
    const int maxX = 10, maxY = 10;
public:
    GameCharacter() : x(0), y(0) {}
    bool move(int dx, int dy) {
        int newX = x + dx;
        int newY = y + dy;
        if(newX >= 0 && newX < maxX && newY >= 0 && newY < maxY) {
            x = newX;
            y = newY;
            return true;
        }
        return false;
    }
    void printPosition() {
        cout << "Current position: (" << x << ", " << y << ")" << endl;
    }
};

int main() {
    GameCharacter c;
    c.move(3, 4);
    c.printPosition();
    if(!c.move(10, 0)) {
        cout << "Cannot move beyond boundary!" << endl;
    }
    return 0;
}

基于时间的平滑移动

#include <iostream>
#include <chrono>
using namespace std;

class SmoothCharacter {
    float x, y;
    float speed;
public:
    SmoothCharacter(float s = 1.0f) : x(0), y(0), speed(s) {}
    void update(float dx, float dy, float deltaTime) {
        x += dx * speed * deltaTime;
        y += dy * speed * deltaTime;
    }
    void printPos() {
        cout << "Position: (" << x << ", " << y << ")" << endl;
    }
};

int main() {
    SmoothCharacter player(2.0f);
    auto lastTime = chrono::high_resolution_clock::now();
    
    while(true) {
        auto now = chrono::high_resolution_clock::now();
        float deltaTime = chrono::duration<float>(now - lastTime).count();
        lastTime = now;
        
        player.update(1.0f, 0.5f, deltaTime);
        player.printPos();
    }
    return 0;
}

使用SDL库的2D角色控制

上下左右控制

#include <SDL.h>

class SDLCharacter {
    SDL_Rect rect;
    int speed;
public:
    SDLCharacter(int x, int y, int w, int h, int s) 
        : rect{x, y, w, h}, speed(s) {}
    
    void handleEvent(SDL_Event& e) {
        if(e.type == SDL_KEYDOWN) {
            switch(e.key.keysym.sym) {
                case SDLK_UP: rect.y -= speed; break;
                case SDLK_DOWN: rect.y += speed; break;
                case SDLK_LEFT: rect.x -= speed; break;
                case SDLK_RIGHT: rect.x += speed; break;
            }
        }
    }
    
    void render(SDL_Renderer* renderer) {
        SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
        SDL_RenderFillRect(renderer, &rect);
    }
};

基于网格的移动系统

#include <iostream>
#include <vector>
using namespace std;

class GridCharacter {
    int gridX, gridY;
    vector<vector<bool>> grid;
public:
    GridCharacter(int width, int height) 
        : gridX(0), gridY(0), grid(height, vector<bool>(width, false)) {}
    
    bool moveTo(int x, int y) {
        if(x >= 0 && x < grid[0].size() && y >= 0 && y < grid.size() && !grid[y][x]) {
            gridX = x;
            gridY = y;
            grid[y][x] = true;
            return true;
        }
        return false;
    }
    
    void printPosition() {
        cout << "Grid position: [" << gridX << "," << gridY << "]" << endl;
    }
};

带加速度的运动

#include <iostream>
using namespace std;

class PhysicsCharacter {
    float x, y;
    float vx, vy;
    float ax, ay;
public:
    PhysicsCharacter() : x(0), y(0), vx(0), vy(0), ax(0), ay(0) {}
    
    void applyForce(float fx, float fy) {
        ax += fx;
        ay += fy;
    }
    
    void update(float dt) {
        vx += ax * dt;
        vy += ay * dt;
        x += vx * dt;
        y += vy * dt;
        ax = ay = 0;
    }
    
    void printState() {
        cout << "Pos: (" << x << "," << y << ") ";
        cout << "Vel: (" << vx << "," << vy << ")" << endl;
    }
};

使用状态模式的移动控制

#include <iostream>
using namespace std;

class MovementState {
public:
    virtual void move(float& x, float& y) = 0;
};

class WalkingState : public MovementState {
public:
    void move(float& x, float& y) override {
        x += 1.0f;
        cout << "Walking..." << endl;
    }
};

class RunningState : public MovementState {
public:
    void move(float& x, float& y) override {
        x += 2.0f;
        cout << "Running!" << endl;
    }
};

class Character {
    float x, y;
    MovementState* state;
public:
    Character() : x(0), y(0), state(new WalkingState()) {}
    
    void setState(MovementState* newState) {
        delete state;
        state = newState;
    }
    
    void move() {
        state->move(x, y);
    }
    
    ~Character() {
        delete state;
    }
};

基于组件的移动系统

键盘的上下左右

#include <iostream>
#include <memory>
using namespace std;

class MovementComponent {
public:
    virtual void move(float& x, float& y) = 0;
};

class KeyboardMovement : public MovementComponent {
public:
    void move(float& x, float& y) override {
        char input;
        cin >> input;
        switch(input) {
            case 'w': y++; break;
            case 's': y--; break;
            case 'a': x--; break;
            case 'd': x++; break;
        }
    }
};

class GameObject {
    float x, y;
    unique_ptr<MovementComponent> movement;
public:
    GameObject(float x, float y) : x(x), y(y) {}
    
    void setMovement(unique_ptr<MovementComponent> m) {
        movement = move(m);
    }
    
    void update() {
        if(movement) {
            movement->move(x, y);
        }
        cout << "Position: (" << x << ", " << y << ")" << endl;
    }
};

基于路径点的移动

#include <iostream>
#include <vector>
using namespace std;

struct Point {
    float x, y;
    Point(float x, float y) : x(x), y(y) {}
};

class PathMover {
    vector<Point> path;
    int currentIndex;
    float speed;
public:
    PathMover(const vector<Point>& p, float s) 
        : path(p), currentIndex(0), speed(s) {}
    
    void update(float dt) {
        if(currentIndex >= path.size()) return;
        
        Point& target = path[currentIndex];
        float dx = target.x - x;
        float dy = target.y - y;
        float distance = sqrt(dx*dx + dy*dy);
        
        if(distance < speed * dt) {
            x = target.x;
            y = target.y;
            currentIndex++;
        } else {
            x += (dx / distance) * speed * dt;
            y += (dy / distance) * speed * dt;
        }
    }
    
    float x, y;
};

以上示例展示了不同场景下的角色移动控制实现方法,包括基础移动、输入控制、物理运动、组件化设计等。实际应用中可以根据具体需求选择合适的实现方式或组合多种方法。

Unreal Engine 5

以下是Unreal Engine 5的实用案例分类整理,涵盖从基础到进阶的开发场景,帮助开发者快速掌握UE5的核心功能。

以下是一些基于C++的可交互门实例的示例代码和实现方法,涵盖不同场景和功能。这些例子展示了如何创建、操作和交互不同类型的门(如开关门、自动门、密码门等)。


基础门类实现

开门关门控制

class Door {
public:
    Door() : isOpen(false) {}
    virtual void open() { isOpen = true; }
    virtual void close() { isOpen = false; }
    bool isOpenNow() const { return isOpen; }
protected:
    bool isOpen;
};

开关门实例

class SimpleDoor : public Door {
public:
    void toggle() {
        if (isOpen) close();
        else open();
    }
};

自动门实例

class AutomaticDoor : public Door {
public:
    void senseMotion(bool motionDetected) {
        if (motionDetected) open();
        else close();
    }
};

密码门实例

输入密码,正确密码开门

class PasswordDoor : public Door {
public:
    PasswordDoor(const std::string& pass) : password(pass) {}
    void tryOpen(const std::string& input) {
        if (input == password) open();
    }
private:
    std::string password;
};

定时门实例

#include <chrono>
class TimedDoor : public Door {
public:
    void openFor(int seconds) {
        open();
        auto now = std::chrono::system_clock::now();
        closeTime = now + std::chrono::seconds(seconds);
    }
    void checkTimer() {
        auto now = std::chrono::system_clock::now();
        if (now >= closeTime) close();
    }
private:
    std::chrono::system_clock::time_point closeTime;
};


多重验证门实例

class MultiAuthDoor : public Door {
public:
    void addKey(const std::string& key) {
        keys.insert(key);
    }
    void tryOpen(const std::vector<std::string>& inputs) {
        for (const auto& input : inputs) {
            if (keys.find(input) == keys.end()) return;
        }
        open();
    }
private:
    std::unordered_set<std::string> keys;
};

声控门实例

类似输入语音,芝麻开门

class VoiceDoor : public Door {
public:
    void setVoiceCommand(const std::string& cmd) {
        voiceCommand = cmd;
    }
    void receiveVoice(const std::string& input) {
        if (input == voiceCommand) toggle();
    }
private:
    std::string voiceCommand;
};

网络控制门实例

#include <curl/curl.h>
class NetworkDoor : public Door {
public:
    void sendOpenRequest(const std::string& url) {
        CURL* curl = curl_easy_init();
        if (curl) {
            curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
            curl_easy_perform(curl);
            curl_easy_cleanup(curl);
            open();
        }
    }
};

生物识别门实例

class BiometricDoor : public Door {
public:
    void enrollUser(const std::string& userId, const std::string& biometricData) {
        users[userId] = biometricData;
    }
    void tryOpen(const std::string& userId, const std::string& inputData) {
        if (users.count(userId) && users[userId] == inputData) {
            open();
        }
    }
private:
    std::unordered_map<std::string, std::string> users;
};

以上是部分示例,如果需要更多实例或特定功能的实现,可以进一步扩展这些基础类。每个示例都展示了不同的交互方式,可以根据实际需求进行组合或修改。

物品拾取系统核心实现

使用C++构建物品拾取系统需要结合碰撞检测与库存管理逻辑。以下是实现框架及关键代码示例:

// 碰撞检测模块
class CollisionDetector {
public:
    bool checkCollision(const sf::FloatRect& player, const sf::FloatRect& item) {
        return player.intersects(item);
    }
};
// 库存系统模块
class Inventory {
private:
    std::vector<Item*> items;
    const int MAX_SIZE = 10;
public:
    bool addItem(Item* newItem) {
        if(items.size() < MAX_SIZE) {
            items.push_back(newItem);
            return true;
        }
        return false;
    }
};

物体交互逻辑实现

// 物品类定义
class Item {
public:
    sf::Sprite sprite;
    ItemType type;
    
    void draw(sf::RenderWindow& window) {
        window.draw(sprite);
    }
};
// 玩家类扩展
class Player {
public:
    void pickupItem(std::vector<Item*>& worldItems, Inventory& inv) {
        for(auto it = worldItems.begin(); it != worldItems.end(); ) {
            if(CollisionDetector::checkCollection(hitbox, (*it)->sprite.getGlobalBounds())) {
                if(inv.addItem(*it)) {
                    it = worldItems.erase(it);
                    continue;
                }
            }
            ++it;
        }
    }
};

完整工作流程示例

// 主游戏循环示例
int main() {
    Player player;
    Inventory inventory;
    std::vector<Item*> worldItems;
    
    while(gameRunning) {
        player.update();
        player.pickupItem(worldItems, inventory);
        
        for(auto& item : worldItems) {
            item->draw(window);
        }
    }
}

性能优化技巧

使用空间划分算法加速碰撞检测:

class SpatialHash {
public:
    void insert(const sf::Vector2f& pos, Item* item) {
        auto cell = getCellCoordinates(pos);
        grid[cell].push_back(item);
    }
    
    std::vector<Item*> query(const sf::FloatRect& area) {
        // 返回潜在碰撞物品
    }
};

库存UI展示方法

// 库存渲染示例
void Inventory::render(sf::RenderWindow& window) {
    for(int i=0; i<items.size(); ++i) {
        items[i]->sprite.setPosition(100 + i*50, 500);
        window.draw(items[i]->sprite);
    }
}

物品类型枚举

// 物品分类系统
enum class ItemType {
    WEAPON,
    CONSUMABLE,
    QUEST_ITEM,
    MATERIAL
};

事件驱动拾取方案

// 按键触发拾取
void Player::handleInput() {
    if(sf::Keyboard::isKeyPressed(sf::Keyboard::E)) {
        attemptPickup();
    }
}

数据持久化实现

// 库存保存/加载
void Inventory::saveToFile(const std::string& filename) {
    std::ofstream file(filename);
    for(auto& item : items) {
        file << static_cast<int>(item->type) << "\n";
    }
}

网络同步方案

// 多人游戏物品同步
class NetworkedItem {
public:
    void syncState(sf::Packet& packet) {
        packet << position.x << position.y;
    }
};

基于C++设计简易UI血量条

以下是基于C++设计简易UI血量条的多种实现方式,涵盖不同库和框架的示例。由于篇幅限制,提供核心代码片段和实现思路,可根据需求扩展。

使用ASCII字符绘制血量条(终端)

#include <iostream>
#include <string>

void drawHealthBar(int current, int max) {
    const int barWidth = 20;
    float ratio = static_cast<float>(current) / max;
    int filled = static_cast<int>(ratio * barWidth);

    std::string bar(filled, '#');
    bar.append(barWidth - filled, '-');

    std::cout << "[" << bar << "] " << current << "/" << max << std::endl;
}

使用Windows API(GDI)

#include <windows.h>

void DrawHealthBar(HDC hdc, int x, int y, int width, int current, int max) {
    float ratio = static_cast<float>(current) / max;
    int filledWidth = static_cast<int>(ratio * width);

    // Background
    HBRUSH bgBrush = CreateSolidBrush(RGB(50, 50, 50));
    FillRect(hdc, &RECT{x, y, x + width, y + 20}, bgBrush);
    DeleteObject(bgBrush);

    // Foreground
    HBRUSH fgBrush = CreateSolidBrush(current < max * 0.3 ? RGB(255, 0, 0) : RGB(0, 255, 0));
    FillRect(hdc, &RECT{x, y, x + filledWidth, y + 20}, fgBrush);
    DeleteObject(fgBrush);
}

使用SFML库

#include <SFML/Graphics.hpp>

void createSFMLHealthBar(sf::RenderWindow& window, float x, float y, float width, float current, float max) {
    float ratio = current / max;
    sf::RectangleShape background(sf::Vector2f(width, 20));
    background.setPosition(x, y);
    background.setFillColor(sf::Color::Black);

    sf::RectangleShape health(sf::Vector2f(width * ratio, 20));
    health.setPosition(x, y);
    health.setFillColor(ratio > 0.3f ? sf::Color::Green : sf::Color::Red);

    window.draw(background);
    window.draw(health);
}

使用OpenGL(现代)

void renderHealthBarGL(float x, float y, float width, float current, float max) {
    float ratio = current / max;
    
    glBegin(GL_QUADS);
    // Background
    glColor3f(0.2f, 0.2f, 0.2f);
    glVertex2f(x, y);
    glVertex2f(x + width, y);
    glVertex2f(x + width, y + 20);
    glVertex2f(x, y + 20);

    // Foreground
    ratio > 0.3f ? glColor3f(0, 1, 0) : glColor3f(1, 0, 0);
    glVertex2f(x, y);
    glVertex2f(x + width * ratio, y);
    glVertex2f(x + width * ratio, y + 20);
    glVertex2f(x, y + 20);
    glEnd();
}

使用Qt框架

#include <QPainter>

void HealthBar::paintEvent(QPaintEvent*) {
    QPainter painter(this);
    float ratio = static_cast<float>(currentHealth) / maxHealth;

    // Background
    painter.fillRect(0, 0, width(), 20, Qt::darkGray);

    // Foreground
    QColor color = ratio > 0.3 ? Qt::green : Qt::red;
    painter.fillRect(0, 0, static_cast<int>(width() * ratio), 20, color);

    // Text
    painter.drawText(rect(), Qt::AlignCenter, 
        QString("%1/%2").arg(currentHealth).arg(maxHealth));
}

使用Unreal Engine(UMG)

void UHealthBar::NativeTick(const FGeometry& Geometry, float DeltaTime) {
    Super::NativeTick(Geometry, DeltaTime);
    
    if (HealthProgressBar) {
        HealthProgressBar->SetPercent(CurrentHealth / MaxHealth);
        HealthProgressBar->SetFillColorAndOpacity(
            (CurrentHealth / MaxHealth < 0.3f) ? FLinearColor::Red : FLinearColor::Green
        );
    }
}

使用ImGUI库

#include <imgui.h>

void DrawImGuiHealthBar(float current, float max) {
    float ratio = current / max;
    ImVec4 color = ratio > 0.3f ? ImVec4(0, 1, 0, 1) : ImVec4(1, 0, 0, 1);

    ImGui::ProgressBar(ratio, ImVec2(200, 20), "");
    ImGui::SameLine();
    ImGui::Text("%.0f/%.0f", current, max);
}

使用SDL2库

#include <SDL2/SDL.h>

void drawSDLHealthBar(SDL_Renderer* renderer, int x, int y, int width, int current, int max) {
    float ratio = static_cast<float>(current) / max;
    
    // Background
    SDL_SetRenderDrawColor(renderer, 50, 50, 50, 255);
    SDL_Rect bgRect = {x, y, width, 20};
    SDL_RenderFillRect(renderer, &bgRect);

    // Foreground
    ratio > 0.3f ? SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255) 
                 : SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
    SDL_Rect healthRect = {x, y, static_cast<int>(width * ratio), 20};
    SDL_RenderFillRect(renderer, &healthRect);
}

使用文本文件持久化

#include <fstream>

void saveHealthToFile(int current, int max) {
    std::ofstream file("health.txt");
    file << current << "\n" << max;
    file.close();
}

void loadHealthFromFile(int& current, int& max) {
    std::ifstream file("health.txt");
    file >> current >> max;
    file.close();
}

使用Curses库(跨平台终端)

#include <curses.h>

void drawCursesHealthBar(int current, int max) {
    int max_x = getmaxx(stdscr);
    float ratio = static_cast<float>(current) / max;
    int filled = static_cast<int>(ratio * (max_x - 10));

    attron(COLOR_PAIR(ratio > 0.3 ? 1 : 2));
    for (int i = 0; i < filled; ++i) mvaddch(0, i, '#');
    attroff(COLOR_PAIR(ratio > 0.3 ? 1 : 2));

    for (int i = filled; i < max_x - 10; ++i) mvaddch(0, i, '-');
    mvprintw(0, max_x - 9, "%d/%d", current, max);
}

以上示例展示了不同技术栈的实现方法,可根据项目需求选择或组合使用。实际开发中需考虑性能优化、动画过渡、多平台兼容性等扩展功能。

敌人AI巡逻逻辑实例

以下是一些基于C++和Behavior Tree的敌人AI巡逻逻辑实例,涵盖了不同场景和需求。

基础巡逻逻辑

// 巡逻任务节点
class PatrolTask : public BT::SyncActionNode
{
public:
    PatrolTask(const std::string& name, const BT::NodeConfiguration& config)
        : BT::SyncActionNode(name, config)
    {}

    static BT::PortsList providedPorts()
    {
        return { BT::InputPort<std::vector<Vector3>>("waypoints") };
    }

    BT::NodeStatus tick() override
    {
        auto waypoints = getInput<std::vector<Vector3>>("waypoints");
        if (!waypoints)
        {
            return BT::NodeStatus::FAILURE;
        }

        // 获取当前巡逻点
        static size_t currentWaypoint = 0;
        Vector3 target = waypoints.value()[currentWaypoint];
        
        // 移动到目标点
        if (MoveTo(target))
        {
            currentWaypoint = (currentWaypoint + 1) % waypoints.value().size();
            return BT::NodeStatus::SUCCESS;
        }

        return BT::NodeStatus::RUNNING;
    }
};

带随机路径的巡逻

class RandomPatrolTask : public BT::SyncActionNode
{
public:
    RandomPatrolTask(const std::string& name, const BT::NodeConfiguration& config)
        : BT::SyncActionNode(name, config)
    {}

    static BT::PortsList providedPorts()
    {
        return { BT::InputPort<std::vector<Vector3>>("waypoints") };
    }

    BT::NodeStatus tick() override
    {
        auto waypoints = getInput<std::vector<Vector3>>("waypoints");
        if (!waypoints || waypoints.value().empty())
        {
            return BT::NodeStatus::FAILURE;
        }

        // 随机选择巡逻点
        static size_t currentWaypoint = rand() % waypoints.value().size();
        Vector3 target = waypoints.value()[currentWaypoint];
        
        if (MoveTo(target))
        {
            currentWaypoint = rand() % waypoints.value().size();
            return BT::NodeStatus::SUCCESS;
        }

        return BT::NodeStatus::RUNNING;
    }
};

带视觉检测的巡逻

class PatrolWithDetection : public BT::SyncActionNode
{
public:
    PatrolWithDetection(const std::string& name, const BT::NodeConfiguration& config)
        : BT::SyncActionNode(name, config)
    {}

    static BT::PortsList providedPorts()
    {
        return { 
            BT::InputPort<std::vector<Vector3>>("waypoints"),
            BT::OutputPort<bool>("player_detected")
        };
    }

    BT::NodeStatus tick() override
    {
        auto waypoints = getInput<std::vector<Vector3>>("waypoints");
        if (!waypoints)
        {
            return BT::NodeStatus::FAILURE;
        }

        // 检测玩家
        if (DetectPlayer())
        {
            setOutput("player_detected", true);
            return BT::NodeStatus::FAILURE;
        }

        // 正常巡逻
        static size_t currentWaypoint = 0;
        Vector3 target = waypoints.value()[currentWaypoint];
        
        if (MoveTo(target))
        {
            currentWaypoint = (currentWaypoint + 1) % waypoints.value().size();
            return BT::NodeStatus::SUCCESS;
        }

        return BT::NodeStatus::RUNNING;
    }
};

带休息时间的巡逻

class PatrolWithRest : public BT::SyncActionNode
{
public:
    PatrolWithRest(const std::string& name, const BT::NodeConfiguration& config)
        : BT::SyncActionNode(name, config), restTime(0)
    {}

    static BT::PortsList providedPorts()
    {
        return { 
            BT::InputPort<std::vector<Vector3>>("waypoints"),
            BT::InputPort<float>("rest_duration")
        };
    }

    BT::NodeStatus tick() override
    {
        auto waypoints = getInput<std::vector<Vector3>>("waypoints");
        auto restDuration = getInput<float>("rest_duration");
        if (!waypoints || !restDuration)
        {
            return BT::NodeStatus::FAILURE;
        }

        // 休息状态
        if (restTime > 0)
        {
            restTime -= GetDeltaTime();
            return BT::NodeStatus::RUNNING;
        }

        // 移动状态
        static size_t currentWaypoint = 0;
        Vector3 target = waypoints.value()[currentWaypoint];
        
        if (MoveTo(target))
        {
            currentWaypoint = (currentWaypoint + 1) % waypoints.value().size();
            restTime = restDuration.value();
            return BT::NodeStatus::SUCCESS;
        }

        return BT::NodeStatus::RUNNING;
    }

private:
    float restTime;
};

带环境交互的巡逻

class PatrolWithEnvironmentInteraction : public BT::SyncActionNode
{
public:
    PatrolWithInteraction(const std::string& name, const BT::NodeConfiguration& config)
        : BT::SyncActionNode(name, config)
    {}

    static BT::PortsList providedPorts()
    {
        return { 
            BT::InputPort<std::vector<Vector3>>("waypoints"),
            BT::InputPort<std::vector<InteractionPoint>>("interaction_points")
        };
    }

    BT::NodeStatus tick() override
    {
        auto waypoints = getInput<std::vector<Vector3>>("waypoints");
        auto interactionPoints = getInput<std::vector<InteractionPoint>>("interaction_points");
        if (!waypoints || !interactionPoints)
        {
            return BT::NodeStatus::FAILURE;
        }

        // 检查是否有交互点
        for (const auto& point : interactionPoints.value())
        {
            if (DistanceTo(point.position) < point.interactionRadius)
            {
                PerformInteraction(point.interactionType);
                return BT::NodeStatus::SUCCESS;
            }
        }

        // 正常巡逻
        static size_t currentWaypoint = 0;
        Vector3 target = waypoints.value()[currentWaypoint];
        
        if (MoveTo(target))
        {
            currentWaypoint = (currentWaypoint + 1) % waypoints.value().size();
            return BT::NodeStatus::SUCCESS;
        }

        return BT::NodeStatus::RUNNING;
    }
};

动态调整路径的巡逻

class DynamicPatrolTask : public BT::SyncActionNode
{
public:
    DynamicPatrolTask(const std::string& name, const BT::NodeConfiguration& config)
        : BT::SyncActionNode(name, config)
    {}

    static BT::PortsList providedPorts()
    {
        return { 
            BT::InputPort<std::vector<Vector3>>("waypoints"),
            BT::InputPort<bool>("path_blocked")
        };
    }

    BT::NodeStatus tick() override
    {
        auto waypoints = getInput<std::vector<Vector3>>("waypoints");
        auto pathBlocked = getInput<bool>("path_blocked");
        if (!waypoints)
        {
            return BT::NodeStatus::FAILURE;
        }

        // 路径被阻挡,寻找替代路线
        if (pathBlocked && pathBlocked.value())
        {
            Vector3 alternativePath = FindAlternativePath();
            if (MoveTo(alternativePath))
            {
                return BT::NodeStatus::SUCCESS;
            }
            return BT::NodeStatus::FAILURE;
        }

        // 正常巡逻
        static size_t currentWaypoint = 0;
        Vector3 target = waypoints.value()[currentWaypoint];
        
        if (MoveTo(target))
        {
            currentWaypoint = (currentWaypoint + 1) % waypoints.value().size();
            return BT::NodeStatus::SUCCESS;
        }

        return BT::NodeStatus::RUNNING;
    }
};


网站公告

今日签到

点亮在社区的每一天
去签到