Plan & Goal

A plan is simply a sequence of actions that satisfy a goal wherein the actions take the agent from a starting state to whichever state satisfies the goal.

A goal is any condition that an agent wants to satisfy. In GOAP, goals simply define what conditions need to be met to satisfy the goal, the steps required to reach these satisfactory conditions are determined in real time by the GOAP planner. A goal is able to determine its current relevance and when it is satisfied.

Actions

Every agent is assigned actions which are a single, atomic step within a plan that makes an agent do something. Examples of an action are playing an animation, playing a sound, altering the state, picking up flowers, etc.

Every action is encapsulated and ignorant of the others.

Each defined action is aware of when it is valid to be executed and what its effects will be on the game world. Each action has both a preconditions and effects attributes which are used to chain actions into a valid plan. A precondition is the state required for an action to run and effects are the changes to the state after an action has executed. For example if the agent wants to harvest wheat using a scythe, it must first acquire the tool and make sure the precondition is met. Otherwise the agent harvests using its hands.

GOAP determines which action to execute by evaluating each action’s cost. The GOAP planner evaluates which sequence of actions to use by adding up the cumulative cost and selecting the sequence with lowest cost. Actions determine when to transition into and out of a state as well as what occurs in the game world due to the transition.

The GOAP Planner

An agent develops a plan in real time by supplying a goal to satisfy a planner. The GOAP planner looks at an actions preconditions and effects in order to determine a queue of actions to satisfy the goal. The target goal is supplied by the agent along with the world state and a list of valid actions; This process is referred to as “formulating a plan”

If the planner is successful it returns a plan for the agent to follow. The agent executes the plan until it is completed, invalidated, or a more relevant goal is found. If at any point the goal is completed or another goal is more relevant then the character aborts the current plan and the planner formulates a new one.

The planner finds the solution by building a tree. Every time an action is applied it is removed from the list of available actions.

Visualization of planning tree

Furthermore, you can see that the planner will run through all available actions to find the most optimal solution for the target goal. Remember, different actions have different costs and the planner is always looking for the solution with the cheapest cost.

On the right you can see that the planner attempts to run the Use Tool action but it validates as an unsuccessful solution, this is because of the precondition and effect attributes assigned to all actions.

The Use Tool action is unable to run because it’s precondition is that the agent has a tool which it finds by utilizing the find tool action. But, the agent can harvest wheat regardless of whether they have a tool or not, but it has a much lower cost without the tool, so the agent will prioritize finding a tool if it is available.

In Unity, using c# preconditions can be implemented using a Hash Set containing a Key Value Pair wherein the string is the precondition identifier and the object is a Boolean.

HashSet<KeyValuePair<string, object>> preconditions;

HashSet<KeyValuePair<string, object>> effects;

These are only used for planning and do not effect the Agent until the actions are actually run.

Moreover, some actions need to use data from the world state to determine if they are able to run. These preconditions are called procedural preconditions. In the diagram, the Find Tool action will only validate itself to the planner if it is able to find some container containing a tool for it to use. If the procedural precondition is unable to be met for Find Tool then the planner tells the agent to harvest wheat manually.

For example the procedural condition for harvest wheat would look like this in c#

// This function checks for the nearest supply pile to deposit wheat at

// Harvesters in different areas of the world will go to their respective chests, but if they are destroyed they can intelligently find another closer one public bool CheckProceduralCondition(GameObject agent)

{

Chest[] chests = GameWorld.chests;

Chest closestChest = null; float distChest; foreach (Chest chest in chests)

{

if (closestChest == null)

{

closestChest = chest;

distChest = Distance(closestChest.position - agent.position);

}

else

{

// Check if this chest is closer

float distance = Distance(chest.position - agent.position); if (distance < distChest)

{

closestChest = chest;

distChest = distance;

} // If no chest is found, terminate the action plan

if (cloestChest == null)

return false; SetTarget(closestChest); return closestChest != null;

}

These procedural conditions can get taxing when a game is utilizing a large amount of agents, in that scenario GOAP planning should run on a thread separately than the render thread so that the planner can continuously plan minimizing effect on the game play experience.

How GOAP and FSM work together

In my implementation, I used a FSM with 3 states which were

Idle MoveTo PerformAction

The idle state is the default state an agent starts at. During this state, the agent passes its defined goal (which can be anything you choose, and there can me multiple different goals) to the planner along with the world and agent states.

It looks something like this:

// Remember the hashsets we used to store preconditions and effects?

// We also use those to hold the worldstate (which is the agents initial precondition) and the effect (which is the goal for the plan)

// This makes the implementation more straightforward HashSet<KeyValuePair<string, object>> worldState = getWorldState();

HashSet<KeyValuePair<string, object>> goal = createGoalState(); Queue<Action> plan = planner.plan(actions, worldState, goal);

After the agent has found a plan it checks if it is within range to execute the command or else transitions to the MoveTo state. When within range of the target, the agent transitions to the PerformAction state in which the agent executes the action the planner specified next.

When there are no actions left to perform or the agent has fulfilled its goal, the agent returns to the Idle state and waits for a new plan.

Some Implementation Details

A standard GOAP implementation requires at least 6 base classes for it to work. These are:

FSM

FSM State

GOAP Interface

Agent

Action

Planner

The GOAP interface is implemented by all agents and it is how the Planner accesses world data and ties events from the FSM and Planner together.

For an agent to work it must have the following components:

An Agent implementation An GOAP Interface implementation An Action implementation

Each agent will fulfill their action until they have fulfilled their goal.

Tech Demo: