Unity基础
3D数学
数学计算Mathf公共类
Math:是C#中封装好的用于数学计算的工具)类) —— 位于System命名空间中;
Mathf:是Unity中封装好的用于数学计算的工具)结构体) —— 位于UnityEngine命名空间中,Mathf 是Unity专门封装的,不仅包含Math中的方法,还多了一些适用于游戏开发的方法,使用Mathf中的方法用于Unity游戏开发中的数学计算即可;
API
知识点三 Mathf中的常用方法——一般计算一次
//1.π - PI
print(Mathf.PI);
//2.取绝对值 - Abs
print(Mathf.Abs(-10));
print(Mathf.Abs(-20));
print(Mathf.Abs(1));
//3.向上取整 - CeilToInt
float f = 1.3f;
int i = (int)f;
print(i);
print(Mathf.CeilToInt(f));
print(Mathf.CeilToInt(1.00001f));
//4.向下取整 - FloorToInt
print(Mathf.FloorToInt(9.6f));
//5.钳制函数 - Clamp
print(Mathf.Clamp(10, 11, 20));
print(Mathf.Clamp(21, 11, 20));
print(Mathf.Clamp(15, 11, 20));
//6.获取最大值 - Max
print(Mathf.Max(1, 2, 3, 4, 5, 6, 7, 8));
print(Mathf.Max(1, 2));
//7.获取最小值 - Min
print(Mathf.Min(1, 2, 3, 4, 545, 6, 1123, 123));
print(Mathf.Min(1.1f, 0.4f));
//8.一个数的n次幂 - Pow
print("一个数的n次方" + Mathf.Pow(4, 2));
print("一个数的n次方" + Mathf.Pow(2, 3));
//9.四舍五入 - RoundToInt
print("四舍五入" + Mathf.RoundToInt(1.3f));
print("四舍五入" + Mathf.RoundToInt(1.5f));
//10.返回一个数的平方根 - Sqrt
print("返回一个数的平方根" + Mathf.Sqrt(4));
print("返回一个数的平方根" + Mathf.Sqrt(16));
print("返回一个数的平方根" + Mathf.Sqrt(64));
//11.判断一个数是否是2的n次方 - IsPowerOfTwo
print("判断一个数是否是2的n次方" + Mathf.IsPowerOfTwo(4));
print("判断一个数是否是2的n次方" + Mathf.IsPowerOfTwo(8));
print("判断一个数是否是2的n次方" + Mathf.IsPowerOfTwo(3));
print("判断一个数是否是2的n次方" + Mathf.IsPowerOfTwo(1));
//12.判断正负数 - Sign
print("判断正负数" + Mathf.Sign(0));
print("判断正负数" + Mathf.Sign(10));
print("判断正负数" + Mathf.Sign(-10));
print("判断正负数" + Mathf.Sign(3));
print("判断正负数" + Mathf.Sign(-2));
知识点四 Mathf中的常用方法——一般不停计算
//插值运算 - Lerp
//Lerp函数公式
result = Mathf.Lerp(start, end, t);
//t为插值系数,取值范围为 0~1
result = start + (end - start)*t
//插值运算用法一
//每帧改变start的值——变化速度先快后慢,位置无限接近,但是不会得到end位置
start = Mathf.Lerp(start, 10, Time.deltaTime);
//插值运算用法二
//每帧改变t的值——变化速度匀速,位置每帧接近,当t>=1时,得到结果
time += Time.deltaTime;
result = Mathf.Lerp(start, 10, time);
UnityEngine.Mathf - Unity 脚本 API (unity3d.com)
三角函数
1.角度和弧度都是度量角的单位:
角度:1°
弧度:1 radian
圆一周的角度:360°
圆一周的弧度:2π radian
2.角度和弧度的转换关系:
π rad = 180°
1 rad = (180 / π)°=> 1 rad = 180 / 3.14 ≈ 57.3°;
1°= (π / 180) rad => 1°= 3.14 / 180 ≈ 0.01745 rad;
由此可以得出
弧度 * 57.3 = 对应角度
角度 * 0.01745 = 对应弧
3.角度弧度相关API:
知识点一 弧度、角度相互转化
//弧度转角度
float rad = 1;
float anger = rad * Mathf.Rad2Deg;
//角度转弧度
anger = 1;
rad = anger * Mathf.Deg2Rad;
知识点二 三角函数)可以让物体做曲线运动)
//注意:Mathf中的三角函数相关函数,传入的参数需要时弧度值
print(Mathf.Sin(30 * Mathf.Deg2Rad));//0.5
print(Mathf.Cos(60 * Mathf.Deg2Rad));//0.5
知识点三 反三角函数
//注意:反三角函数得到的结果是 正弦或者余弦值对应的弧度
rad = Mathf.Asin(0.5f);
print(rad * Mathf.Rad2Deg);
rad = Mathf.Acos(0.5f);
print(rad * Mathf.Rad2Deg);
坐标系说明
1.世界坐标系:
原点:世界的中心点
轴向:世界坐标系的三个轴向是固定的
2.物体坐标系:
原点:物体的中心点)建模时决定)
轴向:
物体右方为x轴正方向
物体上方为y轴正方向
物体前方为z轴正方向
3.屏幕坐标系:
原点:屏幕左下角
轴向:
向右为x轴正方向
向上为y轴正方向
最大宽高:
Screen.width
Screen.height
4.视口坐标系:
原点:屏幕左下角
轴向:
向右为x轴正方向
向上为y轴正方向
特点:
左下角为)0,0)
右上角为)1,1)
和屏幕坐标类似,将坐标单位化
汇总
知识点一 世界坐标系
//目前学习的和世界坐标系相关的
//this.transform.position;
//this.transform.rotation;
//this.transform.eulerAngles;
//this.transform.lossyScale(全局缩放大小,只能得);
//修改他们 会是相对世界坐标系的变化
知识点二 物体坐标系
//相对父对象的物体坐标系的位置 本地坐标 相对坐标
//this.transform.localPosition;
//this.transform.localEulerAngles;
//this.transform.localRotation;
//this.transform.localScale;
//修改他们 会是相对父对象物体坐标系的变化
知识点三 屏幕坐标系
//Input.mousePosition
//Screen.width;
//Screen.height;
知识点四 视口坐标系
//摄像机上的 视口范围
坐标转换相关
//世界转本地
//this.transform.InverseTransformDirection
//this.transform.InverseTransformPoint
//this.transform.InverseTransformVector
//本地转世界
//this.transform.TransformDirection
//this.transform.TransformPoint
//this.transform.TransformVector
//世界转屏幕
//Camera.main.WorldToScreenPoint
//屏幕转世界
//Camera.main.ScreenToWorldPoint
//世界转视口
//Camera.main.WorldToViewportPoint
//视口转世界
//Camera.main.ViewportToWorldPoint
//视口转屏幕
//Camera.main.ViewportToScreenPoint
//屏幕转视口
//Camera.main.ScreenToViewportPoint;
Vector3向量
UnityEngine.Vector3 - Unity 脚本 API (unity3d.com)
向量模长和单位向量
1.标量:
有数值大小,没有方向
2.向量:
有数值大小,有方向的矢量
3.两点决定一向量:
A点:(Xa,Ya,Za)
B点:(Xb,Yb,Zb)
从A指向B的向量为AB向量
B-A = (Xb-Xa, Yb-Ya, Zb-Za)
从B指向A的向量为BA向量
A-B = (Xa-Xb, Ya-Yb, Za-Zb)
口诀:终点减起点
4.零向量和负向量:
零向量
(0,0,0)
零向量是唯一一个大小为0的向量
负向量
(x,y,z)的负向量为(-x,-y,-z)
负向量和原向量大小相等
负向量和原向量方向相反
5.向量的模长:
向量的模长就是向量的长度
向量是由两个点算出,所以向量的模长就是两个点的距离
模长公式:
A向量(x,y,z)
模长 = √x² + y² + z²
6.单位向量:
模长为1的向量为单位向量
任意一个向量经过归一化就是单位向量
只需要方向,不想让模长影响计算结果时使用单位向量
归一化公式:
A向量(x,y,z)
模长 = √x² + y² + z²
单位向量 = (x/模长, y/模长, z/模长)
7.向量模长和单位向量相关API:
知识点一 向量
//三维向量 - Vector3
//Vector3有两种几何意义
//1.位置 —— 代表一个点
print(this.transform.position);
//2.方向 —— 代表一个方向
print(this.transform.forward);
print(this.transform.up);
Vector3 v = new Vector3(1, 2, 3);
Vector2 v2 = new Vector2(1, 2);
知识点二 两点决定一向量
//A和B此时 几何意义 是两个点
Vector3 A = new Vector3(1, 2, 3);
Vector3 B = new Vector3(5, 1, 5);
//求向量
//此时 AB和 BA 他们的几何意义 是两个向量
Vector3 AB = B - A;
Vector3 BA = A - B;
知识点三 零向量和负向量
print(Vector3.zero);
print(Vector3.forward);
print(-Vector3.forward);
知识点四 向量的模长
//Vector3中提供了获取向量模长的成员属性
//magnitude
print(AB.magnitude);
Vector3 C = new Vector3(5, 6, 7);
print(C.magnitude);
print(Vector3.Distance(A, B));
知识点五 单位向量
//Vector3中提供了获取单位向量的成员属性
//normalized
print(AB.normalized);
print(AB / AB.magnitude);
总结
//模长相当于可以得到 两点之间的距离 单位向量 主要是用来进行移动计算的 它不会影响我们想要的移动效果
向量加减乘除
1.向量加法:向量A + 向量B = (Xa + Xb, Ya + Yb);
位置+位置:两个位置相加没有任何几何意义
向量+向量:两个向量相加得到一个新向量;向量 + 向量 = 向量)向量相加,首尾相连)
位置+向量:位置加向量得到一个新位置;位置 + 向量 = 位置;向量 + 位置 = 位置)位置和向量相加=平移位置)
2.向量减法
向量A - 向量B = (Xa - Xb, Ya - Yb, Za);
位置-位置:两个位置相减得到一个新向量;位置 - 位置 = 向量)两点决定一向量,终点 - 起点);
向量-向量:两个向量相减得到一个新向量;向量 - 向量 = 向量)向量相减,头连头,尾指尾,A - B = B头指A头);
位置-向量:位置减向量相当于 加负向量;位置 + (-向量)=位置)位置减向量 = 平移位置);
向量-位置:向量减位置没有任何几何意义
3.向量乘除
向量只会和标量进行乘除法运算
向量A标量a = (xa, ya, za);
向量A/标量a = (x/a, y/a, z/a);
向量 *or/ 标量 = 向量;
向量 *or/ 正数,方向不变,放大缩小模长;
向量 *or/ 负数,方向相反,放大缩小模长;
向量 * 0,得到零向量;
4.知识点:
知识点一 向量加法
//this.transform.position += new Vector3(1, 2, 3);
this.transform.Translate(Vector3.forward * 5);
知识点二 向量减法
//this.transform.position -= new Vector3(1, 2, 3);
this.transform.Translate(-Vector3.forward * 5);
知识点三 向量乘除标量
this.transform.localScale *= 2;
this.transform.localScale /= 2;
向量点乘
1.点乘计算公式:
向量A·向量B = XaXb + YaYb + Za*Zb=标量;
点乘可以得到一个向量:在自己向量上投影的长度)我们可以用这个规律判断敌方的大致方位);
点乘结果 > 0 两个向量夹角为锐角;
点乘结果 = 0 两个向量夹角为直角;
点乘结果 < 0 两个向量夹角为钝;
2.公式推导:
Cosβ = 直角边 / 单位向量B模长
直角边 = Cosβ * 单位向量B模长)直角边 = 单位向量A · 单位向量B);
↓
Cosβ * 单位向量B模长 = 单位向量A · 单位向量B;
↓
Cosβ = 单位向量A · 单位向量B;
↓
推出结果:β = Acos(单位向量A · 单位向量B);
3.点乘API
知识点一 通过点乘判断对象方位)判断前后)
//Vector3 提供了计算点乘的方法
Debug.DrawRay(this.transform.position, this.transform.forward, Color.red);
Debug.DrawRay(this.transform.position, target.position - this.transform.position, Color.red);
//得到两个向量的点乘结果
//向量 a 点乘 AB 的结果
float dotResult = Vector3.Dot(this.transform.forward, target.position - this.transform.position);
if( dotResult >= 0 )
{
print("它在我前方");
}
else
{
print("它在我后方");
}
知识点二 通过点乘推导公式算出夹角)判断夹角,但是夹角为0-180之间,不能判断左右)
//步骤
//1.用单位向量算出点乘结果
dotResult = Vector3.Dot(this.transform.forward, (target.position - this.transform.position).normalized);
//2.用反三角函数得出角度
print("角度-" + Mathf.Acos(dotResult) * Mathf.Rad2Deg);
//Vector3中提供了 得到两个向量之间夹角的方法
print("角度2-" + Vector3.Angle(this.transform.forward, target.position - this.transform.position));
向量叉乘
1.叉乘计算公式:
向量A x 向量B = (YaZb - ZaYb,ZaXb - XaZb,XaYb - YaX)=向量;
2.几何意义
A x B 得到的向量同时垂直A和B;
A x B 向量垂直于A和B组成的平面;
A x B = -(B x A);
3.叉乘API
知识点一 叉乘计算
print(Vector3.Cross(A.position, B.position));
知识点二 叉乘几何意义
//假设向量 A和B 都在 XZ平面上
//向量A 叉乘 向量 B
//y大于0 证明 B在A右侧
//y小于0 证明 B在A左侧
Vector3 C = Vector3.Cross(A.position, B.position);
if( C.y > 0)
{
print("B在A的右侧");
}
else
{
print("B在A的左侧");
}
向量插值运算
插值API:
知识点一 线性插值
//公式result = start + (end - start) * t
//1.先快后慢 每帧改变start位置 位置无限接近 但不会得到end位置
A.position = Vector3.Lerp(A.position, target.position, Time.deltaTime);
//2.匀速 每帧改变时间 当t>=1时 得到结果
//这种匀速移动 当time>=1时 我改变了 目标位置后 它会直接瞬移到我们的目标位置
//应该相当于Vector3.MoveToWards()
if(nowTarget != target.position)
{
nowTarget = target.position;
time = 0;
startPos = B.position;
}
time += Time.deltaTime;
B.position = Vector3.Lerp(startPos, nowTarget, time);
知识点二 球形插值
//做弧线运动,可以做弓箭射击,太阳东升西落
C.position = Vector3.Slerp(Vector3.right * 10, Vector3.left * 10 + Vector3.up*0.1f, time*0.01f);
Quaternion四元数
UnityEngine.Quaternion - Unity 脚本 API (unity3d.com)
为什么使用四元数
1.欧拉角)transform.eulerAngles):由三个角度(x,y,z)组成,遵守heading-pitch-bank旋转序列约定)heading:物体绕自身的对象坐标系的Y轴,旋转的角度;pitch:物体绕自身的对象坐标系的X轴,旋转的角度;bank:物体绕自身的对象坐标系的Z轴,旋转的角度);
1.1.优点:直观、易理解;存储空间小)三个数表示);可以进行从一个方向到另一个方向旋转大于180度的角度;
1.2缺点:同一旋转的表示不唯一;万向节死锁;
2.万向节死锁:当某个特定轴达到某个特殊值时,绕一个轴旋转可能会覆盖住另一个轴的旋转,从而失去一维自由度;Unity中X轴达到90度时,会产生万向节死锁;
四元数是什么
1.轴-角对:在3D空间中,任意旋转都可以表示,绕着某个轴旋转一个旋转角得到;
2.四元数:对于给定旋转,假设为绕着n轴,旋转β度,n轴为(x,y,z);四元数Q = [cos(β/2), sin(β/2)x, sin(β/2)y, sin(β/2)z];
3.四元数基本API
知识点一 四元数 Quaternion
//四元数Q = [cos(β/2), sin(β/2)x, sin(β/2)y, sin(β/2)z]
//计算原理
//Quaternion q = new Quaternion(Mathf.Sin(30 * Mathf.Deg2Rad), 0, 0, Mathf.Cos(30 * Mathf.Deg2Rad));
//提供的轴角对 初始化 四元数的方法
Quaternion q = Quaternion.AngleAxis(60, Vector3.right);
知识点二 四元数和欧拉角转换
//1.欧拉角转四元数
Quaternion q2 = Quaternion.Euler(60, 0, 0);
GameObject obj2 = GameObject.CreatePrimitive(PrimitiveType.Cube);
obj2.transform.rotation = q2;
//2.四元数转欧拉角
print(q2.eulerAngles);
知识点三 四元数弥补的欧拉角缺点
//1.同一旋转的表示不唯一 四元数旋转后 转换后的欧拉角 始终是 -180~180度
//2.万向节死锁 通过四元数旋转对象可以避免万向节死锁
//必备知识点:四元数相乘代表旋转四元数
this.transform.rotation *= Quaternion.AngleAxis(1, Vector3.up);
四元数常用方法
知识点一 单位四元数
print(Quaternion.identity);
//testObj.rotation = Quaternion.identity;
Instantiate(testObj, Vector3.zero, Quaternion.identity);
知识点二 插值运算
//在四元数中Lerp和Slerp只有一些细微差别
//由于算法不同,Slerp的效果会好一些
//Lerp的效果相比Slerp更快但是如果旋转范围较大效果较差,所以建议使用Slerp进行插值运算
//无限接近 先快后慢
A.transform.rotation = Quaternion.Slerp(A.transform.rotation, target.rotation, Time.deltaTime);
//匀速变化 time>=1到达目标
time += Time.deltaTime;
B.transform.rotation = Quaternion.Slerp(start, target.rotation, time);
识点三 LookRotation
//Quaternion q = Quaternion.LookRotation(lookB.position - lookA.position);
//lookA.rotation = q;
lookA.MyLookAt(lookB);
四元数计算
知识点一 四元数相乘
//两个四元数相乘得到一个新的四元数,代表两个旋转量的叠加,相当于旋转(旋转相对的坐标系 是物体自身坐标系)
Quaternion q = Quaternion.AngleAxis(20, Vector3.up);
this.transform.rotation *= q;
知识点二 四元数乘向量
//四元数乘向量返回一个新向量,可以将指定向量旋转对应四元数的旋转量,相当于直接旋转向量
Vector3 v = Vector3.forward;
v = Quaternion.AngleAxis(45, Vector3.up) * v;
Mono重要内容
延迟(延时)函数
知识点一 什么是延迟函数
//延迟函数顾名思义
//就是会延时执行的函数
//我们可以自己设定延时要执行的函数和具体延时的时间
//是MonoBehaviour基类中实现好的方法
知识点二 延迟函数的使用
1.延迟函数
//Invoke
//参数一:函数名 字符串
//参数二:延迟时间 秒为单位
Invoke("DelayDoSomething", 1);
//注意:
//1-1.延时函数第一个参数传入的是函数名字符串
//1-2.延时函数没办法传入参数 只有包裹一层
//1-3.函数名必须是该脚本上申明的函数
2.延迟重复执行函数
//InvokeRepeating
//参数一:函数名字符串
//参数二:第一次执行的延迟时间
//参数三:之后每次执行的间隔时间
InvokeRepeating("DelayRe", 5, 1);
//注意:
//它的注意事项和延时函数一致
3.取消延迟函数
//3-1取消该脚本上的所有延时函数执行
CancelInvoke();
//3-2指定函数名取消
//只要取消了指定延迟 不管之前该函数开启了多少次 延迟执行 都会统一取消
CancelInvoke("DelayDoSomething");
4.判断是否有延迟函数
if( IsInvoking())
{
print("存在延迟函数");
}
if( IsInvoking("DelayDoSomething") )
{
print("存在延迟函数DelayDoSomething");
}
知识点三 延迟函数受对象失活销毁影响
//脚本依附对象失活 或者 脚本自己失活
//延迟函数可以继续执行 不会受到影响的
//脚本依附对象销毁或者脚本移除
//延迟函数无法继续执行
协同程序
1.Unity支持多线程,只是新开线程无法访问主线程中Unity相关内容(注意:Unity中的多线程 要记住关闭);
2.协同程序不是多线程,它是将线程中逻辑进行分时执行,避免卡顿,继承MonoBehavior的类都可以使用协程;
3.协程只有当组件单独失活时不受影响,其它情况协程会停止
4.API
知识点一 协程的使用
//继承MonoBehavior的类 都可以开启 协程函数
//第一步:申明协程函数
// 协程函数2个关键点
// 1-1返回值为IEnumerator类型及其子类
// 1-2函数中通过 yield return 返回值; 进行返回
//第二步:开启协程函数
//协程函数 是不能够 直接这样去执行的!!!!!!!
//这样执行没有任何效果
//MyCoroutine(1, "123");
//常用开启方式
//IEnumerator ie = MyCoroutine(1, "123");
//StartCoroutine(ie);
Coroutine c1 = StartCoroutine( MyCoroutine(1, "123") );
Coroutine c2 = StartCoroutine( MyCoroutine(1, "123"));
Coroutine c3 = StartCoroutine( MyCoroutine(1, "123"));
//第三步:关闭协程
//关闭所有协程
//StopAllCoroutines();
//关闭指定协程
//StopCoroutine(c1);
知识点二 yield return 不同内容的含义
//1.下一帧执行
//yield return 数字;
//yield return null;
//在Update和LateUpdate之间执行
//2.等待指定秒后执行
//yield return new WaitForSeconds(秒);
//在Update和LateUpdate之间执行
//3.等待下一个固定物理帧更新时执行
//yield return new WaitForFixedUpdate();
//在FixedUpdate和碰撞检测相关函数之后执行
//4.等待摄像机和GUI渲染完成后执行
//yield return new WaitForEndOfFrame();
//在LateUpdate之后的渲染相关处理完毕后之后
//5.一些特殊类型的对象 比如异步加载相关函数返回的对象
//之后讲解 异步加载资源 异步加载场景 网络加载时再讲解
//一般在Update和LateUpdate之间执行
//6.跳出协程
//yield break;
协同程序原理
1.协程的本质 就是利用 C#的迭代器函数”分步执行”的特点+协程调度逻辑 实现的一套分时执行函数的规则;
2.代码实现:
知识点一 协程的本质
//协程可以分成两部分
//1.协程函数本体
//2.协程调度器
//协程本体就是一个能够中间暂停返回的函数
//协程调度器是Unity内部实现的,会在对应的时机帮助我们继续执行协程函数
//Unity只实现了协程调度部分
//协程的本体本质上就是一个 C#的迭代器方法
知识点二 协程本体是迭代器方法的体现
//1.协程函数本体
//如果我们不通过 开启协程方法执行协程
//Unity的协程调度器是不会帮助我们管理协程函数的
IEnumerator ie = Test();
//但是我们可以自己执行迭代器函数内容
ie.MoveNext();//会执行函数中内容遇到 yield return为止的逻辑
print(ie.Current);//得到 yield return 返回的内容
//ie.MoveNext();
//print(ie.Current);
//ie.MoveNext();
//print(ie.Current);
//ie.MoveNext();
//TestClass tc = ie.Current as TestClass;
//print(tc.time);
//MoveNext 返回值 代表着 是否到了结尾)这个迭代器函数 是否执行完毕)
//2.协程调度器
//继承MonoBehavior后 开启协程
//相当于是把一个协程函数)迭代器)放入Unity的协程调度器中帮助我们管理进行执行
//具体的yield return 后面的规则 也是Unity定义的一些规则
#Resources资源动态加载
Unity中的特殊文件夹
知识点一 工程路径获取
//注意 该方式 获取到的路径 一般情况下 只在 编辑模式下使用
//我们不会在实际发布游戏后 还使用该路径
//游戏发布过后 该路径就不存在了
print(Application.dataPath);
知识点二 Resources 资源文件夹
//路径获取:
//一般不获取
//只能使用Resources相关API进行加载
//如果硬要获取 可以用工程路径拼接
print(Application.dataPath + "/Resources");
//注意:
//需要我们自己将创建
//作用:
//资源文件夹
//1-1.需要通过Resources相关API动态加载的资源需要放在其中
//1-2.该文件夹下所有文件都会被打包出去
//1-3.打包时Unity会对其压缩加密
//1-4.该文件夹打包后只读 只能通过Resources相关API加载
知识点三 StreamingAssets 流动资源文件夹
//路径获取:
print(Application.streamingAssetsPath);
//注意:
//需要我们自己将创建
//作用:
//流文件夹
//2-1.打包出去不会被压缩加密,可以任由我们摆布
//2-2.移动平台只读,PC平台可读可写
//2-3.可以放入一些需要自定义动态加载的初始资源
知识点四 persistentDataPath 持久数据文件夹
//路径获取:
print(Application.persistentDataPath);
//注意:
//不需要我们自己将创建
//作用:
//固定数据文件夹
//3-1.所有平台都可读可写
//3-2.一般用于放置动态下载或者动态创建的文件,游戏中创建或者获取的文件都放在其中
知识点五 Plugins 插件文件夹
//路径获取:
//一般不获取
//注意:
//需要我们自己将创建
//作用:
//插件文件夹
//不同平台的插件相关文件放在其中
//比如IOS和Android平台
知识点六 Editor 编辑器文件夹
//路径获取:
//一般不获取
//如果硬要获取 可以用工程路径拼接
print(Application.dataPath + "/Editor");
//注意:
//需要我们自己将创建
//作用:
//编辑器文件夹
//5-1.开发Unity编辑器时,编辑器相关脚本放在该文件夹中
//5-2.该文件夹中内容不会被打包出去
知识点七 默认资源文件夹 Standard Assets
//路劲过去:
//一般不获取
//注意:
//需要我们自己将创建
//作用:
//默认资源文件夹
//一般Unity自带资源都放在这个文件夹下
//代码和资源优先被编译
UnityEngine.Application - Unity 脚本 API (unity3d.com)
Resources同步加载
知识点一 常用资源类型
//1.预设体对象——GameObject)要实例化)
//2.音效文件——AudioClip
//3.文本文件——TextAsset
//4.图片文件——Texture
//5.其它类型——需要什么用什么类型
知识点二 资源同步加载 普通方法
//在一个工程当中 Resources文件夹 可以有多个 通过API加载时 它会自己去这些同名的Resources文件夹中去找资源
//打包时 Resources文件夹 里的内容 都会打包在一起
//1.预设体对象 想要创建在场景上 记住实例化
// 第一步:要去加载预设体的资源文件(本质上 就是加载 配置数据 在内存中)
Object obj = Resources.Load("Cube");
//第二步:如果想要在场景上 创建预设体 一定是加载配置文件过后 然后实例化
Instantiate(obj);
// 第一步:要去加载预设体的资源文件(本质上 就是加载 配置数据 在内存中)
Object obj2 = Resources.Load("Sphere");
//第二步:如果想要在场景上 创建预设体 一定是加载配置文件过后 然后实例化
Instantiate(obj2);
//2.音效资源
//第一步:就是加载数据
Object obj3 = Resources.Load("Music/BKMusic");
//第二步:使用数据 我们不需要实例化 音效切片 我们只需要把数据 赋值到正确的脚本上即可
audioS.clip = obj3 as AudioClip;
audioS.Play();
//3.文本资源
//文本资源支持的格式
//.txt
//.xml
//.bytes
//.json
//.html
//.csv
//.....
TextAsset ta = Resources.Load("Txt/Test") as TextAsset;
//文本内容
print(ta.text);
//字节数据组
//print(ta.bytes);
//4.图片
tex = Resources.Load("Tex/TestJPG") as Texture;
GUI.DrawTexture(new Rect(0, 0, 100, 100), tex);)(画出图片)
//5.其它类型 需要什么类型 就用什么类型就行
//6.问题:资源同名怎么办
//Resources.Load加载同名资源时 无法准确加载出你想要的内容
//可以使用另外的API
//6-1加载指定类型的资源
//tex = Resources.Load("Tex/TestJPG", typeof(Texture)) as Texture;
ta = Resources.Load("Tex/TestJPG", typeof(TextAsset)) as TextAsset;
//print(ta.text);
//6-2加载指定名字的所有资源
Object[] objs = Resources.LoadAll("Tex/TestJPG");
知识点三 资源同步加载 泛型方法
TextAsset ta2 = Resources.Load<TextAsset>("Tex/TestJPG");
print(ta2.text);
tex = Resources.Load<Texture>("Tex/TestJPG");
Resources异步加载
知识点一 Resources异步加载方法
//注意:
//异步加载 不能马上得到加载的资源 至少要等一帧
//1.通过异步加载中的完成事件监听 使用加载的资源
//这句代码 你可以理解 Unity 在内部 就会去开一个线程进行资源下载
//ResourceRequest rq = Resources.LoadAsync<Texture>("Tex/TestJPG");
//马上进行一个 资源下载结束 的一个事件函数监听
//rq.completed += LoadOver;
print(Time.frameCount);
//这个 刚刚执行了异步加载的 执行代码 资源还没有加载完毕 这样用 是不对的
//一定要等加载结束过后 才能使用
//rq.asset ××××××××××××
//2.通过协程 使用加载的资源
StartCoroutine(Load());
IEnumerator Load()
{
//迭代器函数 当遇到yield return时 就会 停止执行之后的代码
//然后 协程协调器 通过得到 返回的值 去判断 下一次执行后面的步骤 将会是何时
ResourceRequest rq = Resources.LoadAsync<Texture>("Tex/TestJPG");
print(Time.frameCount);
//第一部分
//Unity 自己知道 该返回值 意味着你在异步加载资源
//yield return rq;
//Unity 会自己判断 该资源是否加载完毕了 加载完毕过后 才会继续执行后面的代码
print(Time.frameCount);
//判断资源是否加载结束
while(!rq.isDone)
{
//打印当前的 加载进度 (0-1浮点型值)
print(rq.progress);
yield return null;
}
tex = rq.asset as Texture;
}
总结:
//1.完成事件监听异步加载
//好处:写法简单
//坏处:只能在资源加载结束后 进行处理
//“线性加载”
//2.协程异步加载
//好处:可以在协程中处理复杂逻辑,比如同时加载多个资源,比如进度条更新
//坏处:写法稍麻烦
//“并行加载”
//注意:
//理解为什么异步加载不能马上加载结束,为什么至少要等1帧
//理解协程异步加载的原理
Resources卸载资源
知识点一 Resources重复加载资源会浪费内存吗?
//其实Resources加载一次资源过后
//该资源就一直存放在内存中作为缓存
//第二次加载时发现缓存中存在该资源
//会直接取出来进行使用
//所以 多次重复加载不会浪费内存
//但是 会浪费性能)每次加载都会去查找取出,始终伴随一些性能消耗)
知识点二 如何手动释放掉缓存中的资源
//1.卸载指定资源
//Resources.UnloadAsset 方法
//注意:
//该方法 不能释放 GameObject对象 因为它会用于实例化对象
//它只能用于一些 不需要实例化的内容 比如 图片 和 音效 文本等等
//一般情况下 我们很少单独使用它
//GameObject obj = Resources.Load<GameObject>("Cube");
//即使是没有实例化的 GameObject对象也不能进行卸载
//Resources.UnloadAsset(obj);
//2.卸载未使用的资源
//注意:
//一般在过场景时和GC一起使用
Resources.UnloadUnusedAssets();
GC.Collect();
UnityEngine.Resources - Unity 脚本 API (unity3d.com)
场景切换
需要把要加载的场景Build Settings中。
知识点一 场景同步切换
//在切换场景时
//Unity会删除当前场景上所有对象
//并且去加载下一个场景的相关信息
//如果当前场景 对象过多或者下一个场景对象过多
//这个过程会非常的耗时 会让玩家感受到卡顿
SceneManager.LoadScene("Lesson20Test");
知识点二 场景异步切换
//场景异步加载和资源异步加载 几乎一致 有两种方式
//1.通过事件回调函数 异步加载
//AsyncOperation ao = SceneManager.LoadSceneAsync("Lesson20Test");
//当场景异步加载结束后 就会自动调用该事件函数 我们如果希望在加载结束后 做一些事情 那么久可以在该函数中
//写处理逻辑
//ao.completed += (a) =>
//{
// print("加载结束");
//};
//ao.completed += LoadOver;
//2.通过协程异步加载
//需要注意的是 加载场景会把当前场景上 没有特别处理的对象 都删除了
//所以 协程中的部分逻辑 可能是执行不了的
//解决思路
//让处理场景加载的脚本依附的对象 过场景时 不被移除
//该脚本依附的对象 过场景时 不会被 移除
DontDestroyOnLoad(this.gameObject);
StartCoroutine(LoadScene("Lesson20Test"));
IEnumerator LoadScene(string name)
{
//第一步
//异步加载场景
AsyncOperation ao = SceneManager.LoadSceneAsync(name);
//Unity内部的 协程协调器 发现是异步加载类型的返回对象 那么就会等待
//等待异步加载结束后 才会继续执行 迭代器函数中后面的步骤
print("异步加载过程中 打印的信息");
//协程的好处 是异步加载场景时 我可以在加载的同时 做一些别的逻辑
//yield return ao;
//第二步
print("异步加载结束后 打印的信息");
//比如 我们可以在异步加载过程中 去更新进度条
//第一种 就是利用 场景异步加载 的进度 去更新 但是 不是特别准确 一般也不会直接用
//while(!ao.isDone)
//{
// print(ao.progress);
// yield return null;
//}
//离开循环后 就会认为场景加载结束
//可以把进度条顶满 然后 隐藏进度条
//第二种 就是根据你游戏的规则 自己定义 进度条变化的条件
yield return ao;
//场景加载结束 更新20%进度
//接着去加载场景中 的其它信息
//比如
//动态加载怪物
//这时 进度条 再更新20%
//动态加载 场景模型
//这时 就认为 加载结束了 进度条顶满
//隐藏进度条
}
SceneManagement.SceneManager - Unity 脚本 API (unity3d.com)
画线功能Linerenderer组件
知识点一 LineRenderer是什么
//LineRenderer是Unity提供的一个用于画线的组件
//使用它我们可以在场景中绘制线段
//一般可以用于
//1绘制攻击范围
//2武器红外线
//3辅助功能
//4其它画线功能
知识点二ineRender代码相关
//动态添加一个线段
GameObject line = new GameObject();
line.name = "Line";
LineRenderer lineRenderer = line.AddComponent<LineRenderer>();
//首尾相连
lineRenderer.loop = true;
//开始结束宽
lineRenderer.startWidth = 0.02f;
lineRenderer.endWidth = 0.02f;
//开始结束颜色
lineRenderer.startColor = Color.white;
lineRenderer.endColor = Color.red;
//设置材质
m = Resources.Load<Material>("M");
lineRenderer.material = m;
//设置点
//一定注意 设置点 要 先设置点的个数
lineRenderer.positionCount = 4;
//接着就设置 对应每个点的位置
lineRenderer.SetPositions(new Vector3[] { new Vector3(0,0,0),
new Vector3(0,0,5),
new Vector3(5,0,5)});
lineRenderer.SetPosition(3, new Vector3(5, 0, 0));
//是否使用世界坐标系
//决定了 是否随对象移动而移动
lineRenderer.useWorldSpace = false;
//让线段受光影响 会接受光数据 进行着色器计算
lineRenderer.generateLightingData = true;
UnityEngine.LineRenderer - Unity 脚本 API (unity3d.com)
核心系统
UnityEngine.Physics - Unity 脚本 API (unity3d.com)
物理系统之范围检测
知识点二 如何进行范围检测
//必备条件:想要被范围检测到的对象 必须具备碰撞器
//注意点:
//1.范围检测相关API 只有当执行该句代码时 进行一次范围检测 它是瞬时的
//2.范围检测相关API 并不会真正产生一个碰撞器 只是碰撞判断计算而已
//范围检测API
//1.盒状范围检测
//参数一:立方体中心点
//参数二:立方体三边大小
//参数三:立方体角度
//参数四:检测指定层级)不填检测所有层)
//参数五:是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal
//返回值:在该范围内的触发器)得到了对象触发器就可以得到对象的所有信息)
print(LayerMask.NameToLayer("UI"));
Collider[] colliders = Physics.OverlapBox( Vector3.zero, Vector3.one, Quaternion.AngleAxis(45, Vector3.up),
1 << LayerMask.NameToLayer("UI") |
1 << LayerMask.NameToLayer("Default"), QueryTriggerInteraction.UseGlobal);
//0000 0001
//0010 0000
//重要知识点:
//关于层级
//通过名字得到层级编号 LayerMask.NameToLayer
//我们需要通过编号左移构建二进制数
//这样每一个编号的层级 都是 对应位为1的2进制数
//我们通过 位运算 可以选择想要检测层级
//好处 一个int 就可以表示所有想要检测的层级信息
//层级编号是 0~31 刚好32位
//是一个int数
//每一个编号 代表的 都是二进制的一位
//0—— 1 << 0——0000 0000 0000 0000 0000 0000 0000 0001 = 1
//1—— 1 << 1——0000 0000 0000 0000 0000 0000 0000 0010 = 2
//2—— 1 << 2——0000 0000 0000 0000 0000 0000 0000 0100 = 4
//3—— 1 << 3——0000 0000 0000 0000 0000 0000 0000 1000 = 8
//4—— 1 << 4——0000 0000 0000 0000 0000 0000 0001 0000 = 16
//5—— 1 << 5——0000 0000 0000 0000 0000 0000 0010 0000 = 32
//另一个API
//返回值:碰撞到的碰撞器数量
//参数:传入一个数组进行存储
//Physics.OverlapBoxNonAlloc()
if(Physics.OverlapBoxNonAlloc(Vector3.zero, Vector3.one, colliders) != 0)
//2.球形范围检测
//参数一:中心点
//参数二:球半径
//参数三:检测指定层级)不填检测所有层)
//参数四:是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal
//返回值:在该范围内的触发器)得到了对象触发器就可以得到对象的所有信息)
colliders = Physics.OverlapSphere(Vector3.zero, 5, 1 << LayerMask.NameToLayer("Default"));
//另一个API
//返回值:碰撞到的碰撞器数量
//参数:传入一个数组进行存储
//Physics.OverlapSphereNonAlloc
if( Physics.OverlapSphereNonAlloc(Vector3.zero, 5, colliders) != 0 )
//3.胶囊范围检测
//参数一:半圆一中心点
//参数二:半圆二中心点
//参数三:半圆半径
//参数四:检测指定层级)不填检测所有层)
//参数五:是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal
//返回值:在该范围内的触发器)得到了对象触发器就可以得到对象的所有信息)
colliders = Physics.OverlapCapsule(Vector3.zero, Vector3.up, 1, 1 << LayerMask.NameToLayer("UI"), QueryTriggerInteraction.UseGlobal);
//另一个API
//返回值:碰撞到的碰撞器数量
//参数:传入一个数组进行存储
//Physics.OverlapCapsuleNonAlloc
if ( Physics.OverlapCapsuleNonAlloc(Vector3.zero, Vector3.up, 1, colliders ) != 0 )
物理系统之射线检测
知识点一 射线对象
//1.3D世界中的射线
//假设有一条
//起点为坐标(1,0,0)
//方向为世界坐标Z轴正方向的射线
//注意:
//理解参数含义
//参数一:起点
//参数二:方向)一定记住 不是两点决定射线方向,第二个参数 直接就代表方向向量)
//目前只是申明了一个射线对象 对于我们来说 没有任何的用处
Ray r = new Ray(Vector3.right, Vector3.forward);
//Ray中的参数
print(r.origin);//起点
print(r.direction);//方向
//2.摄像机发射出的射线
// 得到一条从屏幕位置作为起点
// 摄像机视口方向为 方向的射线
Ray r2 = Camera.main.ScreenPointToRay(Input.mousePosition);
知识点三 碰撞检测函数
//Physics类中提供了很多进行射线检测的静态函数
//他们有很多种重载类型 我们只需要掌握核心的几个函数 其它函数自然就明白什么意思了
//注意:
//射线检测也是瞬时的
//执行代码时进行一次射线检测
//1.最原始的射线检测
// 准备一条射线
Ray r3 = new Ray(Vector3.zero, Vector3.forward);
// 进行射线检测 如果碰撞到对象 返回true
//参数一:射线
//参数二: 检测的最大距离 超出这个距离不检测
//参数三:检测指定层级)不填检测所有层)
//参数四:是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal
//返回值:bool 当碰撞到对象时 返回 true 没有 返回false
if (Physics.Raycast(r3, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal))
//还有一种重载 不用传入 射线 直接传入起点 和 方向 也可以用于判断
//就是把 第一个参数射线 变成了 射线的 两个点 一个起点 一个方向
if (Physics.Raycast(Vector3.zero, Vector3.forward, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal))
//2.获取相交的单个物体信息
//物体信息类 RaycastHit
RaycastHit hitInfo;
//参数一:射线
//参数二:RaycastHit是结构体 是值类型 Unity会通过out 关键在 在函数内部处理后 得到碰撞数据后返回到该参数中
//参数三:距离
//参数四:检测指定层级)不填检测所有层)
//参数五:是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal
if( Physics.Raycast(r3, out hitInfo, 1000, 1<<LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal) )
{
//碰撞器信息
print("碰撞到物体的名字" + hitInfo.collider.gameObject.name);
//碰撞到的点
print(hitInfo.point);
//法线信息
print(hitInfo.normal);
//得到碰撞到对象的位置
print(hitInfo.transform.position);
//得到碰撞到对象 离自己的距离
print(hitInfo.distance);
//RaycastHit 该类 对于我们的意义
//它不仅可以得到我们碰撞到的对象信息
//还可以得到一些 碰撞的点 距离 法线等等的信息
}
//还有一种重载 不用传入 射线 直接传入起点 和 方向 也可以用于判断
if (Physics.Raycast(Vector3.zero, Vector3.forward, out hitInfo, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal))
//3.获取相交的多个物体
//可以得到碰撞到的多个对象
//如果没有 就是容量为0的数组
//参数一:射线
//参数二:距离
//参数三:检测指定层级)不填检测所有层)
//参数四:是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal
RaycastHit[] hits = Physics.RaycastAll(r3, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal);
//还有一种重载 不用传入 射线 直接传入起点 和 方向 也可以用于判断
//之前的参数一射线 通过两个点传入
hits = Physics.RaycastAll(Vector3.zero, Vector3.forward, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal);
//还有一种函数 返回的碰撞的数量 通过out得到数据
if(Physics.RaycastNonAlloc(r3, hits, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal) > 0 )