NodeLink2 with custom movement scheme

Hi, and first of all congratulations for the great project.

I’m currently working on a game where the characters move with the Kinematic Character Controller, and I wanted to implement custom movements when the AI is using the NodeLinks. My idea was to use the links for the AI to know and calculate its path properly, while handling the normal movement using the steeringTarget property from the FollowerEntity as to set the input direction of the controller.

So far this has worked perfectly, but I’m now implementing custom logics and movements with the NodeLinks and I find myself in a pinch: the character properly goes to the NodeLink and uses it, which triggers my custom logic for, in this case, jumping towards the end of the link. But then when the AI has to move again, its steeringTarget is set towards the beginning of the link because it still thinks that it needs to traverse it.

I found a working solution on changing the FollowerEntity.ClearPath function to public and calling it from outside as the character lands, but this doesn’t seem to be the best approach.

Is there a more proper way to tell the agent that its current link has been succesfully traversed? Thank you for the help in advance!

Hi

Are you using the approach outlined in Off-mesh links - A* Pathfinding Project ?

Thanks for such a fast response :smiley:

I’m following it, but I handle the movement myself. In this case, when the agent uses the NodeLink I then stop reading its steeringTarget and simply set its fake inputs towards the relative end of the link. When the controller detects that the player is about to fall, I trigger its jump and once they land I return to read its steeringTarget.

I debugged why the steeringTarget was still set towards the beginning of the link and I found that it seems that they still think they’re using it. Am I understanding something wrong? I’m using the FollowerEntity together with AIDestinationSetter, but I simply ignore the agent trying to move and then I say to the controller that a fake player is moving its inputs towards the steeringTarget.

Could you post your code?

When I’m on the MoveTowards state:

if (_destinationSetter.target == null && m_Target.Value != null)
{
    _destinationSetter.target = m_Target.Value.transform;
}

var direction = (_agent.steeringTarget - transform.position).normalized;
if (_agent.reachedDestination || _agent.reachedEndOfPath || direction == Vector3.zero)
{
    _controller.SetDirectionAndRotation(Vector3.zero);
    return TaskStatus.Success;
}

CheckMoveSpeed();
if (_blockToWalkSpeed)
{
    direction.x = Mathf.Clamp(direction.x, -_controller.m_playerKinematicModel.RunningInputThreshold, _controller.m_playerKinematicModel.RunningInputThreshold);
    direction.z = Mathf.Clamp(direction.z, -_controller.m_playerKinematicModel.RunningInputThreshold, _controller.m_playerKinematicModel.RunningInputThreshold);
}

_controller.SetDirectionAndRotation(direction);

Then, when using the link:

void OnEnable() => GetComponent<NodeLink2>().onTraverseOffMeshLink = this;
void OnDisable() => GetComponent<NodeLink2>().onTraverseOffMeshLink = null;

public IOffMeshLinkStateMachine GetOffMeshLinkStateMachine(AgentOffMeshLinkTraversalContext context)
{
    return this;
}

void IOffMeshLinkStateMachine.OnFinishTraversingOffMeshLink(AgentOffMeshLinkTraversalContext context)
{
    Debug.Log("An agent finished traversing an off-mesh link");
}

IOffMeshLinkStateMachine IOffMeshLinkHandler.GetOffMeshLinkStateMachine(AgentOffMeshLinkTraversalContext context)
{
    context.gameObject.GetComponent<BehaviorDesigner.Runtime.BehaviorTree>().SendEvent<object>("Link", context.link.relativeEnd);
    return this;
}

And finally, when jumping towards the end of the link:

public override void OnStart()
{
    base.OnStart();
    _controller = GetComponent<AIController>();
    _agent = GetComponent<FollowerEntity>();

    Vector3 direction = (m_linkEndPoint.Value - transform.position).normalized;
    _controller.SetDirectionAndRotation(direction);
    _controller.ExecuteJump();
}


public override TaskStatus OnUpdate()
{
    if (!_controller.isActiveAndEnabled)
    {
        return TaskStatus.Inactive;
    }

    if (_controller.m_KinematicCharacterMotor.GroundingStatus.IsStableOnGround)
    {
        if (m_stopWhenClose.Value)
        {
            _controller.SetDirection(Vector3.zero);
        }
        _agent.ClearPath();
        return TaskStatus.Success;
    }

    return TaskStatus.Running;
}

You are not implementing the IOffMeshLinkStateMachine.OnTraverseOffMeshLink method. That means it will fall back on the default behavior, which your other code is explicitly ignoring.

You will need to implement this coroutine. When the coroutine finishes, the FollowerEntity will treat the off-mesh link as finished.

I just tested it and it works perfectly. I wasn’t implementing IOffMeshLinkStateMachine.OnTraverseOffMeshLink because I didn’t want to use a coroutine for such a small piece of code, I assumed that by simply using IOffMeshLinkHandler.GetOffMeshLinkStateMachine it would work just fine.

Thanks for such a fast response! :laughing: