当前位置: 首页 > news >正文

Unity笔记(七)——四元数、延迟函数、协同程序

写在前面:

写本系列(自用)的目的是回顾已经学过的知识、记录新学习的知识或是记录心得理解,方便自己以后快速复习,减少遗忘。主要是C#代码部分。

六、四元数

欧拉角具有旋转约定,也就是说,无论你调整角度的顺序是什么,最终物体旋转的顺序都会是约定的旋转序列。最常见用约定是Y-X-Z约定,也就是物体按Y-X-Z的方向进行旋转。

无论你如何更改角度,系统都会重新从初始状态按顺序Y-X-Z进行调整,这和万向锁相似。也就是说,先转动的轴会带动后转动的轴转动,但后转动的轴不能影响先转动的轴。欧拉角会带来万向节死锁的问题,导致在特定情况下丢失一个自由度。

此外,同样的角度能使用不同的欧拉角角度来描述,因此,引入四元数来解决欧拉角的这两个问题。

1、四元数

一个四元数包含一个标量和一个3D向量

[w, v],w为标量,v为3D向量,也就是[w, (x, y, z)],对于给定的任意一个四元数,表示3D空间中的一个旋转量。

(1)四元数的公式

假设绕着n轴旋转β度,n轴为(x,y,z),那么可以构成四元数为:

四元数Q = [cos(β/2),sin(β/2)n] = [cos(β/2),sin(β/2)x,sin(β/2)y,sin(β/2)z]

四元数Q表示绕着n轴,旋转β度的旋转量。

2、Unity中的四元数

可以有两种初始化方法:

1、Quaternion q = new Quaternion(sin(β/2)x,sin(β/2)y,sin(β/2)z, cos(β/2));

2、 Quaternion q2 = Quaternion.AngleAxis(旋转度数, 旋转轴);

一般使用第二种初始化方法:

void Start()
{//绕着x轴转60度Quaternion q = new Quaternion(Mathf.Sin(Mathf.Deg2Rad), 0, 0, Mathf.Cos(30*Mathf.Deg2Rad));GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);obj.transform.rotation = q;Quaternion q2 = Quaternion.AngleAxis(60, Vector3.right);
}

(1)四元数和欧拉角转换

欧拉角转为四元数使用的API是:Quaternion.Euler(),括号内传入需要转化的欧拉角,比如Quaternion.Euler(60, 0, 0),转化出来的四元数是绕x轴转60度的四元数。

四元数转欧拉角使用的API是:.eulerAngles

void Start()
{//欧拉角转四元数,这个就是绕着x轴转60度Quaternion q3 = Quaternion.Euler(60, 0, 0);//四元数转欧拉角print(q2.eulerAngles);
}

最后,需要注意的是,四元数的角度区间是-180~180,因此四元数表示的角度是唯一的。

3、四元数的常用方法

(1)单位四元数

单位四元数表示没有旋转量(角位移),当角度为0或者360度时,对于给定轴都会得到单位四元数。

例如[1,(0, 0, 0)]和[-1, (0, 0, 0)]都是单位四元数,表示没有旋转量。原因是,由四元数Q = [cos(β/2),sin(β/2)n] = [cos(β/2),sin(β/2)x,sin(β/2)y,sin(β/2)z],角度为0时,cos0 = 1, sin0均为0;或是cos180度等于-1,sin180都等于0。

在Unity中提供了表示单位四元数的API:Quaternion.identity

void Start()
{print(Quaternion.identity);testObj.rotation = Quaternion.identity;
}

单位四元数可用于初始化。初始化在0,0,0点,方向为0, 0, 0的物体:

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

(2)四元数插值运算

四元数中的Lerp和Slerp只有一些细微差别,由于算法不同,Slerp的效果会好一些,Lerp的效果相比Slerp更快,但当旋转范围较大时,效果不太好,所以建议使用Slerp进行插值运算。与之前的插值运算区别不大,只不过这里插值的是角度,同样演示了两种方法:

public Transform target;
public Transform A;
public Transform B;private Quaternion start;
private float time = 0;void Start()
{start = B.rotation;
}void Update()
{A.rotation = Quaternion.Slerp(A.rotation, target.rotation, Time.deltaTime);time+= Time.deltaTime;B.rotation = Quaternion.Slerp(start,target.rotation, time);
}

(3)向量指向转四元数

向量指向转四元数的API是:Quaternion.LookRotation(向量)

这个方法可以将传入的面朝向量转换为对应四元数角度信息。例如,物体A想要看向物体B,就可以将向量AB传入。这个方法会返回转为AB方向的四元数,这时候将物体A的角度信息改为该四元数即可实现A看向B。

public Transform LookA;
public Transform LookB;void Start()
{Quaternion q = Quaternion.LookRotation(LookB.position - LookA.position);LookA.rotation = q;
}

4、四元数计算

(1)四元数相乘

两个四元数相乘得到一个新的四元数,代表两个旋转量的叠加。旋转相对的坐标系是物体自身的坐标系。

 void Start(){Quaternion q = Quaternion.AngleAxis(20, Vector3.up);this.transform.rotation *= q;this.transform.rotation *= q;}

(2)四元数乘向量

四元数乘向量返回一个新向量,相当于将这个向量旋转了对应四元数的旋转量。

 void Start(){Vector3 v = Vector3.forward;print(v);//四元数×向量顺序不能改变v = Quaternion.AngleAxis(45, Vector3.up) * v;print(v);}

七、延迟函数

延迟函数就是会延时执行的函数,我们可以自己设定延时要执行的函数和具体延时时间。

1、延迟函数

延迟函数使用的API是:Invoke(),里面传入两个参数,第一个参数是需要延迟执行的函数的名字的字符串,第二个参数是延迟几秒钟执行。

void Start()
{Invoke("DelayDoSomething", 5);
}private void DelayDoSomething()
{print("执行");
}

延迟函数无法调用有参数的函数,需要进行包裹。函数名必须是该脚本上声明的函数。只能和有参函数一样进行包裹。例如下例,只有这样才能执行有参数的延迟函数。

void Start()
{Invoke("DelayDoSomething", 5);}private void DelayDoSomething()
{print("执行");TestFunc(2);
}private void TestFunc(int i)
{print("执行" + i);
}

2、延迟重复执行函数

延迟函数可以一直重复执行,使用的API是:InvokeRepeating(),括号内传入三个参数,第一个参数是重复执行的函数名的字符串,第二个参数是第一次执行延迟多少秒,第三个参数是之后执行延迟多少秒。如下例函数,会在第5秒时执行一次,之后每隔1秒执行一次。

void Start()
{InvokeRepeating("DelayRe", 5, 1);
}private void DelayRe()
{print("重复执行");
}

3、取消延迟函数

(1)取消所有

使用CancelInvoke()后会取消所有延迟函数。

void Start()
{//取消所有CancelInvoke();
}

(2)指定函数名取消

也可以通过传入函数名的字符串指定取消延迟函数。只要取消了指定延迟,不管之前函数开启了多少次,延迟执行都会统一取消。此外,如果没有该延迟函数也不会报错。

void Start()
{CancelInvoke("DelayRe");
}

4、判断是否有延迟函数

使用IsInvoking()可以检查是否存在延迟函数,同样的,也可以传入参数判断是否有指定延迟函数。

void Start()
{if(IsInvoking()){print("存在延迟函数");}if(IsInvoking("DelayDoSomething")){print("存在延迟函数DelayDoSomething");}
}

5、延迟函数受对象失活的影响

脚本挂载的对象失活或是脚本失活,延迟函数只要开启了,就依然可以执行。脚本或者挂载的对象删除了或是脚本销毁了,延迟函数就会失效。

八、协同程序

1、Unity的多线程

Unity是支持多线程的,需要引用命名空间using System.Threading。创建一个新线程使用的是:Thread t = new Thread(),需要注意的是,括号内需要传入一个委托函数,该函数会在新线程中执行。传入函数后,t.Start()即可开启线程。

线程与编辑器的生命周期一样长,要记得手动关闭多线程。可以在对象销毁时运行的生命周期函数OnDestroy()中关闭线程。线程的关闭使用t.Abort()即可。

Thread t;void Start()
{t = new Thread(Test);t.Start();
}private void Test()
{while (true){Thread.Sleep(1000);print("新开线程");}
}private void OnDestroy()
{t.Abort();t = null;
}

此外,需要注意的是,新开线程无法访问Unity相关对象的内容,也就是无法控制场景中的对象,这些对象的逻辑只能在主线程中控制。

2、协同程序

(1)概念

协同程序简称协程,它是假的多线程。它将代码分时执行,不卡主线程,也就是它把可能会让主线程卡顿的耗时的逻辑分时分布执行。主要使用的场景有:异步加载文件,异步下载文件,场景异步加载,批量创建时防止卡顿。

(2)协程和线程的区别

新开一个线程是独立的一个管道,和主线程并行执行;新开协程是在原线程之上开启,进行逻辑分时分步执行。

3、协程的使用

(1)声明

协同程序函数的返回值必须是 IEnumerator或者继承了它的类型。协程函数当中必须使用yield return进行返回。例如:

IEnumerator MyCoroutine(int i, string str)
{print(i);yield return new WaitForSeconds(5f);print(str);
}

该协程函数中 yield return new WaitForSeconds(5f);指的是第二部分延迟5s执行。也就是说在print(i)过后会延迟5s再执行print(str)。

此外,yield return可以写多个,可以把代码分成几个部分来执行。

IEnumerator MyCoroutine(int i, string str)
{print(i);yield return new WaitForSeconds(5f);print(str);yield return new WaitForSeconds(1f);print("end");
}

(2)开启协程

协程函数不能直接调用。有两种调用方式,第一种先创建一个 IEnumerator变量接收返回值,再将返回值传入函数StartCoroutine(返回值)。第二种,直接使用StartCoroutine(函数名)。

void Start()
{IEnumerator ie = MyCoroutine(1, "123");StartCoroutine(ie);StartCoroutine(MyCoroutine(1, "123"));
}

(3)关闭协程

有两种关闭协程的方式:第一种方式为关闭所有协程StopAllCoroutines(),它会关闭你开启的所有协程。第二种方式为关闭指定协程,可以创建Coroutine c1来存储协程返回的变量,而后使用StopCoroutine(c1);关闭指定协程。

 void Start(){Coroutine c1 = StartCoroutine(MyCoroutine(1, "123"));Coroutine c2 = StartCoroutine(MyCoroutine(1, "123"));Coroutine c3 = StartCoroutine(MyCoroutine(1, "123"));StopCoroutine(c1);StopAllCoroutines();}

4、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 newWaitForEndOfFrame()。在LateUpdate之后的渲染相关处理完毕之后执行。

5、一些特殊类型的对象,比如异步加载相关函数返回的对象,后续介绍。一般在Update和LateUpdate之间执行

6、跳出协程 yield break;

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

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

6、协同程序原理

(1)协程的本质

协程可以分成两部分,协程函数本体和协程函数调度器。协程函数本体是一个能够中间暂停返回的函数,协程调度器是Unity内部实现的,会在对应时机 帮助我们继续执行协程函数。

(2)协程函数本体

创建一个迭代器IEnumerator ie来接受协程函数,利用迭代器的ie.MoveNext()方法就可以依次调度到下一个yield return为止的函数。利用ie.Current可以获得yield return中传入的值。如下:

public class TestClass
{public int time;public TestClass(int time){this.time = time;  }
}IEnumerator Test()
{print("第一次执行");yield return 1;print("第二次执行");yield return 2;print("第三次执行");yield return "123";print("第四次执行");yield return new TestClass(10);
}void Start()
{IEnumerator ie = Test();ie.MoveNext();print(ie.Current);ie.MoveNext();print(ie.Current);ie.MoveNext();print(ie.Current);ie.MoveNext();TestClass ts = ie.Current as TestClass;print(ts.time);
}

可见,协程的本质就是一个迭代器,传入Unity的协程调度器后,Unity自动执行协程。

http://www.dtcms.com/a/351735.html

相关文章:

  • 【Linux】Keepalived + Nginx高可用方案
  • [pilot智驾系统] 驾驶员监控守护进程(dmonitoringd)
  • 从代码学习深度强化学习 - 多智能体强化学习 IPPO PyTorch版
  • pytorch_grad_cam 库学习笔记——基类ActivationsAndGradient
  • vue2 和 vue3 生命周期的区别
  • 【Android】不同系统API版本_如何进行兼容性配置
  • 2014-2024高教社杯全国大学生数学建模竞赛赛题汇总预览分析
  • VMDK 文件
  • 软考-系统架构设计师 计算机系统基础知识详细讲解二
  • springcloud篇5-微服务保护(Sentinel)
  • Spring Boot mybatis-plus 多数据源配置
  • 【CVE-2025-5419】(内附EXP) Google Chrome 越界读写漏洞【内附EXP】
  • Kafka面试精讲 Day 1:Kafka核心概念与分布式架构
  • Elasticsearch中的协调节点
  • 详解kafka基础(一)
  • JavaScript常用的算法详解
  • Cherry-pick冲突与Git回滚
  • Oracle跟踪及分析方法
  • 力扣100+补充大完结
  • MySql 事务 锁
  • 推荐系统学习笔记(十四)-粗排三塔模型
  • 庖丁解牛:深入解析Oracle SQL语言的四大分类——DML、DDL、DCL、TCL
  • KubeBlocks for Oracle 容器化之路
  • 高校党建系统设计与实现(代码+数据库+LW)
  • 从零开始的 Docker 之旅
  • HIVE的高频面试UDTF函数
  • 【软考论文】论面向对象建模方法(动态、静态)
  • 无人机倾斜摄影农田航线规划
  • HTML应用指南:利用GET请求获取中国银行人民币存款利率数据
  • SciPy科学计算与应用:SciPy线性代数模块入门-矩阵运算与应用