web前端

unity-四元数

2022-12-29  本文已影响0人  姜治宇

什么是四元数?

欧拉角和四元数,都是表示物体旋转的。但欧拉角有个万向节死锁的问题,为了解决这个问题,因此引入了四元数。
四元数,就是用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
因为我们做了向量标准化,因此,当玩家向量第一次进行旋转后,计算出来的向量是一个世界坐标,也就是从原点处出发的向量。这个向量的位置不对,因为这时如果移动玩家的位置,这个向量是不会跟着变化的。
我们需要的是图示中的红色向量位置。因此要跟玩家的向量相加,才能得到我们想要的坐标位置。
上一篇下一篇

猜你喜欢

热点阅读