如何生成均匀间隔的游戏对象并将它们沿圆形路径移动?

时间:2019-09-03 19:45:53

标签: c# unity3d coroutine lerp

我不知道为什么我的代码无法按预期工作!

当我将速度= 1时,它工作正常。但是,如果我提高速度,那是行不通的。

我也尝试在circle类上使用FixedUpdate,但是并不能解决问题。

我不知道我还要做什么。

实际行为: Link for actual behavior

预期的行为: Link for expected behavior

轨道舱:

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;

 public class Orbit : MonoBehaviour
 {
     public Circle circlePrefab;
     public Vector2 centerPoint = Vector2.zero;
     [Range(3, 360)] public int segments = 5;
     public float xRadius = 2f;
     public float yRadius = 2f;
     public int numberOfCircles = 0;
     public float speed = 0f;

     private Vector2 initPosition = new Vector2(0, 2f);
     private Vector2[] points;
     private List<float> distances = new List<float>();
     private float totalDistance = 0;

     // Start is called before the first frame update
     void Start()
     {
         points = new Vector2[segments];

         for (var i = 0; i < segments; i++)
         {
             Vector2 point = GetPathPoint(i / (float) segments);

             if (i > 0)
                 totalDistance += AddSegment(points[i - 1], point);

             points[i] = point;
         }

         totalDistance += AddSegment(points[segments - 1], points[0]);

         StartCoroutine(InitCircles());
     }

     private Vector2 GetPathPoint(float t)
     {
         var angle = t * 360f * Mathf.Deg2Rad;
         var x = Mathf.Sin(angle) * xRadius;
         var y = Mathf.Cos(angle) * yRadius;
         return new Vector2(centerPoint.x + x, centerPoint.y + y);
     }

     private float AddSegment(Vector2 from, Vector2 to)
     {
         float distance = (from - to).sqrMagnitude;
         distances.Add(distance);
         return distance;
     }

     private IEnumerator InitCircles()
     {
         yield return new WaitForSeconds(1);

         var time = new WaitForSeconds(totalDistance / speed / numberOfCircles);

         for (var i = 0; i < numberOfCircles; i++)
         {
             Circle circle = Instantiate(circlePrefab, initPosition, transform.rotation);
             circle.transform.parent = transform;
             circle.name = "circle " + i;
             circle.points = points;
             circle.distances = distances;
             circle.speed = speed;

             yield return time;
         }
     }
 }

圈子类别:

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;

 public class Circle : MonoBehaviour
 {
     public Vector2[] points;
     public float speed = 1f;
     public List<float> distances = new List<float>();

     private float distance = 0;
     private int currentIndex = 0;
     private float time = 0;
     private Vector2 currentPoint;
     private Vector2 nextPoint;

     // Start is called before the first frame update
     void Start()
     {
         currentPoint = points[currentIndex];
         nextPoint = points[currentIndex + 1];
     }

     // Update is called once per frame
     void Update()
     {
         if (transform.position == (Vector3) nextPoint)
         {
             currentIndex++;
             currentIndex %= distances.Count;

             time = 0;
             currentPoint = points[currentIndex];
             nextPoint = points[(currentIndex + 1) % points.Length];
         }

         time += Time.deltaTime;
         distance = time * speed / distances[currentIndex];
         transform.position = Vector2.Lerp(currentPoint, nextPoint, distance);
     }
 }

1 个答案:

答案 0 :(得分:2)

一个问题是,由于您使用的是Vector2.Lerp,因此您的圈子可能会超出目的地。相反,可以考虑找到您需要行驶的距离与到下一个点的距离之间的最小值,和/或使用Vector2.MoveTowards,以确保您永远不会超过目的地。

您还需要跟踪行驶和循环行驶的距离,直到覆盖了此框架所需的所有行驶距离为止:

 void Update()
 {
     float distanceToTravel = speed * Time.deltaTime;

     while (distanceToTravel > 0) 
     {
         if (transform.position == (Vector3) nextPoint)
         {
             currentIndex = (currentIndex + 1) % distances.Count;

             currentPoint = points[currentIndex];
             nextPoint = points[(currentIndex + 1) % points.Length];
         }

         float distanceThisIteration = Mathf.Min(distanceToTravel,
                 Vector2.Distance(transform.position, nextPoint));

         transform.position = 
                 Vector2.MoveTowards(transform.position, nextPoint, distanceThisIteration);

         distanceToTravel -= distanceThisIteration;
     }
 }

在问题代码中,当/如果您确实用Lerp超出了目的地,那么您将输入条件transform.position == (Vector3) nextPoint永远解析为{{1 }}。相反,使用false可以保证MoveTowards最终将解析为true(只要transform.position == (Vector3) nextPoint为非零!)。

此外,speed也不是计算距离的一种方法!使用Vector2.sqrMagnitudeVector2.magnitude代替:

Vector2.Distance(v1,v2)

最后一个问题是,使用 private float AddSegment(Vector2 from, Vector2 to) { float distance = Vector2.Distance(from, to); distances.Add(distance); return distance; } 时会出现四舍五入的错误。来自the documentation

  

有些因素可能意味着实际等待的时间与指定的时间不完全匹配:

     
      
  1. 在当前帧的末尾开始等待。如果您在较长的帧中以持续时间“ t”开始WaitForSeconds(例如,执行较长的操作会阻塞主线程,例如同步加载),则协程将在帧结束后返回“ t”秒,不是被调用后的“ t”秒。

  2.   
  3. 允许协程在经过“ t”秒之后的第一帧继续播放,而不是在经过“ t”秒之后的确切时刻。

  4.   

因此,由于Unity通常会在WaitForSeconds秒过去后的第一帧恢复协程,因此实际上它只增加了错误的几分之一秒。因此,每次连续产生t时,您都会累加该错误,从而导致第一个WaitForSeconds和最后一个Circle彼此非常接近。

要解决此问题,您可以创建一个协程,该协程将创建以WaitForSeconds开头的每个球体,所有球体都从同一帧开始:

private IEnumerator InitCircles()
{
    yield return new WaitForSeconds(1);


    for (var i = 0; i < numberOfCircles; i++)
    {
        StartCoroutine(WaitCreateCircle(i));
    }
}

private IEnumerator WaitCreateCircle(int index)
{
    var time = index * totalDistance / speed / numberOfCircles;

    yield return new WaitForSeconds(time);
    Circle circle = Instantiate(circlePrefab, initPosition, transform.rotation);
    circle.transform.parent = transform;
    circle.name = "circle " + index;
    circle.points = points;
    circle.distances = distances;
    circle.speed = speed;

}