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

How to spawn evenly spaced game objects and move them in a circular path?

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

当我设置 speed = 1 时,它工作正常。但是如果我提高速度,它就不起作用了。

我也尝试在圈子 class 上使用 FixedUpdate,但没有解决问题。

我不知道我还需要做什么。

实际行为:

预期行为:

轨道Class:

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

圆class:

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

一个问题是您的圈子可能会超出您的目的地,因为您正在使用 Vector2.Lerp。相反,请考虑使用 Vector2.MoveTowards 找到您需要行驶的距离与到下一个点的剩余距离 and/or 之间的最小值,以保证您永远不会超过目的地。

您还需要记录您需要行进和循环的距离,直到覆盖此帧需要行进的所有距离:

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

在问题的代码中,when/if你确实用Lerp超越了你的目的地,那么你将进入一个条件transform.position == (Vector3) nextPoint永远 解析为 false。使用 MoveTowards 可以保证 transform.position == (Vector3) nextPoint 最终会解析为真(只要 speed 不为零!)。

此外,Vector2.sqrMagnitude 不是可接受的距离计算方式!使用 Vector2.magnitudeVector2.Distance(v1,v2) 代替:

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

最后一个问题是使用WaitForSeconds时出现舍入类型的错误。来自 the documentation:

There are some factors which can mean the actual amount of time waited does not precisely match the amount of time specified:

  1. Start waiting at the end of the current frame. If you start WaitForSeconds with duration 't' in a long frame (for example, one which has a long operation which blocks the main thread such as some synchronous loading), the coroutine will return 't' seconds after the end of the frame, not 't' seconds after it was called.

  2. Allow the coroutine to resume on the first frame after 't' seconds has passed, not exactly after 't' seconds has passed.

因此,由于 Unity 通常会在 t 秒后的第一帧恢复协程,因此它实际上会增加几分之一秒的错误。所以,每次你连续 yield 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;

}