2. Idle
Goal
In this tutorial we will create a simple GOAP system that will make an agent wander around when idle. The agent can also pick up apples and eat them. The agent will only eat apples when it's hungry.
Setup in Unity
The package comes with a
Generator Scriptable
that can help you quickly boilerplate all the classes that are used by the GOAP system. Let's get started by creating a new location for our scripts to go. Create a new folder calledGetting Started
in yourAssets
folder.Right-click the
Getting Started
folder and selectCreate > GOAP > Generator
. Call the scriptableGettingStartedGenerator
.When you select the
GettingStartedGenerator
you can see all it's properties in the inspector. The generator requires you to set a base namespace in the inspector. To make following the getting started easier, please set the namespace toCrashKonijn.Docs.GettingStarted
.If you like using assembly definitions you can add it to the
Getting Started
folder. Please make sure to also set theRoot Namespace
toCrashKonijn.Docs.GettingStarted
. Also make sure to include thecom.crashkonijn.goap.core
,com.crashkonijn.goap.runtime
,com.crashkonijn.agent.core
andcom.crashkonijn.agent.runtime
assemblies.
Generating classes
Let's generate the required
Goals
,Actions
,WorldKeys
andTargetKeys
using the generator. In the inspector of theGettingStartedGenerator
please fill in the following classes in their respective fields:Goals:
IdleGoal
Actions:
IdleAction
WorldKeys:
IsIdle
TargetKeys:
IdleTarget
Hit the
Generate
button! The generator will now create all the classes for you. Unity doesn't always register the new files, you can fix this by going to another program and then going back to Unity. All the classes should now be visible in theGetting Started
folder, in their respective subfolders.Later on we also need sensor classes, but these can't be generated by the generator.
Sensors
Each WorldKey
or TargetKey
that is used in general also needs a value assigned to it. To get this value we use Sensors
. Sensors are classes that can read the current state of the world and provide this information to the WorldState
when it's needed.
In this part of the demo we use two keys, the IsIdle (WorldKey)
and the IdleTarget (TargetKey)
. The IsIdle
key in this example is mostly used to match the IdleGoal
and IdleAction
together, it doesn't actually require to actually update the value. 'Manually' coupling a goal and action together is generally bad practice, but for this demo it's fine.
The IdleTarget
key does need a value, so we need to create a sensor for it. To create a sensor we need to use the correct base class. The correct base class is determined by the Type
of key (Eg WorldKey
or TargetKey
) and the Scope
of the sensor (Eg Global
or Local
). Global sensors are used to provide information for all agents (eg PlayerPosition
), while local sensors are used to provide information for a single agent (eg ClosestTree
).
In this case the IdleTarget
is a TargetKey
and it is for a single Agent
, so we require the LocalTargetSensorBase
.
Let's create a new folder in the
Getting Started
folder calledSensors
.In the
Sensors
folder create a new script calledIdleTargetSensor
that extendsLocalTargetSensorBase
.
ScriptableObjects or Code
The GOAP system can be setup in two ways. You can either use Code
or ScriptableObjects
. The Code
way is more flexible and allows you to create your own setup systems and use generics. The ScriptableObjects
way is more visual and allows you to set up the system in the Unity Editor. Please pick the one that fits your project best.
Creating the scene
In the
Getting Started
folder create a new scene calledGettingStarted
. Open this scene.
Adding the GOAP system
Create a new GameObject and name it
GOAP
.Add the
GoapBehaviour
to theGOAP
GameObject.Each
GoapBehaviour
needs aController
. The controllers determine when and how the resolver is run. For the demo we will use theReactiveController
. Add theReactiveControllerBehaviour
to theGOAP
GameObject.
Capabilities
The GOAP system is build around the concept of Capabilities
. These capabilities are used to determine what an AgentType
can do. Each AgentType
can have multiple capabilities. Capabilities are re-usable subset of Goals
, Actions
and Sensors
that are merged together into an AgentType
. For this demo we will start with a single capability called IdleCapability
.
Let's create a new folder in the
Getting Started
folder calledCapabilities
.
In our newly created folder lets create a script called
IdleCapabilityFactory
. This script will include aCapabilityBuilder
that will help us create ourCapability
.
Agent Type
Each agent belongs to an AgentType
. The AgentType
holds all available goals, actions and sensors for the agent to use and are shared between all agent of that same AgentType
.
Let's create a new folder in the
Getting Started
folder calledAgentTypes
.
Because ALL goals, actions and sensors are shared between all agents of the same AgentType
it is important to make sure that all these classes are Stateless. This means that you can not store any information in these classes that is specific to a single agent. The system provides various ways to store or access agent specific information.
In our newly created folder lets create a script called
DemoAgentTypeFactory
. This script will include anAgentTypeBuilder
that will help us create ourAgentType
.
In the open scene, add a child GameObject to the GOAP called
ScriptDemoAgent
Add the newly created
DemoAgentTypeFactory
script to theScriptDemoAgent
GameObject.On the
GOAP
GameObject, add theScriptDemoAgent
GameObject to theAgent Type Config Factories
list.With the
ScriptDemoAgent
GameObject selected, you can now open up theGraph Viewer
to view the generated graph for thisAgentType
. You can open theGraph Viewer
by going toTools > GOAP > Graph Viewer
, or by pressing the shortcutCtrl + G
orCmd + G
(on Mac)
Creating the agent
Let's create a sphere in the scene and call it
Agent
. (GameObject > 3D Object > Sphere) This will be our agent that will wander around.For this demo we won't use any physics. You can remove the
Sphere Collider
from theAgent
.Each agent always needs an
AgentBehaviour
component. Add theAgentBehaviour
component to theAgent
.Each agent also needs an
ActionProvider
, let's add theGoapActionProvider
to theAgent
.On the
AgentBehaviour
, set theAction Provider Base
value to that of theGoapActionProvider
on the same GameObject.
No further steps required for code.
Moving the agent
In order to move the agent you can use the events
on the AgentBehaviour
. These events are called when the agent is in range of a target, when the target changes and when the target is no in range. Based on these events you can determine when and where to move the agent.
Let's create a new folder in the
Getting Started
folder calledBehaviours
.In the
Behaviours
folder create a new script calledAgentMoveBehaviour
.
Add the
AgentMoveBehaviour
to theAgent
GameObject.
Deciding what goal to perform
Deciding what goal to perform is very game specific and can be done in many different ways. For this demo we will use a simple 'FSM' script that I like to call a Brain
. The Brain
will decide what goal to perform based on the current state of the agent.
Let's create a script called
AgentBrain
that extendsMonoBehaviour
.
Add it to the
Agent
GameObject.
Play the scene!
When you play the scene, your freshly created agent should start moving around!
You can open up the Graph Viewer
and select the agent in the scene to see what it's doing!
Updating the IdleAction
Currently, our idle action works because it's target is a random position. The agent will move in range of that target, then start performing the action. By default, the action script immediately completes the action and the resolver will kick off again. This will result in the agent moving to a new random position.
Let's update the IdleAction
to actually wait for a few seconds before completing the action.
The
Generator
created a boilerplate including all the possible method you can use. We only use theStart
andPerform
methods right now. The other ones can be removed.Update the
Data
subclass to include apublic float Timer { get; set; }
property.In the
Start
method, let's initialize theTimer
to a random value between 0.5f and 1.5f.
In the
Perform
method, let's update theTimer
and check if it's below 0. If it is, we can complete the action.
Your IdleAction
should now look like this:
When playing the scene the agent will now wait for a while before moving to a new position!
Last updated