需要帮助 睡姿和轮换
Need help Slerping positions and rotations
我的相机上有 2 个脚本,一个用于跟随玩家 ( FollowTarget.cs ),另一个应该随着时间的推移,在玩家死亡时 Slerp 相机的变换位置和旋转 ( DeathCam.cs ).
使用 DeathCam.cs 我试图在死亡时随着时间的推移向 [desired/set in inspector] 位置和旋转值进行平滑的移动,但目前相机直接跳到该位置并且检查器中的轮换设置。我认为这可能归结为 transform.position = camPos;在更新中。
我曾尝试在 PlayDeathCam() 函数中设置位置和旋转,但无法正常工作,目前已被注释掉并被一个 bool 取代,该 bool 会检查 Update 以查看死亡摄像头是否处于活动状态
代码: FollowTarget.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FollowTarget : MonoBehaviour{
[Header("----- Target To Follow --------------------------------------------------")]
public GameObject target;
[Header("----- Adjustments To Make ------------------------------------------")]
public Vector3 adjustVal;
void Update(){
pos = new Vector3( target.transform.position.x + adjustVal.x,
target.transform.position.y + adjustVal.y,
target.transform.position.z + adjustVal.z);
transform.position = pos;
}
}
代码: DeathCam.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DeathCam : MonoBehaviour{
[Header("----- Duration -----------------------------------------------------")]
[SerializeField] float duration = 5.0f;
[Header("----- Positions ----------------------------------------------------")]
[SerializeField] float finalPosX;
[SerializeField] float finalPosY;
[SerializeField] float finalPosZ;
[Header("----- Angles -------------------------------------------------------")]
[SerializeField] float tiltAngleX;
[SerializeField] float tiltAngleY;
[SerializeField] float tiltAngleZ;
[Header("--------------------------------------------------------------------")]
[SerializeField] Vector3 camPos;
[SerializeField] Quaternion camRot;
[SerializeField] bool deathCamActive;
///[ Start]
void Start(){
}
///[ Update]
void Update(){
if (deathCamActive) {
camPos = new Vector3((transform.position.x + finalPosX),
(transform.position.y + finalPosY),
(transform.position.z + finalPosZ));
transform.position = camPos;
}
}
///[ PlayDeathCam]
public void PlayDeathCam(){
float tiltAroundX = camPos.x * tiltAngleX;
float tiltAroundY = camPos.y * tiltAngleY;
float tiltAroundZ = camPos.z * tiltAngleZ;
// camPos = new Vector3((transform.position.x + finalPosX),
// (transform.position.y + finalPosY),
// (transform.position.z + finalPosZ));
camRot = Quaternion.Euler(tiltAroundX, tiltAroundY, tiltAroundZ);
transform.position = Vector3.Slerp(transform.position, camPos, Time.deltaTime * duration);
deathCamActive = true;
transform.rotation = Quaternion.Slerp(transform.rotation, camRot, Time.deltaTime * duration);
StartCoroutine("DeathCamSlow");
}
///[ DeathCamSlow]
IEnumerator DeathCamSlow(){
Time.timeScale = 0.3f;
yield return new WaitForSeconds(1.6f);
Time.timeScale = 1.0f;
}
}
您的代码有一些问题,它应该是可以修复的,但首先让我们尝试反汇编一些似乎正在发生的混乱。
对于初学者来说,用 slerp 插入位置看起来是错误的。
但是您似乎已经放置了您认为将在只会调用一次的方法中执行动画的代码。
我喜欢通过使用类似于
的模式来分离我对触发动画的想法
void StartAnimation()
{
StartCorouting(LerpDriverRoutine());
}
IEnumerator LerpDriverRoutine()
{
float x=0;
while (x<1)
{
ApplyLerp(x);
x+=Time.deltaTime*speed;
yield return null;
}
ApplyLerp(1);
}
void ApplyLerp(float lerpAmount) // this will get called with varying amount as time passes
{
// use lerp amount here
}
虽然 lerping x=lerp(x,other,Time.deltaTime) 有时有意义,但更多时候却没有意义。这里你不是在值 A 和 B 之间进行 lerping,而是在当前值和目标之间进行 lerping,这会导致类似的结果,但通常不会感觉平滑。如果您想慢慢地确定一个目标值,通常使用 Mathf.SmoothDamp 会好得多。但是如果线性插值是你想要的,并且学习编写动画代码你可能想从它开始,你需要将你的起始位置存储在某个地方并将它用于 lerp(动画是 运行 的时间),而不是只使用当前值,至少在你知道自己在做什么之前不要使用
IEnumerator LerpDriverRoutine()
{
float x=0;
Vector3 startPosition=transform.position;
Quaternion startRotation=transform.rotation;
while (x<1)
{
transform.position=Vector3.Lerp(startPosition,camPos,x);
transform.rotation=Quaternion.Slerp(startRotation,camRot,x);
x+=Time.deltaTime*speed;
yield return null;
}
}
按要求编辑:这是一个使用 smoothdamp 的示例。它有几个优点:它始终缓慢开始和结束,准确达到峰值速度 mid-way 并在接近目标时开始减速。您不需要提前知道动画需要多长时间才能确定新状态。您还可以在动画进行时更改目标值,只要不丢失速度值,就不会出现任何可见的抖动。 SmoothDamp 使用一个保持当前速度的外部辅助值(缺点是你需要检查你想要的错误,因为运动会收敛到目标值但实际上可能不会达到它)当行进的距离更长时,动画将也需要更长的时间,但它将保留由 smoothTime 参数表示的一般动态。请注意,smoothTime 大致相当于动画运行 half-way 而非 all-the 所需的时间,因为它永远不会完全运行。默认值 .75 意味着(非常粗略地)运动需要 1.5 秒左右才能达到所需值。
IEnumerator SmoothDampMovement(Vector3 target, float smoothTime=0.75f, float epsilon=0.01f)
{
float velocityX=0;
float velocityY=0;
float velocityZ=0;
var position=transform.position;
while (transform.position-target).Vector3.sqrMagnitude >epsilon)
{
position.x = Mathf.SmoothDamp(position.x, target.x, ref velocityX, smoothTime);
position.y = Mathf.SmoothDamp(position.y, target.y, ref velocityY, smoothTime);
position.z = Mathf.SmoothDamp(position.z, target.z, ref velocityZ, smoothTime));
transform.position=position;
yield return null;
}
}
对于旋转有 Mathf.SmoothDampAngle 处理 360 度回到 0 的边缘情况,我相信你可以根据上面的例子计算出细节
我的相机上有 2 个脚本,一个用于跟随玩家 ( FollowTarget.cs ),另一个应该随着时间的推移,在玩家死亡时 Slerp 相机的变换位置和旋转 ( DeathCam.cs ).
使用 DeathCam.cs 我试图在死亡时随着时间的推移向 [desired/set in inspector] 位置和旋转值进行平滑的移动,但目前相机直接跳到该位置并且检查器中的轮换设置。我认为这可能归结为 transform.position = camPos;在更新中。
我曾尝试在 PlayDeathCam() 函数中设置位置和旋转,但无法正常工作,目前已被注释掉并被一个 bool 取代,该 bool 会检查 Update 以查看死亡摄像头是否处于活动状态
代码: FollowTarget.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FollowTarget : MonoBehaviour{
[Header("----- Target To Follow --------------------------------------------------")]
public GameObject target;
[Header("----- Adjustments To Make ------------------------------------------")]
public Vector3 adjustVal;
void Update(){
pos = new Vector3( target.transform.position.x + adjustVal.x,
target.transform.position.y + adjustVal.y,
target.transform.position.z + adjustVal.z);
transform.position = pos;
}
}
代码: DeathCam.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DeathCam : MonoBehaviour{
[Header("----- Duration -----------------------------------------------------")]
[SerializeField] float duration = 5.0f;
[Header("----- Positions ----------------------------------------------------")]
[SerializeField] float finalPosX;
[SerializeField] float finalPosY;
[SerializeField] float finalPosZ;
[Header("----- Angles -------------------------------------------------------")]
[SerializeField] float tiltAngleX;
[SerializeField] float tiltAngleY;
[SerializeField] float tiltAngleZ;
[Header("--------------------------------------------------------------------")]
[SerializeField] Vector3 camPos;
[SerializeField] Quaternion camRot;
[SerializeField] bool deathCamActive;
///[ Start]
void Start(){
}
///[ Update]
void Update(){
if (deathCamActive) {
camPos = new Vector3((transform.position.x + finalPosX),
(transform.position.y + finalPosY),
(transform.position.z + finalPosZ));
transform.position = camPos;
}
}
///[ PlayDeathCam]
public void PlayDeathCam(){
float tiltAroundX = camPos.x * tiltAngleX;
float tiltAroundY = camPos.y * tiltAngleY;
float tiltAroundZ = camPos.z * tiltAngleZ;
// camPos = new Vector3((transform.position.x + finalPosX),
// (transform.position.y + finalPosY),
// (transform.position.z + finalPosZ));
camRot = Quaternion.Euler(tiltAroundX, tiltAroundY, tiltAroundZ);
transform.position = Vector3.Slerp(transform.position, camPos, Time.deltaTime * duration);
deathCamActive = true;
transform.rotation = Quaternion.Slerp(transform.rotation, camRot, Time.deltaTime * duration);
StartCoroutine("DeathCamSlow");
}
///[ DeathCamSlow]
IEnumerator DeathCamSlow(){
Time.timeScale = 0.3f;
yield return new WaitForSeconds(1.6f);
Time.timeScale = 1.0f;
}
}
您的代码有一些问题,它应该是可以修复的,但首先让我们尝试反汇编一些似乎正在发生的混乱。
对于初学者来说,用 slerp 插入位置看起来是错误的。
但是您似乎已经放置了您认为将在只会调用一次的方法中执行动画的代码。
我喜欢通过使用类似于
的模式来分离我对触发动画的想法void StartAnimation()
{
StartCorouting(LerpDriverRoutine());
}
IEnumerator LerpDriverRoutine()
{
float x=0;
while (x<1)
{
ApplyLerp(x);
x+=Time.deltaTime*speed;
yield return null;
}
ApplyLerp(1);
}
void ApplyLerp(float lerpAmount) // this will get called with varying amount as time passes
{
// use lerp amount here
}
虽然 lerping x=lerp(x,other,Time.deltaTime) 有时有意义,但更多时候却没有意义。这里你不是在值 A 和 B 之间进行 lerping,而是在当前值和目标之间进行 lerping,这会导致类似的结果,但通常不会感觉平滑。如果您想慢慢地确定一个目标值,通常使用 Mathf.SmoothDamp 会好得多。但是如果线性插值是你想要的,并且学习编写动画代码你可能想从它开始,你需要将你的起始位置存储在某个地方并将它用于 lerp(动画是 运行 的时间),而不是只使用当前值,至少在你知道自己在做什么之前不要使用
IEnumerator LerpDriverRoutine()
{
float x=0;
Vector3 startPosition=transform.position;
Quaternion startRotation=transform.rotation;
while (x<1)
{
transform.position=Vector3.Lerp(startPosition,camPos,x);
transform.rotation=Quaternion.Slerp(startRotation,camRot,x);
x+=Time.deltaTime*speed;
yield return null;
}
}
按要求编辑:这是一个使用 smoothdamp 的示例。它有几个优点:它始终缓慢开始和结束,准确达到峰值速度 mid-way 并在接近目标时开始减速。您不需要提前知道动画需要多长时间才能确定新状态。您还可以在动画进行时更改目标值,只要不丢失速度值,就不会出现任何可见的抖动。 SmoothDamp 使用一个保持当前速度的外部辅助值(缺点是你需要检查你想要的错误,因为运动会收敛到目标值但实际上可能不会达到它)当行进的距离更长时,动画将也需要更长的时间,但它将保留由 smoothTime 参数表示的一般动态。请注意,smoothTime 大致相当于动画运行 half-way 而非 all-the 所需的时间,因为它永远不会完全运行。默认值 .75 意味着(非常粗略地)运动需要 1.5 秒左右才能达到所需值。
IEnumerator SmoothDampMovement(Vector3 target, float smoothTime=0.75f, float epsilon=0.01f)
{
float velocityX=0;
float velocityY=0;
float velocityZ=0;
var position=transform.position;
while (transform.position-target).Vector3.sqrMagnitude >epsilon)
{
position.x = Mathf.SmoothDamp(position.x, target.x, ref velocityX, smoothTime);
position.y = Mathf.SmoothDamp(position.y, target.y, ref velocityY, smoothTime);
position.z = Mathf.SmoothDamp(position.z, target.z, ref velocityZ, smoothTime));
transform.position=position;
yield return null;
}
}
对于旋转有 Mathf.SmoothDampAngle 处理 360 度回到 0 的边缘情况,我相信你可以根据上面的例子计算出细节