13.Quaternion四元数计算

13.3D数学-Quaternion四元数-四元数计算


13.1 知识点

主要学习内容

四元数相乘

AngleAxis静态方法 通过轴角对API进行旋转

AngleAxis静态方法通过轴角对API进行旋转。

//通过轴角对API传入要转的轴和角度
//绕着y轴转20度
Quaternion q = Quaternion.AngleAxis(20, Vector3.up);
//乘上这个脚本挂载的立方体当前旋转四元数 让这个脚本挂载的立方体绕着y轴转20度
//this.transform.rotation = this.transform.rotation * q;
this.transform.rotation *= q;
//注意1:用四元数旋转完可能和Inspector窗口上的旋转对不上 不用管它
//注意2:旋转相对的坐标系 是物体自身坐标系 

四元数乘向量

AngleAxis静态方法 用一个向量乘一个四元数

AngleAxis静态方法用于将一个向量乘以一个四元数,相当于将那个向量按四元数的轴旋转了四元数的度数。

    //用一个向量乘一个四元数 相当于把那个向量按四元数的轴旋转了四元数的度数

    //目标:把指向z轴的向量连续往y轴转两次45度 最后会和x轴重合

    Vector3 v = Vector3.forward;
    print(v);//(0.0, 0.0, 1.0)

    v = Quaternion.AngleAxis(45, Vector3.up) * v;
    print(v);//(0.7, 0.0, 0.7)

    v = Quaternion.AngleAxis(45, Vector3.up) * v;
    print(v);//(1.0, 0.0, 0.0)


13.2 知识点代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//这个脚本挂载到立方体上
public class Lesson13_3D数学_Quaternion四元数_四元数计算 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 四元数相乘

        //通过轴角对API传入要转的轴和角度
        //绕着y轴转20度
        Quaternion q = Quaternion.AngleAxis(20, Vector3.up);
        //乘上这个脚本挂载的立方体当前旋转四元数 让这个脚本挂载的立方体绕着y轴转20度
        //this.transform.rotation = this.transform.rotation * q;
        this.transform.rotation *= q;
        //注意1:用四元数旋转完可能和Inspector窗口上的旋转对不上 不用管它
        //注意2:旋转相对的坐标系 是物体自身坐标系 

        #endregion

        #region 知识点二 四元数乘向量

        //用一个向量乘一个四元数 相当于把那个向量按四元数的轴旋转了四元数的度数

        //目标:把指向z轴的向量连续往y轴转两次45度 最后会和x轴重合

        Vector3 v = Vector3.forward;
        print(v);//(0.0, 0.0, 1.0)

        v = Quaternion.AngleAxis(45, Vector3.up) * v;
        print(v);//(0.7, 0.0, 0.7)

        v = Quaternion.AngleAxis(45, Vector3.up) * v;
        print(v);//(1.0, 0.0, 0.0)

        #endregion
    }
}

13.3 练习题

用目前所学知识,模拟飞机发射不同类型子弹的方法:单发,双发,扇形,环形

声明发射类型枚举

public enum E_FireType
{
    // 单发
    One,
    // 双发
    Two,
    // 扇形
    Three,
    // 环形
    Round
}

创建飞机,创建飞机脚本,声明发射类型变量,把飞机脚本添加到飞机上

public class Lesson13_练习题_Airplane : MonoBehaviour
{
    // 声明发射类型变量 
    private E_FireType nowType = E_FireType.One;
}

飞机脚本 Update 内添加按按键切换发射类型逻辑

if (Input.GetKeyDown(KeyCode.Alpha1))
{
    nowType = E_FireType.One;
}
else if (Input.GetKeyDown(KeyCode.Alpha2))
{
    nowType = E_FireType.Two;
}
else if (Input.GetKeyDown(KeyCode.Alpha3))
{
    nowType = E_FireType.Three;
}
else if (Input.GetKeyDown(KeyCode.Alpha4))
{
    nowType = E_FireType.Round;
}

飞机脚本 Update 内添加按按键发射子弹逻辑

// Update 内添加按按键发射子弹逻辑
if (Input.GetKeyDown(KeyCode.Space))
{
    Fire();
}

添加子弹预制体,声明子弹对象,拖拽赋值

添加子弹预制体,声明子弹对象,拖拽赋值

//子弹一直往前飞,五秒后销毁
public class Lesson13_练习题_Bullet : MonoBehaviour
{
    public float moveSpeed = 10;
    
    void Start()
    {
        Destroy(this.gameObject, 5);
    }
    
    void Update()
    {
        this.transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);
    }
}

实现发子弹逻辑

// 开火方法
private void Fire()
{
    switch (nowType)
    {
        case E_FireType.One:
            // 一个子弹直接创建在飞机当前位置和保持飞机当前旋转即可
            Instantiate(bullet, this.transform.position, this.transform.rotation);
            break;
        case E_FireType.Two:
            // 两个子弹直接创建飞机当前位置左右各偏移一些和保持飞机当前旋转即可
            Instantiate(bullet, this.transform.position - this.transform.right * 0.5f, this.transform.rotation);
            Instantiate(bullet, this.transform.position + this.transform.right * 0.5f, this.transform.rotation);
            break;
        case E_FireType.Three:
            // 一个子弹朝飞机自己的面朝向发射
            Instantiate(bullet, this.transform.position, this.transform.rotation);
            // 左边的子弹朝飞机自己左侧旋转 20 度再发射
            Instantiate(bullet, this.transform.position, this.transform.rotation * Quaternion.AngleAxis(-20, Vector3.up));
            // 右边的子弹朝飞机自己右侧旋转 20 度再发射
            Instantiate(bullet, this.transform.position, this.transform.rotation * Quaternion.AngleAxis(20, Vector3.up));
            break;
        case E_FireType.Round:
            // 360 度除以你要发射多少子弹得到每个子弹偏移的角度 这样每转这个角度就发射一个子弹
            float angle = 360f / roundNum;
            for (int i = 0; i < roundNum; i++)
            {
                // 角度是当前偏移度数乘飞机自己的旋转
                Instantiate(bullet, this.transform.position, this.transform.rotation * Quaternion.AngleAxis(i * angle, Vector3.up));
            }
            break;
    }
}

用所学3D数学知识实现摄像机跟随效果,摄像机在人物斜后方,通过角度控制倾斜率,通过鼠标滚轮可以控制摄像机距离人物的距离(有最大最小限制),摄像机看向人物头顶上方一个位置(可调节),Vector3.Lerp实现相机跟随人物,Quaternino.Slerp实现摄像机朝向过渡效果

对于前三点进行分析

  • 摄像机看向人物头顶上方一个位置(可调节)可以理解为当前对象的Vector3.up在乘一个头顶的标量系数,控制到底看头顶的哪里,称之为头顶向量。可以理解为看向头顶多高的位置。
  • 像机在人物斜后方,通过角度控制倾斜率可以理解为对于在头顶向量终点乘一个四元数偏转一个角度,角度可以变化实现控制倾斜率
  • 同时这个向量在乘一个标量大小实现控制摄像机距离人物的距离,通过鼠标滚轮控制

创建摄像机移动脚本,挂载到摄像机上,声明必要的变量

//目标对象
public Transform target;

//相对头顶的偏移位置 看向头顶多高的位置
public float headOffsetH = 1;

//摄像机倾斜的角度
public float offsetAngle = 45;

//默认的 摄像机离观测点的距离
public float cameraDis = 5;

//摄像机离观测点的距离必须是3和10之间
public float minDis = 3;
public float maxDis = 10;

//鼠标中间滚动控制的移动速度
public float roundSpeed = 1;

//看向对象时 四元数 旋转的速度
public float lookAtSpeed = 2;

//跟随对象移动的 速度
public float moveSpeed = 2;

//当前摄像机应该在的位置
Vector3 nowPos;

//头顶一个在的位置
Vector3 headPos;

//头顶位置指向摄像机的方向向量
private Vector3 nowDir;

Update 内实现摄像机相关逻辑

void Update()
{
    //实现了鼠标中键 滚动 来改变摄像机远近
    cameraDis += Input.GetAxis("Mouse ScrollWheel") * roundSpeed;

    //加紧函数 取最大值和最小值之间的数
    cameraDis = Mathf.Clamp(cameraDis, minDis, maxDis);

    //向头顶偏移位置
    headPos = target.position + target.up * headOffsetH;

    //用要倾斜的角度和轴算出要旋转的四元数 乘上当前向后的向量 就能得到摄像机偏移角度后往后方偏移位置
    //通俗理解 nowDir就是头顶位置指向摄像机的方向向量
    nowDir = Quaternion.AngleAxis(offsetAngle, target.right) * -target.forward;

    //用头顶位置加上要偏移的角度乘摄像机离观测点的距离 就是当前摄像机应该在的位置
    nowPos = headPos + nowDir * cameraDis;

    //直接把算出来的位置 进行赋值
    //this.transform.position = nowPos;

    //通过插值运算缓慢移动相机位置
    this.transform.position = Vector3.Lerp(this.transform.position, nowPos, Time.deltaTime * moveSpeed);

    //这里是通过插值运算来缓动看向物体
    //摄像机要看的是nowDir的反向向量 所以通过LookRotation算出旋转成的四元数 传入当做插值函数的终点
    this.transform.rotation = Quaternion.Slerp(this.transform.rotation, Quaternion.LookRotation(-nowDir), Time.deltaTime * lookAtSpeed);

    //画出头顶和摄像机的连线
    Debug.DrawLine(this.transform.position, headPos);
}

0度和45度时



13.4 练习题代码

Lesson13_练习题_Airplane

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#region 练习题一
//用目前所学知识,模拟飞机发射不同类型子弹的方法:单发,双发,扇形,环形

//声明发射类型枚举
public enum E_FireType
{
    //单发
    One,
    //双发
    Two,
    //扇形
    Three,
    //环形
    Round
}

public class Lesson13_练习题_Airplane : MonoBehaviour
{
    //声明发射类型变量 
    private E_FireType nowType = E_FireType.One;

    //子弹
    public GameObject bullet;

    public int roundNum = 4;
    
    void Start()
    {
        //bullet = Resources.CoroutineAsynLoad<GameObject>("Bullet");
    }
    


    void Update()
    {
        //Update内添加按按键切换发射类型逻辑
        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            nowType = E_FireType.One;
        }
        else if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            nowType = E_FireType.Two;
        }
        else if (Input.GetKeyDown(KeyCode.Alpha3))
        {
            nowType = E_FireType.Three;
        }
        else if (Input.GetKeyDown(KeyCode.Alpha4))
        {
            nowType = E_FireType.Round;
        }

        //Update内添加按按键发射子弹逻辑
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Fire();
        }
    }

    //开火方法
    private void Fire()
    {
        switch (nowType)
        {
            case E_FireType.One:
                //一个子弹直接创建在飞机当前位置和保持飞机当前旋转即可
                Instantiate(bullet, this.transform.position, this.transform.rotation);
                break;
            case E_FireType.Two:
                //两个子弹直接创建飞机当前位置左右各偏移一些和保持飞机当前旋转即可
                Instantiate(bullet, this.transform.position - this.transform.right * 0.5f, this.transform.rotation);
                Instantiate(bullet, this.transform.position + this.transform.right * 0.5f, this.transform.rotation);
                break;
            case E_FireType.Three:
                //一个子弹朝飞机自己的面朝向发射
                Instantiate(bullet, this.transform.position, this.transform.rotation);
                //左边的子弹朝飞机自己左侧旋转20度再发射——知识点 四元数*四元数=一个新的四元数 相当于是旋转量的叠加
                Instantiate(bullet, this.transform.position, this.transform.rotation * Quaternion.AngleAxis(-20, Vector3.up));
                //右边的子弹朝飞机自己右侧旋转20度再发射——知识点 四元数*四元数=一个新的四元数 相当于是旋转量的叠加
                Instantiate(bullet, this.transform.position, this.transform.rotation * Quaternion.AngleAxis(20, Vector3.up));
                break;
            case E_FireType.Round:
                //360度除以你要发射多少子弹得到每个子弹偏移的角度 这样每转这个哦安逸角度就发射一个子弹
                float angle = 360 / roundNum;
                for (int i = 0; i < roundNum; i++)
                    //角度是当前偏移度数乘飞机自己的旋转
                    Instantiate(bullet, this.transform.position, this.transform.rotation * Quaternion.AngleAxis(i * angle, Vector3.up));
                break;
        }
    }
}

#endregion

Lesson13_练习题_Bullet

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#region 练习题一
//用目前所学知识,模拟飞机发射不同类型子弹的方法:单发,双发,扇形,环形


//子弹一直往前飞,五秒后销毁
public class Lesson13_练习题_Bullet : MonoBehaviour
{
    public float moveSpeed = 10;
    
    void Start()
    {
        Destroy(this.gameObject, 5);
    }
    
    void Update()
    {
        this.transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);
    }
}

#endregion

Lesson13_练习题_CameraMove

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#region 练习题二
//用所学3D数学知识实现摄像机跟随效果
//摄像机在人物斜后方,通过角度控制倾斜率
//通过鼠标滚轮可以控制摄像机距离人物的距离(有最大最小限制)
//摄像机看向人物头顶上方一个位置(可调节)
//Vector3.Lerp实现相机跟随人物
//Quaternino.Slerp实现摄像机朝向过渡效果

//这个脚本要挂载到摄像机上
public class Lesson13_练习题_CameraMove : MonoBehaviour
{
    //目标对象
    public Transform target;

    //相对头顶的偏移位置 看向头顶多高的位置
    public float headOffsetH = 1;

    //摄像机倾斜的角度
    public float offsetAngle = 45;

    //默认的 摄像机离观测点的距离
    public float cameraDis = 5;

    //摄像机离观测点的距离必须是3和10之间
    public float minDis = 3;
    public float maxDis = 10;

    //鼠标中间滚动控制的移动速度
    public float roundSpeed = 1;

    //看向对象时 四元数 旋转的速度
    public float lookAtSpeed = 2;

    //跟随对象移动的 速度
    public float moveSpeed = 2;

    //当前摄像机应该在的位置
    Vector3 nowPos;

    //头顶一个在的位置
    Vector3 headPos;

    //头顶位置指向摄像机的方向向量
    private Vector3 nowDir;

    
    void Update()
    {
        //实现了鼠标中键 滚动 来改变摄像机远近
        cameraDis += Input.GetAxis("Mouse ScrollWheel") * roundSpeed;

        //加紧函数 取最大值和最小值之间的数
        cameraDis = Mathf.Clamp(cameraDis, minDis, maxDis);

        //向头顶偏移位置
        headPos = target.position + target.up * headOffsetH;

        //用要倾斜的角度和轴算出要旋转的四元数 乘上当前向后的向量 就能得到摄像机偏移角度后往后方偏移位置
        //通俗理解 nowDir就是头顶位置指向摄像机的方向向量
        nowDir = Quaternion.AngleAxis(offsetAngle, target.right) * -target.forward;

        //用头顶位置加上要偏移的角度乘摄像机离观测点的距离 就是当前摄像机应该在的位置
        nowPos = headPos + nowDir * cameraDis;

        //直接把算出来的位置 进行赋值
        //this.transform.position = nowPos;

        //通过插值运算缓慢移动相机位置
        this.transform.position = Vector3.Lerp(this.transform.position, nowPos, Time.deltaTime * moveSpeed);

        //这里是通过插值运算来缓动看向物体
        //摄像机要看的是nowDir的反向向量 所以通过LookRotation算出旋转成的四元数 传入当做插值函数的终点
        this.transform.rotation = Quaternion.Slerp(this.transform.rotation, Quaternion.LookRotation(-nowDir), Time.deltaTime * lookAtSpeed);

        //画出头顶和摄像机的连线
        Debug.DrawLine(this.transform.position, headPos);
    }
}

#endregion


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏