2.Mathf数学计算类常用方法

2.3D数学-Mathf数学计算公共类


2.1 知识点

Mathf和Math

  • Math是C#中封装好的用于数学计算的工具类,位于System命名空间中。
  • Mathf是Unity中封装好的用于数学计算的工具结构体,位于UnityEngine命名空间中。
  • 他们都是提供来用于进行数学相关计算的。

Mathf和Math的区别

  • Mathf 和 Math中的相关方法几乎一样。
  • Math - C#自带数学库,提供基本的数学计算方法。
  • Mathf - Unity专门针对游戏开发增强的数学库,包含了Math中的方法,并添加了一些适用于游戏开发的方法。

Mathf中的常用方法——一般计算一次

PI常量 获取圆周率π

// 众所周知的“3.14159265358979...”值(只读)。
print(Mathf.PI);//3.141593

Abs静态方法 取绝对值

// 返回 f 的绝对值。
print(Mathf.Abs(-10));//10
print(Mathf.Abs(-20));//20
print(Mathf.Abs(1));//1

CeilToInt静态方法 向上取整

// 返回大于或等于 f 的最小整数。
float f = 1.3f;
int i = (int)f;
print(i);//1
print(Mathf.CeilToInt(f));//2
print(Mathf.CeilToInt(1.00001f));//2

FloorToInt静态方法 向下取整

// 返回小于或等于 f 的最大整数。
print(Mathf.FloorToInt(9.6f));//9

Clamp静态方法 钳制函数

// 在给定的最小浮点值和最大浮点值之间钳制给定值。如果在最小和最大范围内,则返回给定值。
// 比最小还小,就取最小,比最大还大,就取最大,两者之间,就取本身
print(Mathf.Clamp(10, 11, 20));//11
print(Mathf.Clamp(21, 11, 20));//20
print(Mathf.Clamp(15, 11, 20));//15

Max静态方法 获取最大值

// 返回两个或更多值中的最大值。
// 内部有一个可变长的参数
print(Mathf.Max(1, 2, 3, 4, 5, 6, 7, 8));//8
print(Mathf.Max(1, 2));//2

Min静态方法 获取最小值

// 返回两个或更多值中的最小值。
// 内部有一个可变长的参数
print(Mathf.Min(1, 2, 3, 4, 545, 6, 1123, 123));//1
print(Mathf.Min(1.1f, 0.4f));//0.4

Pow静态方法 一个数的n次幂

// 返回 f 的 p 次幂。
print("一个数的n次方" + Mathf.Pow(4, 2));//16
print("一个数的n次方" + Mathf.Pow(2, 3));//8

RoundToInt静态方法 四舍五入

// 返回舍入为最近整数的 / f /。
print("四舍五入" + Mathf.RoundToInt(1.3f));//1
print("四舍五入" + Mathf.RoundToInt(1.5f));//5

Sqrt静态方法 返回一个数的平方根

// 返回 f 的平方根。
print("返回一个数的平方根" + Mathf.Sqrt(4));//2
print("返回一个数的平方根" + Mathf.Sqrt(16));//4
print("返回一个数的平方根" + Mathf.Sqrt(64));//8

IsPowerOfTwo静态方法 判断一个数是否是2的n次方

// 如果值是 2 的幂,则返回 true。
print("判断一个数是否是2的n次方" + Mathf.IsPowerOfTwo(4));//true
print("判断一个数是否是2的n次方" + Mathf.IsPowerOfTwo(8));//true
print("判断一个数是否是2的n次方" + Mathf.IsPowerOfTwo(3));//false
print("判断一个数是否是2的n次方" + Mathf.IsPowerOfTwo(1));//true

Sign静态方法 判断正负数

// 返回 f 的符号。
// 正数和0就返回1,负数就返回-1
print("判断正负数" + Mathf.Sign(0));//1
print("判断正负数" + Mathf.Sign(10));//1
print("判断正负数" + Mathf.Sign(-10));//-1
print("判断正负数" + Mathf.Sign(3));//1
print("判断正负数" + Mathf.Sign(-2));//-1

Mathf中的常用方法——一般不停计算

Lerp静态方法 插值运算

用于执行线性插值运算。线性插值是一种基本的数学运算,用于在两个值之间进行平滑的过渡。在游戏开发中,它通常用于在两个状态之间进行平滑过渡,例如在动画中对象的位置、旋转或颜色之间进行插值以实现平滑的动画效果。

public static float Lerp(float a, float b, float t);
  • a:起始值。
  • b:目标值。
  • t:插值参数,通常介于0和1之间。当t为0时,返回起始值a;当t为1时,返回目标值b;当t在0和1之间时,返回起始值a和目标值b之间的插值。

Lerp函数插值的计算方式是通过如下公式来实现的:

result = a + (b - a) * t

这个公式实际上是线性插值的定义:它从起始值a开始,根据参数t与目标值b之间的差异,按比例插值到目标值。当t为0时,插值结果等于起始值a;当t为1时,插值结果等于目标值b;当t在0和1之间时,插值结果在起始值a和目标值b之间进行平滑过渡。

插值运算用法一:先快后慢,无限接近终点
//每帧改变start的值——变化速度先快后慢,位置无限接近,但是不会得到end位置
//可以理解为 每一帧的时间不变 计算出来的下一帧的位置肯定会更大
//但是因为下一帧的位置更大 (end - start)就会变小 每一帧的位置移动相比起来就更小了
start = Mathf.Lerp(start, end, Time.deltaTime);
插值运算用法二:匀速接近终点
//每帧改变t的值——变化速度匀速,位置每帧接近,当t>=1时,得到结果
//当time大于1时,插值会继续进行,但是Mathf.Lerp方法返回的结果将始终等于end,因为此时参数t等于1,即线性插值的终点。
//因此,当time大于1时,result将保持等于end,不再发生变化。
//可以理解为 start和end都不变 只有t在每一帧都在累加一个恒定值
//比如第一帧t是0.1秒 第二帧就是0.2秒 第五帧t就是0.5秒了
//这样算出来没帧的结果就是接近线性的
time += Time.deltaTime;
result = Mathf.Lerp(start, end, time);

2.2 知识点代码

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

public class Lesson02_3D数学_Mathf数学计算公共类 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 Mathf和Math

        //Math是C#中封装好的用于数学计算的工具类 —— 位于System命名空间中
        //Mathf是Unity中封装好的用于数学计算的工具结构体 —— 位于UnityEngine命名空间中
        //他们都是提供来用于进行数学相关计算的

        #endregion

        #region 知识点二  Mathf和Math的区别

        //Mathf 和 Math中的相关方法几乎一样
        //Math 是C#自带的工具类 主要就提供一些数学相关计算方法
        //Mathf 是Unity专门封装的,不仅包含Math中的方法,还多了一些适用于游戏开发的方法
        //所以我们在进行Unity游戏开发时
        //使用Mathf中的方法用于数学计算即可

        #endregion

        #region 知识点三 Mathf中的常用方法——一般计算一次

        //PI常量 代表圆周率π 
        //众所周知的“3.14159265358979...”值(只读)。
        print(Mathf.PI);//3.141593

        //Abs静态方法 取绝对值
        //返回 f 的绝对值。
        print(Mathf.Abs(-10));//10
        print(Mathf.Abs(-20));//20
        print(Mathf.Abs(1));//1

        //CeilToInt静态方法 向上取整 
        //返回大于或等于 f 的最小整数。
        float f = 1.3f;
        int i = (int)f;
        print(i);//1
        print(Mathf.CeilToInt(f));//2
        print(Mathf.CeilToInt(1.00001f));//2

        //FloorToInt静态方法 向下取整
        //返回小于或等于 f 的最大整数。
        print(Mathf.FloorToInt(9.6f));//9

        //Clamp静态方法 钳制函数 
        //在给定的最小浮点值和最大浮点值之间钳制给定值。如果在最小和最大范围内,则返回给定值。
        //比最小还小,就取最小,比最大还大,就取最大,两者之间,就取本身
        print(Mathf.Clamp(10, 11, 20));//11
        print(Mathf.Clamp(21, 11, 20));//20
        print(Mathf.Clamp(15, 11, 20));//15

        //Max静态方法 获取最大值
        //返回两个或更多值中的最大值。
        //内部有一个可变长的参数
        print(Mathf.Max(1, 2, 3, 4, 5, 6, 7, 8));//8
        print(Mathf.Max(1, 2));//2

        //Min静态方法 获取最小值 
        //返回两个或更多值中的最小值。
        //内部有一个可变长的参数
        print(Mathf.Min(1, 2, 3, 4, 545, 6, 1123, 123));//1
        print(Mathf.Min(1.1f, 0.4f));//0.4

        //Pow静态方法 一个数的n次幂
        //返回 f 的 p 次幂。
        print("一个数的n次方" + Mathf.Pow(4, 2));//16
        print("一个数的n次方" + Mathf.Pow(2, 3));//8

        //RoundToInt静态方法 四舍五入
        //返回舍入为最近整数的 / f /。
        print("四舍五入" + Mathf.RoundToInt(1.3f));//1
        print("四舍五入" + Mathf.RoundToInt(1.5f));//5

        //Sqrt静态方法 返回一个数的平方根 - 
        //返回 f 的平方根。
        print("返回一个数的平方根" + Mathf.Sqrt(4));//2
        print("返回一个数的平方根" + Mathf.Sqrt(16));//4
        print("返回一个数的平方根" + Mathf.Sqrt(64));//8

        //IsPowerOfTwo静态方法 判断一个数是否是2的n次方
        //如果值是 2 的幂,则返回 true。
        print("判断一个数是否是2的n次方" + Mathf.IsPowerOfTwo(4));//true
        print("判断一个数是否是2的n次方" + Mathf.IsPowerOfTwo(8));//true
        print("判断一个数是否是2的n次方" + Mathf.IsPowerOfTwo(3));//false
        print("判断一个数是否是2的n次方" + Mathf.IsPowerOfTwo(1));//true

        //Sign静态方法 判断正负数
        //返回 f 的符号。
        //正数和0就返回1,负数就返回-1
        print("判断正负数" + Mathf.Sign(0));//1
        print("判断正负数" + Mathf.Sign(10));//1
        print("判断正负数" + Mathf.Sign(-10));//-1
        print("判断正负数" + Mathf.Sign(3));//1
        print("判断正负数" + Mathf.Sign(-2));//-1

        #endregion
    }

    #region 知识点四 Mathf中的常用方法——一般不停计算

    //插值运算外面定义的变量
    //开始值 有时开始值直接拿来算后当做结果值
    float start = 0;
    //目标值
    float end = 10;
    //结果值
    float result = 0;
    //累加的时间
    float time = 0;

    #endregion

    void Update()
    {
        #region 知识点四 Mathf中的常用方法——一般不停计算

        //Lerp静态方法 插值运算
        //用于执行线性插值运算。
        //线性插值是一种基本的数学运算,用于在两个值之间进行平滑的过渡。
        //在游戏开发中,它通常用于在两个状态之间进行平滑过渡,例如在动画中对象的位置、旋转或颜色之间进行插值以实现平滑的动画效果。

        //public static float Lerp(float a, float b, float t);
        //a:起始值。
        //b:目标值。
        //t:插值参数,通常介于0和1之间。当t为0时,返回起始值a;当t为1时,返回目标值b;当t在0和1之间时,返回起始值a和目标值b之间的插值。

        //Lerp函数公式
        //result = a + (b - a) * t
        //这个公式实际上是线性插值的定义:
        //它从起始值a开始,根据参数t与目标值b之间的差异,按比例插值到目标值。
        //当t为0时,插值结果等于起始值a;当t为1时,插值结果等于目标值b;
        //当t在0和1之间时,插值结果在起始值a和目标值b之间进行平滑过渡。

        //一般写在Update内
        //result = Mathf.Lerp(start, end, time);

        ////插值运算外面定义的变量
        ////开始值 有时开始值直接拿来算后当做结果值
        //float start = 0;
        ////目标值
        //float end = 10;
        ////结果值
        //float result = 0;
        ////累加的时间
        //float testNum = 0;


        //result = start + (end - start)*time

        //插值运算用法一
        //每帧改变start的值——变化速度先快后慢,位置无限接近,但是不会得到end位置
        //可以理解为 每一帧的时间不变 计算出来的下一帧的位置肯定会更大
        //但是因为下一帧的位置更大 (end - start)就会变小 每一帧的位置移动相比起来就更小了
        start = Mathf.Lerp(start, end, Time.deltaTime);

        //插值运算用法二
        //每帧改变t的值——变化速度匀速,位置每帧接近,当t>=1时,得到结果
        //当time大于1时,插值会继续进行,但是Mathf.Lerp方法返回的结果将始终等于end,因为此时参数t等于1,即线性插值的终点。
        //因此,当time大于1时,result将保持等于end,不再发生变化。
        //可以理解为 start和end都不变 只有t在每一帧都在累加一个恒定值
        //比如第一帧t是0.1秒 第二帧就是0.2秒 第五帧t就是0.5秒了
        //这样算出来没帧的结果就是接近线性的
        time += Time.deltaTime;
        result = Mathf.Lerp(start, end, time);

        #endregion
    }
}

2.3 练习题

使用线性插值实现一个方块跟随另一个方块移动

场景设置

在场景中创建两个方块A和B,目的是让方块A跟随方块B移动。

脚本编写

创建一个脚本,将其挂载到方块A上。在脚本中添加必要的变量,包括要跟随的对象B、移动速度等。

// 使用线性插值实现方块A跟随方块B移动的脚本

//要跟随的对象B
public Transform B;

//移动速度
public float moveSpeed;

//位置中介
private Vector3 pos;

//B当前的位置
private Vector3 bNowPos;

//开始位置
private Vector3 startPos;

//匀速运动累加的时间
private float time;

//模式
public int mode = 1;

切换模式逻辑

使用GUI编写一个切换模式的逻辑。

private void OnGUI()
{
    if(GUI.Button(new Rect(0, 0, 200, 200), "切换模式"))
    {
        if(mode == 1)
        {
            mode = 2;
        }
        else if(mode == 2)
        {
            mode = 1;
        }
    }
}

根据不同模式进行移动

在Update函数中根据不同的模式进行移动处理。

void Update()
{
    if(mode == 1)
    {
        //第一种:先快后慢的形式

        //记录当前这一帧A的位置
        pos = this.transform.position;

        //传入这一帧A的位置算出下一帧A的位置
        pos.x = Mathf.Lerp(pos.x, B.position.x, Time.deltaTime * moveSpeed);
        pos.y = Mathf.Lerp(pos.y, B.position.y, Time.deltaTime * moveSpeed);
        pos.z = Mathf.Lerp(pos.z, B.position.z, Time.deltaTime * moveSpeed);

        //算出下一帧A的位置赋值给A
        this.transform.position = pos;
    }
    else if(mode == 2)
    {
        //第二种:匀速运动

        //发现存储的B的位置不等于当前B的位置
        if (bNowPos != B.transform.position)
        {
            //清空时间,重写设置B的位置,开始位置是A当前的位置
            time = 0;
            bNowPos = B.transform.position;
            startPos = this.transform.position;
        }

        //时间每一帧运算累加
        time += Time.deltaTime;

        //startPos和bNowPos其实定死了,只有time在变化,传入算出下一帧A的位置
        pos.x = Mathf.Lerp(startPos.x, bNowPos.x, time * moveSpeed);
        pos.y = Mathf.Lerp(startPos.y, bNowPos.y, time * moveSpeed);
        pos.z = Mathf.Lerp(startPos.z, bNowPos.z, time * moveSpeed);

        //算出下一帧A的位置赋值给A
        this.transform.position = pos;
    }
}

移动B观察不同模式下A的跟随效果


在场景中移动B,观察不同模式下A的跟随效果。


2.4 练习题代码

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

//这个脚本将要挂载到方块A上 实现方块A跟随着方块B
public class Lesson02_练习题 : MonoBehaviour
{
    #region Lesson02 练习题一
    //使用线性插值实现一个方块跟随另一个方块移动

    //要跟随的对象B
    public Transform B;

    //移动速度
    public float moveSpeed;

    //位置中介
    private Vector3 pos;

    //B当前的位置
    private Vector3 bNowPos;

    //开始位置
    private Vector3 startPos;

    //匀速运动累加的时间
    private float time;

    //模式
    public int mode = 1;
    
    void Update()
    {
        if(mode == 1)
        {
            //第一种 就是 先快后慢的形式

            //记录当前这一帧A的位置
            pos = this.transform.position;

            //传入这一帧A的位置算出下一帧A的位置
            pos.x = Mathf.Lerp(pos.x, B.position.x, Time.deltaTime * moveSpeed);
            pos.y = Mathf.Lerp(pos.y, B.position.y, Time.deltaTime * moveSpeed);
            pos.z = Mathf.Lerp(pos.z, B.position.z, Time.deltaTime * moveSpeed);

            //算出下一帧A的位置赋值给A
            this.transform.position = pos;
        }
        else if(mode == 2)
        {
            //第二种 就是 匀速运动

            //发现存储的B的位置不等于当前B的位置
            if (bNowPos != B.transform.position)
            {
                //清空时间 重写设置B的位置 开始位置是A当前的位置
                time = 0;
                bNowPos = B.transform.position;
                startPos = this.transform.position;
            }

            //时间每一帧运算累加
            time += Time.deltaTime;

            //startPos和bNowPos其实定死了 只有time在变化 传入算出下一帧A的位置
            pos.x = Mathf.Lerp(startPos.x, bNowPos.x, time * moveSpeed);
            pos.y = Mathf.Lerp(startPos.y, bNowPos.y, time * moveSpeed);
            pos.z = Mathf.Lerp(startPos.z, bNowPos.z, time * moveSpeed);

            //算出下一帧A的位置赋值给A
            this.transform.position = pos;
        }
    }

    private void OnGUI()
    {
        if(GUI.Button(new Rect(0, 0, 200, 200), "切换模式"))
        {
            if(mode == 1)
            {
                mode = 2;
            }
            else if(mode == 2)
            {
                mode = 1;
            }
             
        }
    }
    #endregion
}


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

×

喜欢就点赞,疼爱就打赏