Source file unity-csharp/game-ai/goap/GoapPlanner.cs from the GDnD code examples. Download raw file
// GDnD wiki example
// Demonstrates: an A* planner that searches over world states, with actions as the edges
// Related pages: [[goal-oriented-action-planning]], [[pathfinding-algorithms]]
using System.Collections.Generic;
// Pathfinding A* searches over places; this A* searches over world states.
// Node = a world state. Edge = an action whose preconditions hold in that state.
// g = total action cost so far. h = number of goal facts still unmet.
// Returns the cheapest action list, or null when no plan exists.
public static class GoapPlanner
{
private class Node
{
public WorldState State;
public Node Parent;
public GoapAction Action; // the action that led here
public float G;
public float F;
}
public static List<GoapAction> Plan(WorldState start, Dictionary<string, bool> goal,
List<GoapAction> actions, int maxExpansions = 1000)
{
var open = new List<Node> { new Node { State = start, G = 0, F = start.CountUnmet(goal) } };
var bestCost = new Dictionary<string, float> { [start.Key()] = 0 };
for (int i = 0; i < maxExpansions && open.Count > 0; i++)
{
// Take the open node with the lowest f = g + h. A plain list keeps this
// readable; a priority queue is the usual upgrade for bigger action sets.
Node current = open[0];
foreach (Node n in open) if (n.F < current.F) current = n;
open.Remove(current);
if (current.State.Satisfies(goal)) return BuildPlan(current);
foreach (GoapAction action in actions)
{
if (!current.State.Satisfies(action.Preconditions)) continue;
WorldState next = action.ApplyTo(current.State);
float g = current.G + action.Cost;
string key = next.Key();
// Skip states we have already reached at least as cheaply.
if (bestCost.TryGetValue(key, out float known) && known <= g) continue;
bestCost[key] = g;
open.Add(new Node { State = next, Parent = current, Action = action,
G = g, F = g + next.CountUnmet(goal) });
}
}
return null;
}
// Walk back from the goal node to the start, then reverse into execution order.
private static List<GoapAction> BuildPlan(Node node)
{
var plan = new List<GoapAction>();
for (; node.Action != null; node = node.Parent) plan.Add(node.Action);
plan.Reverse();
return plan;
}
}