In this lesson, you will learn how to prevent your GOAP (Goal-Oriented Action Planning) agent from attempting to perform physically impossible tasks simultaneously by implementing resource-based constraints.
The Conflict Problem
In complex AI, we often run into scenarios where two actions share a limited resource. Imagine an NPC that has two actions: DrinkPotion and UseWand. Both require the NPC's HandSlot to be "Empty." If your planner doesn't understand that these actions are mutually exclusive, it might generate a plan where the NPC tries to hold a potion and a wand at the same time, leading to animation clipping or logic errors.
Implementing Constraints
To solve this, we extend our Action definition to include a Constraint System. Instead of just looking at Preconditions (what must be true to start) and Effects (what changes after), we add a ResourceRegistry check.
When the planner evaluates a potential action, it must check if the required resources are currently "locked" by another action. We can model this by adding a ResourceID property to our actions:
public class Action
{
public string Name;
public List<ResourceID> RequiredResources;
public Dictionary<string, bool> Preconditions;
public bool CanPerform(WorldState state)
{
// Check if any required resource is already in use
foreach(var resource in RequiredResources)
{
if (state.IsResourceLocked(resource)) return false;
}
return CheckPreconditions(state);
}
}
Applying the Logic
By assigning a ResourceID (like HandSlot_Right) to all actions that require that hand, the planner will automatically treat those actions as mutually exclusive. If the HandSlot_Right is marked as Locked in the current WorldState because the NPC is already Drinking, the planner will skip any other action requiring that hand until the DrinkPotion action finishes and releases the lock.
This approach forces the planner to find an alternative sequence—perhaps waiting for the hand to become free—rather than generating a broken plan that forces conflicting animations or state changes.
Key Takeaway: By binding actions to shared resource identifiers within your world state, you transform your planner from a simple task-sequence generator into a constraint-aware engine capable of avoiding physical conflicts.