Home > OS >  Calling a list of coroutines one after another in unity
Calling a list of coroutines one after another in unity

Time:08-03

New to C# and unity, what I trying to do is have a unit perform a sequence of actions one after another, and this sequence can potentially be interrupted to perform a more urgent sequence of actions.

For instance , for a unit type woodcutter, his sequence of actions might be:

  1. FindHome
  2. GoHome
  3. FindTreeToCut
  4. GoToTree
  5. cutTree
  6. collectLumber
  7. GoHome

Now while he is doing this sequence of actions , he might be interrupted by some more important sequence, like runAway, with actions like:

  1. FindSafeSpace
  2. gotoSafeSpace
  3. HideInSafeSpace

After reading a bit found that couroutines might a good candidate for this, this is what I have right now:

Unit.cs:

  public Dictionary<string, Action> actions;

  private void Awake() {
    initActions();
    Task task = TaskManager.Instance.getTask(unit);
    UnitManager.Instance.performTask(unit, task);

  }
  private void initActions() {
        actions = new Dictionary<string, Action>() {
            { "GoHome", () => GoHome()},
            { "FindHome", () => FindHome()},
            { "FindTreeToCut", () => FindTreeToCut()},
            { "CutTree", () => CutTree()},
             // and so on
        };
    }
    public void GoHome() {
      //some code
    }

   //More functions 

UnitManager.cs

//**Not sure how to implement this:**
public void performTask(Unit unit, Task task) {
  for(String action in task.actions) {
    // start action
    StartCoroutine(unit.actions[action]());//? 
    // wait for coroutine to finish then continue loop

  }

}

TaskManager.cs

public getTask() {
  //returns some task
}
//approx defination..
public class Task {
  public string id;
  public int weight;
  public List<string> actions
}

Now lets forget about interrupting current sequence for now as that I think is a different question, right now trying to understand what is the best way to implement UnitManager.performTask(), I included the interrupt-sequence aspect just incase if it affects the performTask implementation in anyway

CodePudding user response:

You can use the following line to wait until coroutine is completed:

yield return StartCoroutine(unit.actions[action]());

Keep in mind that this will only work if this code is inside a coroutine, so performTask should be a coroutine or you can create a proxy method for this. The first case will look like this:

public IEnumerator performTask(Unit unit, Task task) 
{
  foreach (String action in task.actions) 
  {
    // start action
    yield return StartCoroutine(unit.actions[action]());
  }
}

The second case is a proxy method:

public void performTask(Unit unit, Task task) 
{
    StartCoroutine(PerformAll(unit, task));
}

private IEnumerator PerformAll(Unit unit, Task task) 
{
    foreach (String action in task.actions) 
    {
        // start action
        yield return StartCoroutine(unit.actions[action]());
    }
}

There is one very important detail, in order for coroutine delay to work as expected make sure that they only stop when their work is over

CodePudding user response:

I think it might be beneficial to wrap each "action" (coroutine) inside a separate class. Otherwise your Unit class can end up having a huge number of methods within it.

public abstract class UnitRoutine
{
    protected abstract IEnumerator Perform();

    public static implicit operator IEnumerator(UnitRoutine routine)
    {
        return routine.Perform();
    }
}

I've also added an implicit operator above, which makes it possible to just pass an instance of the class to MonoBehaviour.StartCoroutine or MonoBehaviour.StopCoroutine, and it will be automatically converted into an IEnumerator which the methods accept.

Then you could define new routines like this:

public class GoHomeRoutine : UnitRoutine
{
    protected override IEnumerator Perform()
    {
        while(!HasReachedHome())
        {
            MoveTowardsHome();
            yield return null;
        }
    }
}

public class CutTreeRoutine : UnitRoutine
{
    protected override IEnumerator Perform()
    {
        while(!HasReachedTree())
        {
            MoveTowardsTree();
            yield return null;
        }
        
        while(!HasCutDownTree())
        {
            SwingAxe();
            yield return new WaitForSeconds(3f);
        }
    }
}

This system also makes it possible to compose multiple smaller coroutines together into a larger one.

public class WoodcutterRoutine : UnitRoutine
{
    private readonly goHomeRoutine = new GoHomeRoutine();
    private readonly cutTreeRoutine = new CutTreeRoutine();

    protected override IEnumerator Perform()
    {
        while(true)
        {
            yield return StartCoroutine(goHomeRoutine);
            yield return StartCoroutine(cutTreeRoutine);
        }
    }
}

With this ability it could be that you don't even need to have your UnitManager keep track of a list of tasks, but you could instead compose the list of tasks by defining a new UnitRoutine instead like the one above.

Then implementing the class responsible for running these routines becomes really simple:

public class UnitRoutineRunner : MonoBehaviour
{
    private UnitRoutine currentRoutine = null;

    public void StartRoutine(UnitRoutine routine)
    {
        if(currentRoutine != null)
        {
            StopCoroutine(currentRoutine);
        }
        
        currentRoutine = routine;
        StartCoroutine(routine);
    }
}
  • Related