【问题标题】:How to spawn evenly spaced game objects and move them in a circular path?如何生成均匀分布的游戏对象并沿圆形路径移动它们?
【发布时间】:2020-01-06 17:27:12
【问题描述】:

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

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

我也尝试在 circle 类上使用 FixedUpdate,但没有解决问题。

我不知道我还要做什么。

实际行为:

预期行为:

轨道等级:

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

【问题讨论】:

  • 我认为你不能像现在这样重用 Wait___ 对象。
  • @Draco18s 是的,你可以!我确实缓存了我在项目中到处使用的单个 WaitForEndOfFrame() var,它工作得非常好,所以我猜任何 Waiter 都应该是一样的
  • @user14492 我添加了实际和预期行为的链接
  • @Jichael WaitForEndOfFrame 将具有与 WaitForSeconds 不同的条件逻辑(它必须跟踪它的存在时间)。但我只是给它一个测试,它看起来确实有效。好吧,不知道 Unity 是如何知道重置时间的,但无论如何。
  • @Ruzihm 问题中的代码

标签: c# unity3d coroutine lerp


【解决方案1】:

一个问题是您的圈子可能会超出您的目的地,因为您使用的是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永远解析为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

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

  1. 在当前帧结束时开始等待。如果您在长帧中以持续时间“t”启动 WaitForSeconds(例如,具有阻塞主线程的长操作,例如某些同步加载),协程将在帧结束后返回“t”秒,不是在它被调用后的“t”秒。

  2. 允许协程在 't' 秒过后的第一帧恢复,而不是正好在 't' 秒过后。

因此,由于 Unity 通常会在 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;

}

【讨论】:

  • @WaelElSawy 我添加了逻辑以确保在框架中移动的全部距离都被移动。让我知道这是否能解决问题。
  • 我现在就试试,但是在while 函数内运行while 循环是否有效?
  • 我试过你的代码。它导致圆圈之间的距离增加,但第一个和最后一个圆圈变得如此接近。
  • 不,Vector2.Distance(from, to); 做了同样的行为。
  • 完美!太棒了!,非常感谢你:)
猜你喜欢
  • 2021-11-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-22
  • 1970-01-01
  • 2020-09-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多