什么是四元数?
欧拉角和四元数,都是表示物体旋转的。但欧拉角有个万向节死锁的问题,为了解决这个问题,因此引入了四元数。
四元数,就是用4个数字来表示旋转。但这四个数就不是角度了。假设我们的旋转轴是V轴,那这四个数字分别是:
x=sin(θ/2)*Vx
y=sin(θ/2)*Vy
z=sin(θ/2)*Vz
w=cos(θ/2)
这四个数字的取值范围是-1到1。
四元数可以与欧拉角进行无缝切换。有了它,我们就可以解决任意角度的旋转问题了。
using UnityEngine;
using System.Collections;
public class VectorDemo2 : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
private void OnGUI(){
if (GUILayout.Button ("设置物体旋转角度")) {
Quaternion qt = new Quaternion();
this.transform.rotation = Quaternion.Euler(0,60,0);
}
if (GUILayout.RepeatButton ("沿x轴旋转")) {
Quaternion qt = new Quaternion();
this.transform.rotation *= Quaternion.Euler(1,0,0);
}
if (GUILayout.RepeatButton ("沿y轴旋转")) {
Quaternion qt = new Quaternion();
this.transform.rotation *= Quaternion.Euler(0,1,0);
}
if (GUILayout.RepeatButton ("沿z轴旋转")) {
Quaternion qt = new Quaternion();
this.transform.Rotate(0,0,1);
}
}
}
Quaternion.Euler这个函数,可以将欧拉角直接转为四元数。
四元数左乘向量,其实就是旋转角度的叠加,Rotate方法其实就是这个原理。
GIF 2022-12-30 9-39-49.gif
练习
计算当前物体右前方30度、10米远的敌人位置。
using UnityEngine;
using System.Collections;
public class VectorDemo2 : MonoBehaviour {
public Vector3 vect;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
Debug.DrawLine (this.transform.position,vect);
if (Input.GetMouseButtonDown (0)) {
vect = this.transform.rotation * new Vector3(0,0,10);//根据当前物体的旋转而旋转
vect = Quaternion.Euler(0,30,0) * vect;//vect向量沿y轴旋转30度
vect = this.transform.position + vect;//vect向量移动到当前位置
}
}
}
再来一个复杂点的——炸弹攻击范围的判断。
比如炸弹爆炸范围为半径10米,在这个范围内就会被炸到,反之则不会。
8.png
如果仅判断玩家中心点和炸弹的距离比较容易。但考虑到障碍物,有时玩家可以躲到墙体后面只露半个身体,这时又如何判断呢?
那就要判断玩家身体的具体位置跟炸弹之间的距离了。
如果头部位置跟炸弹的距离在爆炸范围内,就会掉血;
而头部和足部位置都在爆炸范围内,那就会死亡。
随着玩家的不断跑动,我们需要实时计算玩家的头部和足部的位置,然后不断跟炸弹的距离做比较。
8.png
已知的条件是:玩家的半径和位置、炸弹的位置。
我们需要实时计算炸弹和玩家形成的黄色切线坐标,然后跟炸弹的位置做比较,是否也在10米爆炸范围内?
一旦抽象为数学问题就比较容易了。如图所示,切线形成一个直角三角形,这样就确定了一个角;而由炸弹和玩家位置,就可以确定斜边的距离。已知一边一角,就可以计算玩家到切线的那个夹角。
有了这个夹角,我们就可以旋转当前向量,从而得到切线向量(也就是坐标),有了这个坐标,再跟炸弹进行比较,就可以判断出是否在爆炸范围内了。
using UnityEngine;
using System.Collections;
public class VectorDemo2 : MonoBehaviour { //挂载到炸弹物体下
public string playerTag = "Player";//玩家标签,根据这个标签查找物体
public Transform playerTF;
public float radius;//玩家半径
private Vector3 leftTan,rightTan;
// Use this for initialization
void Start () {
GameObject player = GameObject.FindWithTag (playerTag);
playerTF = player.transform;
radius = playerTF.GetComponent <CapsuleCollider>().radius;//获取玩家的半径,玩家用圆柱体代替
}
// Update is called once per frame
void Update () {
CalcPosition ();
}
public void CalcPosition(){
Vector3 playerToExplosion = this.transform.position - playerTF.position;
Vector3 playerToExplosionDir = playerToExplosion.normalized * radius;
float angle = Mathf.Acos (radius / playerToExplosion.magnitude)* Mathf.Rad2Deg;//已知一边一角,求角度,注意弧度转角度
Vector3 leftRoll = Quaternion.Euler (0, -angle, 0) * playerToExplosionDir;//当前玩家左旋转
Vector3 rightRoll = Quaternion.Euler (0, angle, 0) * playerToExplosionDir;//当前玩家右旋转
leftTan = playerTF.position + leftRoll;//转换坐标
rightTan = playerTF.position + rightRoll;
Debug.DrawLine (this.transform.position,leftTan);
Debug.DrawLine (this.transform.position,rightTan);
}
}
9.png
新建一个cube当炸弹,然后将脚本挂载过去。
然后新建一个圆柱体当玩家,标签可以直接选择Player。
78.png
圆柱体的半径在Capsule Collider这个组件下,可以更改这个值。
从炸弹寻找玩家,可以通过GameObject.FindWithTag这个方法查找到;
从玩家对象中寻找一个组件,可以用GetComponent方法。
最后一步,转换坐标这块不是很好理解。
3.png
因为我们做了向量标准化,因此,当玩家向量第一次进行旋转后,计算出来的向量是一个世界坐标,也就是从原点处出发的向量。这个向量的位置不对,因为这时如果移动玩家的位置,这个向量是不会跟着变化的。
我们需要的是图示中的红色向量位置。因此要跟玩家的向量相加,才能得到我们想要的坐标位置。
网友评论