如何使游戏对象沿着Bezier曲线在Unity中依次遵循路径

时间:2019-07-09 08:35:55

标签: c# unity3d bezier

我有一个沿着Bezier曲线移动的物体,但是我有多个物体需要按照此路径顺序移动,但它们都同时跟随。该物体是太空射击游戏中的蛇型敌人。 >

到目前为止,我一直试图使所有对象成为子对象,但是这样做时,按照贝塞尔曲线,它们与父对象保持一条直线。 我还使所有对象分离,并将Bezier脚本附加到这些对象上,以便它们都遵循相同的路径,并且可以工作,但只有它们同时遵循相同的路径。

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

我认为我需要做的是让每个对象都不是子对象,并且所有对象都附加脚本以遵循该路线,但是在沿路径执行之前还有一个延迟的时间,但是不确定如何执行此操作。我当时认为这可能需要在单独的脚本中完成,因为在贝塞尔曲线脚本中已对其进行了设置,因此一旦到达终点,对象便会在路线的起点再次开始

2 个答案:

答案 0 :(得分:1)

我知道您要实现的目标,并且我相信您可以使用额外的脚本来完成此任务,而无需更改当前代码。

在这里,我编写了一个名为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游戏,则可能需要调整方向以适合您的需求,但这应该适合您。 让我知道您的生活状况,并随时直接与我联系。

答案 1 :(得分:0)

这种方法怎么样:

  1. BezierCurve 附加到所有需要该行为的游戏对象。不要保持任何亲子关系。
  2. 防止 BezierCurve 自动关注/开始。如有必要,请保持停顿状态。
  3. 创建一个新脚本 BezierCurveBatch ,将其附加到main(Parent)gameObject上,并让 List 包含子代的引用。在同一脚本中,保持 float ,例如 delayStartCurve 来管理两次 BezierCurve 启动之间的时间。
  4. 在BezierCurveBatch中,在每个 delayStartCurve 之后,从 List 子项启动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;
        }
    }
}