Custom fields (Context, method, condition) 19
The version which is recommended for this editor is 2021.x.x and up.
Importing the package in your project can be done via the package manager when the package is coupled to your Unity account.
The editor also supports the addition of the Unity Atoms package which can be used to fire events within the editor. The required packages for this to work are the base atoms and the core.
https://unity-atoms.github.io/unity-atoms/
When the package has been imported in Unity, an extra tab will show up in the toolbar as “Tools > AI Editor”. This will open the graph. When opening the graph for the first time, the editor will be empty.
A graph can be created inside your project when right clicking in the project. Under
“Create > AI > Main Graph” a main graph can be created. This object will hold all the behaviour data for the AI. Selecting this graph object, will open the behaviour in the editor. When the editor has not been opened, double clicking will open the editor with the selected behaviour.
The first thing you will see in the graph is the start node. To add new nodes, you can right click within the graph. The main component of the graph is the task node with conditions and actions.
In this task you can add user created conditions/actions in the inspector of the node.
To create a custom condition/action a partial class has to be created which inherits from a Condition or AgentAction class. Within this class you can override the function Execute()
This function is called every agent tick where the logic of the agent is located. This function also has the responsibility to set the Status property to determine the state of the condition/action.
There is also a template available to start immediately. “Create > AI > Template”
Exposing variables to the editor is possible with the [ShowInGraph] attribute.
The agent is the entity that uses the behaviour graph.
The system is built on graphs which are a collection of tasks. These tasks together create the required behaviour.
Tasks make up the main elements of the behaviour of the Agent. A task can be used in two ways. Or the task is set up to handle conditions or it can handle actions.
The conditions are able to be executed and enable the user to store objects which are found or are a result of the condition check.
The actions are elements which are executed after the conditions together are valid.
These conditions and actions are the custom scripts which the user can write. These conditions and actions can be used only on task nodes. But these components are the foundation on which the system works.
The next important node is the subgraph node in which nested behaviour can be created to make complex systems. These nodes have a subgraph assigned to them, which is user created. There are also other nodes which have special functions which will be explained in the paragraph “Nodes”.
The toolbar consists of buttons to control the basic functions of the editor. The functions include Saving, reloading, importing, focus on nodes, duplicate, node snapping.
The hierarchy at the top of the editor shows the current path of the graph. The path is relative to the main graph which was selected. Clicking on any of the paths will open the selected graph. The rightmost graph is the current graph. The leftmost graph is the relative root of the current graph.
On the right side of the editor the inspector is shown, which functions as a custom inspector for the nodes within the graph. The inspector can be hidden for extra space.
The left tab displays the graph and global variables. These variables can be set in runtime, or have a default starting value. These variables can be used within the tasks to reference runtime objects. The default values can not reference a scene object.
To add variables this button can be clicked, then selecting the desired type.
The graph variables can also be cleared when they are not in use. The variable is in use when the graph has been saved.
This does not work for the global variables.
The main field in the middle displays the nodes.
Controls:
Double click on nodes: default action
Right click on nodes: actions
Left click on nodes: select
Left click on nodes and drag: move selected nodes
Shift + Left click on nodes: additional select
Left click and drag on grid: box select
Middle mouse and drag on grid: move grid and nodes
Scrollwheel: zoom
Ctrl+Q: Action node
Ctrl+W: Condition node
Moving fields
Right-clicking (space) opens node menu
The debugger is able to display custom errors which can be sent from within the implemented behaviours. The debugger itself is simple with a couple of options.
Errors have different warning levels, the editor has filters to only view certain errors. These can be toggled to view the selected type of error. The editor can also pause when the debugger receives an error. This can be set by toggling on the error pause button.
The clear button removes all the current messages.
There are a couple of nodes which are the building blocks of this AI system.
Input nodes such as the start, event and need nodes provide a way to enter the behaviour cycle. Output nodes which can be an exit or subgraph node. An exit node stops the current active behaviour. A subgraph enters a new behaviour cycle creating an nested behaviour.
Then there are two nodes which execute code, condition and action nodes. These nodes are what make up the bulk of the behaviour of the system.
Each node has a connection point which can have different colours to display their function. The different types includes:
Input : white
Success : green
Failure : red
Finally : blue
Combined : yellow : Not interactable
When clicking on these points a line will appear which can connect other nodes.
Right clicking on the point when a connection is present will clear any connections.
The conditions and actions on the node can be inspected by clicking on the node.
The shown fields are generated based on the tagged fields within the custom behaviour scripts.
The context system is used to reference runtime objects in the editor. The user is responsible for setting these context variables. There are a couple different ways to get context objects.
The first is from conditions, which can emit an object when it has executed its logic. The current and next node can access these variables in a ContextInput field. This field allows the user to assign a context object to the condition or action.
The second way is to add a variable which is accessible from the entire graph. This is the graph variable. These variables are unique for each agent with the same behaviour.
Subgraphs can have separate variables from the main graph.
The last type of context is global which is the same in any graph or agent.
The custom field as mentioned earlier will be discussed later on in the “Scripting Reference” paragraph.
Graph variable fields can be assigned. A default value can be assigned in the editor, these values are the starting value of the graph.
The following types are supported: UnityEninge.Object, Int, Float, String, Vector2, Vector3 and finally a List.
When deleting variables and the variable is in use, a warning is displayed. When deleting the variable, the system will clean all the references within the graph, causing unreferenced
ContextInputs when they referenced that variable.
Clearing the variables will delete any variables that have zero references.
The global variables when loading a graph are always the same in every graph. This means any changes cause things to change in every graph. Deleting global variables will cause the graphs to be updated and delete references.
The start node can not be created/deleted and is standard in any graph as this node is the entry point for any behaviour.
A task node contains the custom conditions and actions, which will be executed at runtime. By default when a condition fails and it has a fail task assigned it will try again after the execution. In the condition node this type of execution can be changed. The types of check are: Retry, Wait to fail/succeed, or, nor, and, nand, xor and xnor.
The node displays the conditions and actions which are assigned. It also has success/failure/finally points which are executed depending on the exit state of the node. The finally is always called no matter the exit state after the success or failure state.
The collapsed node still displays the status of the conditions and nodes for the debug mode.
In the inspector the user will see the actions and conditions depending on the type of the task node. Clicking on the plus symbol will add an extra condition/action.
Clicking on the min symbol will remove the condition/action from the list.
Opens the script on the behaviour.
Sub graph nodes allow for nested behaviours within the behaviour. These nodes contain a reference to a subgraph. These subgraphs can be created by the user and filled with custom behaviour. When the node is encountered at runtime it will execute the behaviour within from the start node.
Creating sub graphs should be done inside the resources folder in order to get the editor to use them in the spawning menu.
The exit node allows the user to go back to the root of the behaviour. By default the exit quits to the root of the current graph, so when in a subgraph it will go back to the entry point.
But when selecting the option in the inspector, the node will quit to the root entry point of the behaviour.
The need node is meant to represent the need of the agent, such as hunger or thirst. The node sends out an task assigned in the graph, only when a value has reached below the threshold.
The event node is a way to add tasks via external triggers. When assigning an Unity Atom event to the inspector and triggering that event will add the connected task to the agent.
The agent component can be assigned to any GameObject.
Behaviour
This agent script handles all the logic of executing the assigned behaviour. To give an agent its behaviour, the user can add a graph to the references tab of the agent.
Runtime
The agent has some runtime fields which display the current task list which the agent is going through. It also contains the needs which the agent is using. These values are all copied from the original graph data.
Execution speed
The speeds at which the agent handles the conditions and actions can be set within certain limits. It can also be stepped manually for debugging.
Ability
Abilities can be assigned to the agent to add custom functionality such as an inventory. These abilities can be accessed from within the agent inspector.
The ContextSetter component can be used to automatically set the variables for the behaviours.
The context setter is able to set the global or graph context. Setting this context is done at runtime. Which can be done at OnEnable when set.
When setting the context of a graph an agent can be specified or it will try to set the context for every agent in the scene.
The component will warn the user when the global context can not be found.
The component cannot warn the user when the graph context is set.
Note: The name of the context is case sensitive.
Under “Create > AI > Templates” the two templates can be found for a condition or action.
These templates provide a quick start to creating a behaviour.
The graph supports event handling with the use of Unity Atoms. When the package has been installed. From the atoms package only the Core and Base atoms are required for the system to function.
When selecting an agent while in play mode, the open editor will show the internal state of the agent. The graph will display the name of the agent and a white overlay. The agent has a task list which is shown on the tasks. The status which has been set in the custom conditions and actions is shown in the task with the following colour coding:
Green : Success
Red : Failure
Dark Blue : Pending
Orange : Running
Lightblue : Waiting
Grey : Not in use
The indicators are displayed on the task itself too. When a task is being used it will also display the orange bug indicator.
When initialising the agent, the agent will copy the assigned graph. The agent will ask the graph for tasks when needed.
When the agent does not have any tasks in its list, it will try to add the start task again.
So the behaviour will infinitely repeat.
When the behaviour encounters a subgraph it will open the graph and follow the current task.
The conditions/actions have an Execute() function which can be overwritten to implement your behaviour. The function relies on the user to set the Status property. This status will show up when debugging this behaviour.
To end a condition or action the status can be set to Failure or Success depending on the user’s behaviour.
To show any public variable in the graph inspector of the behaviour the attribute [ShowInGraph]
This will be able to display any of the default inspector elements. Plus some custom fields seen in the paragraf “Custom Fields”.
Getting an object from the context requires the use of the ContextInput class. This will give the option to select a context object which will be resolved at runtime.
To get an object from this input the following function can be used:
ResolveContext(ContextInput, out ContextInstance)
or
ResolveContext<T>(ContextInput, out T)
This will return an object of the given type.
The ContextInstance is the data container where the object will be stored in. The object can be collected by using <Instance>.obj.
Conditions are able to set the context values. This is mostly used to find objects and then storing that object. To set an object the function:
SetThisContext(object)
Can be used to update the context for the given object’s index.
There are more attributes applicable to the conditions and actions. These will be discussed in the Tags paragraf.
The executing objects used in the editor are cached, when using these objects the values from the cache need to be copied over. This is automatically done for any variables with the tag [ShowInGraph]. But to load values outside of the
The function:
LoadCache(ICachable)
can be used to load data. This function is called when the task is added to the task list on the agent.
When returning the object to the cache, the function
ReturnCache()
Is called to reset values which were previously set.
The conditions and actions in the editor can be validated to set custom errors when values are missing. Adding the IValidator interface is required to validate the values in the executing object.
The Validate() function can be used to send errors and a custom message which is displayed as a tooltip. The function returns a tuple with the error type and the custom message string.
return default; can be used to send nothing.
When adding a condition or action the script will be visible in the task inspector. But these will be unstructured. To group these scripts together the attribute [GraphPath(string)] is used to change under which path the script is grouped under.
To let the editor know what kind of object is sent to the context values. Otherwise the type will be unknown in the graph. This is nothing more than a visual representation and helps in the editor. It does not actually restrict the object which can be stored.
[ContextOutput(Type)]
To create an ability the class AgentAbility has to be inherited.
This ability is a scriptableobject on which the system is built.
Variables in the ability do not have to be tagged with [ShowInGraph] to show up in the agent inspector. It uses normal Unity serialisation.
On the agent the following functions exist to handle the abilities.
Check if an ability of a type exists, returns true if the ability is present.
HasAbility<T>()
Get the ability with the given type, outputs the found instance. Returns true if type is found.
GetAbility<T>(out T)
Add an ability to the list of abilities
AddAbility<T>(T)
Remove an ability to the list of abilities
RemoveAbility<T>(T)
A need has two functions which can be overridden. The function of the need is to add tasks based on a counter. This counter can be manipulated by default in the need node.
Each agent tick the update function is called. With the agent as context and an increment on the timer which is by default 0.
UpdateNeed(Agent, float)
The following function is used by default to add the task to the agent.
AddNeed(Agent)
Note: The [ShowInGraph] tag on variables in an AgentNeed class does not mean the values are initialised automatically.
The event node uses the Unity Atom package to receive events. These events can be selected in the inspector of the node. The event is automatically subscribed to, and the return object is stored in the context value when called.
When using the ContextInput in a condition/action or ContextInstance in a function.
In the editor the field will be visible in the inspector, when clicking on this field the available options will be shown.
Examples
Variable:
[ShowInGraph] public ContextInput input;
Function:
[ShowInGraph]
public void ExampleFunction(ContextInstance instance)
When no context has been selected “<Context>” will be shown.
When using MethodHolder objects the editor will automatically display this as a selector for tagged functions seen in the previous paragraph.
To execute the method which has been selected the following function can be used:
ExecuteFunction(AgentExecuter, GameObject)
To Execute this function the object itself is needed and the object on which the selected component can be found.
When using ConditionHolder objects the editor will automatically display this as a field for reflection or variables.
The following two functions are available:
When using the context field
CheckContext(AgentExecuter)
When using the reflection to check a field
CheckReflection(Object)
The types which are the system is able to check are:
float, int and system.object.
But only int and float support more checks other than equal and not equal.
To show custom warnings in the debugger of the editor there are some functions to handle the sending of messages. Sending an agent will display the origin of the message by displaying the task. Sending this agent is optional.
Meta.Debug.Log(object, Agent)
Meta.Debug.LogWarning(object, Agent)
Meta.Debug.LogError(object, Agent)
There are some static functions and extensions to support setting values for the graph. These functions are available in the ContextHelper class.
There are a couple of extensions to get and set the context value for a particular agent.
There are two functions to get a context value via the out parameter. The functions also return true when a value has been found.
GetGraphContext<T>(this Agent, Task, string, out T)
GetGraphContext(this Agent, string, out object)
A context value can be set with the following function
SetGraphContext(this Agent, string, object)
This function will be able to add the given object to a list value in the context variables.
SetObjectInContextList(this Agent, Object, string)
The context can also be set for all the active agents in the scene, this will update the given object for each agent. This only works for an agent if the agent has the given context name in this graph or subgraphs.
SetAllAgentsContext(this object, string)
The global context can also be set with the help of some static functions.
To get a global context value this function can be used, also returning true when the variable was found.
GetGlobalContext(string, out object)
Setting a global variable can be done with this function
SetGlobalContext(this object, string)
Setting a value inside a list global variable can be done with this function, these lists only allow UnityEngine.Object to be passed.
SetObjectInGlobalContextList(Object, string)
When creating conditions or actions, the variables need to be initialised to save changes. This is done by auto generating the necessary code. This file will be generated in the same folder as the condition/action in a new folder called “Generated”.
The user should not interact with this script. But when moving/deleting the accompanying file, it moves or is deleted along with the other file.
NOTE: The conditions/actions should not be moved/deleted outside of the unity project. When doing this, a generated file could be placed in the wrong folder or linger in the project which will throw errors.
Company: Trive Technology
Tutorial:
Support email: support@trivetechnology.com
Website: www.Trivetechnology.com