[关闭]
@SovietPower 2022-04-21T11:15:25.000000Z 字数 11655 阅读 978

Unity 笔记

学习笔记



手册:https://docs.unity.cn/cn/2020.3/Manual/UnityManual.html
https://docs.unity.cn/cn/2020.3/ScriptReference/index.html

教程

使用前:
修改Cache位置https://www.bilibili.com/read/cv14045564

入门:
Flappy Bird:https://www.bilibili.com/video/BV1Qb411n7xz?p=15

2D骨骼:
https://www.bilibili.com/video/BV1E7411c7td
https://www.bilibili.com/video/BV1F4411M77W?p=1

教学项目:
https://www.bilibili.com/video/BV1SB4y1w7VY?p=1
https://www.bilibili.com/video/BV1FA411j7ug
https://space.bilibili.com/335835274/channel/seriesdetail?sid=289371&ctype=0

tools:
https://github.com/RyanNielson/awesome-unity#utilities
https://github.com/kellygravelyn/UnityToolbag

粒子轨迹:
https://www.bilibili.com/video/BV1Af4y1g7AK
https://www.bilibili.com/video/BV1HZ4y1P731

游戏操作性:
https://www.bilibili.com/video/BV1ky4y1t7NG

游戏特效:
https://space.bilibili.com/67936256/channel/seriesdetail?sid=535617

人物足迹:
https://indienova.com/u/onyx/blogread/25098

Nitrome Must Die:
https://tieba.baidu.com/p/5299393100?pn=4
https://nitrome.fandom.com/wiki/Nitrome_Must_Die

笔记

生命周期

https://blog.csdn.net/angry_youth/article/details/117469722
https://blog.csdn.net/qq_28849871/article/details/78137261
MonoBehaviour 运行周期:Awake -> OnEnable -> Reset -> Start -> FixedUpdate

当一个物体实例化后,就会执行Awake。
但是,只有在一个物体被启用且执行第一个Update前,才会调用Start,一般比OnEnable要晚。
所以OnEnable中要用的东西,需要在Awake初始化,而不是Start(尤其是对象池)。

注意,脚本间Awake执行的顺序是不确定的。
Edit > Project Settings > Script Execution Order调整必要的运行顺序。执行时间在default之前的(负数)会在所有其它脚本之前执行。

Coroutine

https://www.cnblogs.com/yizhen/p/10684622.html
当一个协程执行完后,之前赋值为该协程的变量也不会变为null。

刚体

https://docs.unity.cn/cn/2020.3/Manual/class-Rigidbody2D.html
https://blog.csdn.net/yjy99yjy999/article/details/112839298

碰撞

trigger的触发条件
两个游戏对象中至少一个拥有动态/运动学刚体组件
两个游戏对象都拥有碰撞体组件
其中一个游戏对象的碰撞体组件被标记为触发器
两个游戏对象所属的层在层碰撞矩阵中设置为可互相产生碰撞

collision(物理碰撞)的触发条件
两个游戏对至少一个拥有动态刚体组件
两个游戏对象都拥有碰撞体组件,都不是触发器
两个游戏对象所属的层在层碰撞矩阵中设置为可互相产生碰撞

子弹

子弹应使用刚体、非trigger,以便可以拥有物理效果。
这样敌人的hitbox应使用trigger,否则子弹与敌人的hitbox接触时,在处理前就会将敌人向后退。敌人的实际碰撞(地面、障碍)使用另一个非trigger碰撞体,不与子弹可碰撞(如果可碰撞,则体积需比hitbox稍小)。
但是,想要获得碰撞点的信息(位置、法线),必须使用非trigger的collision。触发碰撞体没有接触点。
所以只能获取离该包围盒上最近的点,作为碰撞点、特效生成的中心。法线等信息无法获取。方法为:Collider2D.bounds.ClosestPoint(transform.position)
令特效偏转角为子弹移动向量*-1,可实现简单的特效方向。

很奇怪的一点是,使用非trigger,即使hitbox是Enemy的子对象,与hitbox触发的Collision2D.gameObject依旧返回了Enemy。可能是因为碰撞体被绑定到了最近的拥有刚体碰撞体的对象上。
所以改用trigger后,应使用Collider2D.GetComponentInParent,因为TryGetComponent没有获取父元素组件的方法。所以为了性能,应先判断是否是Enemy:使用collider.CompareTag("Enemy"),然后再GetComponentInParent。这需要给hitbox添加tag

例:

  1. void OnTriggerEnter2D(Collider2D collider)
  2. {
  3. if (collider.CompareTag("Enemy"))
  4. {
  5. Enemy enemy = collider.GetComponentInParent<Enemy>();
  6. // VFX
  7. var contactPoint = collider.bounds.ClosestPoint(transform.position);
  8. VFXPool.Get(hitVFXName, contactPoint);
  9. HitEnemy(enemy, contactPoint);
  10. gameObject.SetActive(false);
  11. }
  12. }

如果一定要GetContact(),可以在子弹前加射线,使用射线而不是物理接触判断命中。射线接触点也有normal等信息。如:https://www.bilibili.com/video/BV1ia4y1j78A?t=1592.7

Shader

http://www.alanzucconi.com/2015/06/10/a-gentle-introduction-to-shaders-in-unity3d

性能

profiler:https://www.jianshu.com/p/a7cee5e548cf
https://blog.csdn.net/kaitiren/article/details/45071997
https://www.zhihu.com/column/c_1007957610112335872

https://docs.unity3d.com/cn/current/Manual/class-NavMeshObstacle.html
https://www.cnblogs.com/sifenkesi/p/4004215.html
通过NavMeshObstacle解决一群单位被NavMesh卡住:https://blog.csdn.net/qq_37724011/article/details/115184131(要参考下手册,应不使用Carve,或使用Carve Only Stationary)
使用时注意高度要匹配。

NavMesh导航时会忽略Physics系统本身的碰撞,即NavMeshAgent在移动的过程中不会被Collider阻挡,而是会直接走过去(但是OnTriggerEnter等触发功能正常)。
动态碰撞的功能对很多游戏都是一个基本的需求,而根据NavMesh提供的接口,唯一可以实现阻挡功能的只有NavMeshObstacle,但NavMeshObstacle只有Capsule和Box。所以:
(1)导航网格的行走/碰撞区域只能预烘焙;
(2)动态碰撞体只能通过挂载NavMeshObstacle组件来实现;
(3)动态碰撞体的形状只能有Capsule和Box。
所以,除非预定义地图并bake,否则基本不能使用各种形状的Collider来动态生成场景阻挡物。唯一方法是将Capsule和Box作为基本元素来模拟其它形状。

参数:
Carve:是否打开在导航网格挖洞。
Move Threshold:当模式为Carve时,此物体的移动距离超过这个阀值后,更新当前的导航网格(重新挖洞)。
注:
1.在Bake场景的时候,Navigation窗口的Bake页面有一个高度值,场景中的导航网格通常作为一个平面,当NavMeshObstacle 距离小于这个高度时,才会在导航网格上挖洞,否则NavMeshObstacle 还是以普通模式存在的。
2.NavMeshObstacle 在刚创建的时候最好先关闭NavMeshObstacle 这个组件,需要时再打开。在跟NavMeshAgent混用时,不能共用(同时激活)。
3.碰撞还是使用trigger
4.最好不要同时使用RigidBody,有bug,新版本可能改好了,参考链接。
5.在挖洞时,设备掉帧比较明显。善用Move Threshold。

修改带有NavMeshAgent的对象的transform时,NavMeshAgent可能会覆盖并修改对象的transform,或是不能识别外部对其position的改变,导致错误。
方法0:看下是否禁用了NavMeshAgent。
方法1:不要使用transform直接改变位置(如果更改了位置、在执行Warp前线程执行了SetDestination等,会导致错误),用对象的NavmeshAgent脚本的navmeshAgent.Warp(Vector3 position),使其瞬移至指定位置而不经过寻路。
方法2:在设置transform前先禁用agent组件,设置好了再设置回true。

  1. newEnemy.GetComponent<NavMeshAgent>().enabled = false;
  2. newEnemy.transform.position = newPosition;
  3. newEnemy.GetComponent<NavMeshAgent>().enabled = true;

方法3:如果还不行,只能在SetDestination前加判断:if (!enemy.GetComponent<NavMeshAgent>().isOnNavMesh)
此外,也可能是没有烘焙网格,或对象没有在网格上。

Physical Material

https://blog.csdn.net/qq_26399665/article/details/52507001

Particle System

startColor 可能需要通过如下方式修改。
获取 ParticleSystem.MinMaxGradient color = main.startColor 的方式好像不行。

  1. // 方法1,改粒子起始颜色
  2. ParticleSystem.MainModule main = obj.GetComponent<ParticleSystem>().main;
  3. main.startColor = color;
  4. // 方法2,改Renderer颜色
  5. obj.GetComponent<Renderer>().material.color = color;

Audio

Multiple sounds can be played on one AudioSource using PlayOneShot. You can play a clip at a static position in 3D space using AudioSource.PlayClipAtPoint.

UI

一些优化:https://blog.csdn.net/weixin_44302602/article/details/107441058

Canvas
注意Canvas设置UI缩放模式为:随屏幕大小缩放。

Raycast Target
没有交互的UI可以关掉,避免额外的消耗。
但是需要交互的UI,要留着?
如果希望 Unity 将图像视为射线投射的目标,则需启用 Raycast Target。勾选表示鼠标点击到该物体后不再穿透到下面的物体,取消勾选则穿透该物体?

UI组件绑定函数的参数,是用来区分不同UI组件的(预先设置会传递什么值)。
组件的参数,通过访问组件来获取。

坐标

两类坐标系统:https://blog.csdn.net/u010217552/article/details/61916301

世界坐标转Canvas坐标:

技巧

输入
应在Update而不是FixedUpdate中获取玩家操作。

使用Input System
https://gamedevbeginner.com/input-in-unity-made-easy-complete-guide-to-the-new-system/#input_system_get_mouse_position
获取鼠标不能用原来的Input.mousePosition,而是Mouse.current.position.ReadValue()
例子:

  1. using UnityEngine;
  2. using UnityEngine.InputSystem;
  3. public class ReportMousePosition : MonoBehaviour
  4. {
  5. void Update()
  6. {
  7. Vector2 mousePosition = Mouse.current.position.ReadValue();
  8. if(Keyboard.current.anyKey.wasPressedThisFrame)
  9. Debug.Log("A key was pressed");
  10. if (Gamepad.current.aButton.wasPressedThisFrame)
  11. Debug.Log("A button was pressed");
  12. }
  13. }

注意,不同对象实例的事件listener是互不相同的函数!在对象Disable或Destroy时,必须取消订阅该对象所有事件,否则会调用之前的已不可用的对象的函数(不会自动取消订阅或检查是否有效)。
所以应在OnEnable时添加,OnDisable时删除(OnDestroy前会先OnDisable)。

人物跳跃

velocity直接修改物体的速度,无视各种外力
addforce直接模仿物理受力。给物体施加一个力,也会收到其他力的作用

  • ForceMode.Force: 添加一个可持续力到刚体,使用它的质量
  • ForceMode.Acceleration: 添加一个可持续加速度到刚体,忽略它的质量
  • ForceMode.lmpulse: 添加一个瞬间冲击力到刚体,使用它的质量
  • ForceMode.VelocityChange: 添加个瞬间速率变化给刚体,忽略它的质量

取随机数
将设总份数为1,选择的份数为,则在中随机生成浮点数,依次检查,如果,则令x-=p_i继续,否则取
不用小数,用整数也可以。
加权连续随机可以用AnimationCurve,选择一条曲线函数取随机自变量,见 https://blog.csdn.net/qq_43040880/article/details/118307864

Debug

https://blog.csdn.net/qq_38642203/article/details/80151194

计时器

https://blog.csdn.net/xiumang/article/details/88750110

  1. 每帧将时间减去帧间隔时间 Time.deltaTime(适合可能多次频繁触发)。
  2. 在协程中返回需要等待的时间(适合长期执行)。
  3. 延迟调用:使用MonoBehaviour.Invoke()。两个参数,分别是要调用的方法名和延时调用的时间(适合单次触发)。

浮动原点
一直移动的游戏可能会导致非常大的数字,导致超出浮点上限。
一种方法是,不真正移动视角,而是移动其它东西,超出一定距离后,将这些东西再移回来(如FlappyBird)。
浮动原点通常用于3D。当经过一定距离,将所有的内容移回到原点。

对象池

https://www.bilibili.com/video/BV14T4y197bW

存档

使用PlayerPrefs(保存在注册表中,在 BuildSettings-PlayerSettings 中可查看):https://www.bilibili.com/video/BV1nQ4y1z7pZ
使用JSON:https://www.bilibili.com/video/BV1Cb4y1b71G
排行榜:https://www.bilibili.com/video/BV16h41187ez

Unity序列化支持的字段(fields):使用JsonUtility.ToJson(Data, true)时,只有Data中 public的或带有[SerializeField]特性的non-static, non-readonly, non-const字段,和带有[field: SerializeField]特性的属性,会被序列化,其它static, readonly, const和未带有[field: SerializeField]特性的属性,会被忽略。

Unity序列化支持的类:

Unity序列化不支持的类(这些类的存储需先转换成支持的类):

能被Unity序列化的数据都可显示在Inspector处(若添加public[SerializeField]仍不能显示,则不支持)。

技能CD效果
https://blog.csdn.net/weixin_39907713/article/details/111165731

获取子对象

https://blog.csdn.net/kenkao/article/details/78819837

transform。如果是组件用GetComponent
注意transform.Find不是递归查,如果不是直接的子节点,需要指明路径,或用子节点的transform查。字符串可以有空格。

  1. // 1
  2. transform.Find("name").gameObject
  3. // 2
  4. foreach(Transform child in transform)
  5. Debug.Log(child.gameObject.name);
  6. // 3
  7. for (int i = 0; i < transform.childCount; i++)
  8. Debug.Log(transform.GetChild(i).name);

物体随鼠标转动
https://www.bilibili.com/video/BV1ia4y1j78A?t=307.0

受攻击变色
使用shader可实现渐变效果:https://www.bilibili.com/video/BV1qt4y1U7aS?t=1421.0
直接改sprite的颜色也可。

rotation四元数与Vector3转换

  1. 1.四元数转化成欧拉角
  2. Vector3 v3 = transform.rotation.eulerAngles;
  3. 2.四元数转化成方向向量
  4. Vector3 vector3 = (transform.rotation * Vector3.forward).normalized;
  5. 或直接用 transform.forward
  6. 3.欧拉角转换成四元数
  7. Quaternion rotation = Quaternion.Euler(vector3);
  8. 4.欧拉角转换成方向向量
  9. Vector3 v3 = (Quaternion.Euler(vector3) * Vector3.forward).normalized;
  10. 5.将方向向量转换为四元数
  11. Quaternion rotation = Quaternion.LookRotation(vector3);
  12. 6.将方向向量转换为欧拉角
  13. Vector3 v3 = Quaternion.LookRotation(vector3).eulerAngles;

在指定锥形中获取随机方向(用于gun dispersion/disperse)

https://answers.unity.com/questions/467742/how-can-i-create-raycast-bullet-innaccuracy-as-a-c.html

两种方式。不知道哪个更好。
可能第一种更均匀,第二种用单位圆不知道均匀不均匀。

  1. public Vector3 RandomInsideCone(float radius) // 半径越小,越收束
  2. {
  3. //(sqrt(1 - z^2) * cosϕ, sqrt(1 - z^2) * sinϕ, z)
  4. float radradius = radius * Mathf.PI / 360;
  5. float z = Random.Range(Mathf.Cos(radradius), 1);
  6. float t = Random.Range(0, Mathf.PI * 2);
  7. var direction = new Vector3(Mathf.Sqrt(1 - z * z) * Mathf.Cos(t), Mathf.Sqrt(1 - z * z) * Mathf.Sin(t), z);
  8. return direction;
  9. }
  10. public Vector3 RandomInsideCone2(float z) // z越大,半径越小,越收束
  11. {
  12. // Generate a random XY point inside a circle:
  13. Vector3 direction = Random.insideUnitCircle;
  14. direction.z = z; // circle is at Z units
  15. direction = transform.TransformDirection( direction.normalized );
  16. return direction;
  17. // Raycast and debug
  18. // Ray r = new Ray( transform.position, direction );
  19. // RaycastHit hit;
  20. // Debug.DrawLine(transform.position, transform.position + direction*20f, Color.green);
  21. // if( Physics.Raycast( r, out hit ) ) {
  22. // Debug.DrawLine( transform.position, hit.point , Color.red);
  23. // }
  24. }

插件

DOTween
https://blog.csdn.net/qq_35361471/article/details/79353071

More Effective Coroutinues

http://trinary.tech/category/mec/free/

注意,CancelWith并不会在SetActive(false)时立刻取消协程?CancelWith只在对象被销毁、或变为inactive时取消?所以OnDisable处需手动取消?

MEC free没有StopAllCoroutines(停止该对象上的所有协程),只有KillCoroutines(停止所有协程)。如果需要停止某对象,则需给每个对象分配独一无二的tag(如类名+static的ID),然后用tag kill,Run时都要加tag。或是保存每个句柄(MEC.CoroutineHandler),一个个kill。再或者每次Run都加CancelWith(见下)。
MEC Pro可以在Kill时附带GameObject作为参数。
但是确实如官方所说,正常情况下基本不会有停止某对象上的协程的需求,只要都CancelWith就好了,但事实是它这个CancelWith好像有时候行有时候不行,可能会出问题,所以还是选择tag。

不要随意使用无参数的KillCoroutines()

TestMesh Pro

https://blog.csdn.net/FlyToCloud/article/details/104402316

脚本中导入,需using TMPro;,使用类型TextMeshProUGUI

字体需使用相应的Font Asset。在Unity中右键字体(可以多选),选择TextMeshPro - Create,可创建相应的Font Asset并调整参数。

与TextMesh的不同是,TestMesh Pro需位于Canvas下,使用Canvas Renderer。而TextMesh不能在Canvas下,使用Mesh Renderer。
Canvas使得对象可以以二维形式在三维中表示(Rect Transform),不需要旋转。

使用问题

赋值
脚本中所有public[SerializeField]的变量,都可以在编辑器中赋值,也可以在脚本中运行时赋值。
编辑器中的赋值会发生在Awake()前。一旦赋值,就不会随脚本声明的值改变。
所以,应在Awake()Start()处,为需要赋值的脚本变量赋值,而不是在声明时。

SetTrigger

https://www.jianshu.com/p/5f192b346829
https://www.cnblogs.com/DGJS/p/13306404.html

要用ResetTrigger重置,否则在较快地状态转变时,会出现不相符的情况(SetTrigger相当于保持一个变量为true一段时间)。
比如:A<->B,初始在A,触发A->Btrigger后,在触发B->Atrigger前,应先Reset A->B的trigger。

vscode不会自动补全
1. (可能需要)下载.NET Framework 4.7.1 Developer Pack(关键是版本4.7.1?):https://dotnet.microsoft.com/en-us/download/dotnet-framework/net471
2. Unity->Edit->Preference->External Tools->External Scripts Editor,指定为VS Code(可能需要自己选择exe)。
3. (可能需要)Unity->Edit->Project setting->Player->Other Settings->Configuration,将Api Compathbility Level 更改为.NET 4.X。
4. vscode安装并启用插件C#。
5. 在unity里,选择文件打开,启动vscode!(或整个项目文件夹但不是自己创建的工作区/随便一个文件夹)这时下面会有project_name.sln

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注