Skip to main content

AI角色和AI智能体

概述

本文档主要介绍如何在项目中创建非游戏玩家的AI角色(AI Character)AI智能体(AI Agent)

配置和驱动默认AI角色

创建角色

在组件编辑器中创建一个新关卡,然后依次点击创建游戏对象(Create Game Object) -> 角色(Character)来创建一个默认角色对象。

配置默认目标角色。在大纲(Hierarchy)面板中选中刚才所创建的角色对象,然后在观察器(Inspector)面板中选中CharacterMesh0(Inherited),点击下方

骨架网格体配置文件(Skeletal Mesh Config File)属性旁的 按钮,在弹出的文件浏览器(File Browser) 窗口中选择一个.actor文件,最后点击OK按钮完成添加。

注意:所选择的组合体(.actor)需预设好骨骼和动画资产。

image-20230313154155040

观察器(Inspector)面板中选中CollisionCylinder(Inherited)和CharacterMesh0(Inherited)后,分别调整胶囊体网格体的位置,使它们均位于地形上方。

image-20230313174457929

创建黑板和行为树

下面以驱动AI角色移动到指定位置为例,创建一个行为树资源(ai_walkman.bt)和一个黑板资源(ai_walkman.bb)。

创建驱动角色行为树文件。可参考行为树编辑器用户手册 -> 创建行为树创建黑板,分别创建行为树和黑板资源。

image-20230314094239992

在资源预览窗口中双击黑板资源ai_walkman.bb,打开行为树编辑器并进入到黑板编辑模式。

行为树编辑器(Behavior Tree Editor)中点击新键(New Key),添加一个名为Init、类型为布尔值(Bool)的黑板键和一个名为WayPoint、类型为向量(Vector)的黑板键,完成添加后点击保存(Save)

添加黑板键的具体步骤可参考编辑黑板

image-20230314102826921

在资源预览窗口中双击行为树资源ai_walkman.bt,打开行为树编辑器并进入行为树编辑模式。

为该行为树指定一个关联的黑板资源,在行为树图中点击Entry节点,然后在详细面板(Details Panel)中设置刚才所创建的黑板资源为该行为树所关联的黑板。

image-20230314095337370

关联完成:

image-20230314103202430

在行为树图中点击Entry节点引脚并拖拽到空白处,在弹出的菜单中选择选择器(Selector)节点。

image-20230314103140676

选择器(Selector)节点引脚处拖拽出引线,依次创建并连接两个序列(Sequence)节点。

image-20230314100104069

在左边的序列(Sequence)节点上右击,依次点击添加新装饰器节点(Add New Decorator Node) -> 黑板(Blackboard),为该序列节点链接一个黑板装饰节点。

image-20230314100320546

选中该黑板装饰节点,在详细面板(Details Panel)面板中设置黑板键(Blackboard Key)为Init,黑板键操作(Key Operation)尚未设置(Is Not Set)

image-20230314103114158

为另一个序列(Sequence)节点也链接一个黑板装饰节点,并将该节点的黑板键(Blackboard Key)设置为Init,黑板键操作(Key Operation)设置为已经设置(Is Set)

image-20230314102520955

为左边的序列节点连接一个等待(Wait)节点,并在详细面板(Details Panel)中将等待时间(Wait Time)改为1.0。

image-20230314103037414

设置了AI角色所要移动到的位置点。继续为左边的序列节点连接一个设置向量值(Set Vector Value)节点,在详细面板(Details Panel)中设置值X(Value X)值Y(Value Y)值Z(Value Z)(此处数值可根据当前所处关卡进行自定义设置),设置该节点名称(Name)为Set WayPoint,将黑板键(Blackboard Key)改为WayPoint。

image-20230314104711778

为左边的序列节点再连接一个设置布尔值(Set Bool Value)节点,修改该节点的名称(Name)为Set BB Key Initialize Value,并勾选当前值(Current Value),并将黑板键(Blackboard Key)修改为Init

133464110547260302

为右边的序列节点连接一个移动至(Move To)节点,并在详细面板(Details Panel)中将节点名称(Name)修改为Move to WayPoint,将黑板键(Blackboard Key)修改为WayPoint。

133232586302282177

最后为右边的序列节点连接一个等待(Wait)节点,将等待(Wait Time)设置为300.0。

image-20230314171450301

编辑完成后,点击保存(Save)

133232588523835668

可通过行为树编辑器用户手册详细了解行为树的相关内容。

设置AI角色

大纲(Hierarchy)面板中选中LCharacter,然后在观察器(Inspector)面板中选中LCharacter(Instance),勾选自动控制AI(Auto Control AI)属性,然后点击行为树(Behavior Tree)旁的 按钮,在弹出的窗口中选择一个行为树文件,此处选择了刚才所创建的行为树文件。

image-20230314130040412

配置AI命令处理脚本。在大纲面板中选中LSceneSettings-1,然后在观察器面板中点击AI脚本(AI Script)旁的 按钮,在弹出的窗口中选择所要配置的脚本文件ai_command_processor。(所用到的脚本文件在引擎资源的editor_script/pl_ai下,可在此先将ai_command_processor.lua和public_attr.lua复制到Project文件夹下的Script文件夹中,再进行添加AI脚本(AI Script)。)

注意:确保资源目录为编辑器可定位到的目录。

image-20230314130828900

脚本文件:

image-20230314130702595

至此,就已经完成配置并驱动一个非游戏玩家的AI角色了。

运行

为了方便运行之后查看结果,可再创建一个默认Pawn(Default Pawn)对象。依次点击创建游戏对象(Create Game Object) -> 默认Pawn(Default Pawn)

image-20230314113059713

创建完成后,调整默认Pawn(Default Pawn)的位置和角度,并将自动持有玩家(Auto Possess Player)改为玩家0(Player 0)

image-20230314131330001

点击运行按钮可查看此例(AI角色移动至指定位置)的最终结果。

133232599777615044

自定义AI行为

引擎目前提供两种回调方式来实现自定义AI行为(AI Behavior),即C++和Lua。

C++代码示例

头文件:i_ai_command_processor.h

函数声明:

// 回调函数原型
// 添加对象
typedef void (*on_add_ai_object)(IAICommandProcessor* from, PERSISTID object,
const char* bt_file);

// 移除对象
typedef void(*on_remove_ai_object)(IAICommandProcessor* from, PERSISTID object,
const char* bt_file);

// 服务
typedef void (*on_service)(IAICommandProcessor* from, PERSISTID object,
const char* bt_file, const char* bb_key);

// 移动到
typedef void (*on_move_to)(IAICommandProcessor* from, PERSISTID object,
bool is_move_to_object, PERSISTID goal_object,
const XMFLOAT3& goal_position, float accept_radius);

// 播放动画
typedef void (*on_play_animation)(IAICommandProcessor* from, PERSISTID object,
const char* animation_name);

// 中止播放动画
typedef void(*on_abort_play_animation)(IAICommandProcessor* from, PERSISTID object,
const char* animation_name);

// 播放声音
typedef void(*on_play_sound)(IAICommandProcessor* from, PERSISTID object,
const char* sound_name, bool is_non_block);

// 自定义任务
typedef void (*on_custom_task)(IAICommandProcessor* from, PERSISTID object,
const char* bt_file, const char* custom_task_name, const char* custom_task_data);

// 中止移动到
typedef void (*on_abort_move_to)(IAICommandProcessor* from, PERSISTID object);

// 中止自定义任务
typedef void(*on_abort_custom_task)(IAICommandProcessor* from, PERSISTID object,
const char* bt_file, const char* custom_task_name, const char* custom_task_data);

// 更新AIObject朝向
typedef void(*on_update_direction)(IAICommandProcessor* from, PERSISTID object,
float x, float y, float z);

回调注册方法:

// 注册回调
virtual bool RegisterOnAddAIObject(on_add_ai_object f);
virtual bool RegisterOnRemoveAIObject(on_remove_ai_object f);
virtual bool RegisterOnService(on_service f);
virtual bool RegisterOnMoveTo(on_move_to f);
virtual bool RegisterOnPlayAnimation(on_play_animation f);
virtual bool RegisterOnCustomTask(on_custom_task f);
virtual bool RegisterOnPlaySound(on_play_sound f) override;
virtual bool RegisterOnUpdateDirection(on_update_direction f) override;

// 注册中止任务回调
virtual bool RegisterOnAbortMoveTo(on_abort move_to f) override;
virtual bool RegisterOnAbortCustomTask(on_abort_custom task f) override;
virtual bool RegisterOnAbortPlayAnimation(on_abort_play_animation f) override;

参数类型:

IAICommandProcessor* from:AIcommandProcessor对象

PERSISTID object:当前AIObject对象ID

const char* bt_file:当前绑定的行为树资产文件

const char* bb_key:当前ServiceNode设置的黑板键

回调函数注册查询:

AICommandProcessor::IsRegisterCallback(EAICommandCallback:: OnService)

Lua代码示例

默认回调文件是组件编辑器中SceneSettings的AI脚本(AI Script)选项中配置的脚本文件。

如:引擎提供的ai_command_processor.lua已经提供了默认实现。

--AI
function on_init(self)
nx_log("ai init")

--开启调试信息发送
local ai_world = nx_value("ai_world")
ai_world.EnableDebug = true

--初始化:添加AI对象回调,可以初始化黑板
nx_callback(self, "on_add_ai_object", "ai_add_ai_object")

--任务:移动
nx_callback(self, "on_move_to", "ai_move_to")

--中断任务:移动
nx_callback(self, "on_abort_move_to", "ai_abort_move_to")

--任务:播放动画
nx_callback(self, "on_play_animation", "ai_play_animation")

--任务:自定义任务(当引擎实现的通用任务暂未包含时用于定制)
nx_callback(self, "on_custom_task", "ai_custom_task")

--服务:定期更新黑板
nx_callback(self, "on_service", "ai_service")

--事件: 更新对象朝向
nx_callback(self, "on_update_object_direction", "update_object_direction")

return 1
end

注册回调函数示例代码:

--AI
function on_init(self)
--服务:更新黑板
nx_callback(self, "on_service", "ai_service")
return 1
end

函数示例代码:

function ai_service(self, target, behavior_tree, blackboard_key)
// 文件判断

if "" == behavior_tree then
// TODO Something
end

// 黑板键判断
if "" == blackboard_key then
// TODO Something
end
end

创建AI智能体

AI智能体(AI Agent)提供了一种让开发者可以重新构建所有AI行为的开发方式。

AI智能体包含AI属性和AI事件的回调功能,可以重载或修改对应的实现来创建新的AI行为。

激活AI智能体

在大纲面板中选中LSceneSettings-1,勾选观察器面板中的使用AI智能体(Use AI Agent)。开启该功能后,关卡中所有的AI角色都会被AI智能体托管。

初始的AI智能体类(AI Agent Class)是引擎提供的默认的AI智能体类,若希望能自定义实现部分或全部的AI行为(AI Behavior),可以重载引擎中提供的默认类LAIAgentObject,并将重载的类名填写在AI行为这一栏中。

注意:设置成功后,关卡中所有的AI角色属性和行为都将回调到新的派生类中。非LAIAgentObject派生的类对象,都将会设置无效。

接口文件名:

i_ai_agent_interface.h

默认回调AI行为(部分):

public: /* 避障 */

/* 是否开启群体避障 */
virtual bool IsEnabledDetourCrowd() const { return false; }

/* 获取当前群组标记 */
virtual int GetAvoidanceGroup() const { return -1; }

public: /* 事件 */
virtual void OnAddedToAIWorld(class IAIWorld* pAIWorld) {}
virtual void OnRemovedFromAIWorld(class IAIWorld* pAIWorld) {}

/* 路径跟踪事件 */
virtual void OnPathFollow() {};
virtual void OnAbortPathFollow() {};
virtual void OnPathFollowFinished(bool bSuccess) {};

public: /* 行为 */

virtual bool OnPlayAnimation(const char* sAniName) { return false; }
virtual bool OnAbortPlayAnimation(const char* sAniName) { return false; }

virtual bool OnPlaySound(const char* sSoundName, bool bNonBlock) { return false; }
virtual bool OnAbortPlaySound(const char* sSoundName) { return false; }

virtual bool OnMoveTo(XMFLOAT3 vGoalPosition, float fGoalRadius,
float fTimeout = 0.0f) { return false; }
virtual bool OnAbortMoveTo() { return false; }

virtual bool OnCustomTask(const char* sFileName, const char* sTaskName,
const char* sTaskData) { return false; };
virtual bool OnAbortCustomTask(const char* sFileName, const char* sTaskName,
const char* sTaskData) { return false; };

默认回调属性(部分):

    virtual void SetScale(float x, float y, float z) {}
virtual void SetAngle(float x, float y, float z) {}
virtual void SetPosition(float x, float y, float z) {};

virtual bool GetScale(float& x, float& y, float& z) const { return false; }
virtual bool GetAngle(float& x, float& y, float& z) const { return false; }
virtual bool GetPosition(float& x, float& y, float& z) const { return false; };
virtual XMFLOAT3 GetPosition() const { return XMFLOAT3(FLT_MAX, FLT_MAX, FLT_MAX); };
virtual XMFLOAT3 GetVelocity() const { return XMFLOAT3(0.0f, 0.0f, 0.0f); };

virtual void SetPositionX(float fValue) {}
virtual void SetPositionY(float fValue) {}
virtual void SetPositionZ(float fValue) {}

virtual bool GetPositionX(float& fOutValue) const { return false; }
virtual bool GetPositionY(float& fOutValue) const { return false; }
virtual bool GetPositionZ(float& fOutValue) const { return false; }

virtual void SetAngleX(float fValue) {}
virtual void SetAngleY(float fValue) {}
virtual void SetAngleZ(float fValue) {}

virtual float GetAngleX() const { return 0.0f; }
virtual float GetAngleY() const { return 0.0f; }
virtual float GetAngleZ() const { return 0.0f; }
    virtual float GetYaw() const { return 0.0f; }
virtual float GetRoll() const { return 0.0f; }
virtual float GetPitch() const { return 0.0f; }

virtual bool GetEyeHeight(float& fOutValue) const { return false; }
virtual bool GetAgentHeight(float& fOutValue) const { return false; }
virtual bool GetAgentRadius(float& fOutValue) const { return false; }

virtual void SetEnableFocus(bool bValue) {}
virtual bool GetEnableFocus() const { return true; }

virtual bool GetAgentBoundsExtent(float& ExtentX, float& ExtentY,
float& ExtentZ) const { return false; }

virtual float GetMaxSpeed() const { return 0.0f; }

virtual bool IsCanFly() { return false; }
virtual bool IsCanWalk() { return false; }
virtual bool IsCanJump() { return false; }
virtual bool IsCanSwim() { return false; }
virtual bool IsCanCrouch() { return false; }

virtual bool IsFlying() { return false; }
virtual bool IsFalling() { return false; }
virtual bool IsWalking() { return false; }
virtual bool IsJumping() { return false; }
virtual bool IsSwimming() { return false; }