c# - 如果旋转速度值例如设置为 0.01,为什么相机根本不移动?
问题描述
例如,如果我将旋转速度设置为 5,它将面向下一个目标航点旋转,然后移动到它。但是相机旋转会太快。
将速度更改为 0.01 使其以良好的缓慢平稳速度旋转。但随后在 0.01 处,相机面向下一个航路点旋转,但从未移动到该航路点。它留在原地。
这是航点脚本:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Waypoints : MonoBehaviour
{
private GameObject[] waypoints;
private Transform currentWaypoint;
private enum CameraState
{
StartRotating,
Rotating,
Moving,
Waiting
}
private CameraState cameraState;
public GameObject player;
public float speed = 5;
public float WPradius = 1;
public LookAtCamera lookAtCam;
void Start()
{
cameraState = CameraState.StartRotating;
}
void Update()
{
switch (cameraState)
{
// This state is used as a trigger to set the camera target and start rotation
case CameraState.StartRotating:
{
// Sanity check in case the waypoint array was set to length == 0 between states
if (waypoints.Length == 0)
break;
// Tell the camera to start rotating
currentWaypoint = waypoints[UnityEngine.Random.Range(0, waypoints.Length)].transform;
lookAtCam.target = currentWaypoint;
lookAtCam.setTime(0.0f);
cameraState = CameraState.Rotating;
break;
}
// This state only needs to detect when the camera has completed rotation to start movement
case CameraState.Rotating:
{
if (lookAtCam.IsRotationFinished)
cameraState = CameraState.Moving;
break;
}
case CameraState.Moving:
{
// Move
transform.position = Vector3.MoveTowards(transform.position, currentWaypoint.position, Time.deltaTime * speed);
// Check for the Waiting state
if (Vector3.Distance(currentWaypoint.position, transform.position) < WPradius)
{
// Set to waiting state
cameraState = CameraState.Waiting;
// Call the coroutine to wait once and not in CameraState.Waiting
// Coroutine will set the next state
StartCoroutine(WaitForTimer(3));
}
break;
}
case CameraState.Waiting:
// Do nothing. Timer has already started
break;
}
}
IEnumerator WaitForTimer(float timer)
{
yield return new WaitForSeconds(timer);
cameraState = CameraState.StartRotating;
}
public void RefreshWaypoints()
{
waypoints = GameObject.FindGameObjectsWithTag("Target");
}
}
看看相机脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LookAtCamera : MonoBehaviour
{
// Values that will be set in the Inspector
public Transform target;
public float RotationSpeed;
private float timer = 0.0f;
public bool IsRotationFinished
{
get { return timer > 0.99f; }
}
// Update is called once per frame
void Update()
{
if (target != null && timer < 0.99f)
{
// Rotate us over time according to speed until we are in the required rotation
transform.rotation = Quaternion.Slerp(transform.rotation,
Quaternion.LookRotation((target.position - transform.position).normalized),
timer);
timer += Time.deltaTime * RotationSpeed;
}
}
public void setTime(float time)
{
timer = time;
}
}
解决方案
问题
您的脚本基本上可以工作!问题出在
private void Update()
{
if (target != null && timer < 0.99f)
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation((target.position - transform.position).normalized), timer);
timer += Time.deltaTime * RotationSpeed;
}
}
有两个问题:
您添加
Time.deltaTime * RotationSpeed
了因此到达1
或在您的情况下0.99
所需的1/RotationSpeed = 100
时间比平时要长。因此,您的相机将保持该Rotating
状态约100 秒- 之后它会正常移动!(这可能是故意的,但请参见下文以获得更好的解决方案)
Quaternion.Slerp
在第一次和第二次旋转之间进行插值。但是您始终使用当前旋转作为起点,因此由于timer
永远不会到达,因此1
您在开始时会获得非常快的旋转,但由于当前旋转和目标旋转之间的距离变小,最终旋转会非常慢(实际上是永无止境)随着时间的推移。
快速修复
这些修复修复了您当前的解决方案,但您应该查看下面的“更好的解决方案”部分;)
一般来说,为了比较这两个
float
值,您应该使用Mathf.Approximately而不是使用实际的目标值1
。if (target != null && !Mathf.Approximately(timer, 1.0f)) { //... timer += Time.deltaTime * RotationSpeed; // clamps the value between 0 and 1 timer = Mathf.Clamp01(timer); }
和
public bool IsRotationFinished { get { return Mathf.Approximately(timer, 1.0f); } }
您应该使用
Quaternion.Slerp
存储原始旋转并将其用作第一个参数(而不是您将看到您需要更大的方式RotationSpeed
)private Quaternion lastRotation; private void Update() { if (target != null && !Mathf.Approximately(timer, 1.0f)) { transform.rotation = Quaternion.Slerp(lastRotation, Quaternion.LookRotation((target.position - transform.position).normalized), timer); timer += Time.deltaTime * RotationSpeed; } else { lastRotation = transform.rotation; } }
或者代替
Quaternion.Slerp
使用Quaternion.RotateTowards
transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.LookRotation((target.position - transform.position).normalized), RotationSpeed * Time.deltaTime);
更好的解决方案
我强烈建议对所有事情都使用协程Update
,而不是在. 它们更容易控制,并使您的代码非常干净。
看看您的脚本将如何缩小,您将不再需要所有属性、字段和比较floats
。你可以做你目前得到和设置的大多数事情,只用几行就可以等待某件事发生。
如果您不知道:您实际上可以简单地按顺序等待它完成yield return
:IEnumerator
航点
public class Waypoints : MonoBehaviour
{
private GameObject[] waypoints;
public GameObject player;
public float speed = 5;
public float WPradius = 1;
public LookAtCamera lookAtCam;
private Transform currentWaypoint;
private void Start()
{
// maybe refresh here?
//RefreshWaypoints();
StartCoroutine(RunWaypoints());
}
private IEnumerator RunWaypoints()
{
// Sanity check in case the waypoint array has length == 0
if (waypoints.Length == 0)
{
Debug.Log("No Waypoints!", this);
yield break;
}
// this looks dnagerous but as long as you yield somewhere it's fine ;)
while (true)
{
// maybe refresh here?
//RefreshWaypoints();
// Sanity check in case the waypoint array was set to length == 0 between states
if (waypoints.Length == 0)
{
Debug.Log("No Waypoints!", this);
yield break;
}
// first select the next waypoint
// Note that you might get the exact same waypoint again you currently had
// this will throw two errors in Unity:
// - Look rotation viewing vector is zero
// - and transform.position assign attempt for 'Main Camera' is not valid. Input position is { NaN, NaN, NaN }.
//
// so to avoid that rather use this (not optimal) while loop
// ofcourse while is never good but the odds that you will
// always get the same value over a longer time are quite low
//
// in case of doubt you could still add a yield return null
// than your camera just waits some frames longer until it gets a new waypoint
Transform newWaypoint = waypoints[Random.Range(0, waypoints.Length)].transform;
while(newWaypoint == currentWaypoint)
{
newWaypoint = waypoints[Random.Range(0, waypoints.Length)].transform;
}
currentWaypoint = newWaypoint;
// tell camera to rotate and wait until it is finished in one line!
yield return lookAtCam.RotateToTarget(currentWaypoint);
// move and wait until in correct position in one line!
yield return MoveToTarget(currentWaypoint);
//once waypoint reached wait 3 seconds than start over
yield return new WaitForSeconds(3);
}
}
private IEnumerator MoveToTarget(Transform currentWaypoint)
{
var currentPosition = transform.position;
var duration = Vector3.Distance(currentWaypoint.position, transform.position) / speed;
var passedTime = 0.0f;
do
{
// for easing see last section below
var lerpFactor = passedTime / duration;
transform.position = Vector3.Lerp(currentPosition, currentWaypoint.position, lerpFactor);
passedTime += Time.deltaTime;
yield return null;
} while (passedTime <= duration);
// to be sure to have the exact position in the end set it fixed
transform.position = currentWaypoint.position;
}
public void RefreshWaypoints()
{
waypoints = GameObject.FindGameObjectsWithTag("Target");
}
}
看相机
public class LookAtCamera : MonoBehaviour
{
// Values that will be set in the Inspector
public float RotationSpeed;
public IEnumerator RotateToTarget(Transform target)
{
var timePassed = 0f;
var targetDirection = (target.position - transform.position).normalized;
var targetRotation = Quaternion.LookRotation(targetDirection);
var currentRotation = transform.rotation;
var duration = Vector3.Angle(targetDirection, transform.forward) / RotationSpeed;
do
{
// for easing see last section below
var lerpFactor = timePassed / duration;
transform.rotation = Quaternion.Slerp(currentRotation, targetRotation, lerpFactor);
timePassed += Time.deltaTime;
yield return null;
} while (timePassed <= duration);
// to be sure you have the corrcet rotation in the end set it fixed
transform.rotation = targetRotation;
}
}
笔记
再次代替,Quaternion.Slerp
您currentRotation
也可以简单地使用Quaternion.RotateTowards
like
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, RotationSpeed * Time.deltaTime);
对于运动,Vector3.MoveTowards
如果您愿意,您仍然可以使用
while (Vector3.Distance(currentWaypoint.position, transform.position) < WPradius)
{
transform.position = Vector3.MoveTowards(transform.position, currentWaypoint.position, Time.deltaTime * speed);
yield return null;
}
但我更愿意使用Lerp
解决方案。为什么我建议宁可使用Lerp
?
您现在可以非常轻松地控制是否要以一定的速度移动/旋转,或者更确切地说,
duration
无论差异有多大,移动/旋转都应该完成 - 或者甚至进行一些额外的检查以决定一个这些选项!您可以轻松进出运动/旋转!见下文 ;)
缓解 Lerp 动作的提示
为了仍然保持缓入和/或缓出的运动和旋转,我发现这个块如何像专业人士一样 Lerp非常有帮助!(采用我的例子)
例如,我们可以使用 sinerp “缓和”:
var lerpFactor = Mathf.Sin(passedTime / duration * Mathf.PI * 0.5f);
或者我们可以使用 coserp “缓和”:
var lerpFactor = 1f - Mathf.Cos(passedTime / duration * Mathf.PI * 0.5f);
我们甚至可以创建指数运动:
var lerpFactor = Mathf.Pow(passedTime / duration, 2);
上面提到的乘法属性是一些缓入缓出的插值方法背后的核心概念,例如著名的“smoothstep”公式:
var lerpFactor = Mathf.Pow(passedTime / duration, 2) * (3f - 2f * passedTime / duration);
或者我个人最喜欢的“smootherstep”:
var lerpFactor = Mathf.Pow(passedTime / duration, 3) * (6f * (passedTime / duration) - 15f) + 10f);
推荐阅读
- python - 在单个属性上定义外键 - Pony ORM
- java - 对本地 sqLite 进行的新数据或修订数据未出现在应用程序中
- reactjs - 如何在状态更改后调用异步函数,以便动态更改另一个状态?
- jenkins - Cake.AppCenter:找不到可执行文件
- reactjs - 在类组件中在哪里分配变量?
- java - 如何在 MainActivity 中使用子活动的数据并将其用作其他方法中的参数
- javascript - 我可以为字节标志中的特定位表示 NULL 吗?
- wxpython - 如何使用 wxpython 为文件路径创建下拉列表?
- tensorflow - 如何在 tensorflow 上对 Keras 模型的输入进行加权?
- python - Python 正则表达式匹配任何包含 n 位数字的单词,但也可以包含其他字符