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)需预设好骨骼和动画资产。
在观察器(Inspector)面板中选中CollisionCylinder(Inherited)和CharacterMesh0(Inherited)后,分别调整胶囊体和网格体的位置,使它们均位于地形上方。
创建黑板和行为树
下面以驱动AI角色移动到指定位置为例,创建一个行为树资源(ai_walkman.bt)和一个黑板资源(ai_walkman.bb)。
创建驱动角色行为树文件。可参考行为树编辑器用户手册 -> 创建行为树和创建黑板,分别创建行为树和黑板资源。
在资源预览窗口中双击黑板资源ai_walkman.bb,打开行为树编辑器并进入到黑板编辑模式。
在行为树编辑器(Behavior Tree Editor)中点击新键(New Key),添加一个名为Init、类型为布尔值(Bool)的黑板键和一个名为WayPoint、类型为向量(Vector)的黑板键,完成添加后点击保存(Save)。
添加黑板键的具体步骤可参考编辑黑板。
在资源预览窗口中双击行为树资源ai_walkman.bt,打开行为树编辑器并进入行为树编辑模式。
为该行为树指定一个关联的黑板资源,在行为树图中点击Entry节点,然后在详细面板(Details Panel)中设置刚才所创建的黑板资源为该行为树所关联的黑板。
关联完成:
在行为树图中点击Entry节点引脚并拖拽到空白处,在弹出的菜单中选择选择器(Selector)节点。
由选择器(Selector)节点引脚处拖拽出引线,依次创建并连接两个序列(Sequence)节点。
在左边的序列(Sequence)节点上右击,依次点击添加新装饰器节点(Add New Decorator Node) -> 黑板(Blackboard),为该序列节点链接一个黑板装饰节点。
选中该黑板装饰节点,在详细面板(Details Panel)面板中设置黑板键(Blackboard Key)为Init,黑板键操作(Key Operation)为尚未设置(Is Not Set)。
为另一个序列(Sequence)节点也链接一个黑板装饰节点,并将该节点的黑板键(Blackboard Key)设置为Init,黑板键操作(Key Operation)设置为已经设置(Is Set)。
为左边的序列节点连接一个等待(Wait)节点,并在详细面板(Details Panel)中将等待时间(Wait Time)改为1.0。
设置了AI角色所要移动到的位置点。继续为左边的序列节点连接一个设置向量值(Set Vector Value)节点,在详细面板(Details Panel)中设置值X(Value X)、值Y(Value Y)和值Z(Value Z)(此处数值可根据当前所处关卡进行自定义设置),设置该节点名称(Name)为Set WayPoint,将黑板键(Blackboard Key)改为WayPoint。
为左边的序列节点再连接一个设置布尔值(Set Bool Value)节点,修改该节点的名称(Name)为Set BB Key Initialize Value,并勾选当前值(Current Value),并将黑板键(Blackboard Key)修改为Init。
为右边的序列节点连接一个移动至(Move To)节点,并在详细面板(Details Panel)中将节点名称(Name)修改为Move to WayPoint,将黑板键(Blackboard Key)修改为WayPoint。
最后为右边的序列节点连接一个等待(Wait)节点,将等待(Wait Time)设置为300.0。
编辑完成后,点击保存(Save)。
可通过行为树编辑器用户手册详细了解行为树的相关内容。
设置AI角色
在大纲(Hierarchy)面板中选中LCharacter,然后在观察器(Inspector)面板中选中LCharacter(Instance),勾选自动控制AI(Auto Control AI)属性,然后点击行为树(Behavior Tree)旁的 按钮,在弹出的窗口中选择一个行为树文件,此处选择了刚才所创建的行为树文件。
配置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)。)
注意:确保资源目录为编辑器可定位到的目录。
脚本文件:
至此,就已经完成配置并驱动一个非游戏玩家的AI角色了。
运行
为了方便运行之后查看结果,可再创建一个默认Pawn(Default Pawn)对象。依次点击创建游戏对象(Create Game Object) -> 默认Pawn(Default Pawn)。
创建完成后,调整默认Pawn(Default Pawn)的位置和角度,并将自动持有玩家(Auto Possess Player)改为玩家0(Player 0)。
点击运行按钮可查看此例(AI角色移动至指定位置)的最终结果。
自定义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; }