diff --git a/Editor/Tests/PauseResumeTest.cs b/Editor/Tests/PauseResumeTest.cs new file mode 100644 index 00000000..a95d3cd9 --- /dev/null +++ b/Editor/Tests/PauseResumeTest.cs @@ -0,0 +1,369 @@ +using System.IO; +using NUnit.Framework; + +namespace NPBehave +{ + public class PauseResumeTest : Test + { + [Test] + // tests pausing and resuming a very simple behavior tree + public void SimpleBehaviorTree() + { + // building a very simple behavior tree: two tasks in a selector + this.Timer = new Clock(); + this.Blackboard = new Blackboard(Timer); + + MockTask firstTask = new MockTask(false); + MockTask secondTask = new MockTask(false); + + Selector selector = new Selector(firstTask, secondTask); + TestRoot behaviorTree = new TestRoot(Blackboard, Timer, selector); + + // starting the tree + behaviorTree.Start(); + + // first task should be active and second inactive + Assert.AreEqual(Node.State.ACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, secondTask.CurrentState); + + // now pause the tree + behaviorTree.Pause(); + + // the previously active task should be stopped (inactive) now + Assert.AreEqual(Node.State.INACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, secondTask.CurrentState); + + // and the containers above should be in pause mode + Assert.AreEqual(Node.State.PAUSED, behaviorTree.CurrentState); + Assert.AreEqual(Node.State.PAUSED, selector.CurrentState); + + // resume the tree again + behaviorTree.Resume(); + + // the first task should be active again and the second inactive + Assert.AreEqual(Node.State.ACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, secondTask.CurrentState); + + // also the containers above should be also active again + Assert.AreEqual(Node.State.ACTIVE, behaviorTree.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, selector.CurrentState); + + // stopping the first task and the first task should be inactive and the second active + firstTask.Stop(); + Assert.AreEqual(Node.State.INACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, secondTask.CurrentState); + } + + [Test] + // tests pausing and resuming a more complex behavior tree + public void SlightlyMoreComplexBehaviorTree() + { + // building a slighly more complex behavior tree + this.Timer = new Clock(); + this.Blackboard = new Blackboard(Timer); + + MockTask firstTask = new MockTask(false); + MockTask secondTask = new MockTask(false); + MockTask thirdTask = new MockTask(false); + + Selector bottomSelector = new Selector(secondTask, thirdTask); + Selector topSelector = new Selector(firstTask, bottomSelector); + TestRoot behaviorTree = new TestRoot(Blackboard, Timer, topSelector); + + // starting the tree + behaviorTree.Start(); + + // first task should be active + Assert.AreEqual(Node.State.ACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, secondTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, thirdTask.CurrentState); + + // now pause the tree + behaviorTree.Pause(); + + // the previously active task should be stopped (inactive) now + Assert.AreEqual(Node.State.INACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, secondTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, secondTask.CurrentState); + + // only the containers that lead to the previously active task should be paused + Assert.AreEqual(Node.State.PAUSED, behaviorTree.CurrentState); + Assert.AreEqual(Node.State.PAUSED, topSelector.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, bottomSelector.CurrentState); + + // resume the tree again + behaviorTree.Resume(); + + // the first task should be active again + Assert.AreEqual(Node.State.ACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, secondTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, thirdTask.CurrentState); + + // also the containers above should be also active again + Assert.AreEqual(Node.State.ACTIVE, behaviorTree.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, topSelector.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, bottomSelector.CurrentState); + + // stopping the first task and the second task should be active + firstTask.Stop(); + Assert.AreEqual(Node.State.INACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, secondTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, thirdTask.CurrentState); + + // pausing the tree and all task should be inactive and the container paused + behaviorTree.Pause(); + Assert.AreEqual(Node.State.INACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, secondTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, thirdTask.CurrentState); + Assert.AreEqual(Node.State.PAUSED, behaviorTree.CurrentState); + Assert.AreEqual(Node.State.PAUSED, topSelector.CurrentState); + Assert.AreEqual(Node.State.PAUSED, bottomSelector.CurrentState); + + // resuming again and the second task should be active again and also the containers + behaviorTree.Resume(); + Assert.AreEqual(Node.State.INACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, secondTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, thirdTask.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, behaviorTree.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, topSelector.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, bottomSelector.CurrentState); + } + + [Test] + // the behavior tree should ignore the blackboard condition (with self stop rule) when paused + public void IgnoreBlackBoardConditionWhenPausedSelf() + { + // building a behavior tree with two task and the first has a condition + this.Timer = new Clock(); + this.Blackboard = new Blackboard(Timer); + + MockTask firstTask = new MockTask(false); + MockTask secondTask = new MockTask(false); + + BlackboardCondition firstCondition = + new BlackboardCondition("first", Operator.IS_EQUAL, true, Stops.SELF, firstTask); + Selector selector = new Selector(firstCondition, secondTask); + TestRoot behaviorTree = new TestRoot(Blackboard, Timer, selector); + + Blackboard.Set("first", true); + + // start the tree + behaviorTree.Start(); + + // first task should be active + Assert.AreEqual(Node.State.ACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, secondTask.CurrentState); + + // now pause the tree + behaviorTree.Pause(); + + // blackboard condition should be paused now, the observers are unregistered + Assert.AreEqual(Node.State.PAUSED, firstCondition.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, secondTask.CurrentState); + + // when changing the condition nothing should happen because it is ignored in pause state + Blackboard.Set("first", false); + Timer.Update(0.1f); + Assert.AreEqual(Node.State.PAUSED, firstCondition.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, secondTask.CurrentState); + + // TODO: this doesn't work here!!! The change isn't notified! + // when resuming the change should be notified and the second task should be active + behaviorTree.Resume(); + Timer.Update(0.1f); + Assert.AreEqual(Node.State.INACTIVE, firstCondition.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, secondTask.CurrentState); + } + + [Test] + public void ServiceInactiveWhenPause() + { + // I am not really sure if the requirement is rigth that the service should be really inactive when paused. + // But if the blackboard condition was set only in a service, the problem above with the + // blackboard condition would be solved, because the blackboard wouldn't be updated in pause state + + // building a behavior tree with only one service and a task + this.Timer = new Clock(); + this.Blackboard = new Blackboard(Timer); + + MockTask task = new MockTask(false); + + int serviceRunCount = 0; + Service service = new Service(() => serviceRunCount++, task); + TestRoot behaviorTree = new TestRoot(Blackboard, Timer, service); + + behaviorTree.Start(); + + // the service run count should be one + Assert.AreEqual(1, serviceRunCount); + + // after update it should be two + Timer.Update(0.1f); + Assert.AreEqual(2, serviceRunCount); + + // pause the tree + behaviorTree.Pause(); + Timer.Update(0.1f); + // service method shouldn't be called. It should still be two. + Assert.AreEqual(2, serviceRunCount); + + // after resuming the service should be active again and the counter should be three + // because when resuming the method is called + behaviorTree.Resume(); + Assert.AreEqual(3, serviceRunCount); + } + + [Test] + public void IgnoreWaitForConditionWhenPause() + { + this.Timer = new Clock(); + this.Blackboard = new Blackboard(Timer); + + int condition = 0; + MockTask task = new MockTask(false); + WaitForCondition waitForCondition = new WaitForCondition(() => condition != 0, task); + + TestRoot behaviorTree = new TestRoot(Blackboard, Timer, waitForCondition); + + behaviorTree.Start(); + Assert.AreEqual(Node.State.ACTIVE, behaviorTree.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, waitForCondition.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, task.CurrentState); + + // when pausing and the condition is met, it should be ignored + behaviorTree.Pause(); + condition = 1; + Timer.Update(0.1f); + Assert.AreEqual(Node.State.PAUSED, behaviorTree.CurrentState); + Assert.AreEqual(Node.State.PAUSED, waitForCondition.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, task.CurrentState); + + // when resuming and the condition is met the task should be active + behaviorTree.Resume(); + Timer.Update(0.1f); + Assert.AreEqual(Node.State.ACTIVE, behaviorTree.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, waitForCondition.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, task.CurrentState); + } + + // Testing different composite types behavior when pausing and the blackboard condition with Stops.IMMEDIATE_RESTART is changed. + // The change of the condition should be ignored. + [Test] + public void IgnoreBlackboardConditionWhenPausedImmediateRestartSelector() + { + this.Timer = new Clock(); + this.Blackboard = new Blackboard(Timer); + + MockTask firstTask = new MockTask(false); + MockTask secondTask = new MockTask(false); + + BlackboardCondition condition = + new BlackboardCondition("first", Operator.IS_EQUAL, true, Stops.IMMEDIATE_RESTART, firstTask); + Selector selector = new Selector(condition, secondTask); + TestRoot behaviorTree = new TestRoot(Blackboard, Timer, selector); + + // set the condition for the first task to false so that the second task should be active + Blackboard.Set("first", false); + behaviorTree.Start(); + Assert.AreEqual(Node.State.INACTIVE, condition.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, secondTask.CurrentState); + + // when pausing the tree and setting the blackboard condition to true nothing should happens + behaviorTree.Pause(); + Blackboard.Set("first", true); + Timer.Update(0.1f); + Assert.AreEqual(Node.State.PAUSED, condition.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, secondTask.CurrentState); + + // now resume the tree and the first task should be the active one because of the true blackboard condition + behaviorTree.Resume(); + Timer.Update(0.1f); + Assert.AreEqual(Node.State.ACTIVE, condition.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, secondTask.CurrentState); + } + + [Test] + public void IgnoreBlackboardConditionWhenPausedImmediateRestartSequence() + { + this.Timer = new Clock(); + this.Blackboard = new Blackboard(Timer); + + MockTask firstTask = new MockTask(true); + MockTask secondTask = new MockTask(true); + + BlackboardCondition condition = + new BlackboardCondition("first", Operator.IS_EQUAL, true, Stops.IMMEDIATE_RESTART, firstTask); + Sequence sequence = new Sequence(condition, secondTask); + TestRoot behaviorTree = new TestRoot(Blackboard, Timer, sequence); + + // set the condition for the first task to true so that the first task should be active + Blackboard.Set("first", true); + behaviorTree.Start(); + // update timer so the first task is active + Timer.Update(0.1f); + firstTask.Finish(true); + Blackboard.Set("first", false); + Assert.AreEqual(Node.State.INACTIVE, condition.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, secondTask.CurrentState); + + // when pausing the tree and setting the blackboard condition to true nothing should happens + behaviorTree.Pause(); + Blackboard.Set("first", true); + Timer.Update(0.1f); + Assert.AreEqual(Node.State.PAUSED, condition.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, secondTask.CurrentState); + + // now resume the tree and the first task should be the active one because of the true blackboard condition + behaviorTree.Resume(); + Timer.Update(0.1f); + Assert.AreEqual(Node.State.ACTIVE, condition.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, secondTask.CurrentState); + } + + [Test] + public void IgnoreBlackboardConditionWhenPausedImmediateRestartParallel() + { + this.Timer = new Clock(); + this.Blackboard = new Blackboard(Timer); + + MockTask firstTask = new MockTask(false); + MockTask secondTask = new MockTask(false); + + BlackboardCondition condition = + new BlackboardCondition("first", Operator.IS_EQUAL, true, Stops.IMMEDIATE_RESTART, firstTask); + Parallel parallel = new Parallel(Parallel.Policy.ALL, Parallel.Policy.ALL, condition, secondTask); + TestRoot behaviorTree = new TestRoot(Blackboard, Timer, parallel); + + // set the condition for the first task to false so that the second task should be active + Blackboard.Set("first", false); + behaviorTree.Start(); + Assert.AreEqual(Node.State.INACTIVE, condition.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, secondTask.CurrentState); + + // when pausing the tree and setting the blackboard condition to true nothing should happens + behaviorTree.Pause(); + Blackboard.Set("first", true); + Timer.Update(0.1f); + Assert.AreEqual(Node.State.PAUSED, condition.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.INACTIVE, secondTask.CurrentState); + + // now resume the tree and the first task should be the active one because of the true blackboard condition + behaviorTree.Resume(); + Timer.Update(0.1f); + Assert.AreEqual(Node.State.ACTIVE, condition.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, firstTask.CurrentState); + Assert.AreEqual(Node.State.ACTIVE, secondTask.CurrentState); + } + } +} \ No newline at end of file diff --git a/Editor/Tests/_utils/MockTask.cs b/Editor/Tests/_utils/MockTask.cs new file mode 100644 index 00000000..8ade1a07 --- /dev/null +++ b/Editor/Tests/_utils/MockTask.cs @@ -0,0 +1,22 @@ +namespace NPBehave +{ + public class MockTask : Task + { + private bool suceedsOnExplicitStop; + + public MockTask(bool suceedsOnExplicitStop) : base("MockTask") + { + this.suceedsOnExplicitStop = suceedsOnExplicitStop; + } + + protected override void DoStop() + { + this.Stopped(suceedsOnExplicitStop); + } + + public void Finish(bool success) + { + this.Stopped(success); + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index c29db781..2a578cc8 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,23 @@ In case your Monster gets killed or you just destroy your GameObject, you should // ... ``` +## Pausing the Tree + +The tree can be paused with `Pause()`. When the tree is paused the current executing task is stopped +and the tree does not change its state anymore. +Then the tree can be resumed with `Resume()` again and the previously stopped task is restarted again. +This is useful for executing behavior tree independent logic while the tree is paused, +e.g. an enemy should be stunned after he got hurt. + +```csharp + public IEnumerator StunEnemy() + { + behaviorTree.Pause(); + yield return enemy.Stun(); + behaviorTree.Resume(); + } +``` + ## The Debugger You can use the `Debugger` component to debug the behavior trees at runtime in the inspector. diff --git a/Scripts/Composite/Composite.cs b/Scripts/Composite/Composite.cs index a00f936d..ae8ba554 100644 --- a/Scripts/Composite/Composite.cs +++ b/Scripts/Composite/Composite.cs @@ -4,8 +4,6 @@ namespace NPBehave { public abstract class Composite : Container { - protected Node[] Children; - public Composite(string name, Node[] children) : base(name) { this.Children = children; diff --git a/Scripts/Container.cs b/Scripts/Container.cs index 22f9efa4..7ec10045 100644 --- a/Scripts/Container.cs +++ b/Scripts/Container.cs @@ -1,10 +1,16 @@ +using System.Collections.Generic; +using System.Linq; using UnityEngine.Assertions; namespace NPBehave { public abstract class Container : Node { + protected Node[] Children; + protected readonly Stack pausedChildren = new Stack(); + private bool collapse = false; + public bool Collapse { get @@ -25,7 +31,47 @@ public void ChildStopped(Node child, bool succeeded) { // Assert.AreNotEqual(this.currentState, State.INACTIVE, "The Child " + child.Name + " of Container " + this.Name + " was stopped while the container was inactive. PATH: " + GetPath()); Assert.AreNotEqual(this.currentState, State.INACTIVE, "A Child of a Container was stopped while the container was inactive."); - this.DoChildStopped(child, succeeded); + + if (currentState != State.PAUSED) + { + this.DoChildStopped(child, succeeded); + } + } + + override public void Pause() + { + if (!IsActive) + return; + currentState = State.PAUSED; + foreach (Node child in Children) + { + if (child is Task) + { + if (child.IsActive) + { + child.Pause(); + this.pausedChildren.Push(child); + } + } + else + { + child.Pause(); + if (child.CurrentState == State.PAUSED) + { + this.pausedChildren.Push(child); + } + } + } + } + + override public void Resume() + { + Assert.AreEqual(this.currentState, State.PAUSED, "Only a paused container can be resumed."); + currentState = State.ACTIVE; + while (pausedChildren.Any()) + { + pausedChildren.Pop().Resume(); + } } protected abstract void DoChildStopped(Node child, bool succeeded); diff --git a/Scripts/Decorator/BlackboardCondition.cs b/Scripts/Decorator/BlackboardCondition.cs index f3bad895..b74e0a43 100644 --- a/Scripts/Decorator/BlackboardCondition.cs +++ b/Scripts/Decorator/BlackboardCondition.cs @@ -46,8 +46,7 @@ public BlackboardCondition(string key, Operator op, Stops stopsOnChange, Node de this.key = key; this.stopsOnChange = stopsOnChange; } - - + override protected void StartObserving() { this.RootNode.Blackboard.AddObserver(key, onValueChanged); diff --git a/Scripts/Decorator/Decorator.cs b/Scripts/Decorator/Decorator.cs index 4bb66c40..d2bfce00 100644 --- a/Scripts/Decorator/Decorator.cs +++ b/Scripts/Decorator/Decorator.cs @@ -8,6 +8,7 @@ public abstract class Decorator : Container public Decorator(string name, Node decoratee) : base(name) { this.Decoratee = decoratee; + Children = new[] {decoratee}; this.Decoratee.SetParent(this); } diff --git a/Scripts/Decorator/ObservingDecorator.cs b/Scripts/Decorator/ObservingDecorator.cs index 63962994..d28736f7 100644 --- a/Scripts/Decorator/ObservingDecorator.cs +++ b/Scripts/Decorator/ObservingDecorator.cs @@ -8,6 +8,7 @@ public abstract class ObservingDecorator : Decorator { protected Stops stopsOnChange; private bool isObserving; + private State beforePauseState; public ObservingDecorator(string name, Stops stopsOnChange, Node decoratee) : base(name, decoratee) { @@ -41,6 +42,47 @@ override protected void DoStop() Decoratee.Stop(); } + public override void Pause() + { + beforePauseState = currentState; + currentState = State.PAUSED; + + // only propagate Pause() on children when it was active + if (beforePauseState != State.ACTIVE) + { + return; + } + + foreach (Node child in Children) + { + if (child is Task task) + { + if (child.IsActive) + { + task.Pause(); + this.pausedChildren.Push(child); + } + } + else + { + child.Pause(); + if (child.CurrentState == State.PAUSED) + { + this.pausedChildren.Push(child); + } + } + } + StopObserving(); + } + + public override void Resume() + { + StartObserving(); + base.Resume(); + currentState = beforePauseState; + Evaluate(); + } + protected override void DoChildStopped(Node child, bool result) { Assert.AreNotEqual(this.CurrentState, State.INACTIVE); @@ -66,6 +108,10 @@ override protected void DoParentCompositeStopped(Composite parentComposite) protected void Evaluate() { + if (ParentNode.CurrentState == State.PAUSED) + { + return; + } if (IsActive && !IsConditionMet()) { if (stopsOnChange == Stops.SELF || stopsOnChange == Stops.BOTH || stopsOnChange == Stops.IMMEDIATE_RESTART) diff --git a/Scripts/Decorator/Service.cs b/Scripts/Decorator/Service.cs index 6f5bc0e9..2e1e5f9f 100644 --- a/Scripts/Decorator/Service.cs +++ b/Scripts/Decorator/Service.cs @@ -31,6 +31,35 @@ public Service(System.Action service, Node decoratee) : base("Service", decorate } protected override void DoStart() + { + startService(); + Decoratee.Start(); + } + + override protected void DoStop() + { + Decoratee.Stop(); + } + + protected override void DoChildStopped(Node child, bool result) + { + stopService(); + Stopped(result); + } + + public override void Pause() + { + base.Pause(); + stopService(); + } + + public override void Resume() + { + startService(); + base.Resume(); + } + + private void startService() { if (this.interval <= 0f) { @@ -46,15 +75,9 @@ protected override void DoStart() { InvokeServiceMethodWithRandomVariation(); } - Decoratee.Start(); - } - - override protected void DoStop() - { - Decoratee.Stop(); } - protected override void DoChildStopped(Node child, bool result) + private void stopService() { if (this.interval <= 0f) { @@ -68,9 +91,8 @@ protected override void DoChildStopped(Node child, bool result) { this.Clock.RemoveTimer(InvokeServiceMethodWithRandomVariation); } - Stopped(result); } - + private void InvokeServiceMethodWithRandomVariation() { serviceMethod(); diff --git a/Scripts/Decorator/TimeMax.cs b/Scripts/Decorator/TimeMax.cs index 954cb6b1..a30c9565 100644 --- a/Scripts/Decorator/TimeMax.cs +++ b/Scripts/Decorator/TimeMax.cs @@ -58,6 +58,18 @@ protected override void DoChildStopped(Node child, bool result) } } + public override void Pause() + { + base.Pause(); + Clock.RemoveTimer(TimeoutReached); + } + + public override void Resume() + { + base.Resume(); + Clock.AddTimer(limit, randomVariation, 0, TimeoutReached); + } + private void TimeoutReached() { if (!waitForChildButFailOnLimitReached) diff --git a/Scripts/Decorator/WaitForCondition.cs b/Scripts/Decorator/WaitForCondition.cs index 0c4c2d8d..7712177d 100644 --- a/Scripts/Decorator/WaitForCondition.cs +++ b/Scripts/Decorator/WaitForCondition.cs @@ -28,6 +28,23 @@ public WaitForCondition(Func condition, Node decoratee) : base("WaitForCon } protected override void DoStart() + { + addTimerOrStartImmediately(); + } + + public override void Pause() + { + base.Pause(); + Clock.RemoveTimer(checkCondition); + } + + public override void Resume() + { + addTimerOrStartImmediately(); + base.Resume(); + } + + private void addTimerOrStartImmediately() { if (!condition.Invoke()) { diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 83b440a6..fd464841 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -9,6 +9,7 @@ public enum State INACTIVE, ACTIVE, STOP_REQUESTED, + PAUSED, } protected State currentState = State.INACTIVE; @@ -138,6 +139,16 @@ public void Stop() #endif DoStop(); } + + public virtual void Pause() + { + + } + + public virtual void Resume() + { + + } protected virtual void DoStart() { @@ -148,8 +159,7 @@ protected virtual void DoStop() { } - - + /// THIS ABSOLUTLY HAS TO BE THE LAST CALL IN YOUR FUNCTION, NEVER MODIFY /// ANY STATE AFTER CALLING Stopped !!!! protected virtual void Stopped(bool success) @@ -198,7 +208,7 @@ protected virtual void DoParentCompositeStopped(Composite composite) override public string ToString() { - return !string.IsNullOrEmpty(Label) ? (this.Name + "{"+Label+"}") : this.Name; + return !string.IsNullOrEmpty(Label) ? (this.Name + "{" + Label + "}") : this.Name; } protected string GetPath() diff --git a/Scripts/Root.cs b/Scripts/Root.cs index 55acc1e1..61181efc 100644 --- a/Scripts/Root.cs +++ b/Scripts/Root.cs @@ -96,5 +96,11 @@ override protected void DoChildStopped(Node node, bool success) Stopped(success); } } + + public override void Pause() + { + Assert.AreEqual(this.currentState, State.ACTIVE, "Only an active tree can be paused."); + base.Pause(); + } } } diff --git a/Scripts/Task/Task.cs b/Scripts/Task/Task.cs index c644f56c..84894184 100644 --- a/Scripts/Task/Task.cs +++ b/Scripts/Task/Task.cs @@ -5,5 +5,15 @@ public abstract class Task : Node public Task(string name) : base(name) { } + + public override void Pause() + { + Stop(); + } + + public override void Resume() + { + Start(); + } } } \ No newline at end of file