Unity基础
文章目录
3D数学 – 基础
一些方法
float value01 = Mathf.PI; // Π
int value03 = Mathf.Abs(-2); // 绝对值
int value04 = Mathf.CeilToInt(1.3f); // 向上取整
int value05 = Mathf.FloorToInt(1.6f); // 向下取整
int value06 = Math.Clamp(5, 10, 20); // 钳制函数:在10~20直接返回值,10~20之外返回接近的那个值
int value07 = Mathf.Min(10, 90, 2); // 最小值,参数可变长
int value08 = Mathf.Max(1, 2, 6); // 最大值,参数可变长
float value09 = Mathf.Pow(5, 2); // 幂运算
int value10 = Mathf.RoundToInt(1.5f); // 四舍五入
float value11 = Mathf.Sqrt(4.0f); // 平方根
插值运算
三角函数
// 弧度转角度
float rad = 5;
float anger = 5 * Mathf.Rad2Deg;
// 角度转弧度
anger = 1;
rad = anger * Mathf.Deg2Rad;
// 三角函数:参数是弧度值
anger = 30 * Mathf.Deg2Rad;
float sin30rad = Mathf.Sin(anger);
// 反三角函数:返回值是弧度值
float asin = Mathf.Asin(1);
print(asin * Mathf.Rad2Deg);
坐标系
3D数学 – 向量
理解
常用函数
// 点乘
Vector3.Dot(dir1, dir2);
// 叉乘
Vector3.Cross(dir1, dir2);
// 角度
Vector3.Angle(dir1, dir2);
// 两点间距离
Vector3.Distance(pos1, pos2);
线性插值
与Mathf.Lerp
用法相同,只是参数为Vector3
3D数学 – 四元数
// 初始化四元数
Quaternion q = Quaternion.AngleAxis(60, Vector3.right);
this.transform.rotation = q;
// 欧拉角转四元数
Vector3 euler = new Vector3(1, 2, 3);
Quaternion qua = Quaternion.Euler(euler);
// 四元数转欧拉角
euler = qua.eulerAngles;
看向
四元数计算
延迟函数
-
延迟函数
参数一:函数名 字符串 参数二:延迟时间 单位秒
注意:延迟函数无法直接调用有参函数,函数名必须是该脚本申明的函数
Invoke("function", 5);
-
延迟重复执行函数
参数一:函数名 字符串 参数二:第一次执行延迟时间 参数三:之后每次执行的间隔时间
注意:同上
InvokeRepeating("function", 5, 5);
-
取消延迟函数
CancelInvoke(); // 取消该脚本上所有延迟函数 CancelInvoke("function"); // 取消指定函数名的延迟函数
-
判断是否有延迟函数
bool isInvoke = IsInvoking(); bool isInvoke2 = IsInvoking("function");
影响延迟函数:
脚本依附对象失活、脚本失活,延迟函数继续执行
脚本依附对象销毁、脚本销毁,延迟函数无法执行
协同程序
多线程
注意:新开的线程无法访问Unity相关对象的内容
线程与Unity编辑器共存亡,所以需要关闭线程
Thread th;
void Start()
{
th = new Thread(Test); // 创建线程
th.Start(); // 开启线程
}
// 线程与Unity编辑器共存亡,所以需要关闭线程
private void OnDestroy()
{
th.Abort(); // 结束线程
th = null;
}
private void Test()
{
while(true)
{
Thread.Sleep(1000);
print("Thread");
}
}
协同程序(协程)
-
“假”的多线程,协程不是多线程
-
将代码分时执行
-
使用场景:异步加载、下载,场景异步加载,批量创建防止卡顿
-
与线程的区别:
线程:新开一个独立的管道和主线程并行执行
协程:新开一个协程在原线程之上开启,进行逻辑的分时分步执行
协程函数的使用
void Start()
{
// 开启协程函数
Coroutine cor = StartCoroutine(MyCoroutine(5, "666"));
// 开启协程函数 或者:
IEnumerator ie = MyCoroutine(5, "666");
StartCoroutine(ie);
// 关闭所有协程
StopAllCoroutines();
// 关闭指定协程
StopCoroutine(cor);
}
// 携程函数声明: 返回值 IEnumerator 或其他继承它的类型
IEnumerator MyCoroutine(int i, string s)
{
print(i);
// 必须使用 yield return 进行返回
yield return new WaitForSeconds(5f); // 等待5秒
print(s);
}
yeild return
-
等待一帧后执行
yield return 数字
yield return null
在update和Lateupdate之间执行
-
等待指定秒后执行
yield return new WaitForseconds(秒)
在Update和LateUpdate之间执行
-
等待下一个固定物理帧更新时执行
yield return new WaitForFixedUpdate()
在Fixedupdate和碰撞检测相关函数之后执行
-
等待摄像机和GUI渲染完成后执行
yield return new WaitForEndofFrame()
在Lateupdate之后的渲染相关处理完毕后之后
-
一些特殊类型的对象 比如异步加载相关函数返回的对象
-
一些特殊类型对象
异步加载
-
跳出协程
yield break
关闭协程函数,返回值会为空
影响协程函数
组件和对象销毁,协程不执行
物体失活协程不执行,组件失活协程执行
协同程序原理
- 协程本体
- 协程可以分为两个部分:协程函数本体 协程调度器
- 协程本体就是一个能中间暂停返回的函数
- 协程调度器是Unity内部实现的,会在对应的时机帮助我们继续执行协程函数
- Unity只实现了协程调度部分,协程本体的本质是一个C#迭代器的方法
- 协程调度器
- 相当于把一个协程函数(迭代器)放入Unity的协程调度器中帮助我们管理进行执行
Resources资源动态加载
一些特殊文件夹
-
工程路径获取
编辑模式下使用,实际游戏发布后路径就不存在了
print(Application.dataPath);
-
Resources 资源文件夹
路径获取:一般不获取路径,通过API进行加载
注意:需要手动创建文件夹
作用:
- 通过Resources相关API动态加载的资源需要放在其中
- 该文件夹下所有文件都会被打包
- 打包时Unity会对其进行压缩加密
- 该文件夹打包后只读,只能通过Resources相关API加载
-
StringmingAssets 流动资源文件夹
路径获取:
Application.streamingAssetsPath
注意:需要手动创建文件夹
作用:
- 打包出去的文件不会被压缩加密
- 移动平台只读,PC平台可读可写
- 可以放入一些需要自定义动态加载的初始资源
-
persisitenDataPath 持久数据文件夹
路径获取:
Application.persistenDataPath
注意:不需要我们自己创建
作用:
- 固定数据文件夹
- 所有平台可读可写
- 一般用于放置动态下载或者动态创建的文件,游戏中创建或者获取的文件都放在其中
-
Plugins 插件文件夹
路径获取:一般不获取
注意:需要手动创建文件夹
作用:
-
插件文件夹,不同平台的插件相关文件放在其中
-
比如IOS和Android平台
-
-
Editor 编辑器文件夹
路径获取:一般不获取路径
注意:需要手动创建文件夹
作用:
-
开发Unity编辑器时,编辑器相关脚本放在该文件夹中
-
该文件夹中的内容不会被打包出去
-
-
默认资源文件夹 Standard Assets
路径获取:一般不获取
注意:需要手动创建,
作用:
- 一般Unity自带资源都放在这个文件夹下
- 代码和资源优先被编译
Resources同步加载
同一个资源多次加载不会造成内存浪费,如果资源已经被加载不会重复加入缓存区,但是会有性能浪费,所有不建议多次加载同一资源
-
Resources资源动态加载的作用
-
通过代码动态加载Resources文件夹下指定路径资源
-
避免繁琐的拖曳操作
-
-
常用资源类型
-
预设体对象 —— GameObject
-
音效文件 —— Audiolip
-
文本文件 —— TextAsset
-
图片文件 —— Texture
-
其他类型 —— 需要什么用什么类型
注意:预设体对象加载需要实例化
-
-
资源同步加载(普通方法)
- 预设体对象
Object obj = Resources.Load("Cube"); // 加载预设体资源文件,配置数据在内存中 Instantiate(obj); // 实例化
-
音效文件
public AudioSource audioS; // 音效源组件 objMusic = Resources.Load("Music/BKMusic"); // 加载数据 audioS.clip = objMusic as AudioClip; // 为组件添加音效切片 AudioS.Play(); // 播放音效
-
文本资源
TextAsset ta = Resources.Load("Txt/Test") as TextAsset; // 加载(返回类型是obj) print(ta.text); // 获得文本内容 print(ta.bytes); // 获得二进制字节数组
-
图片
Texture tex = Resources.Load("Tex/TextJPG") as Texture; // 加载
-
其他类型资源
需要什么资源就用什么类型,比如动画资源等等
-
同名资源
Resources.Load
加载同名资源时,无法准确加载出你想要的内容方法一:加载指定类型的资源
Texture tex = Resources.Load("Tex/TextJPG", typeof(Texture)) as Texture;
方法二:加载指定类型的所有资源
Object[] objs = Resources.LoadAll("Tex/TestJPG");
-
资源同步加载(泛型方法)
常用这种方法
TextAsset tex = Resources.Load<TextAsset>("Tex/TestJPG");
Resources异步加载
- 加载过大资源可能造成程序卡顿,因为从硬盘把数据读取到内存中需要进行计算,越大的资源耗时越长,就会造成掉帧卡顿
- 异步加载是内部新开一个线程进行资源加载,不会造成主线程的卡顿
- 异步加载不能马上得到加载资源,至少需要等待一帧
方法一:通过异步加载中的完成事件监听,使用加载的资源
// 开启异步加载
ResourceRequest rq = Resources.LoadAsync<Texture>("Tex/TestJPG");
// 资源下载结束的事件函数监听
rq.completed += LoadOver;
private void LoadOver( AsynOperation rq)
{
print("加载结束");
}
方法二:通过协程使用加载的资源
StartCoroutine(Load());
IEnumerator Load()
{
ResourceRequest rq = Resources.LoadAsync<Texture>("Tex/TestJPG");
yeild return rq; // Unity自动判断异步加载是否加载完毕
}
区别:
-
完成事件监听异步加载:
优:写法简单
缺:只能在资源加载结束后进行处理 “线性加载”
-
协程异步加载
优:可以在协程中处理复杂逻辑,比如同时加载多个资源,比如进度条更新
缺:写法稍麻烦
Resources资源卸载
方法一:卸载指定资源
注意:该方法不能释放GameObject对象,一般用于不需要实例化的内容如图片,文本,音效等等
if(Input.GetKeyDown(KeyCode.Alpha1))
{
Texture tex = Resources.Load<Texture>("Tex/TestJPG"); // 资源加载
}
if(Input.GetKeyDown(KeyCode.Alpha2))
{
Resources.UnloadAsset(tex); // 资源卸载
tex = null; //
}
方法二:卸载未使用资源
注意:一般在过场景时和GC一起使用
Resources.UnloadUnsedAssets(); // 卸载所有未使用的资源
GC.Collect();
场景切换
场景同步切换
缺点:在场景切换时会删除当前场景上的所有对象,并且去加载下一个场景的相关信息,如果当前场景对象过多或者下一个场景对象过多,会造成卡顿
SceneManager.LoadScene("SceneTest");
场景异步切换
方法一:通过事件回调函数异步加载
AsyncOperation ao = SceneManager.LoadSceneAsync("SceneTest");
ao.completed += (a) =>
{
// 异步加载结束自动调用事件函数
print("加载结束");
};
ao.completed += LoadOver;
private void LoadOver(AsyncOperation ao)
{
print("LoadOver"); //
}
方法二:通过协程异步加载
StartCoroutine(LoadScene("SceneTest"));
DontDestroyOnLoad(this,gameObject); // 脚本依附的对象过场景时
IEnumerator LoadScene(string name)
{
AsyncOperation ao = SceneManager.LoadSceneAsync(name);
yield return ao; // Unity自动判断异步加载是否加载完毕
}
LineRender
LineRenderer lineRenderer = gameObject.AddComponent<LineRenderer>();
// 首位连接
lineRenderer.loop = true;
// 开始与结束的宽度
lineRenderer.startWidth = 0.02f;
lineRenderer.endWidth = 0.02f;
// 开始与结束的颜色
lineRenderer.startColor = Color.red;
lineRenderer.endColor = Color.red;
// 设置材质
lineRenderer.material = Resources.Load<Material>("TestMaterial");
// 设置点
lineRenderer.positionCount = 4; // 需要先定义个数
lineRenderer.SetPositions(new Vector3[]{
new Vector3(-1, 0, -1),
new Vector3(-1, 0, 1),
new Vector3(1, 0, 1),
new Vector3(1, 0, -1),
});
// 是否使用世界坐标系
lineRenderer.useWorldSpace = false;
范围检测
- 检测对象必须具备碰撞器
- 范围检测相关API在执行瞬间进行一次检测,是瞬时检测
- 范围检测API不会产生碰撞器,只是判断碰撞计算
指定层级
层级使用二进制
Debug.Log(LayerMask.NameToLayer("UI")); // 返回对应名字的层数
int layer = 1 << LayerMask.NameToLayer("UI");
Debug.Log(Convert.ToString(layer, 2)); // 层级检测时规则是二进制的
// 通过或运算就可以多选检测层级
int layer1 = 1 << LayerMask.NameToLayer("UI") | 1 << LayerMask.NameToLayer("Water");
Debug.Log(Convert.ToString(layer1, 2));
// 通过同或运算可以排除层级
int layer2 = ~ (0 ^ layer);
Debug.Log(Convert.ToString(layer2, 2));
Box
- 立方体中心,立方体长宽高、角度、指定层级(默认检测所有层)、是否忽略触发器(默认UseGlobal)
- 是否忽略触发器:UseGlobal使用全局设置与设置情况相同,Collide检测触发器,Ignore忽略触发器
UseGlobal:
// 立方体:
Collider[] colliders =
Physics.OverlapBox(Vector3.zero, Vector3.one, Quaternion.identity, layer, QueryTrig gerInteraction.Collide);
foreach (Collider collider in colliders)
{
Debug.Log(collider.gameObject.name);
}
// 返回数量,参数传递Collider数组
if (Physics.OverlapBoxNonAlloc(Vector3.zero, Vector3.one, colliders, Quaternion.identity, layer) != 0)
{
Debug.Log("yes");
}
Sphere、Capsule
中心点、球半径、层级,是否忽略触发器
// 球体
colliders = Physics.OverlapSphere(Vector3.zero, 2f, layer, QueryTriggerInteraction.Collide);
int num1 = Physics.OverlapSphereNonAlloc(Vector3.zero, 2f, colliders, layer);
point1、point2、球半径、层级、是否忽略触发器
// 胶囊体
colliders = Physics.OverlapCapsule(Vector3.zero, Vector3.up, 2f, layer, QueryTriggerInteraction.Collide);
int num2 = Physics.OverlapCapsuleNonAlloc(Vector3.zero, Vector3.up, 2f, colliders,layer);
射线检测
射线
// 射线
Ray r = new Ray(target.transform.position, transform.forward);
Vector3 point = r.origin; // 起点
Vector3 dir = r.direction; // 方向
// 摄像机射线
// 以屏幕位置为起点,摄像机视口为方向的射线
Ray r2 = Camera.main.ScreenPointToRay(Input.mousePosition);
函数
// 重载一
// Ray射线、距离、层级、是否忽略触发器(同范围检测)
if (Physics.Raycast(new Ray(Vector3.zero, Vector3.forward), 1000, 1 << LayerMask.NameToLayer("Water"), QueryTriggerInteraction.UseGlobal))
{
Debug.Log("Raycast1");
}
// Ray = point,direction
if (Physics.Raycast(Vector3.zero, Vector3.forward, 1000))
{
Debug.Log("Raycast1_1");
}
// 重载二
// Ray射线、碰撞信息、距离、层级、是否忽略触发器(同范围检测)
if (Physics.Raycast(new Ray(Vector3.zero, Vector3.forward), out RaycastHit hitInfo , 1000, 1 << LayerMask.NameToLayer("Water"), QueryTriggerInteraction.UseGlobal))
{
Debug.Log("Raycast2");
// 碰撞对象
GameObject hit = hitInfo.collider.gameObject;
Transform transform = hitInfo.transform;
// 碰撞点
Vector3 hitPoint = hitInfo.point;
// 碰撞法线
Vector3 hitNormal = hitInfo.normal;
// 碰撞距离
float hitDistance = hitInfo.distance;
}
// Ray = point,direction
if (Physics.Raycast(Vector3.zero, Vector3.forward, out RaycastHit hitinfo ,1000))
{
Debug.Log("Raycast2_2");
}
// 重载三
// 检测碰撞到的多个对象
RaycastHit[] hits = Physics.RaycastAll(r, 1000, layer, QueryTriggerInteraction.UseGlobal);
foreach (RaycastHit hit in hits)
{
Debug.Log(hit.transform.gameObject.name);
}
// Ray = point,direction
hits = Physics.RaycastAll(Vector3.zero, Vector3.forward ,1000, layer, QueryTriggerInteraction.UseGlobal);
// 重载四
// 返回检测个数,传入检测数组
if (Physics.RaycastNonAlloc(r, hits, 1000, layer) > 0)
{
foreach(RaycastHit hit in hits)
{
Debug.Log(hit.transform.gameObject.name);
}
}