如何使用 C# 创建一个对象以统一在两个位置之间移动,为什么我的代码不起作用?

How do I create an object to move between two positions in unity, using C#, and why is my code not working?

我这里有一段代码,应该使块对象在 startPosendPos 对象之间移动,但它有问题,我不知道是什么。

void FixedUpdate()
{
    if (block.transform.position == startPos.transform.position)
    { 
        check = false; 
    }

    if(block.transform.position == endPos.transform.position)
    { 
        check = true;  
    }

    if (check == false)
    {
        block.transform.position = Vector3.Lerp(block.transform.position, endPos.transform.position, .03f);
    }

    if (check == true)
    { 
        block.transform.position = Vector3.Lerp(block.transform.position, startPos.transform.position, .03f);     
    }
}

在某个时候块将到达 endPos,然后在返回 startPos 的途中它将停止,因为函数将同时执行。但这怎么可能,因为我的假设是正确的,不应该允许这种情况发生?

Vector3.Lerp 的第三个参数是前两个参数之间距离的百分比。当你传入 0.03f 时,你会得到 3% 更接近 到你的最终位置,但实际上永远不会完全(你可以通过记录方块的位置和目标的位置来证明这一点,你会发现它们永远不会完全相等。

而不是用 == operator (which works to an accuracy of 0.00001f) to the target position, you could simply check if it's close enough with Vector3.Distance:

检查你方块的位置
if(Vector3.Distance(block.transform.position, endPos.transform.position) < 0.1f) {

您可以将 "close enough threshold"(示例中的 0.1f)命名为变量,以便于调整直到感觉合适为止。

一般来说,您应该始终使用

而不是

除了 你正在以某种方式处理 Physics (这里似乎不是这种情况)。另见 Update and FixedUpdate Tutorial


Vector3.Lerp 的问题在于它的行为与您预期的不同。

我猜你喜欢它启动快然后变成 "smooth" ... 但实际上这可能是你的问题。

从未真正到达目标位置。它越来越近,越来越慢......

...直到某个时刻,精度为 0.00001f== 最终变为真。

所以看起来它似乎停止了,但实际上它可能仍在移动,只是非常非常慢。


对于以下两个选项,您必须稍微决定要控制的内容:

  1. 选项:速度

    如果您想要对象的线速度,您应该使用

    // adjust via the Inspector
    [SerializeField] private float moveSpeedInUnityUnitPerSecond = 1f;
    
    // you should use Update here in general
    void Update()
    {
        if (block.transform.position == startPos.transform.position)
        { 
            check = false; 
        }
        // always use else in cases where only on condition can be
        // true at the same time anyway
        else if(block.transform.position == endPos.transform.position)
        { 
            check = true;  
        }
    
        block.transform.position = Vector3.MoveTowards(block.transform.position, check ? startPos.transform.position : endPos.transform.position, Time.deltaTime * moveSpeed);
    }
    
  2. 选项:时长 如果您更希望平稳运动控制达到目标所需的持续时间,您应该使用Lerp但有一个因素视时间而定

    // adjust via the Inspector
    [SerializeField] private float moveDurationInSeconds = 1f;
    
    private float passedTime;
    
    // you should use Update here in general
    void Update()
    {
        // prevent overshooting
        passedTime += Mathf.Min(moveDurationInSeconds - passedTime, Time.deltaTime);
    
        if(passedTime >= moveDurationInSeconds)
        {
            check = !check;
            passedTime = 0;
        }
    
        var lerpFactor = passedTime / moveDurationInSeconds;
        // and now add ease-in and ease-out
        var smoothedLerpFactor = Mathf.SmoothStep(0, 1, lerpFactor);
    
        var fromPosition = check ? endPos.transform.position : startPos.transform.position;
        var toPosition = check ? startPos.transform.position : endPos.transform.position;
    
        block.transform.position = Vector3.Lerp(fromPosition, toPosition, smoothedLerpFactor);
    }
    

    为此,您还可以使用 Coroutine,这通常更容易解释和维护:

    // adjust via the Inspector
    [SerializeField] private float moveDurationInSeconds = 1f;
    
    // yes you see correctly one can directly use the Start
    // as a Coroutine
    private IEnumerator Start()
    {
        var fromPosition = startPos.transform.position;
        var toPosition = endPos.transform.position;
    
        // looks strange but as long as you yield somewhere inside
        // the loop it simply means repeat the sequence forever
        // just like the Update method
        while(true)
        {
            var passedTime = 0f;
    
            while(passedTime < moveDurationInSeconds)
            {
                var lerpFactor = passedTime / moveDurationInSeconds;
                // and now add ease-in and ease-out
                var smoothedLerpFactor = Mathf.SmoothStep(0, 1, lerpFactor);
    
                block.transform.position = Vector3.Lerp(fromPosition, toPosition, smoothedLerpFactor);
    
                passedTime += Mathf.Min(moveDurationInSeconds - passedTime, Time.deltaTime);
    
                // reads like: "pause" here, render this frame and continue
                // from here in the next frame
                yield return null;
            }
    
            // once reached flip the positions
            var temp = fromPosition;
            fromPosition = toPosition;
            toPosition = temp;
        }
    }
    

    在这两种情况下,您仍然可以增加更多的灵活性,而不是简单地使用 moveDurationInSeconds 使用

    var fixedDuration = moveDurationInSeconds * Vector3.Distance(fromPosition, toPosition);
    

    这样,如果位置靠得更近,运动时间会更短,如果位置更远,运动时间会更长。这与您之前使用的有关运动平滑度的 Lerp 非常接近,但您可以很好地控制运动需要多长时间。

对于初学者来说,Update() 循环每一帧。 FixedUpdate() 取决于时间设置中设置的每秒帧数。所以,请将您的代码放入 Void Update() 中。

如果您参考 Vector3.MoveTowards 文档,您可能会找到更好的方法来解决您的问题。

下面的脚本应该可以解决问题。目标变量应设置为终点的位置。速度是你的物体移动的速度。

在 Update() 内部,step 变量用于根据对象的速度和自上次移动后经过的时间来确定对象应该移动多远。最后,最后一行更改对象的位置并记录新位置。

public Transform target;
public float speed;

void Update() {
    float step = speed * Time.deltaTime;
    transform.position = Vector3.MoveTowards(transform.position, target.position, step);
}