斯坦福课程 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. Openssl Des3对压缩文件进行加密命令详解

    群友提问: 致力于明天: tar -cvf - WMG_Back_"$Today"|openssl des3 -salt -k hY91gd3GJAAfghECleLwWQAPGK ...

  2. Go语言常用标准库——context

    文章目录 为什么需要Context 基本示例 全局变量方式 通道方式 官方版的方案 Context初识 Context接口 Background()和TODO() With系列函数 WithCance ...

  3. Arduino 板的说明

    Arduino 板的说明 在本章中,我们将了解 Arduino 板上的不同组件.将学习 Arduino UNO 板,因为它是 Arduino 板系列中最受欢迎的.此外,它是开始使用电子和编码的最佳板. ...

  4. gitlab ci 集成 eslint/prettier/tsc 做代码审查,并使用 eslint 输出作为显示代码质量

    前言 想自动化一下公司里代码的部分审查,最初想用 reviewdog 的,但是公司的域名基本都在 VPN 中访问的,gitlab ci 的容器中是访问不到的,于是乎实验了 gitlab 代码质量功能. ...

  5. 删除小程序scroll-view的滚动条

    小程序scroll-view滚动条很丑,想隐藏? 在有scroll-view滚动条页面的wxss里添加: ::-webkit-scrollbar { display: none; width: 0; ...

  6. 鬼谷子的钱袋(lgP2320)

    主要思路:二进制拆分. 先将 \(m\) 进行二进制拆分. 注意金币总数有限,也就是说拆分后可能会多出来一组.多出来的这组如果不是 \(2^n\) 就不需要考虑了,因为不会和前面的重复. 接下来考虑重 ...

  7. 一文读懂强化学习:RL全面解析与Pytorch实战

    在本篇文章中,我们全面而深入地探讨了强化学习(Reinforcement Learning)的基础概念.主流算法和实战步骤.从马尔可夫决策过程(MDP)到高级算法如PPO,文章旨在为读者提供一套全面的 ...

  8. Chromium VIZ架构详解

    1. VIZ的三个端 在设计层面上 viz 的架构如下图所示: 在设计上 viz 分了三个端,分别是 client 端, host 端和 service 端. client 端用于生成要显示的画面(C ...

  9. STL容器详解

    详细总结STL 为什么C++比C更受人欢迎呢?除了C++ 的编译令人感到更舒适,C++的标准模板库(STL)也占了很重要的原因.当你还在用手手写快排.手写二叉堆,挑了半天挑不出毛病的时候,C++党一手 ...

  10. 【封装】Trie

    #include<cstdio> const int N = 1e6 + 5; struct Trie{ int root, id; bool bit[32]; struct Node{ ...