unitySteer + path for racing AI

Hello all,

I have a game that is using "canyon racer" scenario. Essentially vehicles are racing through valleys to get to checkpoints. After a player has reached all the checkpoints a lap will have been completed. I am using UnitySteer and AngryAnt's path to provide the AI. Everything is working great except for two issues:

the first is that once the vehicle has completed the first lap, he is "turned loose" from path and just flies in a straight line (eventually blowing up). What I need is an event that will tell me when my vehicle has completed the path given to it, so that it can cycle to the next destination and continue on through the race until all the laps have been completed.

The second issue is that have no idea how to setup the "pathfollower" vehicle in unitysteer, to better avoid obstacles or adhere to the path. I see the "weights" and have messed around with them (I don't know what the max/min values should be, I assume 0-1). I can tell a difference with the "pathFollowWeight" but not with the "obstacleAvoidanceWeight". I am using a slightly modified version of the script given here: http://answers.unity3d.com/questions/254/unitysteer-and-path only without the use of the "IRadarReceiver" (it was causing to many compile errors, and i don't know how to implement it correctly).

Any help would be greatly appreciated, even if it is just a link that will point me in the right direction. Thanks!

the first is that once the vehicle has completed the first lap, he is "turned loose" from path and just flies in a straight line (eventually blowing up).

Is it possible that you have StopOnArrival set to false?

What I need is an event that will tell me when my vehicle has completed the path given to it, so that it can cycle to the next destination and continue on through the race until all the laps have been completed.

I'd recommend declaring your own event, adding a delegate on the MonoBehavior which registers to it, then invoking them here. You'll see the vehicle is really simple, it should be easy to extend.

The second issue is that have no idea how to setup the "pathfollower" vehicle in unitysteer, to better avoid obstacles or adhere to the path.

Regarding obstacle avoidance, the PathFollower does a very basic one. You may of course want to tweak it, as it got only rudimentary testing. It also bears saying that better avoidance will depend entirely on your scenario.

The three major parameters are:

The values are multipliers clamped between 0 and float.MaxValue, so you could (for instance) set ObstacleAvoidance on 0.5f, NeighborAvoidance on 1 and PathFollow on 1.5f. As you can see on the lines preceding this one, the multipliers are then used before the steering force is applied.

ok so i finally found the answer to my question about how to keep my ship making laps around the track. There is a line where you instantiate the "AngryAntPathway" object, and pass in the constructer's parmeters. the last parameter passed is for the "cyclic" option. This has been set to "false" (i didn't initially even know this existed), once I set it to "true" my ship will effortlessly make laps around the track! Here is the script that I have been using:

using UnityEngine;
using System.Collections;
using PathLibrary;
using UnitySteer;
using UnitySteer.Vehicles;

public class AiControl : MonoBehaviour, ISearchMonitor{
//,  IRadarReceiver (I don't know how to implement the Radar yet)

protected ArrayList path;
protected Seeker seeker;
private PathFollower shipVehicle;
//~ private bool Moving = false;
private Vector3 destination;
private int currWaypoint = 4;
private bool canContinue = false;
private int timesCalled = 0;

public Transform[] waypoints;

public float radius = 5f;
public float maxSpeed = 5f;
public float maxForce = 5f;
public float pathFollowWeight = 1;
public float obstacleAvoidanceWeight = 1;
public float neighborAvoidanceWeight = 1;
public float minCollisionTime = 1;
public float maxFrameTime = 0.1f;
public Vector3 StartDest;

// Use this for initialization
void Start () {
    shipVehicle = new PathFollower(this.transform, 1, null, radius);
    //~ shipVehicle.MovesVertically = false;
    shipVehicle.MinCollisionTime = 2;
    shipVehicle.MaxSpeed = maxSpeed;
    shipVehicle.Mass = 0.1f;
    shipVehicle.MaxForce = maxForce;
    shipVehicle.AvoidDeg = 30;
    shipVehicle.PathFollowWeight = pathFollowWeight;
    shipVehicle.ObstacleAvoidanceWeight = obstacleAvoidanceWeight;
    shipVehicle.NeighborAvoidanceWeight = neighborAvoidanceWeight;
    shipVehicle.MinCollisionTime = minCollisionTime;
    shipVehicle.stopOnArrival = false;

    //~ StartDest = GameObject.FindWithTag("FinishPoint").transform.position;
    destination = waypoints[currWaypoint].transform.position;
    destination.y = 20;
    //~ GoTo(destination);
}

// Update is called once per frame
void Update () {
    Vector3 target, lineOfSightTarget;
    ConnectionAsset currentConnection, connection, lastConnection;

    if(seeker == null)
    {
        //~ Debug.Log("Seeker is null");
        //~ Debug.Log("waypoints: " + waypoints.length());
    }
    if (path == null){
        //~ Debug.Log("path is null");
        GoTo(destination);
        return;
    }

    if( path.Count > 0 )
    // Miles and miles to go...
    {
        currentConnection = ( ConnectionAsset )path[ 0 ];
        lastConnection = ( ConnectionAsset )path[ path.Count - 1 ];

        // Draw the current path //

        Debug.DrawLine( transform.position, currentConnection.To.AbsolutePosition, Color.white );

        for( int i = 1; i < path.Count; i++ )
        {
            connection = ( ConnectionAsset )path[ i ];
            Debug.DrawLine( connection.From.AbsolutePosition, connection.To.AbsolutePosition, Color.white );
        }

        Debug.DrawLine( lastConnection.To.AbsolutePosition, destination, Color.white );

        if( Vector3.Angle( transform.forward, currentConnection.To.AbsolutePosition - transform.position ) < 90.0f )
        // If the next node is in front of us...
        {
            target = transform.position + Vector3.Project( currentConnection.To.AbsolutePosition - transform.position, transform.forward * ( currentConnection.To.AbsolutePosition - transform.position ).magnitude );
                // ... see if we can remain on course

            if( !currentConnection.To.ContainsPoint( target ) )
            // ... if we can't ...
            {
                target = currentConnection.To.GetNearestPoint( transform.position, radius );
                    // ... set target to the nearest point in the node we're approaching
            }
        }
        else
        // Otherwise, just find the nearest point
        {
            target = currentConnection.To.GetNearestPoint( transform.position, radius );
        }
    }
    else
    // Home stretch!
    {
        target = destination;
            // Set the target to out destination
    }

    if(shipVehicle.arrived)
    {
        Debug.Log("Arrived");
        if(canContinue)
        {
            Debug.Log("Getting next destination");
            canContinue = false;
            path = null;
        }
    }

    shipVehicle.Update(Time.deltaTime);
}

public void GetNextDest(){
    Debug.Log("Times Called: " + (++timesCalled ));
    if(currWaypoint < waypoints.Length - 1){
            currWaypoint++;// = 0;
        Debug.Log("Next waypoint: " + currWaypoint);
        }else {
            currWaypoint = 0;//++;
            Debug.Log("Next waypoint (back to zero): " + currWaypoint);
        }
        Debug.Log("currWaypoint: " + currWaypoint);
        destination = waypoints[currWaypoint].transform.position;
        destination.y = 20;
        Debug.Log("GoToing: " + destination);
        seeker.Kill();
        path = null;
        //~ GoTo( destination );
        //~ Start();
}

public void GoTo(Vector3 destination)
{
    seeker = new Seeker( transform.position, destination, maxFrameTime, radius, null );
    if( seeker.Start != null && seeker.End != null )
    {
        //~ Debug.Log("start & end != null");
        shipVehicle.IsMoving = true;
        seeker.AddMonitor(this);
        Control.Instance.StartSeeker( seeker );
    }
}

public void OnSearchCompleted( Seeker seeker )
{
    //~ Debug.Log("Search Complete!");
    if( seeker != this.seeker )
    {
        Debug.LogError( "OnSearchCompleted: Not my seeker!" );
    }
    //~ this.seeker = null;
    this.path = seeker.Solution;
    Debug.Log( path.Count );
    this.destination = seeker.To;

    if (this.path == null) 
    {
        Debug.Log("path is still null");
        return;
    }

    if (seeker.Start != seeker.End)
    {
        shipVehicle.Pathway = new AngryAntPathway(path, 0.25f, true);
    }
    else
    {
        shipVehicle.IsMoving = false;
    }
    canContinue = true;
}

public void OnSearchFailed( Seeker seeker )
{
    if( seeker != this.seeker )
    {
        Debug.LogError( "OnSearchCompleted: Not my seeker!" );
        return;
    }

    Debug.LogError( "Search failed" );

    this.seeker.Kill();
    this.seeker = null;
}

public void OnSeekerInvalidated( Seeker seeker )
{
    if( seeker != this.seeker )
    {
        Debug.LogError( "OnSearchCompleted: Not my seeker!" );
        return;
    }

    //Debug.Log( "Seeker invalidated" );

    this.path = null;
    GoTo( seeker.To );
        // Narrow escape from whereever we are. We don't want to cache this.
}

}