David Marsh
David Marsh

Reputation: 147

How to make game objects follow a path in order in Unity along a bezier curve

I have a single Object moving along a Bezier Curve but i have multiple objects that need to follow this path in order but they all follow at the same time.The object is a snake type enemy in a space shooter game.

I have so far tried to make all objects children but in doing this they remain in a straight line to the parent when follow the Bezier Curve. I have also made all objects separate and attached the Bezier Script to these so they all follow the same route and this works but only they follow the same path at the same time.

public class BezierFollow : MonoBehaviour{

[SerializeField]
private Transform[] routes;

private int routeToGo;

private float tParam;

private Vector2 enemyPosition;

[SerializeField]
public float speedModifier = 0.5f;

private bool coroutineAloud;


// Start is called before the first frame update
void Start()
{
    routeToGo = 0;
    tParam = 0f;
    //speedModifier = 0.5f;
    coroutineAloud = true;
}

// Update is called once per frame
void Update()
{
    if (coroutineAloud)
    {
        StartCoroutine(GoByTheRouteRoutine(routeToGo));
    }
}

private IEnumerator GoByTheRouteRoutine(int routeNumber)
{
    coroutineAloud = false;

    Vector2 p0 = routes[routeNumber].GetChild(0).position;
    Vector2 p1 = routes[routeNumber].GetChild(1).position;
    Vector2 p2 = routes[routeNumber].GetChild(2).position;
    Vector2 p3 = routes[routeNumber].GetChild(3).position;

    while(tParam < 1)
    {
        tParam += Time.deltaTime * speedModifier;

        enemyPosition = Mathf.Pow(1 - tParam, 3) * p0 +
            3 * Mathf.Pow(1 - tParam, 2) * tParam * p1 +
            3 * (1 - tParam) * Mathf.Pow(tParam, 2) * p2 +
            Mathf.Pow(tParam, 3) * p3;

        transform.position = enemyPosition;
        yield return new WaitForEndOfFrame();
    }

    tParam = 0f;

    routeToGo += 1;

    if(routeToGo > routes.Length - 1)
        routeToGo = 0;

    coroutineAloud = true;

}}

Here is the routes script i don't think you will need but will include this

public class Route : MonoBehaviour{
[SerializeField]
private Transform[] controlPoints;

private Vector2 gizmosPos;

private void OnDrawGizmos()
{
    for(float t = 0; t <= 1; t += 0.05f)
    {
        gizmosPos = Mathf.Pow(1 - t, 3) * controlPoints[0].position +
            3 * Mathf.Pow(1 - t, 2) * t * controlPoints[1].position +
            3 * (1 - t) * Mathf.Pow(t, 2) * controlPoints[2].position +
            Mathf.Pow(t, 3) * controlPoints[3].position;

        Gizmos.DrawSphere(gizmosPos, 0.25f);
    }

    Gizmos.DrawLine(new Vector2(controlPoints[0].position.x, controlPoints[0].position.y),
        new Vector2(controlPoints[1].position.x, controlPoints[1].position.y));

    Gizmos.DrawLine(new Vector2(controlPoints[2].position.x, controlPoints[2].position.y),
       new Vector2(controlPoints[3].position.x, controlPoints[3].position.y));
}}

I think what i need to do is have each object not be a child object and all have the script attached to follow the route but also have a delayed time before it follows along the path but not sure how to go about this. I was thinking this might need to be done in a separate script because in the bezier curve script it is set so the object starts again at the beginning of the route once reaches the end

Upvotes: 1

Views: 20677

Answers (2)

mard dean
mard dean

Reputation: 242

I Know what you are trying to achieve and i do believe you can do this with an extra script without changing your current code.

Here i have made a new script called EnemyBehavior.

public class EnemyBehavior : MonoBehaviour{
public Path pathToFollow;

//PATH INFO
public int currentWayPointID = 0;

//speeds
public float speed = 2;
public float reachDistance = 0.4f;
public float rotationSpeed = 5f;

float distance; //DISTANCE TO NEXT PATH POINT

public bool useBezier = false;

//STATE MACHINES
public enum EnemyStates
{
    ON_PATH,        
    IDLE
}
public EnemyStates enemyState;

public int enemyID;

void Update()
{
    switch (enemyState)
    {
        case EnemyStates.ON_PATH:
            MoveOnThePath(pathToFollow);
            break;            
        case EnemyStates.IDLE:

            break;
    }
}

void MoveToFormation()
{
    //transform.position = Vector3.MoveTowards(transform.position, formation.gridList[enemyID], speed * Time.deltaTime);
    //if(Vector3.Distance(transform.position, formation.gridList[enemyID])<= 0.001f)
    {
        //transform.SetParent(formation.gameObject.transform);
        transform.eulerAngles = Vector3.zero;
        enemyState = EnemyStates.IDLE;
    }
}

void MoveOnThePath(Path path)
{
    if (useBezier)
    {
        //MOVING THE ENEMY
        distance = Vector3.Distance(path.bezierObjList[currentWayPointID], transform.position);
        transform.position = Vector3.MoveTowards(transform.position, path.bezierObjList[currentWayPointID], speed * Time.deltaTime);
        //ROTATION OF YOUR ENEMY
        var direction = path.bezierObjList[currentWayPointID] - transform.position;

        if (direction != Vector3.zero)
        {
            direction.z = 0;
            direction = direction.normalized;
            var rotation = Quaternion.LookRotation(direction);
            transform.rotation = Quaternion.Slerp(transform.rotation, rotation, rotationSpeed * Time.deltaTime);
        }
    }
    else
    {
        distance = Vector3.Distance(path.pathObjList[currentWayPointID].position, transform.position);
        transform.position = Vector3.MoveTowards(transform.position, path.pathObjList[currentWayPointID].position, speed * Time.deltaTime);

        //ROTATION OF ENEMY
        var direction = path.pathObjList[currentWayPointID].position - transform.position;

        if (direction != Vector3.zero)
        {
            direction.y = 0;
            direction = direction.normalized;
            var rotation = Quaternion.LookRotation(direction);
            transform.rotation = Quaternion.Slerp(transform.rotation, rotation, rotationSpeed * Time.deltaTime);
        }
    }        
}}

Attach this script to all the game objects that you intend to use the path and don't forget to assign the path on these in the inspector.

You may need to adjust the direction to suit your needs if it is a 2D game but this should work for you. Let me know how you get on and feel free to direct contact me.

Upvotes: 2

MicroEyes
MicroEyes

Reputation: 3742

How about this approach:

  1. Attach BezierCurve to all gameObject that need the behaviour. Don't keep any Parent-Child relationship.
  2. Prevent BezierCurve to follow/start automatically. Keep a bool to stop, in case you need.
  3. Create a new script BezierCurveBatch, attach to main(Parent) gameObject and have List<BezierCurve> contains reference of children. In same script, keep a float, let's say delayStartCurve to manage time between two BezierCurve start.
  4. In BezierCurveBatch, start the BezierCurve from List<BezierCurve> children after every delayStartCurve.

I have provided a demo script code. Not tested but it should work.

public class BezierCurve
{
    //Starts following bezier curve.
    public void StartFollow()
    {
        //some code here.
    }
}

public class BezierCurveBatch : MonoBehaviour
{
    [SerializeField]
    List<BezierCurve> m_lstChildren;

    [SerializeField]
    float m_delayStartCurve = 10;
    float m_timeLeftToStartNextChild = 0;

    bool m_isRunBatchCurve = false;

    /// <summary>
    /// Start batch follow after each interval.
    /// </summary>
    public void StartBatch()
    {
        m_isRunBatchCurve = true;
    }

    private void Update()
    {
        if (!m_isRunBatchCurve)
            return;

        m_timeLeftToStartNextChild -= Time.deltaTime;
        if (m_timeLeftToStartNextChild <= 0.0f)
        {
            if (m_lstChildren.Count > 0) //if we have children left.
            {
                BezierCurve l_bCurveToStart = m_lstChildren[0];     //Getting top object.
                m_lstChildren.RemoveAt(0);                          //removing top object.
                l_bCurveToStart.StartFollow();                      //Start follow bezier curve
                m_timeLeftToStartNextChild = m_delayStartCurve;     //resetting time.
            }

            if (m_lstChildren.Count == 0)       //After processing last object, check if need to continue for next object.
                m_isRunBatchCurve = false;
        }
    }
}

Upvotes: 0

Related Questions