首页 > 解决方案 > 如何使用 Mathf.Lerp 更改对象的平滑位置?

问题描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveNavi : MonoBehaviour
{
    public Transform destinationTransform
    public float speed;
    public float distanceToStop;
    public float lerpTime;
    public static bool naviChildOfHand = false;

    private GameObject rig_f_middle;
    private bool changeNaviChild = false;

    // Start is called before the first frame update
    void Start()
    {
        rig_f_middle = GameObject.Find("rig_f_middle.02.R");
    }

    // Update is called once per frame
    void Update()
    {
        if (IKControl.startMovingNAVI == true)
        {
            var v = rig_f_middle.transform.position - transform.position;
            if (v.magnitude < 0.001f)
            {
                //this.transform.parent = rig_f_middle.transform;
                //this.enabled = false;
                naviChildOfHand = true;
                changeNaviChild = true;
                return;
            }
            Vector3 moveDir = v.normalized;
            transform.position += moveDir * speed * Time.deltaTime;
        }

        if(changeNaviChild == true)
        {
            this.transform.position = Mathf.Lerp(this.transform.position,
                destinationTransform.position, lerpTime * Time.deltaTime);
        }
    }
}

而是使用以下行将对象子对象更改为另一个父对象:

this.transform.parent = rig_f_middle.transform;

我在两个地方复制了对象,当游戏开始时,这个变换是父对象的子对象,destinationTransform 是变换对象的另一个副本,我想使用 Mathf Lerp 从变换平滑切换到destinationTransform。

这是一个屏幕截图:我用红色圆圈标记了目的地名称 NAVI Destination,它与 NAVI Original 的对象相同。而且我想使用 Lerp 来平滑原始到目标之间的变化,因此原始将慢慢禁用,而目标将以某种方式启用。

原始和目的地

主要目标是平滑过渡,将 NAVI 从一个父级更改为另一个父级。

标签: c#unity3d

解决方案


  1. 您可能不会使用Mathf.Lerpwhich 用于插值float,而是使用 which 来Vector3.Lerp插值两个Vector3值。

  2. 我不会Update为此使用并轮询检查某些状态,而是使用更易于维护和控制的协程。它也比大多数时候Update只在一种情况下使用的“无用”运行更有效。

  3. 使用 Lerp 时主要有两个用例

    • 稳定例如用户输入以避免抖动。那是您使用的用例。在这里,您将每帧根据下一个输入目标位置插入当前位置,在靠近目标的同时变慢
    • 从一个位置平稳地移动到另一个位置。这是您真正想要的用例。在这里,您不会使用“固定”因子,而是使用从0to生长1并使用固定startPositionand的因子endPosition

所以就我个人而言,我宁愿使用一般的东西

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ParentTransition : MonoBehaviour
{
    // Avergae move speed in Unity units per second
    [SerializeField] float MoveSpeed = 1f;

    // flag for preventing concurrent routines
    private bool isMoving;

    private IEnumerator MoveToNewParentRoutine(Transform newParent, Action whenDone)
    {
        // prevent multiple concurrent routines
        if(isMoving) yield break;
        isMoving = true;

        // Get current parent and position
        var currentParent = transform.parent;
        var currentPosition = transform.position;

        // Get target position
        var targetPosition = newParent.position;

        // Get the current distance to the target in order to calculate
        // the duration of the animation
        var distance = Vector3.Distance(currentPosition, targetPosition);

        // get the duration of the transition depending on the distance and move speed
        var targetDuration = distance / speed;

        var timePassed = 0f;
        while(timePassed < targetDuration)
        {
            // This grows linear from 0 to 1 
            var factor = timePassed / targetDuration;
            // optionally add some ease-in and ease-out
            factor = Mathf.SmoothStep(0, 1, factor);

            this.transform.position = Vector3.Lerp(currentPosition, targetPosition, factor);

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

            // "Pause" the routine here, render this frame and 
            // continue from here in the next frame
            yield return null;
        }

        // to be sure set the target hard
        transform.position = targetPosition;
        
        // Now transit to the new parent
        transform.parent = newParent;
        naviChildOfHand = true;

        // Give this routine free for a next transition (if needed)
        isMoving = false;

        // Action to execute when the routine is finished
        whenDone?.Invoke();
    }

    public void MoveToNewParent()
    {
        if (!isMoving)
        {
            StartCoroutine(MoveToNewParentRoutine(rig_f_middle));
        }
    }
}

就个人而言,我会Update完全删除,而是添加一个

然后宁愿在需要时从其他地方调用此方法,例如

parentTransitionReference.MoveToNewParent(someTransform);

使用它,您通常可以将相同的组件用于父对象之间的任何转换。

然后在您的特定用例中,您可能会将其附加到您的导航并使用它,例如

public class MoveNavi : MonoBehaviour
{
    [SeriaizeField] ParentTransition parentTransition;
    public Transform destinationTransform
    public static bool naviChildOfHand = false;

    [SerializeField] private GameObject rig_f_middle;

    bool isAlreadyMoving;

    // Start is called before the first frame update
    void Start()
    {
        if(!rig_f_middle) rig_f_middle = GameObject.Find("rig_f_middle.02.R");

        if(!parentTransition)
        {
            if(!TryGetComponent<ParentTransition>(out parentTransition)
            {
                parentTransition = gameObject.AddComponent<ParentTransition>();
            }
        }
    }

    // Update is called once per frame
    void Update()
    {
        if (!isAlreadyMoving && IKControl.startMovingNAVI)
        {
            isAlreadyMoving = true;
            parentTransition.MoveToNewParent(rig_f_middle, () => 
            {
                parentTransition.MoveToNewParent(destinationTransform, () =>
                {
                    naviChildOfHand = true;
                });
            });
        }
    }
}

推荐阅读