目录
- 基础知识点
- 3D数学——基础
- Mathf
- 三角函数
- 坐标系
 
- 3D数学——向量
- 向量模长和单位向量
- 向量的加减乘除
- 向量点乘
- 向量叉乘
- 向量插值运算
 
- 3D数学——四元数
- 为何使用四元数
- 四元数是什么
- 四元数常用方法
- 四元数计算
 
- MonoBehavior中的重要内容
- 延迟函数
- 协同程序
- 协同程序原理
 
- Resources资源动态加载
- 特殊文件夹
- Resources资源同步加载
- Resources资源异步加载
- Resources资源加载
 
- 场景异步切换
- 场景异步加载
 
- LineRenderer
- 物理系统
- 范围检测
- 射线检测
 
 
基础知识点
3D数学——基础
Mathf
Mathf和Math
 Math是C#中封装好的用于数学计算的工具类—位于System命名空间中。
 Mahtf是Unity中封装好的用于数学计算的工具结构体----位于UnityEngine命名空间中。两者都是提供用于进行数学相关计算的。
 区别
 Mathf和Math中的相关方法几乎一样,Math是C#自带的工具类,主要就提供一些数学相关计算方法。Mathf是unity专门封装的,不仅包含Math中的方法,还多了一些适用于游戏开发的方法,所以我们在进行Unity游戏开发时,使用Mathf中的方法用于数学计算即可。
 Mathf中常用的方法----一般计算一次
 1、Π——pi
Mathf.PI;
2、取绝对值——Abs
Mathf.Abs(-10);
3、向上取整——CeilToInt
Mathf.CeilToInt(1.3f);
4、向下取整——FloorToInt
Mathf.FloorToInt(9.6f);
5、钳制函数——Clamp
 比小的还小取最小
 比大的还大取最大
 在中间的取自己
Mathf.Clamp(10,11,20);
6、获取最大值——Max
Mathf.Max(1,2,3,4,5,6,7,8);
7、获取最小值——Min
Mathf.Min(1,2,3,4,5,6,7,8);
8、一个数的n次幂——Pow
Mathf.Pow(4,2);
9、四舍五入——RoundToInt
Mathf.RoundToInt(1.3f);
10、返回一个数的平方根——Sqrt
Mathf.Sqrt(4);
11、判断一个数是否是2的n次方——IsPowerOfTwo
Mathf.isPowerOfTwo(4);
12、判断正负——Sign
Mathf.Sign(0);
Mathf中的常用方法——一般不停计算
 插值运算——Lerp
Lerp函数公式
result = Mathf.Lerp(start,end,t);
t为插值系数,取值范围0~1
result = start + (end - start)*t
插值运算用法一:
 每帧改变start的值,变化速度先快后慢,位置无限接近,但是不会得到end的位置
float start = 0;
start = Mathf.Lerp(start,10,Time.deltaTime);
插值运算用法二:
 每帧改变t的值,变化速度均匀,位置每帧接近,当t>=1时,得到结果
float start = 0;
float result = 0;
float time = 0;
time += Time.deltaTime;
result = Mathf.Lerp(start,10,time);
三角函数
角度和弧度
 角度和弧度都是度量角的单位:
 角度:1°
 弧度:1 radian
 圆一周的角度:360°
 圆一周的弧度:2Π radian
 角度和弧度的转换关系
 
 弧度转角度
float rad = 1;
float anger = rad * Mathf.Rad2Deg;
角度转弧度
anger = 1;
rad = anger * Mathf.Deg2Rad; 
三角函数
 Mathf中的三角函数相关函数,传入的参数需要是弧度值
 例如:
Mathf.Sin(30 * Mathf.Deg2Rad);
Mathf.Cos(60 * Mathf.Deg2Rad);
反三角函数
 反三角函数得到的结果是正弦或者余弦值对应的弧度
rad = Mathf.Asin(0.5f);
rad * Mathf.Rad2Deg;
rad = Mathf.Acos(0.5f);
rad * Mathf.Rad2Deg;
坐标系
世界坐标系
 原点:世界的中心点
 轴向:世界坐标系的三个轴向是固定的
this.transform.position;
this.transform.ratation;
this.transform.eulerAngles;
this.transform.lossyScale;
物体坐标系
 原点:物体的中心点(建模时决定)
 轴向:
 物体右方为x轴正方向
 物体上方为y轴正方向
 物体前方为z轴正方向
//相对父对象的物体坐标系的位置 本地坐标 相对坐标
this.transform.localPosition;
this.transform.localEulerAngles;
this.transform.localRotation;
this.transform.localScale;
屏幕坐标系
 原点:屏幕左下角
 轴向:
 向右为x轴正方向
 向上为y轴正方向
 最大宽高:
 Screen.width
 Screen.height
Input.mousePosition
Screen.width
Screen.height
视口坐标系
 摄像机上的视口范围。
 原点:屏幕左下角
 轴向:
 向右为x轴正方向
 向上为y轴正方向
 特点:
 左下角为(0,0)
 右上角为(1,1)
 和屏幕坐标类似,将坐标单位化
 坐标转换相关
 1、世界转本地
this.transform.InverseTransformDirection;
this.transform.InverseTransformPoint;
this.transform.InverseTransformVector;
2、本地转世界
this.transform.TransformDirection;
this.transform.TransformPoint;
this.transform.TransformVector;
3、世界转屏幕
Camera.main.WorldToScreenPoint;
4、屏幕转世界
Camera.main.ScreenToWorldPoint;
5、世界转视口
Camera.main.WorldToViewportPoint;
6、视口转世界
Camera.main.ViewportToWorldPoint;
7、视口转屏幕
Camera.main.ViewportToScreenPoint;
8、屏幕转视口
Camera.main.ScreenToViewportPoint;
3D数学——向量
向量模长和单位向量
向量
 三维向量——Vector3
 1、位置——代表一个点
this.transform.position;
2、方向——代表一个方向
this.transform.forward;
this.transform.up;
两点决定——向量
 
Vector3 A = new Vector3(1,2,3);
Vector3 B = new Vector3(5,1,5);
Vector3 AB = B - A;
Vector3 BA = A - B;
零向量
Vector3.zero;
负向量
-Vector3.forward;
向量的模长
 模长公式:
 A向量为(x,y,z)

//两种
AB.magnitude;
AB.Distance(A,B);
单位向量
 
AB.normalized;
向量的加减乘除
向量加法
this.transform.Translate(Vector3.forward * 5);
1、位置+位置
 无意义
 2、向量+向量
 向量 + 向量 = 向量
 3、位置+向量
 位置 + 向量 = 位置
向量减法
this.transform.Translate(-Vector3.forward * 5);
1、位置-位置
 位置 - 位置 = 向量
 2、向量-向量
 向量 - 向量 = 向量
 3、位置-向量
 位置 + (-向量) = 位置
 4、向量-位置
 无意义
 向量乘除
this.transform.localScale *= 2;
this.transform.localScale /= 2;
向量只会和标量进行乘除法运算
 
向量点乘
向量 * 向量 = 标量
 
 调试画线
//画线段
//起点 终点
Debug.DrawLine(this.transform.position,this.transform.position + this.transform.forward,Color.red);
//画射线
//起点 方向
Debug.DrawRay(this.transform.position,this.transform.forward,Color.white);
通过点乘判断对象方位
public Transform target;
Debug.DrawRay(this.transform.position,this.transform.forward,Color.red);
Debug.DrawRay(this.transform.position,target.position - this.transform.position,Color.red);
//得到两个向量的点乘结果
Vector3.Dot(this.transform.forward,target.position - this.transform.position);
通过点乘推导公式算出夹角
 
 1、用单位向量算出点乘结果
dotResult = Vector3.Dot(this.transfrom.forward,(target.position - this.transform.position).normalized);
2、用反三角函数得出角度
Mathf.Acos(dotResult) * Mathf.Rad2Deg;
向量叉乘

 叉乘计算
public Transform A;
public Transform B;
Vector3.Cros(A.position,B.position);
1、A×B得到的向量同时垂直A和B
 2、A×B向量垂直于A和B组成的平面
 3、A×B = -(B×A)
 叉乘几何意义
 假设向量A和B都在XZ平面上,向量A×B
 1、y > 0:B在A右侧
 2、y < 0:B在A左侧
Vector3 C = Vector3.Cross(A.position,B.position);
if(C.y > 0)
{print("B在A右侧");
}
else
{print("B在A左侧");
}向量插值运算
线性插值
 
 1、每帧改变start的值(先快后慢)
 2、每帧改变t的值(匀速)
public Transform target;
public Transform A;
public Transform B;
//result = start + (end - start) * t;
//1、先快后慢,每帧改变start位置,位置无限接近,但不会得到end位置
A.position = Vector3.Lerp(A.position,target.position,Time.deltaTime);private Vector3 startPos = B.position;
private float time;
//2、匀速 每帧改变时间,当t>=1时,得到结果
time += Time.deltaTime;
B.position = Vector3.Lerp(startPos,target.position,time);
第二种匀速运动,当time>=1时,我改变了目标位置后,物体会直接瞬移到目标位置,导致看不出匀速运动,解决方法如下:
public Transform target;
public Transform A;
public Transform B;
private Vector3 startPos = B.position;
private float time;
private Vector3 nowTarget;
if(nowTarget != target.position)//若当前位置与目标位置不同
{nowTarget = target.position;//将当前位置更新为目标位置time = 0;//time清0startPos = B.position;//更新物体开始位置
}
time += Time.deltaTime;
B.position = Vector3.Lerp(startPos,nowTarget,time);
球形插值
 线性插值和球形插值的区别
 线性:直接平移,直线轨迹
 球形:直接旋转,弧形轨迹
public Transform C;
C.position = Vector3.Slerp(Vector3.right * 10,Vector3.forward * 10,time * 0.1);
3D数学——四元数
为何使用四元数
欧拉角
 由三个角度(x,y,z)组成,在特定坐标系下用于描述物体的旋转量。空间中的任意旋转都可以分解成绕三个互相垂直轴的三个旋转角组成的序列。
 1、旋转约定
 heading-pitch-bank(Y-X-Z)是一种最常用的旋转序列约定。
 2、Unity中的欧拉角
 Inspector窗口中调节的Rotation就是欧拉角,this.transform.eulerAngles得到的就是欧拉角角度。
 3、优缺点
 优点:直观、易理解,存储空间小(三个数表示)、可以进行从一个方向到另一个方向旋转大于180度的角度。
 缺点:同一旋转的表示不唯一、万向节死锁。
 4、万向节死锁
 当某个特定轴达到某个特殊值时,绕一个轴旋转可能会覆盖住另一个轴的旋转,从而失去一维自由度,Unity中x轴达到90度时,会产生万向节死锁。
四元数是什么
概念: 四元数时简单的超复数,由实数加上三个虚数单位组成,主要用于在三维空间中表示旋转。
 构成
 一个四元数包含一个标量和一个3D向量。
 [w,v]:w为标量,v为3D向量
 [w,(x,y,z)]
 对于给定的任意一个四元数:表示是3D空间中的一个旋转量。
 轴-角对
 在3D空间中,任意旋转都可以表示绕着某个轴旋转一个角得到。该轴并不是空间中的x,y,z轴,而是任意一个轴。
 
 Unity中的四元数
 Quaternion:是Unity中表示四元数的结构体
 1、计算原理
 
Quaternino q = new Quaternino(Mathf.Sin(30 * Mathf.Deg2Rad),0,0,Mathf.Cos(30 * Mathf.Deg2Rad));
2、轴角对初始化四元数的方法
 
Quaternion q2 = Quaternion.AngleAxis(60,Vector3.right);
四元数和欧拉角转换
 1、欧拉角转四元数
 
 2、四元数转欧拉角
 
 四元数弥补的欧拉角缺点
 1、同一旋转的表示不唯一
 2、万向节死锁
四元数常用方法
单位四元数表示没有旋转量(角位移),当角度为0或者360度时,对于给定轴都会得到单位四元数。
 例如:[1,(0,0,0)]和[-1,(0,0,0)]
 单位四元数
Quaternion.identity;
插值运算
 四元数中同样提供如同Vector3的插值运算Lerp和Slerp。在四元数中Lerp和Slerp只有一些细微差别,由于算法不同,Slerp的效果会好一些,Lerp的效果相比Slerp更快但是旋转范围较大效果较差,所以建议使用Slerp进行插值运算。
public Transform target;
public Transform A;
public Transform B;
private Quaternion start;
private float time;
//无限接近 先快后慢
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);
向量指向转四元数
 Quaternion.LookRotation(面朝向量):该方法可以将传入的面朝向量,转换为对应的四元数角度信息。
 例如:当人物面朝向想要改变时,只需要把目标面朝向传入该函数,便可以得到目标四元数角度信息,之后将人物四元数角度信息改为得到的信息即可达到转向。
Quaternion q = Quaternion.LookRotation(lookB.position - lookA.position);
lookA.rotation = q;
四元数计算
四元数相乘
 q3 = q1 * q2:两个四元数相乘得到一个新的四元数,代表两个旋转量的叠加,相当于旋转。旋转相对的坐标系,是物体自身坐标系。
Quaternion q = Quaternion.AngleAxis(20,Vector3.up);
this.transform.rotation *= q;
四元数乘向量
 v2 = q1 * v1:四元数乘向量返回一个新向量,可以将指定向量旋转对应四元数的旋转量,相当于旋转向量。
Vector3 v = Vector3.forward;
v = Quaternion.AngleAxis(45,Vector3.up) * v;//四元数写在前面,向量写在后面
MonoBehavior中的重要内容
延迟函数
会延时执行的函数,我们可以自己设定延时要执行的函数和具体延时的时间,是MonoBehaviour基类中实现好的方法。
 使用
 1、延迟函数
 Invoke:
 参数一:函数名 字符串
 参数二:延迟时间 秒为单位
private void DelayDoSomething()
{
}
Invoke("DelayDoSomething",5);
注意:
 ①、延迟函数第一个参数传入的是函数名字符串
 ②、延迟函数没办法传入参数,只有包裹一层
 ③、函数名必须是该脚本上声明的函数
2、延迟重复执行函数
 InvokeRepeating:
 参数一:函数名字符串
 参数二:第一次执行的延迟时间
 参数三:之后每次执行的间隔时间
InvokeRepeating("DelayRe",5,1);
3、取消延迟函数
 取消该脚本上的所有延迟函数执行
 ①取消该脚本上的所有延迟函数执行
CancelInvoke();
②指定函数名取消
CancelInvoke("DelayDoSomething");
4、判断是否有延迟函数
if(IsInvoking())
{print("存在延迟函数");
}
if(IsInvoking("DelayDoSomething"))
{print("存在延迟函数");
}
延迟函数受对象失活销毁影响
 脚本依附对象失活,延迟函数可以继续执行。
 脚本依附对象销毁或者脚本移除,延迟函数无法继续执行。
协同程序
Unity是否支持多线程?
 unity支持多线程,只是新开线程无法访问unity相关对象的内容。
 注意:unity中的多线程,要记住关闭。
private void Test()
{
}
Thread t;
t = new Thread(Test);
//关闭
private void OnDestroy()
{t.Abort();t = null;
}
协同程序是什么?
 协同程序简称协程,它是”假“的多线程,他不是多线程。
 1、主要作用:将代码分时执行,不卡主线程,就是把可能会让主线程卡顿的耗时的逻辑分时分步执行。
 2、主要使用场景:
- 异步加载文件
- 异步下载文件
- 场景异步加载
- 批量创建时防止卡顿
协同程序和线程的区别
 1、新开一个线程是独立的一个管道,和主线程并行执行。
 2、新开一个协程是在原线程之上开启,进行逻辑分时分步执行。
 协程的使用
 继承MonoBehavior的类,都可以开启协程函数。
 1、声明协程函数
 关键:
 ①返回值为IEnumerator类型及其子类
 ②函数中通过yield return 返回值进行返回
IEnumerator MyCoroutine(int i,string str)
{yield return new WaitForSecond(5f);
}
2、开启协程函数
 常用开启方式
MyCoroutine(1,"123");//错误执行方式
StartCoroutine(MyCoroutine(1,"123"));
IEnumerator ie = MyCoroutine(1,"123");
StartCoroutine(ie);
3、关闭协程
 ①关闭所有协程
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、协程函数本体
 2、协程调度器
 协程本体就是一个能够中间暂停返回的函数,协程调度器是Unity内部实现的,会在对应的时机帮助我们继续执行协程函数。Unity只实现了协程调度部分,协程的本体本质上是一个C#的迭代器方法。
 协程本体是迭代器方法的体现
 1、协程函数本体
 如果不通过开启协程方法执行协程,Unity的协程调度器是不会帮助我们管理协程函数的。
IEnumerator ie = Test();
自己执行迭代器函数的内容
ie.MoveNext();//执行函数中内容遇到yield return为止的逻辑
print(ie.Current);//得到yield return返回的内容
2、协程调度器
 继承MonoBehavior后开启协程,相当于是把一个协程函数(迭代器)放入unity的协程调度器中帮助我们管理进行执行,具体的yield return后面的规则,也是unity定义的一些规则。
Resources资源动态加载
特殊文件夹
工程路径获取
 该方式获取到的路径一般情况下只在编辑模式下使用,不会再实际发布游戏后,还使用该路径。游戏发布后,该路径就不存在了。
Application.dataPath;
Resources资源文件夹
 路径获取:
 一般不获取,只能使用Resources相关API进行加载,如果硬要获取,可以用工程路径拼接。该文件夹需要我们自己来创建。
print(Application.dataPath + "/Resources");
作用:
 1、需要通过Resource相关API动态加载的资源需要放在其中
 2、该文件夹下所有文件都会被打包出去
 3、打包时Unity会对其压缩加密
 4、该文件夹打包后只读,只能通过Resources相关API加载
 StreamingAssets流动资源文件夹
 该文件夹需要自己创建
 路径获取:
print(Application.streamingAssetsPath);
作用:
 1、打包出去不会被压缩加密,可以任由我们摆布
 2、移动平台只读,pc平台可读可写
 3、可以放入一些需要自定义动态加载的初始资源
 persistentDataPath持久数据文件夹
 不需要我们自己创建
 路径获取:
print(Application.persistentDataPath);
作用:
 固定数据文件夹
 1、所有平台都可读可写
 2、一般用于放置动态下载或者动态创建的文件,游戏中创建或者获取的文件都放在其中
 Plugins插件文件夹
 需要我们自己创建
 路径获取:
 一般不获取
 作用:
 不同平台的插件相关文件放在其中,例如:IOS和Android
 Editor编辑器文件夹
 需要自己创建
 路径获取:
 一般不获取,如果硬要获取,可以用工程路径拼接
print(Application.dataPath + "/Editor");
作用:
 1、开发Unity编辑器时,编辑器相关脚本放在该文件夹中
 2、该文件夹中内容不会被打包出去
 默认资源文件夹Standard Asssets
 需要我们自己创建
 路径获取:
 一般不获取
 作用:
 一般unity自带资源都放在这个文件夹下
 代码和资源优先被编译
Resources资源同步加载
Resources资源动态加载的作用
 1、通过代码动态加载Resources文件夹下指定路径资源
 2、避免繁琐的拖曳操作
 常用资源类型
 1、预设体对象——GameObject
 2、音效文件——AudioClip
 3、文本文件——TextAsset
 4、图片文件——Texture
 5、其他类型
 注意:预设体对象加载需要实例化,其他资源加载一般直接用
 资源同步加载——普通方法
 1、预设体对象 想要创建在场景上
 ①加载预设体的资源文件
 本质上就是加载配置数据,在内存中。
Object obj = Resources.Load("Cube");
②实例化
Instantiate(obj);
2、音效资源
public AudioSource audios;
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、图片
pirvate Texture tex;
tex = Resources.Load("Tex/TestJPG") as Texture;
private void OnGUI()
{GUI.DrawTexture(new Rect(0,0,100,100),tex);
}
5、资源同名怎么办?
 Resources.Load加载同名资源时,无法准确加载出你想要的内容
 可以使用另外的API:
- 加载指定类型的资源
tex = Resources.Load("Tex/TestJPG",typeof(Texture)) as Texture;
- 加载指定名字的所有资源
Object[] objs = Resources.LoadAll("Tex/TestJPG");
foreach(Object item in objs)
{}
资源同步加载——泛型方法
TextAsset ta2 = Resources.Load<TextAsset>("Tex/TestJPG");
Resources资源异步加载
Resources异步加载是什么
 在同步加载中,如果加载过大的资源可能会造成程序卡顿,其原因是,从硬盘上把数据读取到内存中,是需要进行计算的,越大的资源耗时越长,就会造成掉帧卡顿。
 Resources异步加载,就是内部新开一个线程进行资源加载,不会造成主线程卡顿。
 Resouces异步加载方法
 异步加载不能马上得到加载的资源,至少要等一帧
 1、通过异步加载中的完成事件监听使用加载的资源
private Texture tex;
private void LoadOver(AsyncOperation rq)
{//asset是资源对象加载完毕后就可以得到tex = (rq as ResourceRequest).asset as Texture;
}
ResourceRequest rq = Resources.LoadAsync<Texture>("Tex/TestJPG");   
rq.completed += LoadOver;//事件函数监听
2、通过协程使用加载的资源
StartCoroutine(Load());
IEnumerator Load()
{ResourceRequest rq = Resources.LoadAsync<Texture>("Tex/TestJPG"); //Unity自己知道该返回值意味着在异步加载资源yield return rq;	//Unity会自己判断该资源是否加载完毕了,加载完毕以后才会继续执行后面的代码。tex = rq.asset as Texture;//判断资源是否加载结束while(!rq.isDone){//打印当前的加载进度,该进度不会特别准确,过渡也不是特别明显print(rq.priority);yield return null;}
}
总结:
 ①完成事件监听异步加载(线性加载)
 优点:写法简单
 缺点:只能在资源加载结束后进行处理
 ②协程异步加载(并行加载)
 优点:可以在协程中处理复杂逻辑,比如同时加载多个资源,比如进度条更新。
 缺点:写法稍麻烦
Resources资源加载
Resources重复加载资源会浪费内存吗?
 其实Resources加载一次资源过后,该资源就一直存放在内存中作为缓存,第二次加载时发现缓存中存在该资源,会直接取出来进行使用,所以多次重复加载不会浪费内存,但是会浪费性能(每次加载都会去查找取出,始终伴随一些性能消耗)
 如何手动释放掉缓存中的资源
 1、卸载指定资源
 Resources.UnloadAsset方法:
 该方法不能释放GameObject对象,因为它会用于实例化对象,它只能用于一下不需要实例化的内容,比如:图片、音效、文本等等。一般情况下,我们很少单独使用它。
 2、卸载未使用的资源
 一般在过场景时和GC一起使用。
Resources.UnloadUnusedAssets();
GC.Collect();
场景异步切换
场景异步加载
回顾场景同步切换
SceneManager.LoadScene("Lesson20Test");
在切换场景时,Unity会删除当前场景上所有对象,并且去加载下一个场景的相关信息,如果当前场景对象过多或者下一个场景对象过多,这个过程会非常的耗时,会让玩家感受到卡顿。
 所以使用异步切换解决该问题。
 场景异步切换
 场景异步切换和资源异步加载,几乎一致,有两种方式:
 1、通过事件回调函数,异步加载
AsyncOperation ao = SceneManager.LoadSceneAsync("Lesson20Test");
//当场景异步加载结束后,就会自动调用该事件函数,
//我们如果希望在加载结束后做一些事情,那么就可以在该函数中写处理逻辑
ao.completed += (a) =>
{print("加载完毕");
}
2、通过协程异步加载
 加载场景会把当前场景上,没有特别处理的对象都删除,所以协程中的部分逻辑可能是执行不了的。
 解决思路:让处理场景加载的脚本依附的对象过场景时,不被移除。
//解决方法:该脚本依附的对象过场景时,不会被移除
DontDestroyOnLoad(this.gameObject);
StartCoroutine(LoadScene("Lesson20Test"));
IEnumerator LoadScene(string name)
{AsyncOperation ao = SceneManager.LoadSceneAsync(name);yield return ao;//unity内部的协程协调器,发现是异步加载类型的返回对象,就会等待//等待异步加载结束后,才会继续执行,迭代器函数后面的步骤。//场景加载完毕后的这部分逻辑无法执行,因为挂载到场景中的对象的该脚本被移除
}
在异步加载过程中,可以去更新进度条
 ①利用场景异步加载的进度去更新,但是不是特别准确,一般也不会直接用。
while(!ao.isDone)
{print(ao.progress);yield return null;
}
离开循环后,就会认为场景加载结束,可以把进度条顶满,然后隐藏进度条。
 ②根据游戏规则自己定义进度条变化的条件
yield return ao;
//场景加载结束更新20%进度
//接着去加载场景中的其他信息
//例如:动态加载怪物再更新20%进度
//动态加载场景模型进度条顶满
//隐藏进度条
总结:
 1、事件回调函数
 优点:写法简单,逻辑清晰
 缺点:只能加载完场景做一些事情,不能在加载过程中处理逻辑
 2、协程异步加载
 优点:可以在加载过程中处理逻辑,比如进度条更新等。
 缺点:写法较为麻烦,要通过协程。
LineRenderer
LineRenderer是什么?
 LineRenderer是unity提供的一个用于画线的组件,使用它我们可以在场景中绘制线段。一般可以用于:
 1、绘制攻击范围
 2、武器红外线
 3、辅助功能
 4、其他画线功能
 LineRenderer参数相关
 1、Loop:是否终点起始自动相连
 2、Positions:线段的点
 3、Width:线段宽度曲线调整
 4、Color:颜色变化
 5、Corner Vertices(角顶点、圆角):此属性指示在一条线中绘制时使用了多少额外的顶点。增加此值,使线角看起来更圆。
 6、End Cap Vertices(终端顶点,圆角):终点圆角
 7、Alignment对齐方式:
- View 视点:线段对着摄像机
- Transform Z:线段面向其Z轴
8、Texture Mode 纹理模式:
- Stretch 拉伸:沿整条线映射纹理一次
- Tile 瓷砖平铺:不停的重复纹理
- Distribute Per Segment 分配执行
- Repeat Per Segment 重复显示
9、Shadow Bias 阴影偏移
 10、Generate Lighting Data 生成光源数据
 11、Use World Space 是否使用世界坐标系
 12、Materials 线使用的材质球
 13、Lighting 光照影响
- Cast Shadows 是否开启阴影
- Receive Shadowss 接收阴影
14、Probes 光照探针
 ①Light Probes 光探测器模式
- 不使用光探针
- 使用内插光探针
- 使用三维网格内插光探针
- 自定义从材质决定
②Reflection Probes 反射探测器模式
- 不使用反射探针
- 启用混合反射探针
- 启用混合反射探针和天空盒混合
- 启用普通探针,重叠式不混合
15、Additional Settings 附加设置
- Motion Vectors 运动矢量:
 ①使用相机运动来跟踪运动
 ②特定对象来跟踪运动
 ③不跟踪
- Dynamic Occludee 动态遮挡剔除
- Sorting Layer 排序图层
- Order in Layer 此线段在排序图层中的顺序
 新编辑模式
  
 LineRenderer代码相关
 1、动态添加一个线段
GameObject line = new GameObject();
line.name = "Line";
LineRenderer lineRenderer = line.AddComponent<LineRenderer>();
2、首尾相连
lineRenderer.loop = true;
3、开始结束宽
lineRenderer.startWith = 0.02f;
lineRenderer.endWith = 0.02f;4、开始结束颜色
lineRenderer.startColor = Color.white;
lineRenderer.endColor = Color.white;
5、设置材质
private Material m = Resouces.Load<Material>("M");
lineRenderer.material = m;
6、设置点
 要先设置点的个数
lineRenderer.positionCount = 4;
lineRenderer.SetPositions(new Vector3[]{new Vector3(0,0,0},new Vector3(0,0,5),new Vector3(5,0,5)});
7、是否使用世界坐标系
 决定了是否随对象移动而移动
lineRenderer.useWorldSpace = false;
8、让线段受光影响
 会接受光数据,进行着色器计算
lineRenderer.generateLightingData = true;
物理系统
范围检测
什么是范围检测
 游戏中瞬时的攻击范围判断一般会使用范围检测
 举例:
 1、玩家在前方5m处释放一个地刺魔法,在此处范围内对象将受到地刺伤害。
 2、玩家攻击,在前方1米圆形范围内对象都受到伤害
 类似这种并没有实体物体,只想要检测在某一范围是否让敌方受到伤害时,便可以使用范围判断。简而言之,在指定位置进行范围判断,我们可以得到处于指定范围内的对象,目的是对对象进行处理,比如受伤 减血等等。
 如何进行范围检测
 必备条件:想要被范围检测到的对象,必须具备碰撞器
 注意点:
 1、范围检测相关API,只有当执行该句代码时,进行一次范围检测,它是瞬时的
 2、范围检测相关API并不会真正产生一个碰撞器,只是碰撞判断计算而已。
范围检测API
 1、盒状范围检测
 参数一:立方体中心点
 参数二:立方体三边大小
 参数三:立方体角度
 参数四:检测指定层级(不检测所有层)
 参数五:是否忽略触发器 UseGlobal——使用全局设置 Collide——检测触发器 Ignore——忽略触发器 不填使用UseGlobal
 返回值:在该范围内的触发器(得到了对象触发器就可以得到对象的所有信息)
Collider[] colliders = Physics.OverlapBox(Vector3.zero,Vector3.one,Quaternion.AngleAxis(45,Vector3.up),1 << LayerMask.NameToLayer("UI") | 1 << LayerMask.NameToLayer("Default"),QueryTriggerInteraction.UseGlobal);
重要知识——关于层级
通过名字得到层级编号 LayerMask.NameToLayer
我们需要通过编号左移构建二进制数,这样每一个编号的层级都是对应位为1的二进制数。
我们通过位运算,可以选择想要检测的层级
好处是一个int就可以表示所有想要检测的层级信息
另一个API:
 返回值:碰撞到的碰撞器数量
 参数:传入一个数组进行存储
Physics.OverlapBoxNonAlloc(Vector3.zero,Vector3.one,colliders);
2、球形范围检测
 参数一:中心点
 参数二:球半径
 参数三:检测指定层级(不填检测所有层)
 参数四:是否忽略触发器 UseGlobal——使用全局设置 Collide——检测触发器 Ignore——忽略触发器 不填使用UseGlobal
 返回值:在该范围内的触发器(得到了对象触发器就可以得到对象的所有信息)
Physics.OverlapSphere(Vector3.zero,5,1 << LayerMask.NameToLayer("Default"));
另一个API:
 返回值:碰撞到的碰撞器数量
 参数:传入一个数组进行存储
Physics.OverlapSphereNonAlloc(Vector3.zero,5,colliders);
3、胶囊范围检测
 参数一:半圆一中心点
 参数二:半圆二中心点
 参数三:半圆半径
 参数四:检测指定层级(不检测所有层)
 参数五:是否忽略触发器 UseGlobal——使用全局设置 Collide——检测触发器 Ignore——忽略触发器 不填使用UseGlobal
 返回值:在该范围内的触发器(得到了对象触发器就可以得到对象的所有信息)
Physics.OverlapCapsule(Vector3.zero,Vector3.up,1,1 << LayerMask.NameToLayer("UI"),QueryTriggerInteraction.UseGlobal);
另一个API
 返回值:碰撞到的碰撞器数量
 参数:传入一个数组进行存储
Physics.OverlapCapsuleNonAlloc(Vector3.zero,Vector3.up,1,colliders);
射线检测
什么是射线检测
 在指定点发射一个指定方向的射线,判断该射线与哪些碰撞器相交,得到对应对象。
 射线对象
 1、3D世界中的射线
 假设有一条起点为坐标(1,0,0),方向为世界坐标z轴正方向的射线。
 参数一:起点
 参数二:方向
 不是两点决定射线方向,第二个参数直接代表方向向量。
Ray r = new Ray(Vector3.right,Vector3.forward);
Ray中的参数
r.origin;//起点
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
Physics.Raycast(r3,1000,1 << LayerMask.NameToLayer("Monster"),QueryTriggerInteraction.UseGlobal);
另一种重载:不用传入射线直接传入起点和方向也可以用于判断,就是把第一个参数射线变成了射线的两个点,一个起点,一个方向。
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);//得到碰撞到的对象离自己的距离
}
另一种重载:
 不用传入射线,直接传入起点和方向,也可以用于判断
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)
另一种重载:
 不用传入射线,直接传入起点和方向,也可以用于判断
RaycastHit[] hits = Physics.RaycastAll(Vector3.zero,Vector3.forward,1000,1 << LayerMask.NameToLayer("Monster"),QueryTriggerInteraction.UseGlobal)
还有一种函数,返回的碰撞的数量,通过out得到数据
Physics.RaycastNonAlloc(r3,hits,1000,1 << LayerMask.NameToLayer("Monster"),QueryTriggerInteraction.UseGlobal);
使用时注意的问题
 距离、层级两个参数都是int类型,当我们传入参数时,一定要明确传入的参数代表的是距离还是层级。