AI Characters and AI Agents
Overview
This chapter mainly introduces how to create non-player AI Characters and AI Agents in the project.
Configuring and Driving Default AI Characters
Creating Characters
Create a new Level in the Component Editor and click Create Game Object -> Character to create a default character object.
Configure the default target character. Select the character object just created in the Hierarchy panel, then select CharacterMesh0(Inherited) in the Inspector panel. Click the button next to the Skeletal Mesh Config File property below. Select an .actor file in the pop-up File Browser window and click OK.
Note: Skeleton and animation assets of the selected .actor file need to be preset.
Adjust the position of the Capsule and the Mesh respectively to make them both slightly above the terrain after selecting the CollisionCylinder(Inherited) and CharacterMesh0(Inherited) in the Inspector panel.
Creating Blackboards and Behavior Trees
Take driving the AI character to move to the specified position as an example. Create a behavior tree resource (ai_walkman.bt) and a blackboard resource (ai_walkman.bb).
Create driving character behavior tree files. Please refer to Creating Behavior Trees and Creating Blackboards sections in the Behavior Tree Editor User Manual to create a behavior tree asset and a blackboard asset respectively.
Double-click the blackboard resource ai_walkman.bb in the Resource Preview window to open the Behavior Tree Editor and enter the blackboard editing mode.
Click New Key in the Behavior Tree Editor to add two blackboard keys respectively, with one named Init of Bool type and the other named WayPoint of Vector type. Click Save after adding.
Please refer to Editing Blackboards for details on adding blackboard keys.
Double-click the behavior tree resource ai_walkman.bt in the Resource Preview window to open the Behavior Tree Editor for editing mode.
Specify an associated blackboard resource for the behavior tree. Click the Entry node in the behavior tree diagram, and then set the blackboard resource just created as the blackboard associated with the behavior tree in the Details Panel.
Association completed:
Click the Entry node pin in the behavior tree diagram and drag it off to the blank space. Select the Selector node in the pop-up menu.
Drag off the Selector node pin to create and connect two Sequence nodes in turn.
Right-click on the left Sequence node and click Add New Decorator Node -> Blackboard to link a Blackboard Decorator node for it.
Select the Blackboard Decorator node and set Blackboard Key as Init and Key Operation as Is Not Set in the Details Panel respectively.
Link a Blackboard Decorator node for the other Sequence node and set Blackboard Key as Init and Key Operation as Is Set respectively.
Connect a Wait node to the Sequence node on the left, and set Wait Time to 1.0 in the Details Panel.
Set the position where the AI character will move to. Continue to connect a Set Vector Value node to the Sequence node on the left, and modify Value X and Value Y and Value Z in the Details Panel (The values here can be customized according to the current level). Set its Name to Set WayPoint and Blackboard Key to WayPoint.
Connect a Set Bool Value node to the Sequence node on the left. Set its Name to Set BB Key Initialize Value, check Current Value, and modify the Blackboard Key to Init.
Connect a Move To node to the Sequence node on the right. Set its Name to Move to WayPoint and Blackboard Key to WayPoint in the Details Panel.
Finally, connect a Wait node to the Sequence node on the right, and set Wait Time to 300.0.
Click Save after all the editing is done.
To learn more about behavior trees, please refer to Behavior Tree Editor User Manual.
Setting AI Characters
Select LCharacter in the Hierarchy panel and LCharacter(Instance) in the Inspector panel respectively. Check the Auto Control AI property, and then click the button next to the Behavior Tree property. Select a behavior tree file in the pop-up window, where the behavior tree file just created is selected here.
Configure AI command processing scripts. Select LSceneSettings-1 in the Hierarchy panel, then click the button next to the AI Script property in the Inspector panel. Select the script file ai_command_processor to be configured in the pop-up window. (The script files used are under editor_script/pl_ai of the Engine resources. The ai_command_processor.lua and public_attr.lua can be copied to the Script folder under the Project folder first, and then add AI Script.)
Note: Make sure that the resource directory is the one that the Editor can locate.
Script files:
At this point, configuring and driving a non-player AI character of is completed.
Playing
To make it easier to view the results after playing, click Create Game Object -> Default Pawn to create another Default Pawn object.
After the creation is complete, adjust the Position and Angle of Default Pawn, and set Auto Possess Player to Player 0.
Click the Play In Editor button to view the final result of this demo (The AI character moves to the specified position).
Customizing AI Behaviors
Two callback methods are provided by the Engine currently to implement custom AI Behavior, i.e. C++ and Lua.
C++ Code Examples
Header file: i_ai_command_processor.h
Function declaration:
// Callback function prototypes
// This will be called when adding the AI object.
typedef void (*on_add_ai_object)(IAICommandProcessor* from, PERSISTID object,
const char* bt_file);
// This will be called when removing the AI object.
typedef void(*on_remove_ai_object)(IAICommandProcessor* from, PERSISTID object,
const char* bt_file);
// This will be called when updating the service node.
typedef void (*on_service)(IAICommandProcessor* from, PERSISTID object,
const char* bt_file, const char* bb_key);
// This will be called when updating the move to node.
typedef void (*on_move_to)(IAICommandProcessor* from, PERSISTID object,
bool is_move_to_object, PERSISTID goal_object,
const XMFLOAT3& goal_position, float accept_radius);
// This will be called when updating the play animation node.
typedef void (*on_play_animation)(IAICommandProcessor* from, PERSISTID object,
const char* animation_name);
// This will be called when aborting the play animation node.
typedef void(*on_abort_play_animation)(IAICommandProcessor* from, PERSISTID object,
const char* animation_name);
// This will be called when updating the play sound node.
typedef void(*on_play_sound)(IAICommandProcessor* from, PERSISTID object,
const char* sound_name, bool is_non_block);
// This will be called when updating the custom task node.
typedef void (*on_custom_task)(IAICommandProcessor* from, PERSISTID object,
const char* bt_file, const char* custom_task_name, const char* custom_task_data);
// This will be called when aborting the move to node.
typedef void (*on_abort_move_to)(IAICommandProcessor* from, PERSISTID object);
// This will be called when aborting the custom task node.
typedef void(*on_abort_custom_task)(IAICommandProcessor* from, PERSISTID object,
const char* bt_file, const char* custom_task_name, const char* custom_task_data);
// This will be called when updating the AI object direction.
typedef void(*on_update_direction)(IAICommandProcessor* from, PERSISTID object,
float x, float y, float z);
Callback registration method:
// Register callback
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;
// Register abort task callback
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;
Parameter types:
IAICommandProcessor* from
: AIcommandProcessor object
PERSISTID object
: The ID of the current AIObject
const char* bt_file
: The currently bound behavior tree asset file
const char* bb_key
: The blackboard key set by the current ServiceNode
Callback function registration query:
AICommandProcessor::IsRegisterCallback(EAICommandCallback:: OnService)
Lua Code Examples
The default callback file is the script file configured in the AI Script option of SceneSettings in the Component Editor.
For example: ai_command_processor.lua provided by the Engine has already provided a default implementation.
--AI
function on_init(self)
nx_log("ai init")
--Enable debug information sending
local ai_world = nx_value("ai_world")
ai_world.EnableDebug = true
--Initialization: Add AI object callback to perform blackboard initialization
nx_callback(self, "on_add_ai_object", "ai_add_ai_object")
--Task: AI move to
nx_callback(self, "on_move_to", "ai_move_to")
--Abort task: Abort task move to
nx_callback(self, "on_abort_move_to", "ai_abort_move_to")
--Task: Play animation
nx_callback(self, "on_play_animation", "ai_play_animation")
--Task: Custom task (For customization when common tasks implemented by the Engine are not yet included)
nx_callback(self, "on_custom_task", "ai_custom_task")
--Service: Update the blackboard regularly
nx_callback(self, "on_service", "ai_service")
--Event: Update AI object direction
nx_callback(self, "on_update_object_direction", "update_object_direction")
return 1
end
Register callback function sample codes:
--AI
function on_init(self)
--Service: update blackboard
nx_callback(self, "on_service", "ai_service")
return 1
end
Function sample codes:
function ai_service(self, target, behavior_tree, blackboard_key)
// File judgment
if "" == behavior_tree then
// TODO Something
end
// Blackboard key judgment
if "" == blackboard_key then
// TODO Something
end
end
Creating AI Agents
AI Agent provides a development method that allows developers to rebuild all AI behaviors.
It contains the callback function of AI properties and AI events, which can be used to create new AI behaviors by overloading and modifying the corresponding implementation.
Activating AI Agents
Select LSceneSettings-1 in the Hierarchy panel, and check Use AI Agent in the Inspector panel. After enabling this feature, all AI characters in the Level will be hosted by AI agents.
The initial AI Agent Class is the default one provided by the Engine. If you want to customize part or all of the AI Behavior, you can overload the default class LAIAgentObject provided in the Engine, and fill in the overloaded class name in the column of AI Behavior.
Note: All AI character properties and behaviors in the Level will be called back to the newly derived class after successful setup. Any class object that is not derived from LAIAgentObject will be invalid.
Interface file name:
i_ai_agent_interface.h
Default callback AI behavior (Partial):
public: /* Avoidance */
/* Is enabled crowd avoidance */
virtual bool IsEnabledDetourCrowd() const { return false; }
/* Get current group mark */
virtual int GetAvoidanceGroup() const { return -1; }
public: /* Event */
virtual void OnAddedToAIWorld(class IAIWorld* pAIWorld) {}
virtual void OnRemovedFromAIWorld(class IAIWorld* pAIWorld) {}
/* Path follow event */
virtual void OnPathFollow() {};
virtual void OnAbortPathFollow() {};
virtual void OnPathFollowFinished(bool bSuccess) {};
public: /* Behavior */
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; };
Default callback properties (Partial):
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; }