Minimal GOAP woodcutter (Unity / C#)
A small, readable Goal-Oriented Action Planning example for
goal-oriented-action-planning. A worker has one goal, WoodDelivered, and four
actions. Nobody scripts the order: an A* planner chains the actions from the
current world state to the goal, the worker carries out the plan one action at a
time, and it replans as soon as the next action’s preconditions stop holding.
About 265 lines of plain C#. No packages, no NavMesh, no input code.
Files
| Script | Responsibility | Key wiki concept |
|---|---|---|
WorldState.cs | Named bool facts (a missing fact counts as false), goal checks, a state key | goal-oriented-action-planning, csharp-collections |
GoapAction.cs | Name, cost, preconditions, effects; plus the target and duration used when executing | goal-oriented-action-planning |
GoapPlanner.cs | A* over world states: nodes are states, edges are actions, g = cost, h = unmet goal facts | pathfinding-algorithms |
WorkerAgent.cs | Defines the actions, asks for a plan, walks to each target, applies effects, replans | ai-state-machine-pattern, behaviour-trees-vs-goap |
The actions
| Action | Cost | Needs | Gives |
|---|---|---|---|
GetAxe | 2 | AxeOnRack, not HasAxe | HasAxe, not AxeOnRack |
ChopWood | 4 | HasAxe, not HasWood | HasWood |
GatherBranches | 8 | not HasWood | HasWood |
DeliverWood | 1 | HasWood | WoodDelivered, not HasWood |
From the start state {AxeOnRack} the planner can reach the goal two ways:
GetAxe > ChopWood > DeliverWood (cost 7) or GatherBranches > DeliverWood
(cost 9). It picks the cheaper one. Once the worker is holding the axe, later
trips are just ChopWood > DeliverWood (cost 5).
Scene setup (about 5 minutes)
- New scene (2D or 3D; the worker only moves its Transform).
- Create four visible markers, e.g. GameObject → 2D Object → Sprites → Square
(or 3D cubes), and name them
AxeRack,Tree,BranchPile,Storehouse. Spread them a few units apart and give each a different colour. - Create one more sprite or capsule named
Workerand addWorkerAgent. - Drag the four markers into the Axe Rack, Tree, Branch Pile and Storehouse fields.
- Press Play and open the Console. The worker logs each new plan, and the Inspector shows Current Plan, Current Facts and Wood Delivered.
What to try
All three disturbances are on the WorkerAgent component’s three-dot (⋮) menu in the Inspector, while in Play mode.
- Break Axe while the worker is walking to the tree.
ChopWoodneedsHasAxe, so the worker logs “no longer possible”, replans, finds the rack empty, and falls back toGatherBranches. - Restock Axe Rack, then wait for the next delivery. The next plan uses the axe again, because it is cheaper. Notice the worker does not switch mid-plan: it only replans when a plan finishes or fails.
- Drop Wood while the worker is carrying wood to the storehouse.
DeliverWoodfails, and the worker goes back for more. - Change a cost in
WorkerAgent.Awake(for example makeGatherBranchescost 3) and predict the first plan before pressing Play.
How it maps onto the theory
- Planning is search.
GoapPlanneris the same A* idea as pathfinding, but each node is a world state and each edge is an action whose preconditions hold. The heuristic counts unmet goal facts; with a one-fact goal and every action costing at least 1, it never overestimates, so the first goal state taken off the open list is a cheapest plan. - Effects are only promises while planning. The planner applies effects to
copies of the world state. In
WorkerAgent, an effect becomes real only after the worker has walked to the target and worked there for the action’s duration. - Replanning is cheap insurance. Before every frame of an action the agent re-checks that action’s preconditions against the live world state.
- Planner and movement stay separate. The planner decides what to do;
Vector3.MoveTowards(swap in a NavMeshAgent or steering-behaviours) decides how to get there.
Extensions
- A soldier instead of a woodcutter. Facts
HasWeapon,WeaponLoaded,TargetDead; actionsPickUpWeapon,Reload,Attack, plus a costlyMeleefallback. ReuseWorldState,GoapActionandGoapPlannerunchanged. - Several goals. Add a
Restedgoal with aSleepaction and pick the goal each time you plan by a priority or a utility score (see utility-ai). - Procedural preconditions. Give
GoapActionaFunc<bool>check that the planner also calls, e.g. “is the tree still standing?”, for facts too expensive or too numerous to keep in the world state. - Replan on opportunity. Replan every few seconds, not only on failure, and switch if the new plan is cheaper than the rest of the current one.
- Faster open list. Replace the linear scan with a small binary heap and
measure with more actions. (.NET 6’s
PriorityQueueis not in the class libraries that ship with Unity 6000.3, so write your own or use a package.)
Background
The approach follows Jeff Orkin’s account of the planner in F.E.A.R. (Orkin, “Three States and a Plan: The A.I. of F.E.A.R.”, GDC 2006): STRIPS-style goals and actions, a cost per action, A* searching over world states, and replanning when the world invalidates a plan. This example is a teaching simplification, not a reconstruction of that system.