2D Weird movement

I have attached some images regarding my issue, basically whenever a repath happens the NPC has some weird angled start point in which he has to change directions to get back on path before moving to the target. It causes flickers in my sprites movements toward it’s target. Any idea on what I can do to fix this?

I am using a grid graph.

Thanks,
BJ Moreton

Image 1: NPC’s Repathed Path

Image 2: NPC moves downward first to(I assume) get on path

Image 3: NPC then moves to the right(instead of the left)?

Image 4: NPC moves correctly towards target again until a repath happens and this loop starts over

Can anyone suggest anything that will help? :frowning:

Hi

What movement script are you using?
Generally this type of problem happens because path calculation takes a frame or so, so by the time the path is returned, the character has moved a small distance. If the movement script then tries to move to the start point of the path, it will turn around move a tiny distance and then turn around again to continue along the path. Are you using a custom movement script? If so you may need to add some code to handle this case better.

This is the movement script I use, I took some elements from the AIPath.cs and modified them a bit to work with what I needed. Mainly in FixedUpdate.

using UnityEngine;
using Pathfinding;
using System.Collections;

public class EnemyBase : MonoBehaviour
{
public float HP;
public float mv_speed = 10;
public float maxWaypointDistance = 2;
public Transform target;

// Path Finding
public float repathRate = 0.5F;
public bool canSearch = true;
private bool canSearchAgain = true;
private float lastRepath = -9999;

//
private Rigidbody2D rigidBody;
private Vector3 startTarget;
private Animator anim;
private Seeker seeker;
private Path path;
private int currentWaypoint;
private Vector3 prevPosition;
private Vector3 movementDirection;
private int lastCheck;
private bool atTarget = false;

// new code(1/2/2016)
private bool startHasRun = false;

// Use this for initialization
void Start()
{
    startHasRun = true;
    enablePathFinder();
}

void enablePathFinder()
{
    startTarget = target.position;
    anim = GetComponent<Animator>();
    seeker = GetComponent<Seeker>();
    rigidBody = GetComponent<Rigidbody2D>();

    if (startHasRun)
    {
        //Make sure we receive callbacks when paths complete
        seeker.pathCallback += OnPathComplete;

        StartCoroutine(RepeatTrySearchPath());
    }
}

public void OnPathComplete(Path p)
{
    ABPath _p = p as ABPath;
    if (_p == null) throw new System.Exception("This function only handles ABPaths, do not use special path types");

    canSearchAgain = true;
    _p.Claim(this);

    if (p.error)
    {
        Debug.Log(string.Format("EnemyBase::OnPathComplete->{0}", p.errorLog));
        _p.Release(this);
        return;
    }

    //Release the previous path
    if (path != null) path.Release(this);

    //Replace the old path
    path = p;

    //Reset some variables
    currentWaypoint = 0;
    startTarget = target.position;
    //atTarget = false;
}

/** Tries to search for a path every #repathRate seconds.
  * \see TrySearchPath
  */
protected IEnumerator RepeatTrySearchPath()
{
    while (true)
    {
        float v = TrySearchPath();
        yield return new WaitForSeconds(v);
    }
}

/** Tries to search for a path.
 * Will search for a new path if there was a sufficient time since the last repath and both
 * #canSearchAgain and #canSearch are true and there is a target.
 *
 * \returns The time to wait until calling this function again (based on #repathRate)
 */
public float TrySearchPath()
{
    if (Time.time - lastRepath >= repathRate && canSearchAgain && canSearch && target != null)
    {
        SearchPath();
        return repathRate;
    }
    else
    {
        //StartCoroutine (WaitForRepath ());
        float v = repathRate - (Time.time - lastRepath);
        return v < 0 ? 0 : v;
    }
}

/** Requests a path to the target */
public virtual void SearchPath()
{
    if (target == null) throw new System.InvalidOperationException("Target is null");

    lastRepath = Time.time;
    Vector3 targetPosition = target.position;

    canSearchAgain = false;

    //if(targetPosition != startTarget) { atTarget = false; }

    seeker.StartPath(this.transform.position, targetPosition);
}

void OnMouseDown()
{
    Debug.Log(string.Format("Mouse Click on {0}!", this.gameObject.name));
    PlayerBase player = GameObject.Find("Knight").GetComponentsInParent<PlayerBase>()[0];
    player.Target = this.gameObject;
}

void OnCollisionEnter2D(Collision2D coll)
{
    if (coll.gameObject.tag == "Player")
    {
        atTarget = true;
        rigidBody.isKinematic = true;
    } else if(coll.gameObject.tag == "Enemy")
    {

    }
    else { Debug.Log("EnemyBase::OnCollisionEnter2D " + coll.gameObject.tag); }
}

void OnCollisionExit2D(Collision2D coll)
{
    if (coll.gameObject.tag == "Player")
    {
        atTarget = false;
        rigidBody.isKinematic = false;
    } else if(coll.gameObject.tag == "Enemy")
    {

    }
    else { Debug.Log("EnemyBase::OnCollisionExit2D " + coll.gameObject.tag); }
}

void OnTriggerEnter2D(Collider2D other)
{
    if (other.tag == "Weapon")
    {
        PlayerBase player = other.gameObject.GetComponentsInParent<PlayerBase>()[0];
        if (player.Target == this.gameObject)
        {
            WeaponBase weapon = other.gameObject.GetComponentsInParent<WeaponBase>()[0];

            HP -= weapon.Damage;
            Debug.Log(string.Format("{0} HIT! - HP:{1}", this.gameObject.name, HP.ToString()));

            if (HP <= 0)
            {
                DestroyObject(this.gameObject);
            }
        }
    }
    else { Debug.Log("EnemyBase::OnTriggerEnter2D " + other.tag); }
}

void OnTriggerExit2D(Collider2D other)
{
    if (other.tag == "Weapon")
    {
        //
    }
    else { Debug.Log("EnemyBase::OnTriggerExit2D " + other.tag); }
}

void FixedUpdate()
{
    if (atTarget) { return; }

    if (path == null)
    {
        return;
    }

    if (currentWaypoint >= path.vectorPath.Count)
    {
        return;
    }

    Vector3 dir = ((path.vectorPath[currentWaypoint] - transform.position).normalized * mv_speed);

    transform.position = transform.position + dir;
    movementDirection = transform.position - prevPosition;
    movementDirection.Normalize();
    if (anim)
    {
        anim.SetFloat("X", Mathf.Clamp(Mathf.Round(movementDirection.x), -1, 1));
        anim.SetFloat("Y", Mathf.Clamp(Mathf.Round(movementDirection.y), -1, 1));
    }

    prevPosition = transform.position;

    if (Vector3.Distance(transform.position, path.vectorPath[currentWaypoint]) < maxWaypointDistance)
    {
        if (Vector3.Distance(transform.position, target.position) > (maxWaypointDistance - 1))
        {
            currentWaypoint++;
        }
    }
}

// Update is called once per frame
void Update()
{

}

}

Hi

Ok, the relevant part that you would need to add from AIPath is in the OnPathComplete method.

// Simulate movement from the point where the path was requested
// to where we are right now. This reduces the risk that the agent
// gets confused because the first point in the path is far away
// from the current position (possibly behind it which could cause
// the agent to turn around, and that looks pretty bad).
Vector3 p1 = Time.time - lastFoundWaypointTime < 0.3f ? lastFoundWaypointPosition : p.originalStartPoint;
Vector3 p2 = GetFeetPosition();
Vector3 dir = p2-p1;
float magn = dir.magnitude;
dir /= magn;
int steps = (int)(magn/pickNextWaypointDist);

#if ASTARDEBUG
Debug.DrawLine(p1, p2, Color.red, 1);
#endif

for (int i = 0; i <= steps; i++) {
	CalculateVelocity(p1);
	p1 += dir;
}

When trying to use anything else out of the AIPath script it causes my NPC’s to move downwards(at an accelerating speed) and not towards the target. Any idea why?

Nevermind I got it figured out, thanks for the point in the right direction I appreciate it. Thanks for making a wonderful asset am definitely looking into buying the pro. copy!

1 Like

Great that you figured it out :smile: