mathf

  • Math工具类:C#中封装好的用于数学计算的工具类,位于System命名空间中
  • Mathf结构体:Unity中封装好的用于数学计算的工具结构体,位于UnityEngine命名空间中

常用

一次运算
Mathf.PI Π
Mathf.Abs(-1);绝对值
Mathf.CeilToInt(1.001f) 向上取整
Mathf.FloorToInt(9.6f) 向下取整
Mathf.Clamp(10, 11, 20)钳制函数 这里输出是11 因为传的数是10 范围要在11到20之间 如果比最小值小那么则输出最小值 最大值同理
Mathf.Max(1,2,3,4,5,6) 最大值
Mathf.Min(1,2,3,5) 最小值
Mathf.Pow(2,4) 一个数的N次幂 这里是2的四次幂
Mathf.RoundToInt(3.5f) 四舍五入
Mathf.Sqrt() 返回一个数的平方根
Mathf.IsPowerOfTwo() 判断一个数是否是2的n次方
Mathf.Sign() 判断正负数 正数输出1 负数输出-1

不停运算
Mathf.Lerp(start, end, t); 插值运算 t的范围是0~1

result = start + (end - start) * t
用法一:

1
start = Mathf.Lerp(start, 10, Time.deltaTime) //每帧改变start的值 变化先快后慢 位置无限接近end

用法二:

1
2
3
time += Time.deltaTime;
result = Lerp(start, 10, time);
//每帧改变t的值 变化速度匀速,位置每帧接近,当t >= 1时得到结果

三角函数

弧度转角度 弧度 * 57.3 = 对应角度
角度转弧度 角度 * 0.01745 = 对应弧度

1
2
angle = rad * Mathf.Rad2Deg;
rad = anger * Mathf.Deg2Rad;

三角函数中需要传入弧度制

1
Mathf.Sin(30 * Mathf.Deg2Rad)

反三角函数 将正弦余弦值输出对应的弧度

1
2
rad = Mathf.Asin(0.5f);
print(rad * Mathf.Rad2Deg);

视口坐标系

只有(0,0)到(1,1)
左下角为(0,0)右上角为(1,1)
摄像机的视口范围
用的比较少

Vector3向量

几何意义有两种 一种是表示点 一种是表示方向

求向量的模长

Vector3中提供了获得向量模长的成员属性
Vector3(5,6,7).magnitude;

求向量的单位向量

Vector().normalized

向量点乘

正负数判断前后方位
Vector3.Dot(Vector3, Vector3);算点乘

公式推导:
![[Pasted image 20260508210722.png]]
适合做那种ai扇形视野判断
这个公式有个函数
Vector3.Angle(向量, 向量)

向量叉乘

得到的是两个向量所在平面的法向量

叉乘几何意义
API
Vector3.Cross(向量, 向量);
假设AB向量都在XZ平面上
向量A 叉乘 向量B的结果
y>0 证明B在A右侧
y<0 证明B在A左侧

插值运算

线性插值

Vector3.Lerp(start, end, t);
类似于Mathf

1·先快后慢

1
A.position = Vector3.Lerp(A.position, target.position, Time.deltaTime);

2·匀速移动

1
2
3
4
time += Time.deltaTime;
B.position = Vector3.Lerp(startPos, target.position, time);
/*但是当time>=1 改变目标位置时,b物体会直接瞬移到目标位置
修改的话前面需要加一句判断 修改如下*/
1
2
3
4
5
6
7
8
if(nowTarget != target.position)
{
nowTarget = target.position;
time = 0;
startPos = B.position;
}
time += Time.deltaTime;
B.position = Vector3.Lerp(startPos, nowTarget, time);
球形插值

弧线运动 用的比较少

1
Vector3.Slarp(start, end, t);

向量的小应用

摄像机跟随

1
2
3
4
5
void LateUpdate()  
{
this.transform.position = target.position + -target.forward * 4 + target.up * 7;
this.transform.LookAt(target);
}

摄像机的处理一般放在LateUpdata里面

调试画线 Debug.DrawLine(点,向量,颜色);

视野中发现物体判断 (向量点乘)

四元数

欧拉角
缺点:表示不唯一 万向节死锁

四元数不存在万向节死锁这个问题

一个四元数包含一个标量和一个3D向量[ w, (x, y, z) ]

Unity中的四元数
基础初始化:
Quaternion q = new Quaternion(Mathf.Sin(30 * Mathf.Deg2Rad) * , 0, 0, Mathf.Cos(30 * Mathf.Deg2Rad))//前三个参数表示轴 * 角度/2的sin值

一般的初始化:
quaternion q2 = Quaternion.AngleAxis(60, Vector3.right);
这个是封装好的静态方法 参数 (角度, 轴)

四元数转化欧拉角q.eulerAngles
欧拉角转化四元数Quaternion.Euler(x, y, z)

四元数相乘代表旋转四元数 旋转相对的坐标系 是物体自身坐标系

1
this.transform.ratation *= Quaternion.AngleAxis(1, Vector3.up);//绕自身Y轴旋转 速度为1

用欧拉角去算旋转的话 会遇到万向节死锁

四元数常用方法

单位四元数

单位四元数 没有旋转量(角位移)
Quaternion.identity

插值运算

四元数中Slerp比Lerp更好

先快后慢

1
A.transform.rotation = Quaternion.Slerp(A.transform.rotation, target.rotation, Time.deltaTime);

匀速变化

1
2
time += Time.deltaTime;
B.transform.rotation = Quaternion.Slerp(Start, target.rotation, time);

向量指向转四元数

Quaternion.LookRotation(面朝向量);

让A的正方向朝向B

1
2
Quaternion q = Quaternion.LookRotation(lookB.position - lookA.position);
lookA.rotation = q;

四元数乘向量

四元数乘向量返回一个新向量
可以将指定向量旋转对应四元数的旋转量
相当于旋转向量

四元数(写在前面) * 向量

mono中重要内容

延迟函数

参数一 函数名 字符串
参数二 延迟时间 秒
Invoke(“”, 5);

注意
延迟函数第一个参数传入的是函数名字符串
延迟函数没办法传入参数 只有包裹一层才能调用有参函数
函数名必须是该脚本上声明的函数

延迟重复执行函数

InvokeReapting
参数一 函数名字符串
参数二 第一次执行的延迟
参数三 之后执行的间隔时间

注意事项与延迟函数相同

取消延迟函数

取消该脚本上所有延时函数执行
CancelInvoke();
指定函数名取消延迟函数
取消所有这个函数的
CancelInvoke(“之前延迟函数中的函数字符串”);

判断是否有延迟函数

IsInvoking() 返回值是bool

重载 IsInvoking(“函数名”);

协同程序

Unity中的多线程

Unity 中的多线程要记住关闭

1
2
3
4
5
Thread t;
t = new Thread(Test); //Test是函数名
t.Start();
t.Abort();//关闭线程
t = null;//置空

新生成的线程无法访问Unity对象的相关内容

协同程序

假的多线程
作用:将代码分时执行,不卡主线程
把可能会让主线程卡顿的好事的逻辑分时分布执行

主要使用场景

  • 异步加载文件
  • 异步下载文件
  • 场景异步加载
  • 批量创建对象时防止卡顿

协程的使用
申明协程函数
1.返回值是IEnumerator类型及其子类
2.函数中通过 yield return 返回值;进行返回

1
2
3
4
5
IEnumerator MyCoroutine(int i,string str)
{
yield return new WaitForSeconds(5f);//必须有yield return
//等待五秒 进行后面的逻辑
}

开启协程函数
不类似于直接调用普通函数

1
StartCoroutine(MyCoroutine(1, "123"));

或者

1
2
IEnumerator ie = MyCoroutine(1, "123");
StartCoroutine(ie);

关闭协程
关闭所有协程

1
StopAllCoroutines();

关闭指定协程

1
2
Coroutine c1 = StartCoroutine( MyCoroutine(1, "123") );
StopCoroutine(c1);

协程是允许写死循环的

下一帧执行
yield return 数字;
yield return null;
在Update 和 LateUpdate之间执行

等待指定秒后执行
yield return new WaitForSceconds(秒);
在Update 和 LateUpdate之间执行

等待下一个固定物理帧更新时执行
yield return new WaitForFixedUpdate();
在LateUpdate 和 碰撞检测相关函数之后执行

等待摄像机和GUI渲染完成后执行
yield return new WaitForEndOfFrame();//一般截图用
在LateUpdate之后的渲染相关处理完毕后执行

一些特殊类型的对象 比如异步加载相关函数返回的对象
在后面讲解 异步加载资源 异步加载场景 网络加载时再讲解
一般在Update 和 LateUpdate之间执行

跳出协程
yield break;

协程开始后
仅组件单独失活不影响执行 其他情况(物体销毁/失活)都会终止协程

  • MoveNext()会执行函数中内容直到遇到yield return为止的逻辑
  • Current属性可以得到yield return返回的内容
  • MoveNext返回值代表是否到了结尾(迭代器函数是否执行完毕)

特殊文件夹

工程路径获取

Application.dataPath;
该方式 获取到的路径 一般只用于编辑模式下使用

Resources资源文件夹

一般不获取路径
一般使用API进行加载

需要自己创建
需要Resources相关API动态加载的资源需要放在其中
该文件夹下所有文件都会被打包出去
打包时Unity会对其加密压缩
该文件夹打包后只读 只能通过Resources相关API加载

StreamingAssets 流动资源文件夹

路径获取
Application.StreamingAssetsPath

需要自己创建
打包出去不会被压缩加密
移动平台只读 PC可读可写
可以放入一些需要自定义动态加载的初始资源

persistentDataPath 持久数据文件夹

Application.persistentDataPath;

不需要自己创建
所有平台都可读可写
一般放置动态下载或者动态创建的文件,游戏中创建或者获取的文件都放在其中

Plugins 插件文件夹

一般不获取

需要自己创建
不同平台的插件相关文件放在其中

Editor 编辑器文件夹

需要自己创建
开发Unity编辑器时 编辑器相关脚本放在该文件夹
打包时 不会被打包

Standard Assets 默认资源文件夹

一般不获取

需要自己创建
一般Unity自带资源都放在这个文件夹
代码和资源都优先被编译

Resources资源同步加载

作用:
通过代码动态加载Resouces文件夹下指定路径资源
避免繁琐的拖曳操作

常用资源类型

GameObject 预设体
AudioClip 音效
TextAsset 文本
Texture 图片
其他类型

注意 预设体一般实例化使用
其他资源加载一般直接用

资源同步加载 普通方法

一个工程中可以有多个Resoueces文件夹 可以有多个 通过API加载时 Unity会自己从所有的Resources文件夹中去找资源
打包时Resources文件夹中的内容都会打包在一起

1.预设体对象 记住要实例化

第一步 要去加载预设体的资源文件
object obj = Resources.Load(“名字”);
第二步 想要在场景中创建预设体 一定是加载配置文件过后 实例化对象
Instantiate(obj);

2音效文件
第一步 加载数据
Object obj = Resources.Load(“Music/BKMusic”);
第二步 使用数据 不需要实例化 音效切片 我们只需要把数据 赋值到正确的脚本上即可

3文本资源
文本资源支持的格式.txt .xml .bytes .josn .html .csv ······

第一步 加载资源
TextAsset ta = Resources.Load(“名字”) as TextAsset;
第二步 使用资源
得到文本内容 ta.text
字节数据组 ta.bytes

4.图片资源
第一步 加载资源
Texture tex = Resources.Load(“”) as Texture;
第二步 使用资源
正常使用图片

资源同名怎么办?
加载指定类型的资源
Resources.Load(“名字”, typeof(Texture));

加载所有指定名字的资源
Object[] obj = Resources.LoadAll(“Name”);
foreach(Object item in objs)
{
if(item is Texture)
{
}
else if(item is TextAsset)
{
}
}

资源同步加载 泛型方法

省去了as的步骤
TextAsset ta2 = Resources.Load< TextAsset >(“Name”);

Resources资源异步加载

内部新开一个线程进行资源加载 不会造成主线程卡顿

异步加载不能马上得到加载的资源 至少要等一帧

1.通过异步加载中的完成事件监听 使用加载的资源

1
2
3
4
5
6
7
8
ResouceRequest rq = Resources.LoadAsync< Texture >("Name");
rq.completed += loadOver;//Unity在内部会开一个线程进行资源下载结束

private void LoadOver(AsyncOperation rq)
{
//处理加载完后的资源
tex = (rq as ResourceRequest).asset as Texture;
}

2通过协程使用资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
IEnumerator Load()
{
ResouceRequest rq = Resources.LoadAsync< Texture >("Name");
//Unity自己知道该返回值意味着你在异步加载资源
//Unity会自己判断该资源是否加载完毕 加载完毕过后 才会执行后面的代码
yield return rq;
tex = rq.asset as Texture;

}

//判断资源是否加载结束
while(!rq.isDone)
{
//打印当前进度
print(rq.priority);
}

Resources 异步卸载资源

卸载指定资源 不能释放GameObject对象 一般很少单独用
Resources.UnloadAsset();

卸载未使用的资源
Resources.UnloadUnusedAssets();
一般过场景和GC时使用

场景异步加载

场景同步加载

SceneManager.loadScene(“”);

场景异步加载

1.事件回调函数 异步加载
Async ao = SceneManager.loadSceneAsync(“”);
//当异步加载结束后 就会自动调用该事件函数 我们如果希望在加载结束后 做一些事情 那么可以在下面的函数中写逻辑
ao.completed += (a) =>
{

};
2.通过协程异步加载
加载场景会把当前场景上没有特殊处理的对象都删除
所以协程中的部分逻辑 可能是执行不了的
解决思路
让处理场景加载的脚本依附的对象过场景时不被移除

LineRenderer

Unity提供的画线的组件
绘制攻击范围 武器红外线 辅助功能 其他画线功能

动态添加线段 GameObject line = new GameObject();
line.name = “Line”;
LineRenderer lineRenderer = line.AddComponent< LineRenderer >();

首尾相连
lineRenderer.loop = true;

开始结束宽
lineRenderer.startWidth = 0.1f;
lineRenderer.endWidth = 0.1f;

颜色
lineRenderer.startColor = Color.white;
lineRenderer.endColor = Color.white;

设置材质
m = Resources.load< Material >(“”);
lineRenderer.material = m;

设置点
一定主要要先设置点的个数
lineRenderer.positionCount = 4;
lineRenderer.SetPositions(new Vector3[] { xxxx});

是否使用世界坐标系
lineRenderer.useWorldSpace = false;

让线段受光影响 会接受光数据
lineRenderer.gernerateLightingData = true;

画圆方法思路

1
2
3
4
5
6
7
8
9
10
public void DrawCircle(Vector3 centerPos, float r, int PointNum)
{
float angle = 360f / pointNum;
for(int i = 0; i < pointNum; i ++)
{
line.SetPosition(i, centerPos +
Quaternion.AngleAxis(angle * i, Vector3.up) *
Vector3.forward * r)
}
}

范围检测

盒状范围检测
参数1 立方体中心点 Vector3
参数2 立方体三边大小 Vector3
参数3 立方体角度 Quaternion
参数4 检测指定层级(不填检测所有层)int ( 1 << LayerMask.NameToLayer(“”) )
参数5 是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填 UseGlobal
返回值 在该范围内的触发器(得到了对象触发器就可以得到对象的所有信息)
Physics.OverlapBox()

另一个API
返回值碰撞到的碰撞器数量
参数 传入一个数组存储
Physics.OverlapBoxNonAlloc()

球状
Physics.OverlapSphere()
Physics.OverlapSphereNonAlloc(中心点,半径,存碰撞器的数组)

胶囊
参数一 半圆1中心点
参数二 半圆2中心点
参数三 半圆半径
参数四 层级检测
…类似盒状
Physics.OverlapCapsule()
Physics.OverlapCapsuleNonAlloc(圆心1,圆心2, 半径, 数组);

射线检测

3d世界的射线
参数一起点 参数二 方向
Ray r = new Ray(Vector3 , Vector3);

Ray中的参数
r.origin 起点
r.direction 方向

从摄像机发射出的射线
得到一条从屏幕位置作为起点
摄像机视口方向为方向的射线

1
Ray r2 = Camera.main.ScreenPointToRay(Input.mousePosition);

碰撞检测函数
参数一 射线
参数二 检测的最大距离
参数三 检测指定层级
参数四 是否忽略触发器
返回值 bool 碰撞到对象时返回true 没有返回false
Physics.Raycast();

获取相交的单个物体信息
物体信息类 RaycastHit
.collider.gameObject.name 碰撞到的名字
.point 碰撞到的点
.normal 法线信息
.transform.position 碰撞对象的位置
.distance 碰撞到对象离自己的距离

参数一 射线
参数二 RaycastHit是结构体 是值类型 Unity会通过out 关键在 在函数内部处理后 得到碰撞数据后返回到该参数中
参数三 距离
参数四 检测指定层级
参数五 是否忽略触发器
Physics.Raycast(r3, out hitInfo, 距离);

ps:out像是c++函数中&的作用

获取相交的多个物体
如果没有就是容量为0的数组
参数一 射线
参数二 距离
参数三 检测层级
参数四 是否忽略触发器

1
2
3
4
5
Raycasthit[] hits = Physics.RaycastAll(r3, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal);
for(int i= 0; i < hits.Length; i ++)
{

}

还有一种函数 返回碰撞的数量 通过out得到数据

1
2
3
4
if (Physics.RaycastNonAlloc(r3, hits, 1000, 1 << LayerMask.NameToLayer("Name"), queryTriggerInteraction.UseGlobal))
{

}

使用时注意的问题
一般第二三个参数要记得是距离
然后才是层级
要记得传距离 要不然会出错