如何使游戏对象沿着贝塞尔曲线在 Unity 中按顺序跟随路径

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

我有一个沿着贝塞尔曲线移动的对象,但我有多个对象需要按顺序沿着这条路径移动,但它们都遵循相同的路径time.The对象是[=中的蛇类敌人21=]射击游戏。

到目前为止,我已经尝试将所有对象设为子对象,但在这样做时,当遵循贝塞尔曲线时,它们与父对象保持在一条直线上。 我还将所有对象分开并将贝塞尔脚本附加到这些对象上,以便它们都遵循相同的路线并且这有效,但只有它们同时遵循相同的路径。

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;

}}

这是路由脚本,我认为您不需要,但会包含它

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));
}}

我认为我需要做的是让每个对象都不是子对象,并且都附加了脚本以跟随路线,但在它沿着路径前进之前也有延迟时间但不确定如何进行这个。我在想这可能需要在一个单独的脚本中完成,因为在贝塞尔曲线脚本中它被设置为一旦到达终点,对象就会在路线的起点再次开始

这种方法怎么样:

  1. BezierCurve 附加到所有需要该行为的游戏对象。不要保持任何 Parent-Child 关系。
  2. 防止 BezierCurve 自动变为 follow/start。保留一个布尔值以备不时之需。
  3. 创建一个新脚本 BezierCurveBatch,附加到主(父)游戏对象并让 List 包含子对象的引用。在同一个脚本中,保持 float,假设 delayStartCurve 来管理两个 BezierCurve 开始之间的时间。
  4. 在 BezierCurveBatch 中,在每个 delayStartCurve.
  5. 之后从 List children 开始 BezierCurve

我提供了演示脚本代码。未经测试,但应该可以。

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;
        }
    }
}

我知道您要实现的目标,我相信您可以在不更改当前代码的情况下使用额外的脚本来实现。

我在这里制作了一个名为 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);
        }
    }        
}}

将此脚本附加到您打算使用该路径的所有游戏对象,并且不要忘记在检查器中为这些对象分配路径。

如果是 2D 游戏,您可能需要调整方向以满足您的需要,但这应该适合您。 让我知道你的进展如何,随时直接与我联系。