Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Task based AI system for all!

Discussion in 'Scripting' started by HAlbera, May 17, 2015.

  1. HAlbera

    HAlbera

    Joined:
    Jun 7, 2013
    Posts:
    63
    Hello all! I think this is the appropriate place to post this.

    I wanted a simple to use Task based system with priority sorting for some basic AI. I had a look at a few different ways of going about this and found GOAP and FSM systems which while good didn't really satisfy my goals for some simple AI.

    So I set about writing this little system. The goals were simple:
    • Must be easy to use.
    • Must be able to define individual Tasks.
    • The tasks must know what they need to do themselves.
    Here it is!

    The TaskManager should be attached to the GameObject you want to have functionality for. I'm fairly sure this could be used for a multitude of things, not just AI.

    TaskManager.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5.  
    6. public class TaskManager : MonoBehaviour {
    7.  
    8.     #region TaskManager Functionality
    9.  
    10.     public List<Task> TaskList;
    11.  
    12.     /*You can simply pause the TaskManager by toggling this bool, note this won't actually stop some things from happening.
    13.     For example, a NavMeshAgent will keep moving to its last target but not the next one, you could handle this in your Tasks though I'm sure.
    14.     Possibly re-organise how Update() handles the pausing mechanic. Possibly checking to see if the TaskManager is Paused, then call a Pause()
    15.     method on the Task and handle it in the task. NOTE: In the HumanTasks.cs See NavMeshTask.Reset() for an idea. ;P
    16.      */
    17.     public bool Paused = false;
    18.  
    19.  
    20.     /*This switches the sorting by priority on or off. This is evaluated at the END of the first Task in the list.
    21.     This is because sorting the list every Update is riduculous, also it introduces problems with adding tasks, whereby
    22.     a new Task could push itself to the top of the list, leaving the previous task unfinished and pushed down the stack.
    23.     This is fine as we can call Reset() on that task and everything would be fine, but sorting the list every update is
    24.     just too much work in my eyes, an alternative is to check if the list count has changed each update, if it has then Reset()
    25.     the current Task, SortListByPriority() and begin again.
    26.  
    27.     My attitude is that if a Task is SO important it MUST be first on the list IMMEDIATELY then you should handle these events
    28.     yourself as they break the natural flow of the system. I have included the AddTaskAtBeginning(Task); to show how to set
    29.     up these tasks. It's quite simple.
    30.     */
    31.  
    32.     public bool PrioritySort = true;
    33.  
    34.     void Awake(){
    35.         TaskList = new List<Task>();
    36.     }
    37.  
    38.     void SortListByPriority(){
    39.         if (TaskList.Count > 0){
    40.             TaskList = TaskList.OrderBy(x => x.Priority).Reverse().ToList();
    41.         }
    42.     }
    43.  
    44.     /*ProcessList() Handles the standard processing of the list including Validity checks, Initialisation and Execution of Tasks.
    45.     It also calls OnTaskStart() and OnTaskEnd() at the appropriate times. */
    46.     void ProcessList(){
    47.  
    48.         //If this Task decides it is invalid, then delete it.
    49.         if (TaskList[0].Valid){
    50.             //If its not initialised, intialise it.
    51.             if (TaskList[0].Initialised){
    52.                 //If the task isn't finished, execute it.
    53.                 if (!TaskList[0].Finished ()){
    54.                     if (TaskList[0].Started == false){
    55.                         TaskList[0].Started = true;
    56.                         TaskList[0].OnTaskStart();
    57.                     }
    58.                     TaskList[0].Execute();                
    59.                 }
    60.                 else if (TaskList[0].Finished ()){
    61.                     Debug.Log ("TaskManager - Task finished, removing!");
    62.                     //Call OnTaskEnd() and then remove the task.
    63.                     TaskList[0].OnTaskEnd();
    64.                     TaskList.RemoveAt(0);
    65.                     //If PrioritySort is on, sort the list now, before we start the next task.
    66.                     if (PrioritySort){
    67.                         SortListByPriority();
    68.                     }
    69.                 }
    70.             }
    71.             else {
    72.                 TaskList[0].Initialise();
    73.             }
    74.         }
    75.         else if (!TaskList[0].Valid){
    76.             Debug.LogWarning ("TaskManager - Invalid Task detected, removing!");
    77.             TaskList.RemoveAt(0);
    78.         }
    79.     }
    80.  
    81.     #region TimedTask Handling
    82.     /* This function is called in update, it simply adds time to the TimedTask's counter and asks if it needs to be killed off. Simple.
    83.     Note the reoccuring theme here where-by the TaskManager ASKS the Task if it is finished. The TaskManager doesn't make that decision.*/
    84.  
    85.     void UpdateTimedTaskCounters(){
    86.  
    87.         List<TimedTask> RemovalList = new List<TimedTask>();
    88.         foreach (TimedTask t in TaskList.OfType<TimedTask>()){
    89.             //Update the TimeInList (time sat in the list).
    90.             t.TimeInList += Time.deltaTime;
    91.  
    92.             //If the current task has begun update the TimeAsCurrentTask.
    93.             if (t.Started == true){
    94.                 t.TimeAsCurrentTask += Time.deltaTime;
    95.             }
    96.             //If the current task now thinks its done, then remove it!
    97.             if (t.Started == false && t.Finished()){
    98.                 t.OnTaskEnd();
    99.                 RemovalList.Add (t);
    100.             }
    101.         }
    102.         foreach (TimedTask rm in RemovalList){
    103.             Debug.Log ("TaskManager - Task expired: " + rm);
    104.             TaskList.Remove (rm);
    105.         }
    106.     }
    107.  
    108.     #endregion
    109.  
    110.     void Update(){
    111.         if (!Paused){
    112.             if (TaskList.Count > 0){
    113.                 //StartCoroutine("UpdateTimedTaskCounters"); << I'm not completely sure if this is necessary, possibly with hundreds of objects in the scene?
    114.                 UpdateTimedTaskCounters();
    115.                 ProcessList();
    116.             }
    117.             else {
    118.                 Debug.Log ("TaskManager - TaskList is empty!");
    119.             }
    120.         }
    121.     }
    122.     #endregion
    123.  
    124.     #region TaskManager Utilities
    125.  
    126.     /* These are just some samples of what can be done with this system. Some of them might be useful others not. I have tried
    127.     to keep them as generic as possible, the NavMeshAgent task down there is a filaing of this desire, but i was working on it
    128.     and thought I would include it anyhow =].
    129.     */
    130.  
    131.     public void AddTaskAtBeginning(Task t){
    132.         Paused = true;
    133.         if (TaskList.Count > 0){
    134.             TaskList[0].Reset();
    135.             TaskList.Insert(0,t);
    136.  
    137.         }
    138.         else {
    139.             TaskList.Add (t);
    140.         }
    141.         Paused = false;
    142.     }
    143.  
    144.     public void AddNavMeshAgentMoveTask(Vector3 MoveTarget){
    145.  
    146.         NavMeshTask NewTask = new NavMeshTask(){
    147.             TaskID = 1,
    148.             Priority = 1,
    149.             ThisGameObject = gameObject,
    150.             Agent = GetComponent<NavMeshAgent>(),
    151.             DestinationPosition = MoveTarget
    152.         };
    153.         TaskList.Add(NewTask);
    154.     }
    155.  
    156.     /* Here you can see the setup of an example task with a TTL (Time To Live).
    157.     To create a Task that will end itself, simply set the TTL of a Task that extends TimedTask.
    158.     Note that TTL will only check when it is the ACTIVE task.
    159.     */
    160.     public void AddPause(float t){
    161.  
    162.         TimedTask NewTask = new TimedTask(){
    163.             TaskID = 1,
    164.             Priority = 1,
    165.             TTL = t
    166.         };
    167.         TaskList.Add (NewTask);
    168.     }
    169.  
    170.     public void AddPauseAtBeginning(float t){
    171.         TimedTask NewTask = new TimedTask(){
    172.             TaskID = 1,
    173.             Priority = 1,
    174.             TTL = t
    175.         };
    176.         AddTaskAtBeginning(NewTask);
    177.     }
    178.  
    179.     /*THIS IS A POINTLESS TASK AND WILL PURELY REPORT THAT IT HAS FINISHED AFTER X AMOUNT OF TIME.
    180.     If you want to make task expirable, simply set the ExpiryTime of a Task that extends TimedTask.
    181.     Note that ExpiryTime, will work at ANY time the Task is in the TaskList.
    182.     */
    183.     public void AddExpirableTask(float t){
    184.         TimedTask NewTask = new TimedTask(){
    185.             TaskID = 1,
    186.             Priority = 1,
    187.             ExpiryTime = t
    188.         };
    189.         TaskList.Add (NewTask);
    190.     }
    191.     #endregion
    192. }
    193.  
    Task.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System;
    4.  
    5. public abstract class Task {
    6.  
    7.     //Returns true if the conditions declared by the Task are met.
    8.     public abstract bool Valid { get; }
    9.  
    10.     //Returns true if the Task has had Initialise() called.
    11.     public bool Initialised { get; set; }
    12.  
    13.     //Returns True if the Task has been started.
    14.     public bool Started { get; set; }
    15.  
    16.     //Used for sorting Tasks.
    17.     public int Priority { get; set; }
    18.     public int TaskID { get; set; }
    19.  
    20.     //Constructor.
    21.     public Task(){
    22.         Initialised = false;
    23.         Started = false;
    24.     }
    25.  
    26.     //To be called before Execute or the Task will (probably) fail, depending on whether the task needs to initialise at all.
    27.     public abstract void Initialise();
    28.  
    29.     //OnTaskStart() is called when the task is Valid AND Initialised!
    30.     public virtual void OnTaskStart(){}
    31.  
    32.     //Execute() needs to be called in update of the TaskManager. This will probably hold the majority of the game logic for a task.
    33.     //It is executed every update of the TaskManager, use this function as if you were to use Update();
    34.     public abstract void Execute();
    35.  
    36.     //Allows the TaskManager to check if a task has finished, each task defines it's own rules as to what finished means.
    37.     public abstract bool Finished();
    38.     public bool _finished = false;
    39.  
    40.     //OnTaskEnd() is called after the Task decides or is told it is finished.
    41.     public virtual void OnTaskEnd(){}
    42.  
    43.     public virtual void Reset(){
    44.         Initialised = false;
    45.         Started = false;
    46.         Debug.Log ("Reset called");
    47.     }
    48.  
    49. }
    50.  
    51. ///////////////////////////////////////////////////////////////
    52. //Purely for copy and pasting!
    53.  
    54. public class TaskTemplate : Task {
    55.  
    56.     public override bool Valid {
    57.         get {
    58.             throw new NotImplementedException ();
    59.         }
    60.     }
    61.  
    62.     public override void Initialise ()
    63.     {
    64.         throw new NotImplementedException ();
    65.     }
    66.  
    67.     //Execute() needs to be called in update of the TaskManager.
    68.     public override void Execute(){
    69.      
    70.     }
    71.  
    72.     public override bool Finished (){
    73.         throw new NotImplementedException ();
    74.     }
    75.  
    76. }
    77.  
    78. ////////////////////////////////////////////////////////////////
    79.  
    Quite proud of these TimedTasks! Simply put, you can set a task up to end itself after a set period of time. Both inside the Task List or as a current Task.

    TimedTask.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System;
    4.  
    5. public class TimedTask : Task{
    6.  
    7.  
    8.     /* TimeInList should not be modified directly, it is updated by the TaskManager.
    9.     TimeAsCurrentTask shouldn't either.
    10.  
    11.     ExpiryTime should be set when the Task is setup, this is the length of time the Task can sit in the TaskManager.
    12.     TTL is the Time To Live as the current Task. This will only countdown once the Task is Valid, Initialised and Started.
    13.     */
    14.     public float TimeInList = 0f;
    15.  
    16.     public float ExpiryTime = -1;
    17.  
    18.     public float TimeAsCurrentTask = 0f;
    19.  
    20.     public float TTL = -1;
    21.  
    22.     //This functions checks the criteria listed below, if any of them fail, it returns false; and when asked by Valid, flags the task as (not)Valid.
    23.     private bool SetupCheck(){
    24.         if (Priority == -1 || TaskID == 0){
    25.             if (ExpiryTime == -1 && TTL == -1){
    26.                 return false;
    27.             }
    28.             Debug.LogWarning("TimedTask - Task was not setup correctly!");
    29.             return false;
    30.         }
    31.         else {
    32.             return true;
    33.         }
    34.     }
    35.     //This simply calls the function above, you can put whatever you want in here to decide if the Task is valid and not use the function above.
    36.     public override bool Valid {
    37.         get {
    38.             if (!SetupCheck()){
    39.                 return false;
    40.             }
    41.             else {
    42.                 return true;
    43.             }
    44.         }    
    45.     }
    46.  
    47.     //Do your Initialisation in here!
    48.     public override void Initialise ()
    49.     {
    50.         Initialised = true;
    51.     }
    52.  
    53.     //Chuck all your normal functionality in here!
    54.     public override void Execute(){
    55.      
    56.     }
    57.  
    58.  
    59.     /* Something important to note here: If you don't setup a TimedTask with a timer then it may just hang in the first position of the list.
    60.     EDIT: For the sake of having foolproof code I have updated the Valid check for you.
    61.  
    62.     */
    63.     public override bool Finished (){
    64.         if (ExpiryTime != -1 && TimeInList >= ExpiryTime){
    65.             return true;
    66.         }
    67.         if (TTL != -1 && TimeAsCurrentTask >= TTL){
    68.             return true;
    69.         }
    70.         else {
    71.             return false;
    72.         }
    73.     }
    74.  
    75.     public override void Reset ()
    76.     {
    77.         Initialised = false;
    78.         Started = false;
    79.         //Remember to reset the current Task timer ;P
    80.         TimeAsCurrentTask = 0f;
    81.     }
    82. }
    83.  
    Complex tasks are a container you can put other tasks inside. In practise they work exactly like normal task in the TaskManager, but will iterate through the tasks stored inside it while it is active in the Manager. This helps to organise complicated tasks whereby you might want to walk over to something and then do something on that object. Or something needs to happen in a specific order where the Priority sorting of the main TaskManager might upset that order.

    ComplexTask.cs
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using System;
    6. using System.Linq;
    7.  
    8.  
    9. public class ComplexTask : Task {
    10.  
    11.     //List of Tasks to complete.
    12.     public List<Task> ComplexTaskList;
    13.     //Constructor
    14.     public ComplexTask(){
    15.         ComplexTaskList = new List<Task>();
    16.     }
    17.  
    18.     //
    19.     void ProcessComplexTask(){
    20.  
    21.         UpdateTimedTaskCounters();
    22.         //If this task is not initialised, initialise it.
    23.         if (ComplexTaskList[0].Valid){
    24.             //If its not initialised, intialise it.
    25.             if (ComplexTaskList[0].Initialised){
    26.                 //If the task isn't finished, execute it.
    27.                 if (!ComplexTaskList[0].Finished ()){
    28.                     if (ComplexTaskList[0].Started == false){
    29.                         ComplexTaskList[0].Started = true;
    30.                         OnTaskStart(ComplexTaskList[0]);
    31.                     }
    32.                     ComplexTaskList[0].Execute();                  
    33.                 }
    34.                 else if (ComplexTaskList[0].Finished ()){
    35.                     Debug.Log ("TaskManager - Task finished, removing!");
    36.                     ComplexTaskList.RemoveAt(0);
    37.                 }
    38.             }
    39.             else {
    40.                 ComplexTaskList[0].Initialise();
    41.             }
    42.         }
    43.         else if (!ComplexTaskList[0].Valid){
    44.             Debug.LogWarning ("TaskManager - Invalid Task detected, removing!");
    45.             ComplexTaskList.RemoveAt(0);
    46.         }  
    47.     }
    48.  
    49.     void UpdateTimedTaskCounters(){
    50.      
    51.         List<TimedTask> RemovalList = new List<TimedTask>();
    52.         foreach (TimedTask t in ComplexTaskList.OfType<TimedTask>()){
    53.             //Update the TimeInList (time sat in the list).
    54.             t.TimeInList += Time.deltaTime;
    55.          
    56.             //If the current task has begun update the TimeAsCurrentTask.
    57.             if (t.Started == true){
    58.                 t.TimeAsCurrentTask += Time.deltaTime;
    59.             }
    60.             //If the current task now thinks its done, then remove it!
    61.             if (t.Started == false && t.Finished()){
    62.                 t.OnTaskEnd();
    63.                 RemovalList.Add (t);
    64.             }
    65.         }
    66.         foreach (TimedTask rm in RemovalList){
    67.             Debug.Log ("TaskManager - Task expired: " + rm);
    68.             ComplexTaskList.Remove (rm);
    69.         }
    70.     }
    71.  
    72.     void OnTaskStart(Task TaskReference){
    73.         Debug.Log ("TaskManager - Executing current task.");
    74.         Debug.Log ("TaskManager - Tasks in queue: " + ComplexTaskList.Count + ".");
    75.     }
    76.  
    77.  
    78.     private bool SetupCheck(){
    79.         if (Priority == 0 || TaskID == 0){
    80.             Debug.LogWarning("ComplexTask - Task was not setup correctly!");
    81.             return false;
    82.         }
    83.         else {
    84.             return true;
    85.         }
    86.     }
    87.  
    88.     public override bool Valid {
    89.         get {
    90.             if (!SetupCheck()){
    91.                 return false;
    92.             }
    93.             else {
    94.                 return true;
    95.             }
    96.         }
    97.     }
    98.  
    99.     //Do your Initialistion stuff in here!
    100.     public override void Initialise ()
    101.     {
    102.         Initialised = true;
    103.     }
    104.  
    105.     //Execute() needs to be called in update of the TaskManager.
    106.     public override void Execute(){
    107.         ProcessComplexTask();
    108.     }
    109.  
    110.     //This simply checks to see if the list is empty. If it is then the Task is obviously done!
    111.     public override bool Finished (){
    112.         if (ComplexTaskList.Count <= 0){
    113.             return true;
    114.         }
    115.         else {
    116.             return false;
    117.         }
    118.     }
    119.  
    120.     public override void Reset ()
    121.     {
    122.         ComplexTaskList[0].Reset();
    123.     }  
    124.  
    125. }
    126.  
    127.  
    This is purely an example of how you can set up a NavMeshAgent to work with this system.

    HumanTasks.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5.  
    6. /*This is an example of a new set of Tasks im working on for a game.
    7. Note how they will inherit from Task.
    8. My personal choice here will be to make them Inherit from HumanTasks also so I can define a few extra bits
    9. that define what a human can and cant do or how it might do it. */
    10.  
    11.  
    12. public abstract class HumanTasks : Task {
    13.  
    14. }
    15.  
    16. public class NavMeshTask : HumanTasks {
    17.  
    18.     //The Game World Coordinates for the NavMeshAgent to head towards.
    19.     public Vector3 DestinationPosition { get; set; }
    20.     //Agent reference.
    21.     public NavMeshAgent Agent;
    22.     //Reference to the GameObject that is using this Task. Required!
    23.     public GameObject ThisGameObject { get; set; }
    24.  
    25.     //Constructor
    26.     public NavMeshTask(){
    27.         Initialised = false;
    28.     }
    29.  
    30.     //Called to check if the task has been setup correctly, returns true if everything seems right.
    31.     private bool SetupCheck(){
    32.         if (Agent == null || Priority == -1 || TaskID == 0 || ThisGameObject == null){
    33.             Debug.LogWarning("NavMeshTask - Task was not setup correctly!");
    34.             return false;
    35.         }
    36.         else {
    37.             return true;
    38.         }
    39.     }
    40.  
    41.     //This tasks implementation of Valid() simply relays the output of the function SetupCheck() waste of a call right now but maybe useful for later Tasks?
    42.     public override bool Valid{
    43.         get {
    44.             if (!SetupCheck()){
    45.                 return false;
    46.             }
    47.             else {
    48.                 return true;
    49.             }
    50.         }
    51.     }
    52.     //This Tasks implementation of Initilise() simply sets the NavMeshAgents 'DestinationPosition'.
    53.     public override void Initialise (){
    54.         Agent.ResetPath ();
    55.         //IMPORTANT that this is now set to true. The TaskManager relies on this variable.
    56.         Initialised = true;
    57.     }
    58.  
    59.     public override void OnTaskStart ()
    60.     {
    61.         Debug.Log ("NavMeshTask - NavMeshAgent moving to: " + DestinationPosition);
    62.     }
    63.  
    64.     //Execute() needs to be called in update of the TaskManager. Setting the destination doesn't need to be done in each update.
    65.     public override void Execute(){
    66.         if (!Agent.hasPath){
    67.             Agent.destination = DestinationPosition;
    68.         }
    69.         if (Started == true){
    70.             if (Agent.pathPending){
    71.                 Debug.Log ("NavMeshTask - Path is being calculated.");
    72.             }
    73.             else {
    74.                 if (Agent.pathStatus == NavMeshPathStatus.PathInvalid || Agent.pathStatus == NavMeshPathStatus.PathPartial){
    75.                     Debug.Log ("NavMeshTask - Path invalid.");
    76.                     //TODO: Handle invalid pathing.
    77.                 }
    78.                 if (Agent.pathStatus == NavMeshPathStatus.PathComplete){
    79.                     //Debug.Log ("MoveTask - Path complete."); << Loads of Logs!
    80.                     if (Agent.remainingDistance == 0){
    81.                         Debug.Log ("NavMeshTask - Destination Reached.");
    82.                         _finished = true;
    83.                     }
    84.                     else {
    85.                         //Debug.Log ("Destination not reached"); << Loads of Logs!
    86.                     }
    87.                 }
    88.                 else {
    89.                 }
    90.             }
    91.         }
    92.         else {
    93.             Debug.LogWarning ("NavMeshTask - This Task has not yet begun! (Something went very wrong?!)");
    94.             //TODO: Work out if this can even happen now I have reorganised the TaskManager flow.
    95.         }
    96.     }
    97.  
    98.     public override bool Finished(){
    99.         return _finished;
    100.     }
    101.  
    102.     public override void Reset(){
    103.         Initialised = false;
    104.         Started = false;
    105.         Agent.ResetPath();
    106.     }
    107.  
    108. }//END OF CLASS
    You can see in the TaskManager I have included a few examples of how to set up a task as well as a few examples of simple functions you can call.

    I really don't have a good enough internet connection to offer you all a demo, but you can try the following.

    1. Create a new scene.
    2. Create a floor using a plane or terrain.
    3. Create a new GameObject.
    4. Add a NavMeshAgent to the GameObject.
    5. Bake the NavMesh and wait for lighting etc.
    6. Attach the TaskManager script to the GameObject.
    7. Attach the following script, that includes some random points to walk to with pauses, to the GameObject.
    CivillianTest.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4.  
    5. public class CivillianTest : MonoBehaviour {
    6.  
    7.     TaskManager TM;
    8.  
    9.     void Awake(){
    10.         TM = GetComponent<TaskManager>();
    11.     }
    12.  
    13.     // Use this for initialization
    14.     void Start () {
    15.         TM.AddExpirableTask(15.0f);
    16.         TM.AddNavMeshAgentMoveTask(new Vector3(5,0,2));
    17.         TM.AddPause(2.0f);
    18.         TM.AddNavMeshAgentMoveTask(new Vector3(0,0,-7));
    19.         TM.AddNavMeshAgentMoveTask(new Vector3(5,0,2));
    20.         TM.AddNavMeshAgentMoveTask(new Vector3(0,0,4));
    21.         TM.AddNavMeshAgentMoveTask(new Vector3(7,0,-8));
    22.  
    23.     }
    24.  
    25.     // Update is called once per frame
    26.     void Update () {
    27.         if (Input.GetKeyDown (KeyCode.O)){
    28.             TM.AddPauseAtBeginning(2.0f);
    29.             Debug.Log ("Added at start");
    30.         }
    31.         if (Input.GetKeyDown (KeyCode.P)){
    32.             if (TM.Paused){
    33.                 TM.Paused = false;
    34.             }
    35.             else TM.Paused = true;
    36.         }
    37.     }
    38. }
    I hope you enjoy!

    EDIT: Forgot to handle TimedTasks in ComplexTask. Fixed.
     
    Last edited: May 17, 2015
    Soaphog, Superflat and krougeau like this.
  2. krougeau

    krougeau

    Joined:
    Jul 1, 2012
    Posts:
    451
    This looks promising, and I'm hoping to find the time to test it out soon. I'm caught up in three other projects at the moment though. I'll return with some proper feedback as soon as my schedule allows me to make more than a cursory examination of the code. :)
     
  3. HAlbera

    HAlbera

    Joined:
    Jun 7, 2013
    Posts:
    63
    Cool, no rush really.

    Ive started using it to make a game finally, I generally lack the determination to finish a game. So far I have a few characters walking about and chatting with each other every now and again.

    The next step is to implement a way to punch them in the face and watch them run away. Using this task system it shouldn't be too hard. Thinking about just adding a task to the front of the queue that makes them run off, with an expiry on it so they don't run forever. Nice and modular.
     
    krougeau likes this.
  4. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    5,996
    Looks like FSM.
    Why did you decide against GOAP?