Unity05_3D数学

Unity05_3D数学

  • 一、Mathf相关
    • [1.1 Mathf 的基础操作](#1.1 Mathf 的基础操作)
    • [1.2 Mathf中的三角函数](#1.2 Mathf中的三角函数)
    • [1.3 坐标系复习](#1.3 坐标系复习)
    • [1.4 完整代码](#1.4 完整代码)
  • 二、向量相关
    • [2.1 向量的基础操作](#2.1 向量的基础操作)
    • [2.2 向量基础运算](#2.2 向量基础运算)
    • [2.3 向量点乘](#2.3 向量点乘)
    • [2.4 向量叉乘](#2.4 向量叉乘)
    • [2.5 向量的插值运算](#2.5 向量的插值运算)
    • [2.6 完整代码](#2.6 完整代码)
  • 三、四元数
    • [3.1 四元数的基础操作](#3.1 四元数的基础操作)
    • [3.2 四元数常用方法](#3.2 四元数常用方法)
    • [3.3 四元数运算](#3.3 四元数运算)
    • [3.4 完整代码](#3.4 完整代码)

一、Mathf相关

1.1 Mathf 的基础操作

(1)Math 和 Mathf 的区别

  • Math 是C# 封装的,用于进行数学计算的 ,位于System空间

  • Mathf 是Unity 中封装好的,用于进行数学计算的 结构体 ,位于UnityEngine空间
    (2)Mathf中的常用属性和方法

  • PI:Mathf.PI

  • 绝对值:Mathf.Abs(num)

  • 向上/向下取整:Mathf.CeilToInt(num) / Mathf.FloorToInt(num)

  • 四舍五入:Mathf.RoundToInt(num)

  • 钳制函数:Mathf.Clamp(num,minNum,maxNum)
    当num大于maxNum时 return maxNum,当num<minNum时 return minNum,反之则 return num
    可以用来让当前生命值 限制在一个合理的范围

  • 取最大值/最小值 Mathf.Max()/Mathf.Min()

  • 一个数的n次方:Mathf.Pow(num,n)

  • 一个数的平方根:Mathf.Sqrt(num)

  • 判断正负数:Mathf.Sign(num)
    num为正数时 return 1,为负数时 return -1
    运行代码

运行结果

(3)Mathf 中的插值运算

  • 作用:用来平滑数值,让他趋近于一个目标值,如可以用来平滑物体移动,或者跟随某个物体移动
  • 语法:Mathf.Lerp(startNum, endNum,t)
    t 是插值系数,在 0 到 1 之间

当t 靠近0时,运算的结果将更加接近于 startNum

当t 靠近1时,运算的结果将更加接近于 endNum
========== 【演示:使用插值运算跟随物体移动】 ==========

(a) 每帧改变startNun 实现先快后慢式的运动

该方法是让 挂载该脚本的对象 每次改变xyz,让其实现趋近于B对象,从而实现跟随

(b)每帧改变t 实现匀速运动(start不改变)

该方法通过每帧改变插值系数,来达到趋近,当插值系数大于等于1 的时候,对象即视为到达B对象,又因为该方法不会改变start,所以要记录初始的位置。

(补充)当当插值系数大于等于1 的时候,此时改变B对象的位置,可以观察到挂载对象直接瞬移过去了,解决方法为,当B对象的位置改变时,应该重新赋值终点位置,并重置各变量

1.2 Mathf中的三角函数

(1)弧度和角度的相互转化

  • 角度转弧度:弧度 = 角度 * Mathf.Rad2Deg
  • 弧度转角度:角度 = 弧度 * Mathf.Deg2Rad
    Mathf.Rad2Deg 是常量 57.3 用于角度转弧度
    Mathf.Deg2Rad 是常量 0.01745 用于弧度转角度
    (2)三角函数:在Mathf中的三角函数,其传入的参数为 弧度

所以要计算对应角度的三角函数值,要先算角度,然后计算值

Mathf.Sin( 弧度值 ) || Mathf.Cos( 弧度值 )
(3)反三角函数:即通过已知三角函数值,来得到对应的弧度

Mathf.ASin( 三角函数值 )
========== 【演示:角度和弧度间的相互转换及三角函数值】 ==========

运行代码

运行结果

========== 【演示:通过三角函数来实现物体的曲线运动】 ==========

运行代码

运行结果

1.3 坐标系复习

(1)unity中的坐标系包含四种

  • 世界坐标系:即unity中Scene的坐标系,他不受到其他条件的干扰,其原点坐标为Sence中心
  • 物体坐标系:又称本地坐标系,受父对象的位置和缩放大小的干扰
  • 屏幕坐标系:屏幕坐标系是指玩家的屏幕窗口,其坐标原点位于左下角
  • 视口坐标系:与屏幕坐标系相似,其坐标原点是左下角,右上角为坐标轴(0,0)点

(屏幕坐标系和视口坐标系的z轴是到摄像机的距离)
(1)坐标系间的相互转换,坐标系的转换一般是通过摄像机在两个空间中投影转换的,因此一般需要借助摄像机实例

  • 世界坐标系转屏幕坐标系:Camera摄像机实例.WorldToScreenPoint()
  • 世界坐标系转视口坐标系:Camera摄像机实例.WorldToViewportPoint()
  • 屏幕坐标系转世界坐标系:Camera摄像机实例.ViewportToWorldPoint()
  • 视口坐标系转屏幕坐标系:Camera摄像机实例.ScreenToWorldPoint();
  • 世界坐标系 和 本地坐标系 是同与空间的不同坐标系,他们的相互转换不需要依赖Camera实例,依赖本地坐标轴的参照transform

世界坐标系转本地:transform.InverseTransformPoint(worldPos);

本地坐标系转世界:transform.TransformPoint(localPos);
========== 【演示:坐标系相互转化】 ==========

运行代码

运行结果

1.4 完整代码

Mathf 的基础操作

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class lesson1_script : MonoBehaviour
{
    //Math 和 Mathf 的区别
    //Math 是C#封装的,用于进行数学计算的 类,在System空间中
    //Mathf 是Unity中封装好的用于数学计算的 结构体,在UnityEngine空间中

    public GameObject B;
    public Vector3 bNowPos;
    public float time;
    public Vector3 startPos;

    // Start is called before the first frame update
    void Start()
    {
        #region 1、常用的方法和属性
        // - PI:Mathf.PI
        // - 绝对值:Mathf.Abs(num)

        // - 向上/向下取整:Mathf.CeilToInt(num) / Mathf.FloorToInt(num)
        //   num为float类型
        // - 四舍五入:Mathf.RoundToInt(num)

        // - 钳制函数:Mathf.Clamp(num,minNum,maxNum)
        //   当num大于maxNum时 return maxNum,当num<minNum时 return minNum,反之则 return num
        // - 取最大值/最小值 Mathf.Max()/Mathf.Min()

        // - 一个数的n次方:Mathf.Pow(num,n)
        // - 一个数的平方根:Mathf.Sqrt(num)

        // - 判断正负数:Mathf.Sign(num)
        //   num为正数时 return 1,为负数时  return -1


        // ========== 【演示:常用属性和方法】 ==========
        print("半径为2的圆,他的面积 { Mathf.PI * 2 * 2 } 为:" + Mathf.PI * 2 * 2);
        print("-3的绝对值 { Mathf.Abs(-3) } 为:" + Mathf.Abs(-3));
        print("1.63f向上取整 { Mathf.CeilToInt(1.63f) } 为:" + Mathf.CeilToInt(1.63f));
        print("1.63f向下取整 { Mathf.FloorToInt(1.63f) } 为:" + Mathf.FloorToInt(1.63f));
        print("1.63f四舍五入 { Mathf.RoundToInt(1.63f) } 为:" + Mathf.RoundToInt(1.63f));
        print("23 经钳制函数 { Mathf.Clamp(23,55,88) }后的结果为:" + Mathf.Clamp(23, 55, 88));
        print("11,22,33,44中的最大值 { Mathf.Max(11,22,33,44) } 为:" + Mathf.Max(11, 22, 33, 44));
        print("11,22,33,44中的最小值 { Mathf.Min(11,22,33,44) } 为:" + Mathf.Min(11, 22, 33, 44));
        print("3 的 4 次方为 { Mathf.Pow(3,4) } 为:" + Mathf.Pow(3,4));
        #endregion

        
    }

    // Update is called once per frame
    void Update()
    {
        #region 2、 常用方法
        // 插值运算:Mathf.Lerp(startNum, endNum,t)
        // t 是插值系数,在 0 到 1 之间
        // 当t 靠近0时,运算的结果将更加接近于 startNum
        // 当t 靠近1时,运算的结果将更加接近于 endNum
        // 可以用来平滑物体移动,或者跟随某个物体
        // ========== 【演示:使用插值运算跟随物体移动】 ==========
        // 该脚本挂载于物体A,他将跟随物体B运动
        // 获取到该脚本的位置信息
        // 方法一 每帧改变startNun 实现先快后慢式的运动
        //Vector3 pos = transform.position;
        //pos.x = Mathf.Lerp(pos.x, B.transform.position.x, Time.deltaTime);
        //pos.y = Mathf.Lerp(pos.y, B.transform.position.y, Time.deltaTime);
        //pos.z = Mathf.Lerp(pos.z, B.transform.position.z, Time.deltaTime);
        //this.transform.position = pos;
        // 方法二 每帧改变t 实现匀速运动(start不改变)
        if (bNowPos != B.transform.position)
        {
            //当B发生了位置改变时,将time归零,
            time = 0;
            //B的当前位置为A移动的终点位置
            bNowPos = B.transform.position;
            //记录当前位置为移动的开始位置
            startPos = this.transform.position;

        }
        Vector3 pos = transform.position;
        time += Time.deltaTime;// start和end 不改变,通过改变time来实现了匀速运动
        pos.x = Mathf.Lerp(startPos.x, bNowPos.x, time);
        pos.y = Mathf.Lerp(startPos.y, bNowPos.y, time);
        pos.z = Mathf.Lerp(startPos.z, bNowPos.z, time);
        this.transform.position = pos;
        #endregion
    }
}

1.2 Mathf中的三角函数

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class lesson2_script : MonoBehaviour
{
    // Start is called before the first frame update
    //移动方向,默认想前移动
    private Vector3 moveDir = Vector3.forward;
    private float time;

    GameObject obj;
    LineRenderer line;
    void Start()
    {
        //(1)弧度和角度的相互转化
        // - 角度转弧度:弧度 = 角度 * Mathf.Rad2Deg
        // - 弧度转角度:角度 = 弧度 * Mathf.Deg2Rad 
        // Mathf.Rad2Deg 是常量 57.3 用于角度转弧度
        // Mathf.Deg2Rad 是常量 0.01745 用于弧度转角度


        //(2)三角函数:在Mathf中的三角函数,其传入的参数为 弧度
        // 所以要计算对应角度的三角函数值,要先算角度,然后计算值
        // Mathf.Sin( 弧度值 ) ||  Mathf.Cos( 弧度值 )

        //(3)反三角函数:即通过已知三角函数值,来得到对应的弧度
        // Mathf.ASin( 三角函数值 )
        // ========== 【演示:角度和弧度间的相互转换及三角函数值】 ==========
        print("将30度角转为弧度 { 30 *  Mathf.Rad2Deg } 的结果为:" + 30 * Mathf.Rad2Deg);
        print("30度的角,sin值 { Mathf.Sin(30 * Mathf.Deg2Rad) } 为:" + Mathf.Sin(30 * Mathf.Deg2Rad));
        print("sin值为0.5f 对应的弧度为 { Mathf.Asin(0.5f) } 为:" + Mathf.Asin(0.5f));
        print("其对应的角度 { Mathf.Asin(0.5f) * Mathf.Rad2Deg } 为:" + Mathf.Asin(0.5f) * Mathf.Rad2Deg);


        //为了方便观察,通过LineRenderer进行绘画运动轨迹
        obj = new GameObject();
        line = obj.AddComponent<LineRenderer>();
    }

    // Update is called once per frame
    void Update()
    {
        //可以通过三角函数来进行模拟 物品的曲线运动
        // ========== 【演示:实现物体的曲线运动】 ==========
        time += Time.deltaTime;
        // 在z轴上向前移动
        this.transform.Translate(Vector3.forward * 5 * Time.deltaTime);
        // 通过Sin计算来进行X轴上的偏移
        this.transform.Translate(Vector3.right * 5 * Time.deltaTime * Mathf.Sin(time) );

        
        //设置线的宽窄
        line.startWidth = 0.2f;
        line.endWidth = 0.2f;
        line.positionCount++;
        line.SetPosition(line.positionCount-1,this.transform.position);
    }
}

1.3 坐标系复习

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class lesson3_script : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        // unity中的坐标系包含四种
        // 世界坐标系:即unity中Scene的坐标系,他不受到其他条件的干扰,其原点坐标为Sence中心
        // 物体坐标系:又称本地坐标系,受父对象的位置和缩放大小的干扰
        // 屏幕坐标系:屏幕坐标系是指玩家的屏幕窗口,其坐标原点位于左下角
        // 视口坐标系:与屏幕坐标系相似,其坐标原点是左下角,右上角为坐标轴(0,0)点
        //(屏幕坐标系和视口坐标系的z轴是到摄像机的距离)

        //(1)坐标系间的相互转换,坐标系的转换是通过摄像机在两个空间中投影转换
        // - 世界坐标系转屏幕坐标系:Camera摄像机实例.WorldToScreenPoint();
        // - 世界坐标系转视口坐标系:Camera摄像机实例.WorldToViewportPoint();

        // - 屏幕坐标系转世界坐标系:Camera摄像机实例.ViewportToWorldPoint();
        // - 视口坐标系转屏幕坐标系:Camera摄像机实例.ScreenToWorldPoint();
        //
        print("世界坐标系点(1,2,3)转 屏幕坐标系的结果为:" + Camera.main.WorldToScreenPoint(new Vector3(1, 2, 3)));
        print("世界坐标系点(1,2,3)转 视口坐标系的结果为:" + Camera.main.WorldToViewportPoint(new Vector3(1, 2, 3)));
        print("视口坐标系点(0.5f,0.7f,1)转 世界坐标系的结果为:" + Camera.main.ViewportToWorldPoint(new Vector3(0.5f,0.7f,1)));
        print("屏幕坐标系点(0.5,0.7,1)转 世界坐标系的结果为:" + Camera.main.ScreenToWorldPoint(new Vector3(0.5f, 0.7f, 1)));
        // - 世界坐标系 和 本地坐标系 的相互转换不需要依赖Camera
        // transform是指本地坐标轴的参照
        //世界坐标系转本地:transform.InverseTransformPoint(worldPos);
        //本地坐标系转世界:transform.TransformPoint(localPos);

    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

二、向量相关

2.1 向量的基础操作

(1)向量的基础概念

  • 向量和标量
    向量:有方向,有大小
    标量:无方向,没有大小
  • Vector3类型变量可以用来表示 向量 和 点 的坐标
    两个点可以确定一个向量

========== 【演示:两个点可以确定一个向量】 ==========

该向量起点为A, 终点为B
(复习,Vector3类型可以进行运算的原因是进行了运算符重载)

运算符重载的语法:public static 返回类型 operator 运算符(参数)

(2)向量的模长

  • 计算两个点的距离 Vector3.Distance(Vector3点A,Vector3点B)
  • 向量的模长 Vector3向量.magnitude

========== 【演示:向量的模长】 ==========


(3)单位向量

  • 语法:Vector3向量.normalized
  • 计算:单位向量 = Vector3向量/向量的模

2.2 向量基础运算

(1)向量相加

  • 点 与 向量 相加/相减,会得到一个新点
    意为:点朝着向量的方向/反方向 移动 对应的模长
  • 向量 与向量的相加/相减,会得到一个新向量

========== 【演示:让摄像机保持在物体的指定位置】 ==========

csharp 复制代码
 void LateUpdate()
 {
     // 注意:摄像机的移动要放在LateUpdata中
     // 作业实现摄像机跟随物体移动
     // 物体的位置是一个点,我要将摄像机保持在一个方向上(向量),现在我可以用点+向量,不停的更新点的坐标来实现跟随,并保持距离
     this.transform.position = obj.position + new Vector3(-4, 0, 7);
     //这里实现了摄像机的跟随,但是没有一直看着物体
     this.transform.LookAt(obj);

 }

========== 【演示:让摄像机保持在物体的指定位置】 ==========

通过LineRenderer来描述移动轨迹

2.3 向量点乘

  • 语法:Vector3.Dot(Vector3向量1, Vector3向量2)
    两个向量的点乘 = 两个向量的模长积 * 向量夹角的Cos值
  • 作用:通过结果可以判断两个向量是 同向,垂直,还是反向

resout > 0 目标对象在我的 前方

resout < 0 目标对象在我的后方

resout = 0 目标对象在我的左侧或右侧

  • 通过点乘计算两个向量的夹角
    方法一:通过Vector3.angle(向量1 向量2)来获得夹角
    方法二:先计算两个向量的单位向量, 然后计算两个向量 的点乘 即对应夹角的三角函数值Cos,最后反三角函数计算

========== 【演示:使用向量点乘判断物体位置】 ==========

模拟游戏中的背袭,当怪物受到攻击时,检测玩家在精灵的方位,若为后方,则触发背袭

该小节运行结果

玩家在不同位置 扔球的效果 对比

2.4 向量叉乘

  • 语法:Vector3.Cross(Vector3向量1, Vector3向量2)
  • 作用:判断左右方向,计算旋转轴
    通过向量叉乘可以得到一个新向量,该向量为原两个向量组成的平面的法向量

========== 【演示:使用向量叉乘判断物体位置】 ======

通过法向量的y值大小可以判断B物体再A物体的那一侧(左右)

运行结果,新向量为 蓝色的射线,当y>0时在物体的左侧,<0在物体的右侧

========== 【演示:使用向量叉乘计算旋转轴】 ======

运行前后对比图

2.5 向量的插值运算

(1)Mathf.Lerp 与 Vector3.Lerp的区别

  • 作用不一样
    Mathf作用于 float数值 其返回值类型也是float类型的值,用来进行平滑一些数值,如光照强度
    Vector3作用于 Vector3坐标 其返回类型是Vector3类型的坐标,用来平滑物体的移动

  • 语法不一样
    语法:Mathf.Lerp(起点值, 终点值, 插值系数 0-1)
    语法:Vector3.Lerp(起点坐标, 终点坐标, 插值系数 0-1)

  • Vector3.Lerp 的本质是 坐标的xyz各执行一遍 Mathf.Lerp

  • 相同点 公式相同:result = start + (end - start) * t
    当 t 趋近于0时,其结果会趋近于start
    当 t 趋近于1时,其结果会趋近于end
    (2)Vector3.Lerp 的使用

  • 先快后慢:每帧改变 start 的值

  • 匀速变换:每帧改变 t 的值
    当 t 大于等于1时,改变 end 位置,物体会直接跟着走,原因是当t=1时会得到结果,不修改的话结果会一直保持end位置

========== 【演示:利用向量插值实现A 跟随B 移动】 ==========

(3)球形插值

  • 语法:Vector3.Slerp(起点,终点,插值系数)
    物体的初始位置不影响,在执行该代码时会自动到达 start 位置

========== 【演示:球型插值】 ==========

运行结果

要想在XY平面上移动,必须至少一个坐标存在y值

2.6 完整代码

2.1 向量的基础操作

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class lesson4_script : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        //(1)基础知识点
        // - 向量和标量
        // 向量:有方向,有大小
        // 标量:无方向,没有大小
        // - Vector3可以用来表示 向量 和 点 的坐标
        //========== 【演示:两个点可以确定一个向量】 ==========
        Vector3 A = new Vector3(1, 2, 3);
        Vector3 B = new Vector3(3, 3, 3);
        print("点A(1,2,3) 和 点B(3,3,3) 组成的向量AB { (B-A) } 为:" + (B - A));
        // 这里可以进行Vector3 类型的运算,是因为进行了运算符重载
        // 补充:运算符重载的方法
        // public static 返回类型 operator 运算符(参数)
        // public static Vector3 operator - (Vector3 A, Vector3 B)   
        // {
        //     return new Vector3(A.x - B.x, A.y - B.y, A.z - B.z);
        // }

        // - 向量的模长
        // 法一:计算两个点的距离 Vector3.Distance(Vector3点A,Vector3点B)
        // 法二:向量的模长 Vector3向量.magnitude
        //========== 【演示:向量的模长】 ========== 
        print("通过计算两个点的距离来计算模长 { Vector3.Distance(A,B) } 为:" + Vector3.Distance(A,B));
        print($"向量AB { B-A } 的模长为:" + (B-A).magnitude);

        // - 常见的向量
        // 单位向量:
        //      Vector3向量.normalized
        //      Vector3向量/向量的模Vector3向量.magnitude
    }

}

2.2 向量的基础运算

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 向量的基础运算
/// </summary>
public class lesson5_script : MonoBehaviour
{
    // Start is called before the first frame update
    public Transform obj;
    private Vector3 nowPos;
    public GameObject obj1;
    public GameObject obj2;
    public LineRenderer line1;//用来描述摄像机的轨迹
    public LineRenderer line2;//用来描述Cube的轨迹
    void Start()
    {
        obj1 = new GameObject();
        obj2 = new GameObject();
        line1 = obj1.gameObject.AddComponent<LineRenderer>();
        line2 = obj2.gameObject.AddComponent<LineRenderer>();
        nowPos = obj.position;
    }

    // Update is called once per frame
    void LateUpdate()
    {
        // 注意:摄像机的移动要放在LateUpdata中
        // 作业实现摄像机跟随物体移动
        // 物体的位置是一个点,我要将摄像机保持在一个方向上(向量),现在我可以用点+向量,不停的更新点的坐标来实现跟随,并保持距离
        this.transform.position = obj.position + new Vector3(-4, 0, 7);
        //绘制摄像机 与 Cube的 轨迹
        #region 绘制摄像机 与 Cube的 轨迹
        if (obj.position != nowPos)
        {
            line1.positionCount++;
            line2.positionCount++;
            line1.startWidth = 0.2f;
            line1.endWidth = 0.2f;
            line2.startWidth = 0.2f;
            line2.endWidth = 0.2f;
            line1.SetPosition(line1.positionCount - 1, this.transform.position);
            line2.SetPosition(line2.positionCount - 1, obj.position);
            nowPos = obj.position;
        }
        #endregion
        //这里实现了摄像机的跟随,但是没有一直看着物体
        this.transform.LookAt(obj);

    }
}

2.3 向量点乘

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 向量的点乘
/// 该脚本也用来模拟背袭 扔球,挂载到球上
/// 正常情况下应该挂载到球的预设体上,然后通过克隆创建预设体的形式来抛球
/// </summary>
public class lesson6_script : MonoBehaviour
{
    //
    public Transform ownerPlayer;

    // Start is called before the first frame update
    void Start()
    {

        //(3)通过点乘计算两个向量的夹角
        // 也可以通过Vector3.angle(向量1 向量2)来获得夹角
        // 先计算两个向量的单位向量
        Vector3 dirA = new Vector3(1, 0, 1);
        dirA = dirA.normalized;//向量A的单位向量
        Vector3 dirB = Vector3.forward;//方位向量是单位向量,其模长为1
        // 计算两个向量 的点乘 即对应夹角的三角函数值Cos
        float result = Vector3.Dot(dirA, dirB);
        // 通过Cos值获取对应弧度/角度
        print("向量A,向量B分别为:" + dirA + ", " + dirB);
        print("向量A 和 向量B 的夹角为: " + Mathf.Acos(result) * Mathf.Rad2Deg);
    }

    // Update is called once per frame
    void Update()
    {
        //(1)画线调试 我发现将代码写在stat中会一闪而过,所以为了直观要每帧更新
        // - 画线段:Debug.DrawLine(Vector3 start, Vector3 end, color)
        // - 画射线:Debug.DrawRaw(Vector3 start, Vector3 dir, color) 
        // 该射线并不会无线延长
        // 
        // 从cube开始向世界坐标系(0,0,5)的位置画线段,颜色为 黄色
        Debug.DrawLine(this.transform.position, Vector3.forward * 5, Color.yellow);
        // 在cube的Y轴正反向画一条射线,颜色为 蓝色
        Debug.DrawRay(this.transform.position, Vector3.up, Color.green);

        //(2)向量点乘
        // - 语法:Vector3.Dot(Vector3向量1, Vector3向量2)
        // 两个向量的点乘 = 两个向量的模长积 * 向量夹角的Cos值
        // - 作用:通过结果可以判断两个向量是 同向,垂直,还是反向

        
    }
    //========== 【演示:使用向量点乘判断物体位置】 ==========
    #region 演示:使用向量点乘判断物体位置
    //模拟游戏中的背袭,玩家在我的后面
    //当 精灵受到攻击时,检测玩家再精灵的方位,若为后方,则触发背袭
    //这里进行简单的模拟,球的拥有者ownerPlayer,直接通过inspector关联
    private void OnTriggerEnter(Collider other)
    {
        // 只要接触到 怪物时 才可以触发
        if(other.CompareTag("Monster"))
        {
            //向量为 怪物的面朝向 , 从怪物到玩家的向量
           float dotResult = Vector3.Dot(other.transform.forward, ownerPlayer.position-  other.transform.position);
            if(dotResult < 0 )
            {
                //结果小于零,玩家在怪物的后面
                print("进入背袭战斗");
            }
            else
            {
                print("正常战斗");
            }
        }
    }
    #endregion
}

2.4 向量叉乘

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson7_script : MonoBehaviour
{
    public Transform A;
    public Transform B;

    //用于演示 计算旋转轴 的两个对象
    public Transform C;
    public Transform D;//D为目标对象
    // Start is called before the first frame update
    void Start()
    {
        //()向量叉乘
        // - 几何意义:通过向量叉乘 可以的到两个向量组成平面的法向量
        // - 物理意义:通过法向量的方向可以判断两向量之间的位置关系
        // - 语法:Vector3.Cross(Vector3 向量A, Vector3 向量B)

    }

    // Update is called once per frame
    void Update()
    {

        //(2)向量叉乘的用途
        // - 通过法向量的y值大小可以判断B物体在A物体的那一侧(左右)
        //========== 【演示:使用向量插乘判断物体位置】 ==========
        Vector3 result = Vector3.Cross(A.position, B.position);
        if (result.y > 0)
        {
            print("物体B在物体A的右边");
        }
        else if(result.y < 0)
        {
            print("物体B在物体A的左边");
        }
        Debug.DrawRay(Vector3.zero, A.position, Color.red);
        Debug.DrawRay(Vector3.zero, B.position, Color.yellow);
        Debug.DrawRay(Vector3.zero, result, Color.blue);//这里将得到的法向量画出来(蓝色)

        // - 已知玩家的面朝向,和目标的位置,通过叉乘计算出法向量,然后围绕法向量进行旋转
        // 第一步,计算出旋转的轴
        Vector3 axis = Vector3.Cross(C.forward, D.position - C.position);
        // 第二步,计算本次旋转的角度(使用点乘/旋转角度API)
        float angle = Vector3.Angle(C.forward, D.position - C.position);
        C.Rotate(axis,angle);
    }
}

2.5 向量的插值运算

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Lesson8_插值运算
/// </summary>
public class lesson8_script : MonoBehaviour
{
    //用来描述运动轨迹CD
    public GameObject obj1;
    public GameObject obj2;
    public LineRenderer line1;
    public LineRenderer line2;


    //用于演示的对象
    public Transform A;
    public Transform B;
    public Transform C;
    public Transform D;
    //用于演示 匀速运动时记录A 的起始位置
    private Vector3 start;
    private float time;
    private Vector3 end;

    // Start is called before the first frame update
    void Start()
    {
        //(1)Mathf.Lerp 与 Vector3.Lerp的区别
        // - 作用不一样
        //   Mathf作用于 float数值 其返回值类型也是float类型的值
        //   Vector3作用于 Vector3坐标 其返回类型是Vector3类型的坐标
        // - 语法不一样
        //   语法:Mathf.Lerp(起点值, 终点值, 插值系数 0-1)
        //   语法:Vector3.Lerp(起点坐标, 终点坐标, 插值系数 0-1)
        // - Vector3.Lerp 的本质是 坐标的xyz各执行一遍 Mathf.Lerp
        // - 数学公式相同:result = start + (end - start) * t
        //   当 t 趋近于0时,其结果会趋近于start
        //   当 t 趋近于1时,其结果会趋近于end


        //匀速运动初始化
        start = A.position;
        end = B.position;
        time = 0;

        //描述运动轨迹
        obj1 = new GameObject();
        obj2 = new GameObject();
        line1 = obj1.gameObject.AddComponent<LineRenderer>();
        line2 = obj2.gameObject.AddComponent<LineRenderer>();
    }

    // Update is called once per frame
    void Update()
    {
        //(2)Vector3.Lerp 的使用
        // - 先快后慢:每帧改变 start 的值
        // - 匀速变换:每帧改变 t 的值
        //   当 t 大于等于1时,改变 end 位置,物体会直接跟着走,原因是当t=1时会得到结果,不修改的话结果会一直保持end位置

        //========== 【演示:利用向量插值实现A 跟随B 移动】 ==========
        // 先快后慢,每帧改变A的位置
        //A.position = Vector3.Lerp(A.position, B.position, Time.deltaTime);

        // 匀速运动,每帧改变插值系数
        // 需要记录A的起始位置
        // 当B的位置被改变时,且需要重新计算起始位置和时间
        if(B.position != end)
        {
            start = A.position;
            end = B.position;//终点位置重置
            time = 0;
        }
        A.position = Vector3.Lerp(start,end, time);
        time += Time.deltaTime;

        //(3)球形插值
        // - 语法:Vector3.Slerp(起点,终点,插值系数)
        //   物体的初始位置不影响,在执行该代码时会自动到达 start 位置
        //========== 【演示:球型插值】 ==========
        // 实现物体C 在XZ 平面上移动
        C.position = Vector3.Slerp(new Vector3(10,0,0),new Vector3(-10,0,0), time);
        // 实现物体D 在XY 平面上移动
        // 这里发先在Slerp中,若坐标的Y值为0,会导致只在XZ平面上旋转
        D.position = Vector3.Slerp(new Vector3(10, 2, 0), new Vector3(-10, 2, 0), time);

        // - 补充,在演示过程中我发现CD一开始在起始位置抖动,这是因为Slerp没有累加Time.deltaTime,需要手动累加

        line1.positionCount++;
        line2.positionCount++;
        line1.startWidth = 0.2f;
        line1.endWidth = 0.2f;
        line2.startWidth = 0.2f;
        line2.endWidth = 0.2f;
        line1.SetPosition(line1.positionCount - 1, C.position);
        line2.SetPosition(line2.positionCount - 1, D.position);
    }
}

三、四元数

3.1 四元数的基础操作

(1)什么是四元数

  • 概念:Quaternion 四元数,是封装好的结构体,由四个分量(X,Y,Z,W)组成,用来解决欧拉角中的万向死锁问题
    X,Y,Z为 Vector3类型的欧拉角的各分量计算,W为标量

如:绕轴 A(x,y,z)旋转60度

X = sin(60 / 2) * x , Y = sin(60/2) * y , Z = sin(60/2) * z

W = cos(60 /2)
(2)为什么使用四元数

  • 解决了使用欧拉角旋转,同一旋转角度不唯一的情况

  • 解决了万向节死锁
    (3)四元数的使用语法

  • 四元数对应的欧拉角取值为 -180 到 180

  • 四元数的创建
    法一:通过公式创建(不推荐)
    法二:通过API创建,其原理为通过某一个轴进行旋转指定角度得到
    法三:通过欧拉角创建
    欧拉角转四元数:Quaternion.Euler(x, y, z);

  • 四元数转欧拉角 q3.eulerAngles

========== 【演示:四元数创建】 ==========


运行结果:依次为绕xyz轴旋转的(该代码对应前两个图)

========== 【演示:四元数转欧拉角】 ==========

运行结果

3.2 四元数常用方法

(1)单位四元数:Quaternion.indentity => (0,0,0,1)

  • 单位四元数是 模长为 1 的四元数
  • 作用:在Unity中代表无旋转状态,可用于将旋转角度未知的物体,进行角度的初始化

========== 【演示:单位四元数】 ==========

运行结果

(2)向量看向转四元数:Quaternion.LookRotation(Vector3向量)

  • 作用:得到对应向量的四元数,通过重新赋值对象的rotation,实现对象看向 向量方向
    (LookAt 内部就是封装调用的 Quaternion.LookRotation)

========== 【演示:让 A 看向 B】 ==========

运行结果

(3)四元数的插值运算:Lerp 和 Slerp

  • 作用:让物体对象 转向 某一个角度
  • 区别:
    Lerp在旋转时是线性的,他类似于割圆,只是尽可能的逼近于 旋转角度
    Slerp在旋转时是弧度,可以正确的旋转,且速度较快,一般情况下推荐使用 Slerp
    (使用逻辑与 Vector3 的逻辑一样,不会主动累加 Time.deltaTime)

========== 【演示:插值运算】 ==========

运行结果

3.3 四元数运算

(1)四元数相乘

  • 作用:两个四元数相乘会得到 一个新的四元数,其旋转角度 是两个四元数对应旋转角度的叠加
    旋转相对的坐标系是物体自身的坐标系

========== 【演示:通过四元数相乘 让其围绕 自身Y轴 分两次旋转90度】 ==========

运行结果

(2)四元数与向量相乘

  • 作用:让向量进行 对应四元数旋转角度 的旋转
    且只能是 四元数 * 向量,不能是 向量 * 四元数!!!

========== 【演示:向量 分两次 绕Y轴 旋转90度 用红蓝分别标记】 ==========

运行结果

可以用来弹幕游戏向四周发射弹幕

这里就是实现的 环形发射 弹幕的逻辑

其实现原理为,利用 四元数与四元数相乘 每次改变子弹创建时的方向

3.4 完整代码

3.1 四元数的基础操作

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson9_10_scripr : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        //(1)什么是四元数
        // - 概念:Quaternion 四元数,是封装好的结构体,由四个分量(X,Y,Z,W)组成,用来解决欧拉角中的万向死锁问题
        //   X,Y,Z为 Vector3类型的欧拉角的各分量计算,W为标量
        //   如:绕轴 A(x,y,z)旋转60度
        //   X = sin(60 / 2) * x , Y = sin(60/2) * y , Z = sin(60/2) * z
        //   W = cos(60 /2)


        //(2)为什么使用四元数
        // - 解决了使用欧拉角旋转,统一旋转角度不唯一的情况
        // - 解决了万向节死锁

        //(3)四元数的使用语法
        // - 四元数对应的欧拉角取值为 -180 到 180
        // - 四元数的创建
        //========== 【演示:四元数创建】 ==========
        //   X轴旋转60
        // 法一:通过公式创建:             X = sin(60 / 2) * x ,             Y = sin(60/2) * y ,                Z = sin(60/2) * z                 W = cos(60 /2)
        Quaternion q1 = new Quaternion(Mathf.Sin(60 * Mathf.Deg2Rad / 2) * 1, Mathf.Sin(60 * Mathf.Deg2Rad / 2) * 0, Mathf.Sin(60 * Mathf.Deg2Rad / 2) * 0,Mathf.Cos(60 * Mathf.Deg2Rad / 2));
        GameObject gameObject1 = GameObject.CreatePrimitive(PrimitiveType.Cube);
        gameObject1.transform.rotation = q1; //创建一个立方体,其绕x轴旋转了60度

        //法二:通过封装方法:Quaternion.AngleAxis(角度,旋转轴)
        Quaternion q2 = Quaternion.AngleAxis(60,Vector3.up);
        GameObject gameObject2 = GameObject.CreatePrimitive(PrimitiveType.Cube);
        gameObject2.transform.rotation = q2;
        gameObject2.transform.position += Vector3.right * 5;// 创建一个立方体,其绕y轴旋转了60度,并向世界坐标系X轴正方向移动 5单位
        // - 欧拉角转四元数 
        //   通过欧拉角创建:Quaternion.Euler(x, y, z);
        Quaternion q3 =  Quaternion.Euler(0, 0, 60);
        GameObject cube3 = GameObject.CreatePrimitive(PrimitiveType.Cube);
        cube3.transform.rotation = q3;
        cube3.transform.position += Vector3.left * 5;// 创建一个立方体,其绕z轴旋转了60度,并向世界坐标系X轴正方向移动 5单位
        // - 四元数转欧拉角 q3.eulerAngles
        print("四元数q3  " + q3 + "  转成欧拉角{ q3.eulerAngles }为:" + q3.eulerAngles);



    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

3.2 四元数常用方法

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class lesson11_script : MonoBehaviour
{
    //用于演示的对象
    public Transform A;
    public Transform B;
    public Transform C;
    public Transform D;

    private float time;
    private Quaternion start;
    // Start is called before the first frame update
    void Start()
    {
        //(1)单位四元数:Quaternion.indentity => (0,0,0,1)
        // - 单位四元数是 模长为 1 的四元数
        // - 作用:将旋转角度未知的物体,进行角度的初始化
        //========== 【演示:单位四元数】 ==========
        print("重置前,A的四元数为:" + A.rotation);
        A.rotation = Quaternion.identity;
        print("重置后,A的四元数 { A.rotation = Quaternion.identity } 为:" +  A.rotation);

        start = D.rotation;
        time = 0;

    }

    // Update is called once per frame
    void Update()
    {
        //(2)向量看向转四元数:Quaternion.LookRotation(Vector3向量)
        // - 作用:得到对应向量的四元数,通过重新赋值对象的rotation,实现对象看向向量方向
        //========== 【演示:让 A 看向 B】 ==========
        A.rotation = Quaternion.LookRotation(B.position - A.position);
        Debug.DrawRay(A.position, B.position, Color.red);// 对象A的Z轴正方向看向B,用红线标记
        //LookAt 内部就是封装调用的 Quaternion.LookRotation


        //(3)四元数的插值运算:Lerp 和 Slerp
        // - 作用:让物体对象 转向 某一个角度
        // - 区别:
        //   Lerp在旋转时是线性的,他类似于割圆,只是尽可能的逼近于 旋转角度
        //   Slerp在旋转时是弧度,可以正确的旋转,且速度较快,一般情况下推荐使用 Slerp
        // - 使用:使用逻辑与 Vector3 的逻辑一样,不会主动累加 Time.deltaTime
        //========== 【演示:插值运算】 ==========
        //让 C 先快后慢的完成旋转 角度于B一样
        C.rotation = Quaternion.Slerp(C.rotation,B.rotation, Time.deltaTime);
        //让 D 保持匀速的完成旋转 角度于B一样
        D.rotation = Quaternion.Slerp(start,B.rotation,time);
        time += Time.deltaTime;
        //这里只简单演示,之后不改变B的旋转,若要实现,将对应值重置
    }
}

3.3 四元数运算

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class lesson12_script : MonoBehaviour
{
    public Transform A;

    // Start is called before the first frame update
    void Start()
    {
        //(1)四元数相乘
        // - 作用:两个四元数相乘会得到 一个新的四元数,其旋转角度 是两个四元数对应旋转角度的叠加
        //   注 旋转相对的坐标系是物体自身的坐标系
        //========== 【演示:通过四元数相乘 让其围绕 自身Y轴 分两次旋转90度】 ==========
        // 用于进行旋转45度的四元数
        Quaternion q = Quaternion.AngleAxis(45,Vector3.up);
        print("旋转前:A的四元数对应角度为:" + A.rotation.eulerAngles);
        A.rotation *= q;
        print("第一次旋转:A的四元数对应角度为:" + A.rotation.eulerAngles);
        A.rotation *= q;
        print("第二次旋转:A的四元数对应角度为:" + A.rotation.eulerAngles);

        //(2)四元数与向量相乘
        // - 作用:让向量进行 对应四元数旋转角度 的旋转
        //   且只能是 四元数 * 向量,不能是 向量 * 四元数!!!
        //========== 【演示:向量 分两次 绕Y轴 旋转90度 用红蓝分别标记】 ==========
        Vector3 v = Vector3.forward;
        print("旋转前:向量v 为:" + v);
        Vector3 v1 = q * v;
        Debug.DrawRay(Vector3.zero, v1, Color.red);
        print("第一次旋转 v 得到 向量v1 为:" + v1);
        Vector3 v2 = q * v1;
        Debug.DrawRay(Vector3.zero, v2, Color.blue);
        print("第一次旋转 v 得到 向量v2 为:" + v2);
        // 线只显示一次,可以在updata中观察
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}
相关推荐
千寻girling1 天前
机器学习 | 线性回归 | 尚硅谷学习
学习·机器学习·线性回归
jasonblog1 天前
对小龙虾openclaw的关注、学习、使用和变化观察
人工智能·学习·ai
Hical_W1 天前
深入学习CPP17_PMR
c++·学习
xuanwenchao1 天前
ROS2学习笔记 - 1、编写运行第一个程序
笔记·学习
惠惠软件1 天前
豆包 AI 学习投喂与排名优化指南
人工智能·学习·语音识别
V搜xhliang02461 天前
OpenClaw、AI大模型赋能数据分析与学术科研 学习
人工智能·深度学习·学习·机器学习·数据挖掘·数据分析
里昆1 天前
【电力电子】某模拟量采集器的上位机设置和遇到的问题解决
学习
卖报的大地主1 天前
TPAMI 2026 | 判别和扩散生成学习融合的礼物:边界细化遥感语义分割
人工智能·笔记·学习
徒 花1 天前
HCIP学习19 BGP 跨自治系统互通综合实验
网络·学习·智能路由器·hcip·ensp
码喽7号1 天前
vue学习六:状态管理VueX
javascript·vue.js·学习