Unity Quaternion四元数 常用API解析 和 旋转插值动画实现
一、Quaternion常用API
Quaternion.FromToRotation(Vector3 fromDirection, Vector3 toDirection)
官方解析:Creates a rotation which rotates from fromDirection to toDirection
中文意思:创建一个从form方向转到to目标方向所需要的旋转
设想一个这样的场景,我们用Quaternion.FromToRotation()这个API,把下图主物体(m_MainTran) 的z轴(蓝色轴) 朝向目标物体(m_TargetTran)应该怎么办呢。
代码
//求出目标朝向
Vector3 targetDir = m_TargetTran.position - m_MainTran.position;
//求出m_MainTran.forward旋转到目标方向需要的旋转四元数
Quaternion q = Quaternion.FromToRotation(m_MainTran.forward, targetDir);
//把求出来的旋转四元数应用到 forward 方向
Vector3 finalDir = q * m_MainTran.forward;
//最后让主目标的 forward 朝向计算出来的方向
m_MainTran.forward = finalDir;
解析都在注释了。接下来看下效果
同样的,如果我们想让主目标的x轴朝向目标,即right方向。可以这样写
//求出目标朝向
Vector3 targetDir = m_TargetTran.position - m_MainTran.position;
//求出m_MainTran.right旋转到目标方向需要的旋转四元数
Quaternion q = Quaternion.FromToRotation(m_MainTran.right,targetDir);
//把求出来的旋转四元数应用到 right 方向
Vector3 finalDir = q * m_MainTran.right;
//最后让主目标的 right 朝向计算出来的方向
m_MainTran.right = finalDir;
效果图
这在2D游戏很有用。因为2D游戏我们一般是让物体的x或y朝向目标,而不是z轴
Quaternion.LookRotation(Vector3 forward, Vector3 upwards = Vector3.up)
- 官方解析:Creates a rotation with the specified forward and upwards directions
- 中文意思:创建一个旋转。使得目标的正方向(z轴)指向目标forward.
- tip:看向目标时y轴可以在z轴上方,也可以在下方。这里用第2个参数控制,不填的话默认是Vector.up上方。想改变方向可以用Vector.down
同样的场景,我们用Quaternion.LookRotation()来让目标朝向目标方向,上代码
//求出目标方向
Vector3 targetDir = m_TargetTran.position - m_MainTran.position;
//计算出z轴朝向目标方向需要的旋转四元数
Quaternion rotation = Quaternion.LookRotation(targetDir,Vector3.up);
//让m_MainTran.rotation等于求出的旋转
m_MainTran.rotation = rotation;
效果图
可以看到,
主物体也成功的朝向了目标。这个方法求出来的旋转表示Z轴朝向目标方向需要的旋转。假如我们想让X轴朝向目标方向怎么办呢。
- 可以在求出结果后,让自己right等于自己的forward。如以下代码
Vector3 targetDir = m_TargetTran.position - m_MainTran.position;
Quaternion rotation = Quaternion.LookRotation(targetDir,Vector3.up);
m_MainTran.rotation = rotation;
//上面的代码会使z轴朝向目标。 这里在这基础上让 right 朝向 forward 就可以了
m_MainTran.right = m_MainTran.forward;
效果图
二、旋转插值与动画实现
Quaternion.Lerp(Quaternion a, Quaternion b, float t)
- 官文解析:Interpolates between a and b by t and normalizes the result afterwards. The parameter t is clamped to the range [0, 1]
- 中文意思:在a和b两个四元数之间进行插值,插值范围t =【0~1】
我们可以用Quaternion.Lerp()来实现旋转动画,注意这里填入的参数是四元数,我们可以用上面的Quaternion.FromToRotation()或Quaternion.LookRotation()来求出我们要的参数。
接下来我们来实现,主目标的Z轴在2秒内转向目标方向
private bool isRotating = false;
private float curTime = 0;
private float AllTime = 3;
private Quaternion oldQ;
private Quaternion targetQ;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
isRotating = true;
curTime = 0;
//当前旋转
oldQ = m_MainTran.rotation;
//目标旋转
targetQ = Quaternion.LookRotation(m_TargetTran.position - m_MainTran.position);
}
if (isRotating)
{
curTime += Time.deltaTime;
float t = curTime / AllTime;
t = Mathf.Clamp(t, 0, 1);
//用t进行插值
Quaternion lerpQ = Quaternion.Lerp(oldQ,targetQ,t);
//设置到目标旋转
m_MainTran.rotation = lerpQ;
Debug.Log($"{GetType()} curT:{t}");
if (t >= 1)
{
isRotating = false;
}
}
}
效果图
有时我们需要的是其他轴,比如X轴朝向目标,那该怎么做呢。接下来我们来实现一下,上代码
private bool isRotating = false;
private float curTime = 0;
private float AllTime = 3;
private Vector3 oldDir;
private Quaternion oldQ;
private Quaternion targetQ;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
isRotating = true;
curTime = 0;
//记录起始点的朝向
oldDir = m_MainTran.right;
//起始插值参数(对于oldDir方向来说,起始不需要旋转。所以这里是Quaternion.identity)
oldQ = Quaternion.identity;
//目标插值参数
targetQ = Quaternion.FromToRotation(m_MainTran.right, m_TargetTran.position - m_MainTran.position);
}
if (isRotating)
{
curTime += Time.deltaTime;
float t = curTime / AllTime;
t = Mathf.Clamp(t, 0, 1);
//用t进行插值
Quaternion lerpQ = Quaternion.Lerp(oldQ,targetQ,t);
//设置到目标旋转
m_MainTran.right = lerpQ * oldDir;
Debug.Log($"{GetType()} curT:{t}");
if (t >= 1)
{
isRotating = false;
}
}
}
效果图:
注释都加在代码上了。按下A键,可以看到我们的X轴转向了目标
Quaternion.Slerp(Quaternion a, Quaternion b, float t)
这个是球型插值,动画效果可能较Quaternion.Lerp()更好一点。
具体用法和Quaternion.Lerp()一样,只需要把上面代码里的Lerp()改成Slerp()既可以看到效果。这里就不罗列出来了。大伙可以自行替换看看。
Quaternion.RotateTowards(Quaternion from,Quaternion to,float maxDegreesDelta)
- 官方解析:Rotates a rotation from towards to
- 中文意思:将旋转从 from 转向 to。 旋转的角度为 maxDegreesDelta ,但最后的旋转不会超过to. 所以这里是理论最大旋转角度。
可以看下源码,其实这里最后也是用Quaternion.SlerpUnclamped()来计算的。先求出总角度,然后把
maxDegreesDelta / 总角度
作为插值t
public static Quaternion RotateTowards(
Quaternion from,
Quaternion to,
float maxDegreesDelta)
{
float num = Quaternion.Angle(from, to);
return (double) num == 0.0 ? to : Quaternion.SlerpUnclamped(from, to, Mathf.Min(1f, maxDegreesDelta / num));
}
有了这个方法,我们可以指定旋转角度来转向目标。接下来我们来试试,上代码
private bool isRotating = false;
private float perAngel = 15;//每秒转15度
private Quaternion targetQ;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
isRotating = true;
curTime = 0;
//目标插值参数
targetQ = Quaternion.LookRotation(m_TargetTran.position - m_MainTran.position);
}
if (isRotating)
{
//计算出每帧的旋转
Quaternion rotQ = Quaternion.RotateTowards(transform.rotation,targetQ,perAngel * Time.deltaTime);
//设置到目标旋转
m_MainTran.rotation = rotQ;
if (m_MainTran.rotation.Equals(targetQ))
{
isRotating = false;
Debug.Log($"{GetType()} exit rot");
}
}
}
效果图:
Quaternion AngleAxis(float angle, Vector3 axis)
-
官方解析:Creates a rotation which rotates angle degrees around axis
-
中文意思:创建一个旋转。代表以axis方向作为旋转轴,旋转angle角度
-
用这个方法,我们可以让我们的方向沿着任意轴旋转任意角度,精确的按我们想要的效果来旋转。
比如在2D游戏中,我们一般是沿着Vector.forward来旋转;
在3D空间中,我们应该沿着哪个方向来旋转呢?
我们可以用叉乘来求出旋转轴
。
叉乘的数学意义
:Vector c = Vector3.Cross(a,b); 求出来的向量c垂直于向量a与b形成的平面。这个向量c就是我们要的旋转轴。
接下来我们使用Quaternion.AngleAxis()来实现前面的旋转动画。上代码
private bool isRotating = false;
private float perAngel = 15;//每秒转15度
private Vector3 rotAxis;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
isRotating = true;
//求出旋转轴
rotAxis = Vector3.Cross(m_MainTran.forward,m_TargetTran.position - m_MainTran.position);
}
if (isRotating)
{
//计算出每帧的旋转
Quaternion rotQ = Quaternion.AngleAxis(perAngel * Time.deltaTime,rotAxis);
//应用旋转
m_MainTran.forward = rotQ * m_MainTran.forward;
//如果旋转到只差2度,就旋转到目标方向,并退出旋转动画
if (Vector3.Angle(m_MainTran.forward,m_TargetTran.position - m_MainTran.position) <= 2)
{
m_MainTran.forward = m_TargetTran.position - m_MainTran.position;
isRotating = false;
Debug.Log($"{GetType()} exit rot");
}
}
}
效果图:
可以看到,主物体也成功的转向目标了。
三、结语
- 四元数是一个比较抽象的概念,但是掌握上面的API,就可以随心所欲的转转转了~