斯坦福课程 UE4 C++ ActionRoguelike游戏实例教程 0.绪论

概述

本文章对应课程第十一章 42节。这篇文章会进一步地为AI添加新功能,创建自定义任务,允许AI发射子弹,并且讲解观察器中断的用法。

最终效果:

目录

  1. 创建自定义任务节点
  2. 观察器中止是什么
  3. 优化AI

创建自定义任务节点 BTTask

这节课我们要为AI小兵添加进入攻击范围后进行射击的功能。正如上节课看到的那样,行为树支持各种各样的任务,包括朝目标前进、Wait等我们已经使用过的UE自带Task。这节课的目标是自定义Task节点,实现开火的功能。具体的细节,我们边做边说。

让我们像以前做的一样,右键编辑器,勾选搜索全部,找到BTTasksNode类,将其作为父类。

创建BTTaskNode的子类

接着修改代码如下,对于BTTaskNode的子类,我们目前只需要重写ExecuteTask函数即可。返回值为EBTNodeResult::Failed和EBTNodeResult::Successd,对应行为树的节点返回失败或者成功。

逻辑也很简单,就是获取对象等一系列API操作,作为初学者熟悉使用即可。

除此以外,我们还添加了一个自定义子弹类型的对象,这些操作在大家学习编写人物的时候已经相当熟悉了。

//SBTTask_RangeAttack.h
UCLASS()
class FPSPROJECT_API USBTTask_RangeAttack : public UBTTaskNode
{
GENERATED_BODY()
protected:
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override; UPROPERTY(EditAnywhere, Category = "AI")
TSubclassOf<AActor> ProjectileClass;
}; //SBTTask_RangeAttack.cpp
EBTNodeResult::Type USBTTask_RangeAttack::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
AAIController* MyController = OwnerComp.GetAIOwner();
if(ensure(MyController))
{
ACharacter* MyPawn = Cast<ACharacter>(MyController->GetPawn());
if(MyPawn == nullptr)
{
return EBTNodeResult::Failed;
} AActor* TargetActor = Cast<AActor>(OwnerComp.GetBlackboardComponent()->GetValueAsObject("TargetActor"));
if(TargetActor == nullptr)
{
return EBTNodeResult::Failed;
} FVector MuzzleLocation = MyPawn->GetMesh()->GetSocketLocation("Muzzle_01");
//方向向量=目标位置-当前位置
FVector Direction = TargetActor->GetActorLocation() - MuzzleLocation;
FRotator MuzzleRotation = Direction.Rotation(); FActorSpawnParameters params;
params.Instigator = MyPawn;
params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; ensure(ProjectileClass);
AActor* NewProj = GetWorld()->SpawnActor<AActor>(ProjectileClass, MuzzleLocation, MuzzleRotation);
return NewProj ? EBTNodeResult::Succeeded : EBTNodeResult::Failed;
} return EBTNodeResult::Failed;
}

接下来验证我们的代码。修改行为树如下,相对于上节课添加了左边两个节点,并添加了一个装饰器,用于检测是否已进入攻击范围。

图中的RangeAttack则是我们刚才编写的自定义任务,添加方式和上节课的MoveTo一样,搜索即可找到。

修改行为树,别忘了设置子弹的类型

PS:笔者在实验的时候,经常贪图方便使用热重载。这次出现了子弹类型没有暴露在蓝图中的问题。关闭UE重新编译才顺利出现,果然不能信任热重载。

被炸烂惹

上图是运行游戏的结果。可以看到,当敌对小兵进入设定的攻击范围后,执行了后面的Wait节点,等待一段时间后,重新回到最左边,执行了RangeAttack任务,由于Selector节点的特性,反复执行了最左边的节点,连续发射了大量子弹进行了狂轰滥炸。到目前为止,我们的初期目标已经达成了。

因为真正的游戏中不会出现这么蠢的AI,我们还需要对这个AI进行一些优化,例如发射子弹有冷却时间,能够三连发,能够在进入范围后立刻开火等功能。

观察器中断的概念

我们首先要实现的是进入攻击范围后能立刻开火。在原来的设计中,当AI跑入攻击范围后,会先执行行为树最右边Wait任务,等待上一段时间。现在我们希望马上执行RangeAttack任务,除了调整节点的顺序以外,行为树还给我们提供了一个很有用的功能,就是观察器中止(Observer Aborts )。由于上节课没有说清楚,我这里详细的说一下。

点击装饰器,可以看到细节面板有观察者中止的选项。

观察器中止

官方文档的解释十分的晦涩难懂,这里我仅以实用的角度简单描述一下观察器中止的用法,实际效果还需要读者自行进行测试。

在行为树中,观察器中断(Observer Aborts)是我们控制行为树任务(BehaviorTreeTask,BTT)执行的最重要的设置之一,其能在满足行为树装饰器(BehaviorTreeDecorator,BTD)临界条件后有条件地中断被观察者中断标记为中断范围的所有节点的执行。

说人话就是,如果你设置了观察器,当装饰器观测到自己负责的这个黑板键值变化时, 这个装饰器就有权利中断后面正在运行的节点,转而从装饰器这个节点开始运行。

其中下拉框有四个选项,其作用分别是:

  • None:不终止执行。
  • Self:终止自己及节点以下的所有子树。
  • Lower Priorit:终止此节点右方(具体的说是拥有最深共同父级)的所有节点。
  • Both:终止自己及节点以下的所有子树及右方所有节点。

如下图所示,当Within Attack Range节点设置了Lower Priorit时,右边的的节点全部变成了蓝色,当Within Attack Range的条件达成后,不管蓝色的节点在运行什么任务,都会被直接中断,转而从这个节点开始运行。

设置为Lower Priorit

当设置为self时,发现只有子树节点变成了绿色。当观测到条件变化时,会中断子树的任务,从装饰器的节点开始运行。

设置为self

both则是两种设置的集合,不再赘述。

设置为Both

另外,通知观察者有两种方式, 用于控制行为树如何通知观察者中断:

一种是结果改变时(On Result Change):对应装饰器的Bool结果发生变化时通知中断

一种是值改变时(On Value Change):对应装饰器的值(一般是行为树对应的黑板中此装饰器使用的黑板键的值)变化时通知中断。

由于我们这里使用的都是Bool,所以都默认On Result Change即可。

通知观察者

优化AI

子弹发射冷却和循环

回到正题来,大家可以慢慢咀嚼一下刚才讲的东西,我们先把子弹发射冷却和循环给做出来。

将行为树修改成这样子。相比于之前,添加了两个装饰器节点和一个Sequence节点。斯坦福课程里使用的是Selector节点,而这里使用的是Sequence节点。因为只有一个子节点,在这里两者是没有区别的,请读者充分理解两者的区别后再提出疑问。

CoolDown和Loop都是装饰器节点

顾名思义,Cooldown节点会控制节点执行的间隔,当节点处于冷却时间时,会直接返回false。Loop装饰器则会循环执行下面的节点,具体情况读者可以自行实验。

到现在为止,这个AI的行为模式为:如果目标在攻击范围外,会向目标移动。进入攻击范围后,会等待一段时间(这里设置为1s)。然后开枪向角色以0.2s为间隔时间射击三次,冷却2s。在此期间,行为树会运行后面两个节点,直到wait结束后重新判断是否结束冷却。

设置优先级

当这个wait设置的时间很长(例如5s)时,有时即使冷却时间结束了他也不会立即攻击,会傻傻地等待wait结束。这时候就需要用到之前提到的观察器中断。

将Within Attack Range的观察器中断设置为Both,Cooldown的观察器中断设置为Lower Priorit,根据之前的理论,当角色进入攻击范围时或者冷却时间结束都会立刻执行当节点。由于Within Attack Range设置为Both,当角色离开攻击范围时也会立刻停止执行后面的节点并返回false。

另外,和上节课一样,将Out of Attack Range节点设置为Both,这样角色在进入攻击范围时就会立刻停下来了。这部分可以实验的东西相当多,请读者自行实验。

最后的效果如下:

这里用走位很细节的在AI射出两发子弹的时候离开了他的攻击范围,这时候他会立即停止射击,执行中间的节点。

设置攻击的时候面朝玩家

这个实际上就是一个小细节。要实现这个功能,我们通常会想到可以在执行攻击动作的时候使用SetActorRotation函数修改AI小兵的朝向,实际上行为树为我们自带了这样一个服务(BTService)。这给我们带来了思路,我们可以使用BTService,在节点激活的情况下定时调整角色的状态,当然这里我们直接使用即可。

设置朝向,你也可以将它放在selector节点

参考链接

https://www.bilibili.com/read/cv13400311

https://www.cnblogs.com/timy/p/9048267.html

斯坦福 UE4 C++ ActionRoguelike游戏实例教程 02.AI自定义任务和观察器中断的更多相关文章

  1. 《Genesis-3D开源游戏引擎--横版格斗游戏制作教程02:关键帧动画导入与切割》

    2. 关键帧动画导入与切割 动画的分割与导入概述: 在游戏当中,游戏角色在不同状态下会有不同的动作,这些动作在引擎里相当于一段段的动画片段.当导入模型资源的时候,连同模型动画都会一并导入到引擎中.开发 ...

  2. Cocos2d-x3.0游戏实例《不要救我》第十篇(结束)——使用Json配置数据类型的怪物

    如今我们有2种类型的怪物,并且创建的时候是写死在代码里的,这是要作死的节奏~ 所以.必须可配置.不然会累死人的. ; i < size; ++i) { int id = root[i][&quo ...

  3. Cocos2d-x3.0游戏实例之《别救我》第八篇——TiledMap实现关卡编辑器

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/musicvs/article/details/25368273 好吧.我真心全然搞不懂.我如今仅仅只 ...

  4. 《Genesis-3D开源游戏引擎完整实例教程-2D射击游戏篇:简介及目录》(附上完整工程文件)

    G-3D引擎2D射击类游戏制作教程 游戏类型: 打飞机游戏属于射击类游戏中的一种,可以划分为卷轴射击类游戏. 视觉表现类型为:2D 框架简介: Genesis-3D引擎不仅为开发者提供一个3D游戏制作 ...

  5. Python导出Excel为Lua/Json/Xml实例教程(一):初识Python

    Python导出Excel为Lua/Json/Xml实例教程(一):初识Python 相关链接: Python导出Excel为Lua/Json/Xml实例教程(一):初识Python Python导出 ...

  6. Web 开发中应用 HTML5 技术的10个实例教程

    HTML5 作为下一代网站开发技术,无论你是一个 Web 开发人员或者想探索新的平台的游戏开发者,都值得去研究.借助尖端功能,技术和 API,HTML5 允许你创建响应性.创新性.互动性以及令人惊叹的 ...

  7. 值得 Web 开发人员收藏的20个 HTML5 实例教程

    当开始学习如何创建 Web 应用程序或网站的时候,最流行的建议之一就是阅读教程,并付诸实践.也有大量的 Web 开发的书,但光有理论没有实际行动是无用的.现在由于网络的发展,我们有很多的工具可以用于创 ...

  8. 对《[Unity官方实例教程 秘密行动] Unity官方教程《秘密行动》(十二) 角色移动》的一些笔记和个人补充,解决角色在地形上移动时穿透问题。

    这里素材全是网上找的. 教程看这里: [Unity官方实例教程 秘密行动] Unity官方教程<秘密行动>(九) 角色初始设定 一.模型设置: 1.首先设置模型的动作无限循环. 不设置的话 ...

  9. Cocos2d-x3.0游戏实例《不要救我》第一章——前言

    我们可以学习? 这是一个非常easy游戏.但更多的东西用(对于初学者).至少,对于它的一个例子,有点多. 笨木头花心贡献.啥?花心?不呢.是用心~ 转载请注明,原文地址:http://www.benm ...

  10. 源于《Unity官方实例教程 “Space Shooter”》思路分析及相应扩展

    教程来源于:Unity官方实例教程 Space Shooter(一)-(五)       http://www.jianshu.com/p/8cc3a2109d3b 一.经验总结 教程中步骤清晰,并且 ...

随机推荐

  1. open与fopen的区别

    1. 来源 从来源的角度看,两者能很好的区分开,这也是两者最显而易见的区别: open是UNIX系统调用函数(包括LINUX等),返回的是文件描述符(File Descriptor),它是文件在文件描 ...

  2. asp.net mvc Core 网页错误提示:An unhandled exception occurred while processing the request.处理请求时发生未处理的异常。

    网页错误提示: An unhandled exception occurred while processing the request. InvalidOperationException: The ...

  3. 普冉PY32系列(九) GPIO模拟和硬件SPI方式驱动无线收发芯片XL2400

    目录 普冉PY32系列(一) PY32F0系列32位Cortex M0+ MCU简介 普冉PY32系列(二) Ubuntu GCC Toolchain和VSCode开发环境 普冉PY32系列(三) P ...

  4. x86平台SIMD编程入门(1):SIMD基础知识

    1.简介 SIMD(Single Instruction, Multiple Data)是一种并行计算技术,它通过向量寄存器存储多个数据元素,并使用单条指令同时对这些数据元素进行处理,从而提高了计算效 ...

  5. 向量数据库Chroma极简教程

    引子 向量数据库其实最早在传统的人工智能和机器学习场景中就有所应用.在大模型兴起后,由于目前大模型的token数限制,很多开发者倾向于将数据量庞大的知识.新闻.文献.语料等先通过嵌入(embeddin ...

  6. Welcome to YARP - 5.压缩、缓存

    目录 Welcome to YARP - 1.认识YARP并搭建反向代理服务 Welcome to YARP - 2.配置功能 2.1 - 配置文件(Configuration Files) 2.2 ...

  7. Service Mesh:微服务架构的救世主还是多余的花招?

    Service Mesh的前世今生 在前面,我们提出了一个问题:随着模块和节点的增多,微服务之间难免会遇到各种网络问题.为了解决这些问题,目前有一个解决方案,即使用Spring Cloud中的各个组件 ...

  8. Java Lambda 表达式常见面试问题与解答

    公众号「架构成长指南」,专注于生产实践.云原生.分布式系统.大数据技术分享. 在本文中,我们将讨论一些重要且常见的 Java Lambda 表达式面试问题和解答 1.什么是 Lambda 表达式? l ...

  9. A-B数对 (hash映射)

    题目大意: 第一行输入N,C 第二行输入n个数字 输出,求A - B = C的数对个数 样例 4 1 1 1 2 3 输出 3 思路:用STL容器map,map<num, times>,建 ...

  10. Windows文件句柄无效

    今天我用FreeFileSync从移动硬盘复制一个名为Con的文件夹到本地硬盘,复制失败. 通过文件夹资源管理器Explorer直接访问文件夹则提示"禁止访问",右键属性切换到安全 ...