meniku / NPBehave

Event Driven Behavior Trees for Unity 3D
MIT License
1.15k stars 195 forks source link

Pause and resume #30

Open lulugo19 opened 4 years ago

lulugo19 commented 4 years ago

Added functionality to pause and resume the behavior tree.

Pausing the tree means stopping the currently executing tasks, then doing nothing (the tree is just in idle mode) and when resuming the previously stopped tasks are started again.

Added Pause() and Resume() methods to the Node class. Pause() stops the current active tasks on that branch, without effecting the children stop logic of the Container nodes above. When Resume() is called the previously stopped tasks are started again.

I used this for my enemies in the game. So when they got hurt, I could pause the tree and play a hurt animation (stun) and then resume the tree again.

Changed some Decorator nodes to remove the timers on pause and register them on resume again

lulugo19 commented 4 years ago

Ignoring BlackboardCondition is pause state not working - no notification after resuming

Hi, I have written some tests and have noted a problem and I don't know how I could fix this. The fourth Test IgnoreBlackBoardConditionWhenPausedSelf() is failing. This test tests if the blackboard condition is ignored in the pause state. The ignoring part is working but after resuming the tree with Resume(), there is no notification sent that the blackboard has updated and the blackboard conditon (with Stops.Self) will not stop.

meniku commented 4 years ago

Ignoring BlackboardCondition is pause state not working - no notification after resuming

Hi, I have written some tests and have noted a problem and I don't know how I could fix this. The fourth Test IgnoreBlackBoardConditionWhenPausedSelf() is failing. This test tests if the blackboard condition is ignored in the pause state. The ignoring part is working but after resuming the tree with Resume(), there is no notification sent that the blackboard has updated and the blackboard conditon (with Stops.Self) will not stop.

Alright, I will look into this

meniku commented 4 years ago

I've fixed it by putting this bit inside the BlackboardCondition:

        public override void Resume()
        {
            base.Resume();
            if( this.CurrentState == State.ACTIVE )
            {
                Evaluate();
            }
        }

it just re-evaluates after the resuming is done.

However I got three other failing tests from you. You need help with those, too?

lulugo19 commented 4 years ago

I've fixed it by putting this bit inside the BlackboardCondition:

        public override void Resume()
        {
            base.Resume();
            if( this.CurrentState == State.ACTIVE )
            {
                Evaluate();
            }
        }

it just re-evaluates after the resuming is done.

However I got three other failing tests from you. You need help with those, too?

Thanks, this is a great solution. I think it would be better to put this code in the parent class ObservingDecorator, so this will also work for the BlackboardQuery. I will do this.

Yeah I know why the other tests are still failing. I may need your help on this, because right know I don't know how to solve this.

It's just the same problem. That there is no evaluation after resuming. But the above doesn't work because the BlackboardCondition isn't actually Paused and so Resume is never called. Only active nodes are paused. Previously inactive nodes stay inactive. And when we have following setup of a tree.

     R (Paused)
     |
     X (Paused)
 ____|____
 |       |
BC      T2 (Paused)
 |
T1

The blackboard condtion (BC) isn't actually Paused an thus no Resume is called.

A possible solution would be to have the BC also paused. Right now the Pause() method in the Container class is the following:

public override void Pause()
{
  Assert.AreEqual(this.currentState, State.ACTIVE, "Only an active container can be paused.");
            currentState = State.PAUSED;

  foreach (Node child in Children)
  {
    if (child.isActive) 
    {
      child.Pause();
      this.pausedChildren.Add(child);
    }
  }
}

We could change it to this:

public override void Pause()
{
  if (!isActive)
    return;
  currentState = State.PAUSED;
  foreach (Node child in Children)
  {
    child.Pause();
    if (child.CurrentState == State.PAUSED)
    {
      this.pausedChildren.Add(child);
    }
  }
}

and overwrite it in the ObservingDecorator class with this:

public override void Pause()
{
  currentState = State.PAUSED;
  StopObserving();
  base.Pause();
}

So that ObservingDecorators are paused altough there are inactive but don't propagate Pause() on its children when inactive.

I think it's a good solution. There is only one minor problem that Pause() can be called on an inactive node which is intended and we don't have the assert prohibiting this. But Pause() should actually only be called on the Root node anyway and I can put the assertion there. Alright I am doing this that way.

lulugo19 commented 4 years ago

Everything is fixed now. I am very satisfied with the solution and overall with the feature. Yeah you're right, it's probably not something that get used so much, but I really like it that I managed to add it without adding more complexity to the rest of the library, because it's mostly independent from the rest.

Thank you very much for your help. 👍

I think I have finished it for now. Maybe I am going to add some additional tests for the Cooldown decorator or something...

meniku commented 4 years ago

sweet, I'll take a look tomorrow! thanks for contributing in such a high quality!

meniku commented 4 years ago

sorry for not responding. I'm trying to create a test for one case I found that breaks, however I've some trouble with my visual studio currently. I didn't forget about this :)

lulugo19 commented 4 years ago

Hello meniku, I made a pause and now I am working further on my game. I have noticed some more mistakes with the Pausing functionality. I am continuing fixing mistakes and will give you insight later whether the Pausing functionality worked for my game so you can consider adding this to the library.