是一种超复数(x,y,z,w),轴角对。按YZX顺序一个轴一个轴的转
例如,将一个物体绕Z轴旋转60度
cube.transform.rotation= new Quaternion(0,0,Mathf.Sin(30*Mathf.Deg2Rad),Mathf.Cos(30*Mathf.Deg2Rad))
Inspector面板中虽然是用Rotation这种欧拉角来显示的,但是底层旋转功能是用Quaternion来实现的。rotation显示的是Quaternion.Euler。
一、LookRotation:看向目标物体
//挂载在相机上
public GameObject cube;
public GameObject cylinder;
// Start is called before the first frame update
void Start()
{
//结果是相机看向了cube
Vector3 relativePos = cube.transform.position - transform.position;
Quaternion rotation = Quaternion.LookRotation(relativePos);
transform.rotation = rotation;
}
二、Angel:它可以计算两个旋转之间的夹角。与Vector3.Angle()作用是一样的。返回欧拉角度
//初始将cylinder绕y轴旋转90度,打印结果是90度。旋转-90度,也是打印90度。
Debug.Log(Quaternion.Angle(cube.transform.rotation, cylinder.transform.rotation));
三、Euler:将一个欧拉形式的旋转转换成四元数形式的旋转。
//将cube的rotation值改为30,30,30
cube.transform.rotation = Quaternion.Euler(new Vector3(30,30,30));
四、FromToRotation:得到从一个方向到另一个方向的旋转。
//摄像机做一个旋转,旋转的值是本身的前方到cube上方之间的旋转值。
//Quaternion.FromToRotation(Quaternion From,Quaternion To);
transform.rotation= Quaternion.FromToRotation(transform.forward, cube.transform.up);
五、Slerp:基本意思就是球形插值地从一个角度旋转到另一个角度,其中,旋转匀速增加t。
//匀速看向cube目标物体
transform.rotation = Quaternion.Slerp(transform.rotation, cube.transform.rotation, 0.01f);
六、四元数相乘
两个四元数相乘,得到的新的四元数,就是两个旋转角度的叠加。
取子物体或者生成新子物体函数
///
/// 从Pool里面拿出子对象或者生成新对象
///
public void GetObjFromPoor()
{
//如果缓存池不空,那么就从中拿出物体
if (Pool.childCount > 0)
{
//利用循环按照所需要的数量将池子中的物体拿出来,并且设置为可见,父节点设置为bulletFather
//利用协程等待一定时间后,将子弹放回池子。
for (int i = 0; i < numsWeNeed; i++)
{
GameObject obj = FindChild("bullet", Pool).gameObject;
obj.SetActive(true);
obj.transform.parent = bulletFather;
StartCoroutine(Delayed(obj));//开启协程
}
}
//如果缓存池空了,生成新的物体
else
{
InstiateByCircle();
}
}
将物体放回对象池
///
/// 将物体放回池子,设为false,状态置0.
///
///
public void PushObjToPoor(GameObject obj)
{
obj.SetActive(false);
放回池子的东西放在一个父节点下,便于管理
obj.transform.position = Vector3.zero;
obj.transform.parent = Pool.transform;
}
协程函数
///
/// 协程函数,将物体放回池子
///
///
///
IEnumerator Delayed(GameObject obj)
{
yield return new WaitForSeconds(delayTime);//3秒之后执行之后的语句
PushObjToPoor(obj);
}
案例示例:环形发射子弹
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Fire1 : MonoBehaviour
{
public GameObject bt;//待生成的子弹预制体,通过resource加载
public Transform bulletFather;//子弹挂载的父节点
public Transform Pool;//对象池
public float delayTime = 2;//协程函数等待时间
public int numsWeNeed=12;//需要取出来的数量
void Start()
{
bt= Resources.Load("bullet");
}
void Update()
{
//生成环形子弹,放到PoorActiveTrue根节点下
if (Input.GetMouseButtonDown(0))
GetObjFromPoor();
transform.rotation *= Quaternion.Euler(new Vector3(0,1,0));
}
///
/// 生成环形子弹
///
/// 环形子弹的列表
public void InstiateByCircle()
{
for (int i = 0; i < 12; i++)
{
GameObject obj = Instantiate(bt, transform.position, Quaternion.Euler(0, i * 30, 0)*transform.rotation);
//生成的子弹都加入到bullets列表中
//bullets.Add(obj);
obj.transform.parent = bulletFather;
obj.name = "bullet";
StartCoroutine(Delayed(obj));//开启协程
}
}
///
/// 协程函数,将物体放回池子
///
///
///
IEnumerator Delayed(GameObject obj)
{
yield return new WaitForSeconds(delayTime);//3秒之后执行之后的语句
PushObjToPoor(obj);
}
///
/// 从Pool里面拿出子对象或者生成新对象
///
public void GetObjFromPoor()
{
//如果缓存池不空,那么就从中拿出物体
if (Pool.childCount > 0)
{
//利用循环按照所需要的数量12将池子中的物体拿出来,并且设置为可见,父节点设置为bulletFather
//利用协程等待一定时间后,将子弹放回池子。
for (int i = 0; i < numsWeNeed; i++)
{
GameObject obj = FindChild("bullet", Pool).gameObject;
obj.SetActive(true);
obj.transform.parent = bulletFather;
StartCoroutine(Delayed(obj));//开启协程
}
}
//如果缓存池空了,生成新的物体
else
{
InstiateByCircle();
}
}
///
/// 将物体放回池子,设为false,状态置0.
///
///
public void PushObjToPoor(GameObject obj)
{
obj.SetActive(false);
放回池子的东西放在一个父节点下,便于管理
obj.transform.position = Vector3.zero;
obj.transform.parent = Pool.transform;
}
///
/// 通过深度递归查找根节点下的某子物体Transform
///
/// 待查找的物体名称
/// 挂载脚本的物体,查找的起点
/// 查找的结果
Transform FindChild(string objectName, Transform Start)//2 4
{
if (objectName == Start.name) //起点位置即为要找的物体,找到就退出,这是退出条件
return Start;
if (Start.childCount < 1) //起点位置不能没有子物体,不然就退出,这是退出条件
return null;
#region 这才是递归的核心,深度递归
Transform obj = null;
for (int i = 0; i < Start.childCount; i++)
{
Transform t = Start.GetChild(i);
obj = FindChild(objectName, t);
if (obj != null)
{
break;
}
}
return obj;
#endregion
}
}
用射线去检测物体,返回检测信息
Unity默认情况下提供了 3 种射线检测器:
由于学习2D为主,则主要关注Physics2D Raycaster。
Physics2D Raycaster
void Update()
{
//最基础的射线,被检测物体一定要有碰撞体
RaycastHit2D info = Physics2D.Raycast(transform.position, Vector2.right);
Debug.Log("被检测到物体的transform:"+info.transform);
Debug.Log("被检测到物体的质心位置信息:" + info.centroid);
Debug.Log("被检测到物体的碰撞体:" + info.collider);
Debug.Log("从射线原点到撞击点的距离:" + info.distance);
Debug.Log("射线上发生命中的距离的分数:" + info.fraction);
Debug.Log("射线命中的表面的法线矢量:" + info.normal);
Debug.Log("世界空间中射线命中碰撞体表面的点:" + info.point);
Debug.Log("附加到命中的对象的 Rigidbody2D:" + info.rigidbody);
//假如我要检测在Layer【14: "Enemy"】发生的碰撞
//也就是说:
//int LayerID = 14
//string LayerName = "Enemy"
//使用Layer ID 检测在Layer14层发生的碰撞
info = Physics2D.Raycast(transform.position, Vector2.right, default, 1 << 14);
//使用Layer ID 检测在Layer12、14层发生的碰撞 (1 << 12) | (1 << 14) 表示包含12、14层 (不包含其余层)
info = Physics2D.Raycast(transform.position, Vector2.right, default, (1 << 12) | (1 << 14));
//使用Layer ID 检测不在Layer14层发生的碰撞 ~(1<<14) 表示不包含14层(包含其他所有层)
info = Physics2D.Raycast(transform.position, Vector2.right, default, ~(1 << 14));
}
异步加载不会引起代码阻塞;
协程:协程不是多线程,是假的“多线程”;
应用场景:1.WWW加载网络数据;2.异步加载资源、场景;
yield关键字是最重要的,直译是屈服、让步于,就是等待其他内容的执行,其他内容执行完了再回到这个协程执行剩下的语句。
下面看八道练习题帮助理解协程执行逻辑。
(1条消息) 八道练习题教你轻松学会运用Unity中的协程用法_听雨眠丨的博客-CSDN博客
1.开始协程:我们通过StartCoroutine()函数来调用协程函数。
调用协程的方法有两种,分别是StartCoroutine(/这里直接调用方法,添加参数/),另一种是StartCoroutine(/这里填写”字符串的方法名字”,方法参数/)。
第一种方法的优势在于可以调用多个参数的方法,后一种方法只能调用不含参数或只包含一个参数的协程方法。但是第一种方法不能通过StopCoroutine(/这里填写”字符串的方法名”/)来结束协程,只能通过StopAllCoroutines来结束。后一种则可以通过StopCoroutine来结束对正在执行的协程的调用。
2.停止协程
在一个协程开始后,同样会对应一个结束协程的方法StopCoroutine与StopAllCoroutines两种方式,但是需要注意的是,两者的使用需要遵循一定的规则,在介绍规则之前,同样介绍一下关于StopCoroutine重载:
StopCoroutine(string methodName):通过方法名(字符串)来进行
StopCoroutine(IEnumerator routine):通过方法形式来调用
StopCoroutine(Coroutine routine):通过指定的协程来关闭
刚刚我们说到他们的使用是有一定的规则的,那么规则是什么呢,答案是前两种结束协程方法的使用上,如果我们是使用StartCoroutine(string methodName)来开启一个协程的,那么结束协程就只能使用StopCoroutine(string methodName)和StopCoroutine(Coroutine routine)来结束协程。
3.暂停协程:使用yield关键字
yield return null; // 下一帧再执行后续代码
yield return 0; //下一帧再执行后续代码
yield return 6;//(任意数字) 下一帧再执行后续代码
yield break; //直接结束该协程的后续操作
yield return asyncOperation;//等异步操作结束后再执行后续代码
yield return StartCoroution(/*某个协程*/);//等待某个协程执行完毕后再执行后续代码
yield return WWW();//等待WWW操作完成后再执行后续代码
yield return new WaitForEndOfFrame();//等待帧结束,等待直到所有的摄像机和GUI被渲染完成后,在该帧显示在屏幕之前执行
yield return new WaitForSeconds(0.3f);//等待0.3秒,一段指定的时间延迟之后继续执行,在所有的Update函数完成调用的那一帧之后(这里的时间会受到Time.timeScale的影响);
yield return new WaitForSecondsRealtime(0.3f);//等待0.3秒,一段指定的时间延迟之后继续执行,在所有的Update函数完成调用的那一帧之后(这里的时间不受到Time.timeScale的影响);
yield return WaitForFixedUpdate();//等待下一次FixedUpdate开始时再执行后续代码
yield return new WaitUntil()//将协同执行直到 当输入的参数(或者委托)为true的时候....如:yield return new WaitUntil(() => frame >= 10);
yield return new WaitWhile()//将协同执行直到 当输入的参数(或者委托)为false的时候.... 如:yield return new WaitWhile(() => frame < 10);
1.UniTask有哪些优点
2.插件安装步骤
方法一:通过Git url连接安装(推荐)
在PackageManage里面通过链接来安装,注意如果电脑未安装git会报错。
https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask
方法二:下载压缩包插件安装
3.基础操作
参考:UniTask · 开发文档集合
参考自: Unity特殊文件夹以及各平台Application.xxxPath的路径图文详解教程
1. StreamingAssets ―― 二进制流 资源文件夹
默认情况下, Unity 是没有 StreamingAssets 文件夹的,我们需要自行创建
StreamingAssets 与 Resources 区别:
Resources文件夹下的资源打包发布时,会进行一次压缩和加密
StreamingAssets 文件夹下的资源打包发布时,原封不动(不进行任何处理)
所以 StreamingAssets 一般放置数据资源文件,比如 Json /Xml 等数据文件
在PC/MAC中可实现对文件的“增删改查”等操作,但在移动端是一个只读路径。
返回的路径 | Application.streamingAssetsPath |
Mac OS / Windows | Application.dataPath + "/StreamingAssets" |
iOS | Application.dataPath + "/Raw" |
Android | "jar:file://" + Application.dataPath + "!/assets/" |
在 Android
平台上使用压缩.apk
文件,streamingAsset
中的数据文件包含所以需要使用 UnityWebRequest
类访问资产,才可对数据进行正确读取
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
///
/// 测试获取 StreamAssets 下的数据资源,(图片),并显示在UI上
///
public class ChinarWebRequest : MonoBehaviour
{
void Start()
{
StartCoroutine(SendUrl(Application.streamingAssetsPath + "/Chinar.png"));
}
///
/// 发起请求
///
/// 地址
private IEnumerator SendUrl(string url)
{
using (UnityWebRequest uw = UnityWebRequest.Get(url))
{
yield return uw.SendWebRequest();
if (uw.error != null)
{
Debug.Log(uw.error);
}
else
{
if (uw.responseCode == 200) //200表示接受成功
{
Texture2D texture = new Texture2D(400, 350); //创建Texture
texture.LoadImage(uw.downloadHandler.data); //加载Image
Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.one / 2); //得到精灵对象
GameObject.Find("Chinar-Image").GetComponent().sprite = sprite; //赋值到UI上
}
}
}
}
}
2.Persistent ―― 持久数据 文件夹
常用于存储一些可持久化的用户数据文件
此目录只对应一个应用程序,只要应用不被卸载 / 或是公司名且应用名称不做更改 / 此目录中的文件不会进行任何更改!
只能在应用程序运行时,才能读写操作,建议将获得的文件,或是用户的基本信息,等等数据文件,保存到此目录中。例如,将 AssetBundle
包中读取的数据,写入该目录中。
3.总结
Application.dataPath |
此属性用于返回程序的数据文件所在文件夹的路径 | |
Application.streamingAssetsPath |
|
|
Application.persistentDataPath |
此属性用于返回一个持久化数据存储目录的路径,可以在此路径下存储一些持久化的数据文件 | |
pplication.temporaryCachePath |
|
各路径在几种平台下的目录:
Android | Windows | |
Application.dataPath |
/data/app/xxx.xxx.xxx.apk | /Assets |
Application.streamingAssetsPath |
jar:file:///data/app/xxx.xxx.xxx.apk/!/assets | /Assets/StreamingAssets |
Application.persistentDataPath |
/data/data/xxx.xxx.xxx/files | C:/Users/xxxx/AppData/LocalLow/CompanyName/ProductName |
Application.temporaryCachePath |
/data/data/xxx.xxx.xxx/cache | C:/Users/xxxx/AppData/Local/Temp/CompanyName/ProductName |