Dub*_*oni 0 c# unity-game-engine
如果我将旋转速度设置为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");
}
}
Run Code Online (Sandbox Code Playgroud)
看看相机脚本:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
你的脚本基本上有效!问题在于
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;
}
}
Run Code Online (Sandbox Code Playgroud)
有两个问题:
你添加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);
}
Run Code Online (Sandbox Code Playgroud)
和
public bool IsRotationFinished
{
get { return Mathf.Approximately(timer, 1.0f); }
}
Run Code Online (Sandbox Code Playgroud)您应该使用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;
}
}
Run Code Online (Sandbox Code Playgroud)
或代替Quaternion.Slerp使用Quaternion.RotateTowards像
transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.LookRotation((target.position - transform.position).normalized), RotationSpeed * Time.deltaTime);
Run Code Online (Sandbox Code Playgroud)我强烈建议将Coroutines用于一切,而不是处理这种东西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");
}
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)
而不是Quaternion.Slerp和currentRotation你也可以简单地使用Quaternion.RotateTowards喜欢
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, RotationSpeed * Time.deltaTime);
Run Code Online (Sandbox Code Playgroud)
对于运动,Vector3.MoveTowards如果你想要,你仍然可以使用
while (Vector3.Distance(currentWaypoint.position, transform.position) < WPradius)
{
transform.position = Vector3.MoveTowards(transform.position, currentWaypoint.position, Time.deltaTime * speed);
yield return null;
}
Run Code Online (Sandbox Code Playgroud)
但我更愿意使用这些Lerp解决方案.为什么我建议宁愿使用Lerp?
你现在可以非常容易地控制你是想要以一定的速度移动/旋转还是固定duration,其中移动/旋转应该完成,无论差异有多大 - 或者甚至需要一些额外的检查来决定一个那些选择!
您可以轻松进行移动/旋转!见下文 ;)
为了保持缓和和/或缓和的运动和旋转,我发现这个块如何像专业人士一样非常有帮助!(通过我的例子)
例如,我们可以通过sinerp"缓解":
Run Code Online (Sandbox Code Playgroud)var lerpFactor = Mathf.Sin(passedTime / duration * Mathf.PI * 0.5f);或者我们可以用coserp"缓和":
Run Code Online (Sandbox Code Playgroud)var lerpFactor = 1f - Mathf.Cos(passedTime / duration * Mathf.PI * 0.5f);我们甚至可以创造指数运动:
Run Code Online (Sandbox Code Playgroud)var lerpFactor = Mathf.Pow(passedTime / duration, 2);上面提到的乘法属性是一些插入方法背后的核心概念,这些插值方法易于使用和缓和,例如着名的"smoothstep"公式:
Run Code Online (Sandbox Code Playgroud)var lerpFactor = Mathf.Pow(passedTime / duration, 2) * (3f - 2f * passedTime / duration);或者我个人最喜欢的"smootherstep":
Run Code Online (Sandbox Code Playgroud)var lerpFactor = Mathf.Pow(passedTime / duration, 3) * (6f * (passedTime / duration) - 15f) + 10f);
| 归档时间: |
|
| 查看次数: |
93 次 |
| 最近记录: |