/// <summary> /// Function to apply the task of this primative node to change the world state /// </summary> public void applyTask(HTN_WorldState worldState) { Task taskName = this.taskName; // use switch of the task name to check precondition (maybe have a better way to do it??) switch (taskName) { //case Task.ROOT_TASK: //return true; // since root task has not precondition // Primitive Tasks: case Task.MOVE_TO_CRATE: worldState.isInFrontOfObstacle = true; break; case Task.MOVE_TO_ROCK: worldState.isInFrontOfObstacle = true; break; case Task.THROW_CRATE: case Task.THROW_ROCK: case Task.WANDER: break; // wander won't have a effect on the Game state } }
/// <summary> /// Function to update the world state in this planner /// </summary> /// <param name="ws"></param> public void updateWorldState(HTN_WorldState ws) { this.currentWorldState.isInAttackRange = ws.isInAttackRange; this.currentWorldState.isInFrontOfObstacle = ws.isInFrontOfObstacle; this.currentWorldState.hasCratesInRange = ws.hasCratesInRange; this.currentWorldState.isTheChosenObstacleDestroyed = ws.isTheChosenObstacleDestroyed; }
/// <summary> /// Function to verify the precondition of the node using the current worldState and the Task name (enum) /// </summary> /// <param name="worldState"></param> /// <returns>Return true if the preconditions are satisfied by the current World State, otherwise return false</returns> public bool verifyPrecondition(HTN_WorldState worldState) { Task taskName = this.taskName; /* * // use switch of the task name to check precondition (maybe have a better way to do it??) * switch (taskName) * { * //case Task.ROOT_TASK: * //return true; // since root task has not precondition * * // Method Tasks: * case Task.DISTANCE_ATTACK: * // if the task is the Method: DISTANCE_ATTACK, check the world state * if (worldState.isInAttackRange && worldState.hasObstablesOnMap) * { * return true; * } * else * { * return false; * } * case Task.CRATE_ATTACK: * // if the task is the Method: DISTANCE_ATTACK, check the world state * if (worldState.isInAttackRange && worldState.hasObstablesOnMap) * { * return true; * } * else * { * return false; * } * case Task.ROCK_ATTACK: * // if the task is the Method: DISTANCE_ATTACK, check the world state * if (worldState.isInAttackRange && worldState.hasObstablesOnMap) * { * return true; * } * else * { * return false; * } * case Task.IDLE: * // if the task is the Method: IDLE, return always true, since it doesn't depend on the World State * return true; * } */ //return false; // for unexcepted error // for simplify the tree, we only put precondition for primitive task, so every method will be valid return(true); }
/// <summary> /// Funtion to return the first method that the World State satisfies its precondition /// (This function will only be called by a Compound node) /// </summary> /// <param name="worldState"></param> /// <returns></returns> public HTN_MethodNode findSatisfiedMethod(HTN_WorldState worldState) { // iterate all the chilren of this compound method (which should always be Method nodes) foreach (HTN_MethodNode method in this.children) { // return the first Method node which its precondition is satisfied by the current World State if (method.verifyPrecondition(worldState)) { return(method); } } // if not found, return null return(null); }
/// <summary> /// Funtion to return all methods that the World State satisfies its precondition /// (This function will only be called by a Compound node) /// </summary> /// <param name="worldState"></param> /// <returns></returns> public ArrayList findSatisfiedMethods(HTN_WorldState worldState) { ArrayList satisfiedMethods = new ArrayList(); // iterate all the chilren of this compound method (which should always be Method nodes) foreach (HTN_MethodNode method in this.children) { // return the first Method node which its precondition is satisfied by the current World State if (method.verifyPrecondition(worldState)) { satisfiedMethods.Add(method); } } return(satisfiedMethods); }
// -------------------------- // Start is called before the first frame update void Start() { navMeshAgent = this.gameObject.GetComponent <NavMeshAgent>(); navMeshAgent.autoBraking = false; // disable auto braking for continuous movement this.monsterYellingText.gameObject.SetActive(false); // disable UI yelling text // generate an HTN tree for this monster rootNode = generateHTNtree(); // generate a World State HTN_WorldState ws = new HTN_WorldState(false, false, false, false, false); // set all false initially, let the update function to update WorldState this.worldState = ws; // create a HTN planner planner = new HTN_Planner(this, worldState); }
/// <summary> /// Function to update the world state for this monster's HTN /// </summary> /// <param name="ws"></param> void updateWorldState(HTN_WorldState ws) { // update isInAttackRange if (Vector3.Distance(this.gameObject.transform.position, player.gameObject.transform.position) <= attackRange) { ws.isInAttackRange = true; } else { ws.isInAttackRange = false; } // update isInAttackRange if (Vector3.Distance(this.gameObject.transform.position, player.gameObject.transform.position) <= yellingRange) { ws.isInYellingRange = true; } else { ws.isInYellingRange = false; } // update hasCratesInRange, check if there is crate in attack range bool hasCrates = false; foreach (GameObject obstacle in gameInitalizer.getObstacleList()) { if (obstacle.tag == "Crate" && Vector3.Distance(this.gameObject.transform.position, obstacle.transform.position) <= attackRange) { hasCrates = true; break; } } if (hasCrates) { ws.hasCratesInRange = true; } else { ws.hasCratesInRange = false; } // update isInFrontOfObstacle if (this.obstacleToBeThrown) { if (Vector3.Distance(this.gameObject.transform.position, this.obstacleToBeThrown.transform.position) <= 6f) { ws.isInFrontOfObstacle = true; } else { ws.isInFrontOfObstacle = false; } } // update isTheChosenCrateDestroyed if (this.obstacleToBeThrown) { // if the current chosen obstacle is destroyed, change the world state isTheChosenCrateDestroyed to true if (this.obstacleToBeThrown.gameObject == null) { ws.isTheChosenObstacleDestroyed = true; //this.obstacleToBeThrown = null; } else { ws.isTheChosenObstacleDestroyed = false; } } // updata the world state in planner this.planner.updateWorldState(this.worldState); }
/// <summary> /// Function to verify the precondition of the node using the current worldState and the Task name (enum) /// </summary> /// <param name="worldState"></param> /// <returns>Return true if the preconditions are satisfied by the current World State, otherwise return false</returns> public bool verifyPrecondition(HTN_WorldState worldState) { Task taskName = this.taskName; // use switch of the task name to check precondition (maybe have a better way to do it??) switch (taskName) { //case Task.ROOT_TASK: //return true; // since root task has not precondition // Primitive Tasks: case Task.MOVE_TO_CRATE: // if the task is the Primitive task: MOVE_TO_CRATE, check the world state if (worldState.isInAttackRange && worldState.hasCratesInRange) { return(true); } else { return(false); } case Task.MOVE_TO_ROCK: // if the task is the Primitive task: MOVE_TO_ROCK, check the world state if (worldState.isInAttackRange) { return(true); } else { return(false); } case Task.THROW_CRATE: // if the task is the Primitive task: THROW_CRATE, check the world state if (worldState.isInFrontOfObstacle && worldState.isInAttackRange && !worldState.isTheChosenObstacleDestroyed) { return(true); } else { return(false); } case Task.THROW_ROCK: // if the task is the Primitive task: THROW_ROCK, check the world state if (worldState.isInFrontOfObstacle && worldState.isInAttackRange && !worldState.isTheChosenObstacleDestroyed) { return(true); } else { return(false); } case Task.YELLING: // if the task is the Primitive task: YELLING, check the world state if (worldState.isInYellingRange) { return(true); } else { return(false); } case Task.WANDER: return(true); // return always true for wander task } return(false); // for unexcepted error }
/// <summary> /// Function to generate a plan for a given root node of HTN tree /// </summary> /// <param name="rootNode"></param> public void planning(HTN_node rootNode) { HTN_WorldState state = this.currentWorldState.clone(); // make a copy of the current state ArrayList finalPlanList = new ArrayList(); Stack <HTN_node> tasksNodes = new Stack <HTN_node>(); // stack for store Method node for backtracking Stack <ArrayList> backTrackingMethods = new Stack <ArrayList>(); // each list in this stack is a list of methods // stack for store planList for backtracking Stack <ArrayList> backTrackingPlan = new Stack <ArrayList>(); Stack <HTN_WorldState> states = new Stack <HTN_WorldState>(); // store the copy of states for backtracking tasksNodes.Push(rootNode); // put the root node into the task stack // while the stack is not empty while (!(tasksNodes.Count == 0)) { HTN_node taskNode = tasksNodes.Pop(); // if the node is a compound node if (taskNode is HTN_CompoundNode) { ArrayList methods = ((HTN_CompoundNode)taskNode).findSatisfiedMethods(this.currentWorldState); //HTN_MethodNode method = ((HTN_CompoundNode)taskNode).findSatisfiedMethod(this.currentWorldState); HTN_MethodNode method = (HTN_MethodNode)methods[0]; methods.RemoveAt(0); if (method != null) { // store the plan and the next method node for backtracking when needed backTrackingPlan.Push(new ArrayList(finalPlanList)); // clone the current plan list backTrackingMethods.Push(methods); states.Push(state.clone()); // push the subtasks of this method into the list (push backward since we want the first subtask to be pop next) for (int i = method.getChildren().Count - 1; i >= 0; i--) { tasksNodes.Push((HTN_node)method.getChildren()[i]); } } else { // restore to the saved backtracking state state = states.Pop(); finalPlanList = backTrackingPlan.Pop(); tasksNodes.Clear(); // empty the tasksNode stack // pop a methods list ArrayList btMethods = backTrackingMethods.Pop(); while (btMethods.Count == 0) { btMethods = backTrackingMethods.Pop(); } // get the first method in that list HTN_MethodNode btMethodNode = (HTN_MethodNode)btMethods[0]; btMethods.RemoveAt(0); // delete that method from the list if (btMethods.Count > 0) { backTrackingMethods.Push(btMethods); // put the method list back } // push the subtasks of this method into the list (push backward since we want the first subtask to be pop next) for (int i = btMethodNode.getChildren().Count - 1; i >= 0; i--) { tasksNodes.Push((HTN_node)btMethodNode.getChildren()[i]); } } } else // primative node { // if the precondition is met if (((HTN_PrimitiveNode)taskNode).verifyPrecondition(state)) { // apply the effect of this task on the world state ((HTN_PrimitiveNode)taskNode).applyTask(state); finalPlanList.Add(taskNode); // add this taskNode to the plan list // if this primitive task is the last in the stack, our planning is finish if (tasksNodes.Count == 0) { break; } } else { // restore to the saved backtracking state state = states.Pop(); finalPlanList = backTrackingPlan.Pop(); tasksNodes.Clear(); // empty the tasksNode stack // pop a methods list ArrayList btMethods = backTrackingMethods.Pop(); while (btMethods.Count == 0) { btMethods = backTrackingMethods.Pop(); } // get the first method in that list HTN_MethodNode btMethodNode = (HTN_MethodNode)btMethods[0]; btMethods.RemoveAt(0); // delete that method from the list if (btMethods.Count > 0) { backTrackingMethods.Push(btMethods); // put the method list back } // push the subtasks of this method into the list (push backward since we want the first subtask to be pop next) for (int i = btMethodNode.getChildren().Count - 1; i >= 0; i--) { tasksNodes.Push((HTN_node)btMethodNode.getChildren()[i]); } } } } this.plan = finalPlanList; // set the current plan to the final plan list }
private float timer = 0f; // using for applying 1second cool-down between each plan //private bool hasReplanning = false; // used for update UI text public HTN_Planner(System.Object agent, HTN_WorldState ws) { this.agent = agent; this.currentWorldState = ws; }