/// <summary> /// Crears all values. /// </summary> private void Clear() { this._parent = null; this._runningCost = 0; this._weight = 0; this._state = null; this._action = null; }
/// <summary> /// Reinitializes node with new values. /// </summary> public void Reinitialize(GOAPNode parent, float runningCost, float weight, Dictionary <string, bool> state, GOAPAction action) { Clear(); this._parent = parent; this._runningCost = runningCost; this._weight = weight; this._state = state; this._action = action; }
/// <summary> /// Compares nodes. /// </summary> /// <param name="valueToCompareWith"></param> public bool BetterThen(GOAPNode valueToCompareWith) { if (_weight > valueToCompareWith._weight && _runningCost < valueToCompareWith._runningCost) { return(true); } if (_weight < valueToCompareWith._weight && _runningCost > valueToCompareWith._runningCost) { return(false); } // Make weight > cost. bool better = (_weight / valueToCompareWith._weight - 1) >= (_runningCost / valueToCompareWith._runningCost - 1); return(better); }
/// <summary> /// Reinitializes free node, turns it into used one and returns it. If there is not enough free nodes, creates a new one. /// </summary> public static GOAPNode GetFreeNode(GOAPNode parent, float runningCost, float weight, Dictionary <string, bool> state, GOAPAction action) { GOAPNode free = null; if (_freeNodes.Count <= 0) { free = new GOAPNode(parent, runningCost, weight, state, action); } else { free = _freeNodes.Pop(); free.Reinitialize(parent, runningCost, weight, state, action); } _usedNodes.Push(free); return(free); }
/// <summary> /// Builds the plan of actions. Returns true if at least one solution was found. /// The possible paths are stored in the leaves list. Each leaf has a 'runningCost' value where the lowest cost will be the best action sequence. /// </summary> /// <param name="parent">Parent node</param> /// <param name="leaves">List of nodes with possible paths</param> /// <param name="usableActions">Actions available to the GOAPAgent</param> /// <param name="goal">Goal that GOAPAgent is trying to achieve</param> private bool BuildGraph(GOAPNode parent, List <GOAPNode> leaves, HashSet <GOAPAction> usableActions, KeyValuePair <string, bool> goal) { bool foundOne = false; // Go through each action available at this node and see if we can use it here. foreach (GOAPAction action in usableActions) { // If the parent state has the conditions for this action's preconditions, we can use it here. if (InState(action.Preconditions, parent._state)) { // Apply the action's effects to the parent state. Dictionary <string, bool> currentState = PopulateState(parent._state, action.Effects); GOAPNode node = GOAPPlannerHelper.GetFreeNode(parent, parent._runningCost + action.GetCost(), parent._weight + action.GetWeight(), currentState, action); // If there's no match between child's precondition and parent's effects or child's precondition is empty. if (parent._action != null && (action.Preconditions.Count == 0 || !CondRelation(action.Preconditions, parent._action.Effects))) { continue; } if (GoalInState(goal, currentState)) { // There is a solution. leaves.Add(node); foundOne = true; } else { // Not at a solution yet, so we're testing all of the remaining actions and branching out the tree. HashSet <GOAPAction> subset = ActionSubset(usableActions, action); bool found = BuildGraph(node, leaves, subset, goal); if (found) { foundOne = true; } } } } return(foundOne); }
/// <summary> /// Plans what sequence of actions can fulfill the goal. /// Returns null if a plan could not be found, or a list of the actions that must be performed, in order, to fulfill the goal. /// </summary> /// <param name="agent">GOAPAgent</param> /// <param name="availableActions">Actions that can be performed</param> /// <param name="worldState">Starting world state</param> /// <param name="goal">Goal that GOAPAgent is trying to achieve</param> /// <param name="AIAgent">Implementing class that provides our world data and listens to feedback on planning</param> public Queue <GOAPAction> Plan(GameObject agent, HashSet <GOAPAction> availableActions, Dictionary <string, bool> worldState, KeyValuePair <string, bool> goal, IAIAgent AIAgent) { // Reset all of the actions that can be performed. foreach (GOAPAction action in availableActions) { action.DoReset(); } // Loops through the available actions and adds the ones that can be performed to a list. HashSet <GOAPAction> usableActions = GOAPPlannerHelper.GetFreeActionSet(); foreach (GOAPAction action in availableActions) { // If the the action can be performed. if (action.CheckProceduralPrecondition(agent, AIAgent.DataHolder)) { usableActions.Add(action); } } // Create a "tree" of actions that will lead to the wanted goal. List <GOAPNode> leaves = new List <GOAPNode>(); GOAPNode start = GOAPPlannerHelper.GetFreeNode(null, 0, 0, worldState, null); bool success = BuildGraph(start, leaves, usableActions, goal); if (!success) { return(null); } // Find the cheapest plan of action out of each generated plan. GOAPNode cheapest = null; foreach (GOAPNode leaf in leaves) { if (cheapest == null) { cheapest = leaf; } else { if (leaf.BetterThen(cheapest)) { cheapest = leaf; } } } // Get its node and work back through the parents. List <GOAPAction> result = new List <GOAPAction>(); GOAPNode n = cheapest; while (n != null) { if (n._action != null) { // Insert the action in the front. result.Insert(0, n._action); } n = n._parent; } // Clean up after we're done. GOAPPlannerHelper.Release(); // Create a final plan to follow. Queue <GOAPAction> finalQueue = new Queue <GOAPAction>(); foreach (GOAPAction action in result) { finalQueue.Enqueue(action); } return(finalQueue); }
/// <summary> /// Constructor. /// </summary> public GOAPNode(GOAPNode parent, float runningCost, float weight, Dictionary <string, bool> state, GOAPAction action) { _ID = MaxID++; Reinitialize(parent, runningCost, weight, state, action); }