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

Posted

技术标签:

【中文标题】如何使用 C# 创建一个对象以统一在两个位置之间移动,为啥我的代码不起作用?【英文标题】:How do I create an object to move between two positions in unity, using C#, and why is my code not working?如何使用 C# 创建一个对象以统一在两个位置之间移动,为什么我的代码不起作用? 【发布时间】:2019-11-22 10:19:18 【问题描述】:

我这里有这段代码应该让块对象在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 的路上它会停止,因为这些函数将同时执行。但这怎么可能,因为我的 if 是正确的,不应该允许这种情况发生?

【问题讨论】:

为什么你认为这两个 lerp 函数都在执行? @Johnny Lerping 在startPosendPos 之间导致线性运动。当前位置和目的地之间的 Lerping 会产生一个移动平均线,看起来更平滑。 据我所知,我认为我应该首先从块当前位置到 endPos,然后回到 startPos,因为我使用的是 FixedUpdate 并且实际上会转换对象的位置每一帧,我错了吗? @Rotem 我明白了,删除评论 ;) tnx 但我不知道这些功能实际上是如何同时结束工作的??我的意思是,这怎么可能?? 【参考方案1】:

一般来说你应该一直使用

Update →调用每一帧

而不是

FixedUpdate →以特定的实时间隔调用

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


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

我猜你喜欢它开始快速然后变得“平滑”......但实际上这可能是你的问题。

永远不会真正到达目标位置。它只是越来越近,越来越慢......

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

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


对于以下两种选择,您必须确定要控制的内容:

    选项:速度

    如果你想为你应该使用的对象设置一个线速度

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

    选项:持续时间 如果您希望平稳移动控制持续时间达到目标所需的时间,您应该使用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 在运动平滑度方面非常接近,但您可以很好地控制运动需要多长时间。

【讨论】:

【参考方案2】:

Vector3.Lerp 的第三个参数是前两个参数之间距离的百分比。当您传入0.03f 时,您将接近 3% 到您的最终位置,但实际上从未准确 到达那里(您可以通过记录块的位置和目标的位置,你会发现它们永远不会完全相等)。

您无需使用== operator(精度为0.00001f)检查您的块的位置是否与目标位置一致,您只需使用Vector3.Distance 检查它是否足够接近

if(Vector3.Distance(block.transform.position, endPos.transform.position) < 0.1f) 

您可以将“足够接近的阈值”(示例中为0.1f)设置为命名变量,以便轻松调整,直到感觉合适为止。

【讨论】:

这可能是一个原因,但 Unity 已经在两个 == 之间使用了 close enough 距离 Vector3: 0.00001f 所以可能需要一段时间,但位置应该“匹配”在一点【参考方案3】:

对于初学者,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);

【讨论】:

以上是关于如何使用 C# 创建一个对象以统一在两个位置之间移动,为啥我的代码不起作用?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Unity C# 中的对象之间传输信息?

如何在 c# 中使用统一组合网格并获取相对于主父级的点击位置

如何将 opencv c++ 代码集成到使用 c# 以统一 3D 开发的移动应用程序中

如何使用 C# 获取两个 DateTime 对象之间的时差?

如何检测新对象何时被生成或从统一中删除以获得评分/分数?

我们如何在统一 3D 中一起移动两个?