"OnExit" behaviour

ManaPotionStudios

New member
Hey everyone, I looked around and can't find a solution to my problem. I feel like I am missing something, because it looks to me that it is a common issue.

So what I need is: lets say I have a build house sequence for a character. First character changes his skin model for a builder and proceeds to build a building.
Then sequence aborts by conditional abort from more important task, and what I want to do is to have some kind of OnExit post processing where I restore character skin, move speed, or even call a subtree with all this restore actions.

Is there a way to do this?

Thanks!

bt on exit.png
 
For this the best way to handle it is to add a "reset" task at the start of the branch. This will allow you to restore the state after an abort and is the same approach that I take with the Deathmatch AI Kit:

 
I dont really find this approach a good way to reset things. As you need reset everything possible when starting a branch, wich could be an overkill. Or you need to track what variables need to be reseted for particular branch, what can also can be a nightmare to follow.

I like more clean approach when you have reset on branch exit, and branch restores only what it changed. This way you dont need to worry each time: "do I need to reset move speed? Could it be changed when entering this branch"

What I endup creating is a modified Sequence when last child called only on exit, and it could be Action or Sequence. Not the best way I wanted but I like it more then reset on enter. Check it out:

C#:
using UnityEngine;

namespace BehaviorDesigner.Runtime.Tasks
{
    [TaskDescription("The sequence task is similar to an \"and\" operation. It will return failure as soon as one of its child tasks return failure. " +
                     "If a child task returns success then it will sequentially run the next task. If all child tasks return success then it will return success.")]
    [TaskIcon("{SkinColor}SequenceIcon.png")]
    public class RunLastChildOnExit : Composite
    {
        public bool lastChildOk;

        // The index of the child that is currently running or is about to run.
        private int currentChildIndex = 0;
        // The task status of the last child ran.
        private TaskStatus executionStatus = TaskStatus.Inactive;

        public override int CurrentChildIndex()
        {
            return currentChildIndex;
        }

        public override bool CanExecute()
        {
            //execute up to before last child
            return currentChildIndex < children.Count - 1 && executionStatus != TaskStatus.Failure;
        }

        public override void OnChildExecuted(TaskStatus childStatus)
        {
            // Increase the child index
            currentChildIndex++;
            
            if (currentChildIndex < children.Count - 1)
            {
                executionStatus = childStatus;
            }
        }

        public override void OnConditionalAbort(int childIndex)
        {
            // Set the current child index to the index that caused the abort
            currentChildIndex = childIndex;
            executionStatus = TaskStatus.Inactive;
        }

        public override TaskStatus OverrideStatus(TaskStatus status)
        {
            return executionStatus;
        }

        public override void OnEnd()
        {
            // All of the children have run. Reset the variables back to their starting values.
            executionStatus = TaskStatus.Inactive;
            currentChildIndex = 0;

            RunLastChild();

            base.OnEnd();
        }

        private void RunLastChild()
        {
            if (children.Count <= 1) return;

            var lastChild = children[children.Count - 1];

            if (lastChild is ParentTask)
            {
                if (lastChild is Sequence)
                {
                    ParentTask parentTask = (ParentTask)lastChild;
                    foreach (var parentTaskChild in parentTask.Children)
                    {
                        RunChildTask(parentTaskChild);
                    }
                }
                else
                {
                    Debug.LogError("OnExitSequence support only Sequence as on exit ParentTask: " + lastChild);
                }
            }
            else
            {
                RunChildTask(lastChild);
            }
        }

        private static void RunChildTask(Task lastChild)
        {
            lastChild.OnStart();
            var status = lastChild.OnUpdate();
            lastChild.OnEnd();

            if (status == TaskStatus.Running)
            {
                Debug.LogError("OnExitSequence doesn't support Running Tasks: " + lastChild);
            }
        }
    }
}
 
Last edited:
That's not a bad approach. I am working on version 2 right now which is a DOTS based approach and it will work with regular MonoBehaviours as well. For that version I am thinking of having event branches that will execute specifically when an event is triggered, such as a conditional abort. This will allow you to then clean things up.

With that said, I haven't implemented this yet so I'm not sure if it will work. Version 2 is still a long ways out and I don't have an ETA.
 
That's not a bad approach. I am working on version 2 right now which is a DOTS based approach and it will work with regular MonoBehaviours as well. For that version I am thinking of having event branches that will execute specifically when an event is triggered, such as a conditional abort. This will allow you to then clean things up.

With that said, I haven't implemented this yet so I'm not sure if it will work. Version 2 is still a long ways out and I don't have an ETA.

Sounds great! We are using DOTS for our AI agents and current version of Behaviour Trees works pretty fast too. A trick I did is for example movement script sets some flag to true, and then later in frame DOTS System run in parallel on all agents that need to move. This way I can distribute huge CPU load while still using BT as AI controlling an agent. Also you could use burstable .Run() jobs in behaviours if you can't parallel the code and still got some good performance.

Any way looking forward for a new version some time in future!

Is there a way to automatially color last child of my custom RunLastChildOnExit, to visially indicate OnExit sequence/action in editor?
 
Top