0
\$\begingroup\$

I have a NavMesh agent that wanders around every x seconds, moves to a random position, and lastly it repeats the sequence. I made a test using a coroutine, which was called through a context menu method.

However, when I reimplemented the logic in a behavior tree, the random point was only generated the first time. In the following turns of the sequence, the random point was always the same. I'm using this library, called Fluid Behavior Tree. I'm really not sure what's wrong, since the logic from the coroutine and the logic from the nodes of the tree is quite literally the same.

The code from the Coroutine:

CheckForSafeDestinationPoint(); // Caculates the random point and sets it as the destination of the agent

// wait until the path is ready
while (agent.pathPending)
{
    yield return null;
}

if (agent.pathStatus == NavMeshPathStatus.PathComplete)
{
    Debug.Log("Path is valid");
    agent.isStopped = false;

    while (agent.GetPathRemainingDistance() > agent.stoppingDistance)
    {
        // Wait until the agent reaches its destination
        Debug.Log("Wandering");
        yield return null;
    }
}

yield break;

The code from the node of the tree (a leaf node called AgentDestination):

protected override void OnStart()
{
    // Literally the same as CheckForSafeDestinationPoint(), with the exception that the random point (the Destination) is first calculated on the parent tree a passed as a parameter to this node
    SetDestinationPoint(Destination);
}

// This code is executed on the Update method
protected override TaskStatus OnUpdate()
{
    while (agent.pathPending)
    {
        return TaskStatus.Continue;
    }

    if (agent.pathStatus == NavMeshPathStatus.PathComplete)
    {
        agent.isStopped = false;

        if (agent.GetPathRemainingDistance() > agent.stoppingDistance)
        {
            // This is the equivalent of yield return null
            return TaskStatus.Continue;
        }
    }

    // Once the node succeeded, the tree leaves this node
    return TaskStatus.Success;
}

The previous logic is later implemented trough the tree:

void Awake()
{
    agent = GetComponent<NavMeshAgent>();

    tree = new BehaviorTreeBuilder(gameObject)
            .Sequence()
                .Wait(waitTicks)
                // Here we pass in the random calculated point as a parameter
                .AgentDestination("RandomDestination", ComputeRandomDestination())
            .End()
        .Build();
}

void Update()
{
    tree.Tick();
}

I have my theories, though I'm not entirely sure about them. First, I think that every time I call the coroutine, a new instance is created, thus creating with it a new random point, as expected. However, I may be confusing the instance creating with the string-based call of the coroutine. Since the tree is created on the Awake() method, the ComputeRandomDestination may be getting cached somehow. However, I think this doesn't make a lot of sense since the agent can follow a moving target, which means that it can calculate a new path every frame, so in theory, it should be capable of calculating a random point more than once, right?

Anyway, point is, I have no idea what's wrong. All I know is that the leaf node AgentDestination is, after the first turn, always going to the same spot. It's not calculating a new random point. The node is in fact exiting, so it seems that it's working as expected.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Hello. The parameter you pass into AgentDestination is not ComputeRandomDestination itself but the return value after calling it once. \$\endgroup\$
    – Mangata
    Jul 4 at 2:36
  • \$\begingroup\$ @Mangata Ahh ok ok, I see. I think I remember reading about something like that long time ago (something about passing parameters by value?), though my memory isn't very clear. May you explain what are you referring to with "...after calling it once"? I'm quite confused about that part \$\endgroup\$ Jul 4 at 2:51
  • 1
    \$\begingroup\$ It only executes once inside Awake(), and save the result value in the behavior tree and won't be called again. Maybe you should put ComputeRandomDestination() into OnStart() of AgentDestination. \$\endgroup\$
    – Mangata
    Jul 4 at 3:04
  • \$\begingroup\$ @Mangata Ahh, so the Awake() theory was right. It works now. Many, many thanks! \$\endgroup\$ Jul 4 at 3:19

1 Answer 1

0
\$\begingroup\$

So basically, the tree was being created on the Awake function, which by nature is caching everything I create inside of it.

The tree is not actually being updated in real-time, and by that I mean, that it isn't creating a new instance of itself on every frame (which would actually dynamically create the random data I was trying to create), the Update function was just reading the tree that was previously created and cached in Awake, which of course makes sense performance-wise.

And that's it, I just moved the function ComputeRandomDestination() to an Update-dependent method: the leaf (action) node has 3 states, Start, Update, and Exit. All of them runs on Update, so they're not cached, as it would on Awake.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .