四元数、协程、Resources同步异步加载资源、异步场景切换、XML周学习总结

目录

什么是万向节死锁

四元数概念

初始化四元数的两种方式->

不常用初始化->

轴角对是什么

常用的初始化四元数→

四元数解决了欧拉角的两大缺点——>

四元数中的常用方法

1.单位四元数

2.Quaternion中的插值运算

3.LookRotation

四元数的计算

1.四元数相乘

2.四元数乘以向量

延迟函数Invoke

对象销毁或失活对脚本的影响——>

协程的使用

yield return 不同内容的含义

协程受对象和组件失活销毁的影响

Resources资源同步加载

1.Resources资源动态加载的作用

2.常用的一些资源类型

3.资源同步加载的普通方法→

4.加载指定同名类型文件的泛型方法

Resources异步资源加载

方法1

方法2

如何对异步加载的资源进行使用

1. 在OnGUI方法中使用

2. 通过SpriteRenderer组件使用

3. 在3D对象上使用

4. 在UI Image组件中使用

场景异步切换

方法1

方法2

如何通过C#读取XML文件

XmlDocument

有两种API供使用

1. 直接根据xml字符串内容 来加载xml文件

2.是通过xml文件的路径去进行加载

读取元素和属性信息

单个节点信息获取

那属性怎么获取呢?

获取 一个节点下的同名节点的方法:XmlNodeList


这周跟着代码随想录在力扣上写了些贪心题目

什么是万向节死锁

万向节死锁是指在使用三个欧拉角表示旋转时,如果其中一个旋转使得另外两个旋转轴对齐,那么就会失去一个自由度,导致无法表示出原来可以通过三个独立旋转实现的某些旋转状态。简而言之,就是在特定的旋转状态下,我们无法使用欧拉角独立控制物体的所有三个旋转方向,好像物体被锁在了某个旋转平面

四元数概念

公式: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));

//创建一个立方体
    GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
    obj.transform.rotation = q;

轴角对是什么

是一种表示3D空间中物体旋转的方式。这种表示方法通过一个旋转轴和一个旋转角度来定义旋转。轴角对的概念基于旋转轴定理,即任何3D旋转都可以通过绕某一固定轴的单一旋转来实现。

不仅仅局限于xyz轴

常用的初始化四元数→

unity中提供的轴角对 初始化 四元数的方法
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.万向节死锁 通过四元数旋转对象可以避免万向节死锁

四元数中的常用方法

1.单位四元数

旋转角度为0的时候,且标量w为1时的四元数我们称之为单位四元数Quaternion.identity

单位四元数常常用来初始化一个物体的旋转角度,如→在实例化物体的时候

Instantiate(testObj, Vector3.zero, Quaternion.identity);

2.Quaternion中的插值运算

在四元数中Slerp比lerp效果更好

//无限接近 先快后慢 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);

3.LookRotation

Quaternion中的这个方法传入一个向量,如果把这个四元数赋值给一个对象的rotation,他就会看向向量所指的方向,类似于transform中的LookAt

返回值是一个四元数

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

A物体就会看向B物体

所以我们可以结合LookRotation和Slerp实现摄像机在物体移动后缓慢转动跟随着物体移动,而不是瞬间锁死

即把LookRotation的返回值作为Slerp的终点rotation

例子如下→

//用目标的位置 减去 摄像机的位置 得到新的面朝向向量 targetQ = Quaternion.LookRotation(target.position - this.transform.position);

//先快后慢 //this.transform.rotation = Quaternion.Slerp(this.transform.rotation, targetQ, Time.deltaTime* roundSpeed);

//匀速旋转 if( targetQ != Quaternion.LookRotation(target.position - this.transform.position)) {

targetQ = Quaternion.LookRotation(target.position - this.transform.position);

roundTime = 0;

startQ = this.transform.rotation;

}

roundTime += Time.deltaTime;

this.transform.rotation = Quaternion.Slerp(startQ, targetQ, roundTime * roundSpeed);

四元数的计算

1.四元数相乘

Quaternion q = Quaternion.AngleAxis(20, Vector3.up);

this.transform.rotation *= q;

//第一次相乘以后,这里的Vector3.up是相对自身坐标系的,所以绕自身的y轴旋转了20°

this.transform.rotation *= q;

//再次旋转20°

2.四元数乘以向量

Vector3 v = Vector3.forward; v = Quaternion.AngleAxis(45, Vector3.up) * v;

v向量绕自身y轴旋转45° v = Quaternion.AngleAxis(45, Vector3.up) * v;’

四元数乘以向量的返回值是一个旋转向量,特别需要注意的是,四元数乘以向量,四元数必须放前面,不然的话就会报错

也就是说当我们得到了一个物体的面朝向向量的时候,我们就可以以该向量为起点,创建不同的实例化对象并为其设置不同方向的初值,类似于一些拐弯的射击啥的

延迟函数Invoke

Monobehavior中已经为我们实现好的延迟函数——>

Invoke 、InvokeRepeating

Invoke("函数名",延迟时间);

延迟函数所调用的函数必须是该脚本上声明的函数

调用延迟函数没办法传入参数,只能包裹一层函数再去调用有参数的函数

InvokeRepeating("函数名",第一次执行的延迟时间 , 往后每一次执行的延迟时间);

取消该脚本上的所有延迟函数→CancelInvoke();

根据名字取消延迟函数——>CancelInvoke("函数名");

判断该脚本上是否有延迟函数(不常用)→IsInvoking()

对象销毁或失活对脚本的影响——>

脚本所依附的对象失活或是脚本本身失活——>延迟函数仍然可以继续执行

但脚本组件被移除或者是对象被销毁——>无法继续执行延迟函数

所以要想在一个对象被失活的时候取消延迟函数可以选择在OnDisable中调用CancelInvoke();

因为OnDisable是在对象失活的前一帧被调用的

同理,可以在对象被激活的时候在OnEnable()中启用延迟函数

协程的使用

继承MonoBehavior的类 都可以开启 协程函数
第一步:申明协程函数
  协程函数2个关键点
 1. 返回值为IEnumerator类型及其子类
  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之间执行 (在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;

协程受对象和组件失活销毁的影响

协程开启后 组件和物体销毁,协程不执行 物体失活协程不执行,组件失活协程执行

Resources资源同步加载

1.Resources资源动态加载的作用

1.通过代码动态加载Resources文件夹下指定路径资源
2.避免繁琐的拖曳操作

2.常用的一些资源类型

1.预设体对象——GameObject
2.音效文件——AudioClip
3.文本文件——TextAsset
4.图片文件——Texture
5.其它类型——需要什么用什么类型

注意:
预设体对象加载需要实例化
其它资源加载一般直接用

3.资源同步加载的普通方法→

//在一个工程当中 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"); //在Resources文件夹中创建了一个Music文件夹
//第二步:使用数据 我们不需要实例化 音效切片 我们只需要把数据 赋值到正确的脚本上即可
//public AudioSource audioS;  //只要创建一个空物体,上面挂一个AudioSource组件,然后拖过来就行
audioS.clip = obj3 as AudioClip;
audioS.Play();

//3.文本资源
//文本资源支持的格式
//.txt
//.xml
//.bytes
//.json
//.html
//.csv
//.....
TextAsset ta = Resources.Load("Txt/Test") as TextAsset;
//既然我们已经知道了我们要动态加载什么样的文件,所以可以直接as
//文本内容
print(ta.text);
//字节数据组
//print(ta.bytes);

//4.图片
//tex = Resources.Load("Tex/TestJPG") as Texture;

//5.其它类型 需要什么类型 就用什么类型就行

//6.问题:资源同名怎么办
//Resources.Load加载同名资源时 无法准确加载出你想要的内容

//可以使用另外的API(重载)
//6-1加载指定类型的资源
//tex = Resources.Load("Tex/TestJPG", ) astypeof(Texture) Texture;

ta = Resources.Load("Tex/TestJPG", typeof(TextAsset)) as TextAsset;
//print(ta.text);
但对于分辨同名资源一般还是用泛型更加的方便
//6-2加载指定名字的所有资源
Object[] objs = Resources.LoadAll("Tex/TestJPG");
foreach (Object item in objs)
{
if (item is Texture)
{

}
else if(item is TextAsset)
{

}

}

4.加载指定同名类型文件的泛型方法

TextAsset ta2 = Resources.Load("Tex/TestJPG"); print(ta2.text);

tex = Resources.Load("Tex/TestJPG");

Resources异步资源加载

方法1

    1.通过异步加载中的完成事件监听 使用加载的资源
    这句代码 你可以理解 Unity 在内部 就会去开一个线程进行资源下载
    ResourceRequest rq = Resources.LoadAsync<Texture>("Tex/TestJPG");
    马上进行一个 资源下载结束 的一个事件函数监听
    rq.completed += LoadOver; //事件函数必须有一个AsyncOperation类型的参数

  private void LoadOver( AsyncOperation rq)
{
print("加载结束");
//asset 是资源对象 加载完毕过后 就能够得到它,该属性(asset)是在ResourceRequest中的
tex = (rq as ResourceRequest).asset as Texture;
}

方法2

通过协程方式

void Start(){

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)
{
//打印当前的 加载进度
//该进度 不会特别准确 过渡也不是特别明显
print(rq.progress);
yield return null;
}
tex = rq.asset as Texture;

//yield return null;
第二部分
//yield return new WaitForSeconds(2f);
第三部分
///



如何对异步加载的资源进行使用

1. 在OnGUI方法中使用

确实,你可以在OnGUI方法中使用Resources.Load来动态加载图片,并通过GUI函数(如GUI.DrawTexture)来绘制这些图片。这种方式主要用于绘制简单的UI元素或进行快速的原型设计。

void OnGUI() { Texture2D texture = Resources.Load("path/to/your/image"); GUI.DrawTexture(new Rect(10, 10, 100, 100), texture); }

2. 通过SpriteRenderer组件使用

如果你是在2D游戏中,可以将加载的图片作为Sprite使用,并通过更改GameObjectSpriteRenderer组件的sprite属性来显示这个图片。

void Start() { Sprite sprite = Resources.Load("path/to/your/sprite");

GetComponent().sprite = sprite; }

3. 在3D对象上使用

对于3D游戏,可以将图片作为纹理动态加载并应用到3D模型的材质上。这通过修改GameObjectMaterialTexture属性实现。

void Start() { Texture2D texture = Resources.Load("path/to/your/texture"); GetComponent().material.mainTexture = texture; }

4. 在UI Image组件中使用

如果你使用的是Unity的UGUI系统,可以将动态加载的图片设置给Image组件的Sprite属性。首先需要将加载的图片转换为Sprite,然后赋值给UI组件。

void Start() { Texture2D texture = Resources.Load("path/to/your/image");

Sprite sprite = Sprite.Create(texture, new Rect(0.0f, 0.0f, texture.width, texture.height), new Vector2(0.5f, 0.5f), 100.0f);

GetComponent().sprite = sprite; }

场景异步切换

方法1

    1.通过事件回调函数 异步加载
    AsyncOperation ao = SceneManager.LoadSceneAsync("Lesson20Test");
    当场景异步加载结束后 就会自动调用该事件函数 我们如果希望在加载结束后 做一些事情 那么久可以在该函数中
    写处理逻辑
    ao.completed += (a) =>
    {
        print("加载结束");
    };

    ao.completed += LoadOver;

private void LoadOver(AsyncOperation ao)
{
print("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%
//动态加载 场景模型
//这时 就认为 加载结束了 进度条顶满
//隐藏进度条

}

如何通过C#读取XML文件

XmlDocument

首先需要通过这个类实例化一个对象出来→

XmlDocument xml = new XmlDocument();

然后通过API先加载xml文件

有两种API供使用

1. 直接根据xml字符串内容 来加载xml文件
存放在Resorces文件夹下的xml文件加载处理

TextAsset asset = Resources.Load("TestXml");

//Resources资源加载文本文件,类型是TextAsset

xml.LoadXml(asset.text);

2.是通过xml文件的路径去进行加载

xml.Load(Application.streamingAssetsPath + "/TestXml.xml");

//为什么是StreamingAssetsPath?

因为当Unity构建项目时,位于Resources文件夹内的资源会被编译和打包进游戏包中,并且资源的原始文件路径会丢失,转而由Unity的资源管理系统管理

Unity会将这个文件夹(StreamingAsset)中的内容原封不动地复制到最终游戏包的相应位置。这意味着这些文件在游戏运行时保持不变,可以通过文件系统的路径来访问

读取元素和属性信息

节点信息类
XmlNode 单个节点信息类(不同名的节点获取)
节点列表信息
XmlNodeList 多个节点信息类(一般是有多个同名节点,所以需要一个表来存取多个节点)

无论是获取单个节点还是获取多个节点,都必须先获取跟节点,假设根节点的名字为Root

则获取xml当中的根节点→ XmlNode root = xml.SelectSingleNode("Root");

单个节点信息获取

再通过根节点 去获取下面的子节点

XmlNode nodeName = root.SelectSingleNode("name");

如果想要获取节点包裹的元素信息 直接 .InnerText

print(nodeName.InnerText);

XmlNode nodeAge = root.SelectSingleNode("age"); print(nodeAge.InnerText);

那属性怎么获取呢?

比方说xml文件中有这样一个节点:

XmlNode nodeItem = root.SelectSingleNode("Item");

方式1→

print(nodeItem.Attributes["id"].Value); print(nodeItem.Attributes["num"].Value);

方式2→

print(nodeItem.Attributes.GetNamedItem("id").Value); print(nodeItem.Attributes.GetNamedItem("num").Value);

获取 一个节点下的同名节点的方法:XmlNodeList

比方说在xml文件中有这样两个同名节点:

<Friend>

<name>小明</name>

<age>18</age>

</Friend>

<Friend>

<name>小红</name>

<age>17</age>

</Friend>

XmlNodeList friendList = root.SelectNodes("Friend");

通过遍历的方式去获得每个节点—>

遍历方式一:迭代器遍历
foreach (XmlNode item in friendList)
{
    print(item.SelectSingleNode("name").InnerText);
    print(item.SelectSingleNode("age").InnerText);
}

遍历方式二:通过for循环遍历
通过XmlNodeList中的 成员变量 Count可以得到 节点数量
for (int i = 0; i < friendList.Count; i++)
{
    print(friendList[i].SelectSingleNode("name").InnerText);
    print(friendList[i].SelectSingleNode("age").InnerText);
}

总结:

1.读取XML文件
XmlDocument xml = new XmlDocument();
读取文本方式1 - xml.LoadXml(传入xml文本字符串)
读取文本方式2 - xml.Load(传入路径)

2.读取元素和属性
获取单个节点: XmlNode node = xml.SelectSingleNode(节点名)
获取多个节点: XmlNodeList nodeList = xml.SelectNodes(节点名)

获取节点元素内容:node.InnerText
获取节点元素属性:
1.item.Attributes["属性名"].Value
2.item.Attributes.GetNamedItem("属性名").Value

通过迭代器遍历或者循环遍历XmlNodeList对象 可以获取到各单个元素节点
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值