【问题标题】:Move gameobject between two points with ease-in and ease-out使用缓入和缓出在两点之间移动游戏对象
【发布时间】:2021-02-18 11:40:46
【问题描述】:

我有一个游戏对象和一个位置列表,可以。每次,我按下一个特定的按钮,它应该改变它的位置。它应该以相同的速度在当前位置和新位置之间移动,无论它们之间的距离有多长。

虽然行进速度应该每次都一样,但在开始和结束时,应该平稳地加速和减速。

这是我当前的代码:

if (Input.GetKey(KeyCode.KeypadPeriod))
            {

                foreach (GameObject element in wagons)
                {
                    element.GetComponent<wagonController>().trainDestinationDisplays.GetComponent<trainDestinationDisplayController>().trainOuterDisplayDestinationChanger(trainDestination);
                }

            }

和:

public void trainOuterDisplayDestinationChanger(string trainDestination)
    {
        foreach (GameObject element in destinationDisplaysOutside)
        {
            Vector3 newPos = new Vector3(element.transform.localPosition.x, -85, element.transform.localPosition.z);
            element.transform.localPosition = Vector3.Lerp(element.transform.localPosition, newPos, Time.deltaTime * 1);
        }

 
    }

首先,我尝试了时间轴动画,但不是很灵活(我需要为每个可能的连接制作动画,例如 Travel von A 到 B、Travel von C 到 A、Travel von B 到 A 等等关于...)。

然后,我使用 Vector3.lerp 进行了尝试,第一感觉是解决方案,但每次按下时,它只会向目标目的地迈出一小步。这是代码:

Vector3 newPos = new Vector3(transform.localPosition.x, -85, transform.localPosition.z);
transform.localPosition = Vector3.Lerp(transform.localPosition, newPos, Time.deltaTime * 1);

然后我在一篇文章中看到,lerp 不是平滑加速和减速的正确解决方案,我应该使用 SmoothDamp。在示例代码中,它看起来像这样:

public Transform target;
    public float smoothTime = 0.3F;
    private Vector3 velocity = Vector3.zero;

    void Update()
    {
        Vector3 targetPosition = target.TransformPoint(new Vector3(0, 5, -10));

        transform.position = Vector3.SmoothDamp(transform.position, targetPosition, ref velocity, smoothTime);
    }

我没有让该代码工作,我也不明白 auf“速度”的用法 - 为什么速度为零?

有人可以将我推向正确的方向吗?实现目标的最佳方式是什么?我现在有点迷茫...

【问题讨论】:

  • 您在更新时设置了目标位置和平滑位置。可能,这就是问题的原因。当我使用 SmoothDamp 时,我在属性中设置目标值,然后更新平滑值并以这种方式将其传递给相关函数。如果您不必在更新时获取目标位置,并且您有一个特定的航点,或者可以在更新功能之外确定,那么就这样做。只需将 targetPosition 变量移到 Update 之外并使用 SmoothDamp
  • 我把它移出时总是出错。但是在切换到 Coroutine 之后,正如@derHugo 指出的那样,我不必再使用更新了 :)

标签: c# unity3d


【解决方案1】:

由于我不知道你所有的脚本和类型,我会说我会做什么——你希望你可以按照你想要/需要的方式实现它;)


首先,我建议宁愿使用Coroutine,这比在Update 中执行所有操作要好得多。

然后请注意,Vector3.SmoothDamp 对您的目标没有用处。随着时间的推移,它会抑制速度,从而使运动变得轻松。但它不会添加任何缓入。

所以我宁愿坚持Vector3.Lerp


假设您现在已经给出了检查点列表、当前索引和基本平均速度(以 Unity 单位/秒为单位):

List<Vector3> checkpoints;
int index;
float velocity;

然后你可以有一个方法来开始一个新的动画到你列表中的下一个(使用索引)检查点并阻止任何并发动画

private bool alreadyMoving;

public void MoveToNext()
{
    if(alreadyMoving) return;

    index++;
    if(index >= checkpoints.Count) return;

    var nextTargetPosition = checkpoints[index];
    
    // Start moving smooth to the target position
    StartCoroutine(MoveToTargetSmooth(nextTargetPosition));
}

现在开始魔法

private IEnumerator MoveToTargetSmooth(Vector3 targetPos)
{
    // block concurrent routines
    if(alreadyMoving) yield break;

    alreadyMoving = true;

    if(velocity <= 0)
    {
        Debug.LogError($"{nameof(velocity)} may not be negative or 0", this);
        // Allow the next routine to start now
        alreadyMoving = false;
        yield break;
    }

    // pre-cache the initial position
    var startPos = transform.position;

    // using the given average velocity calculate how long the animation
    // shall take in total
    var distance = Vector3.Distance(startPos, targetPos);

    if(Mathf.Approximately(distance, 0))
    {
        Debug.LogWarning("Start and end position are equal!", this);
        // Allow the next routine to start now
        alreadyMoving = false;
        yield break;
    }

    var duration = distance / velocity;

    var timePassed = 0f;
    while(timePassed < duration)
    {
        // This factor moves linear from 0 to 1
        var factor = timePassed / duration;
        // This adds ease-in and ease-out 
        // see https://docs.unity3d.com/ScriptReference/Mathf.SmoothStep.html
        // Basically you can use ANY mathematical function that maps
        // the input of [0; 1] again to a range of [0;1] 
        // with the easing you like
        factor = Mathf.SmoothStep(0, 1, factor);

        // And this is how finally you use Lerp in this case
        transform.position = Vector3.Lerp(startPos, targetPos, factor);

        // This tells Unity to "pause" the routine here
        // render this frame and continue from here in the next one
        yield return null;

        // increase by the time passed since last frame
        timePassed += Time.deltaTime;
    }

    // just to be sure to end with clean values
    transform.position = targetPos;


    // Allow the next routine to start now
    alreadyMoving = false;
}

您可以使用任何数学函数代替Mathf.SmoothStep,将01 之间的值映射到所需曲线(here 是一些示例)。

或者,如果您想变得超级花哨,也可以使用AnimationCurve,例如

[SerializeField] private AnimationCurve yourAnimationCurve;

并通过 Inspector 完全根据您的需要进行配置

然后作为一个因素使用AnimationCurve.Evaluate

factor = timePassed / duration;
factor = yourAnimationCurve.Evaluate(factor);

注意:在智能手机上输入,但我希望思路清晰

【讨论】:

  • 效果很好,非常感谢!也感谢您在智能手机上输入这么多信息 :) 有没有办法让加速/减速阶段更长/更顺畅?
  • @hdbrnd 如前所述,而不是 Mathf.SmoothStep,您基本上可以使用 any 数学函数,根据您的需要构建 0 和 1 之间的曲线。参见例如here 用于其他缓动功能
  • 感谢您的进一步解释和链接!
  • @hdbrnd 如果你想变得超级花哨,你也可以使用AnimationCurve 并根据你的需要通过 Inspector 进行配置,然后作为一个因素使用,例如factor = yourAnimationCurve.Evaluate(factor);
  • 对不起,我错过了你的评论。太棒了,谢谢你的补充:)
【解决方案2】:

您尝试过的SmoothDamp 确实是一个非常好的方法。速度参数是方法(它是静态的)需要运行的附加变量,它存储(和修改)有关变化速度的信息,因此它从零开始并加速,因此每次迭代它都会加速到目标只要差异很大,并且在您接近目标时开始减速。 Smoothdamp 保证不会有过冲,但如果您的阻尼不够高,您也可以设计一个过冲的解决方案并在最后为您提供这种摆动的谐波振荡。

对于更简单的解决方案,如果您提前知道起点和终点,则可以使用 Mathf.SmoothStep,它本质上是一个 S 曲线,其结果与 SmoothDamp 几乎相同,并且您可以非常轻松集成到现有代码中,但如果您打算在运动已经开始时更改目标,则效果不佳

您的代码看起来应该可以工作,您确定您的目标计算正确吗?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    • 2014-08-21
    • 1970-01-01
    • 1970-01-01
    • 2018-06-22
    相关资源
    最近更新 更多