c# - 如何使用 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 从一个父级更改为另一个父级。
解决方案
您可能不会使用
Mathf.Lerp
which 用于插值float
,而是使用 which 来Vector3.Lerp
插值两个Vector3
值。我不会
Update
为此使用并轮询检查某些状态,而是使用更易于维护和控制的协程。它也比大多数时候Update
只在一种情况下使用的“无用”运行更有效。使用 Lerp 时主要有两个用例
- 稳定例如用户输入以避免抖动。那是您使用的用例。在这里,您将每帧根据下一个输入目标位置插入当前位置,在靠近目标的同时变慢
- 从一个位置平稳地移动到另一个位置。这是您真正想要的用例。在这里,您不会使用“固定”因子,而是使用从
0
to生长1
并使用固定startPosition
and的因子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;
});
});
}
}
}
推荐阅读
- r - 从布尔表中保留具有现有值的列表
- linux - Linux中的系统调用是否涉及线程切换
- javascript - 需要为单击和拖动列标题设置最小宽度
- f# - 使用匿名记录会产生语法错误:FS0010: Unexpected symbol '|' 在表达
- google-sheets - Google Sheets transposing multiple column data into row
- .net - Difference in hashing algorithms of .Net Identity and .Net Core Identity
- kotlin - 集合实现(mutableList、mutableMap 等)在公共/共享 Kotlin 代码中不可用吗?
- reactjs - 如何使用 getquery 和放大
React 中的组件 - mysql - MySQL: Use select in update query
- ios - 如果我们已经知道用户的高度,如何在不使用 ARKit 的情况下计算 iPhone 设备和用户之间的距离?