@SovietPower
2022-04-21T19:15:25.000000Z
字数 11655
阅读 1239
学习笔记
手册: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之前的(负数)会在所有其它脚本之前执行。
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。
例:
void OnTriggerEnter2D(Collider2D collider)
{
if (collider.CompareTag("Enemy"))
{
Enemy enemy = collider.GetComponentInParent<Enemy>();
// VFX
var contactPoint = collider.bounds.ClosestPoint(transform.position);
VFXPool.Get(hitVFXName, contactPoint);
HitEnemy(enemy, contactPoint);
gameObject.SetActive(false);
}
}
如果一定要GetContact()
,可以在子弹前加射线,使用射线而不是物理接触判断命中。射线接触点也有normal
等信息。如:https://www.bilibili.com/video/BV1ia4y1j78A?t=1592.7。
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
+
,使用StringBuilder,如:https://www.cnblogs.com/superjustin/p/12250571.html。Always Animate
,表示当在屏幕外时也进行动画。Cull Update Transforms
适用于动画会产生位移的Animator Controller,Cull Completely
适用于动画不会产生位移的Animator Controller。sqrt
,则尽量不要,如比较距离。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。
newEnemy.GetComponent<NavMeshAgent>().enabled = false;
newEnemy.transform.position = newPosition;
newEnemy.GetComponent<NavMeshAgent>().enabled = true;
方法3:如果还不行,只能在SetDestination
前加判断:if (!enemy.GetComponent<NavMeshAgent>().isOnNavMesh)
。
此外,也可能是没有烘焙网格,或对象没有在网格上。
https://blog.csdn.net/qq_26399665/article/details/52507001
startColor 可能需要通过如下方式修改。
获取 ParticleSystem.MinMaxGradient color = main.startColor 的方式好像不行。
// 方法1,改粒子起始颜色
ParticleSystem.MainModule main = obj.GetComponent<ParticleSystem>().main;
main.startColor = color;
// 方法2,改Renderer颜色
obj.GetComponent<Renderer>().material.color = color;
Multiple sounds can be played on one AudioSource using
PlayOneShot
. You can play a clip at a static position in 3D space usingAudioSource.PlayClipAtPoint
.
一些优化: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()
例子:
using UnityEngine;
using UnityEngine.InputSystem;
public class ReportMousePosition : MonoBehaviour
{
void Update()
{
Vector2 mousePosition = Mouse.current.position.ReadValue();
if(Keyboard.current.anyKey.wasPressedThisFrame)
Debug.Log("A key was pressed");
if (Gamepad.current.aButton.wasPressedThisFrame)
Debug.Log("A button was pressed");
}
}
注意,不同对象实例的事件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
计时器
MonoBehaviour.Invoke()
。两个参数,分别是要调用的方法名和延时调用的时间(适合单次触发)。浮动原点
一直移动的游戏可能会导致非常大的数字,导致超出浮点上限。
一种方法是,不真正移动视角,而是移动其它东西,超出一定距离后,将这些东西再移回来(如FlappyBird)。
浮动原点通常用于3D。当经过一定距离,将所有的内容移回到原点。
对象池
存档
使用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
获取子对象
用transform
。如果是组件用GetComponent
。
注意transform.Find
不是递归查,如果不是直接的子节点,需要指明路径,或用子节点的transform查。字符串可以有空格。
// 1
transform.Find("name").gameObject
// 2
foreach(Transform child in transform)
Debug.Log(child.gameObject.name);
// 3
for (int i = 0; i < transform.childCount; i++)
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.四元数转化成欧拉角
Vector3 v3 = transform.rotation.eulerAngles;
2.四元数转化成方向向量
Vector3 vector3 = (transform.rotation * Vector3.forward).normalized;
或直接用 transform.forward
3.欧拉角转换成四元数
Quaternion rotation = Quaternion.Euler(vector3);
4.欧拉角转换成方向向量
Vector3 v3 = (Quaternion.Euler(vector3) * Vector3.forward).normalized;
5.将方向向量转换为四元数
Quaternion rotation = Quaternion.LookRotation(vector3);
6.将方向向量转换为欧拉角
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
两种方式。不知道哪个更好。
可能第一种更均匀,第二种用单位圆不知道均匀不均匀。
public Vector3 RandomInsideCone(float radius) // 半径越小,越收束
{
//(sqrt(1 - z^2) * cosϕ, sqrt(1 - z^2) * sinϕ, z)
float radradius = radius * Mathf.PI / 360;
float z = Random.Range(Mathf.Cos(radradius), 1);
float t = Random.Range(0, Mathf.PI * 2);
var direction = new Vector3(Mathf.Sqrt(1 - z * z) * Mathf.Cos(t), Mathf.Sqrt(1 - z * z) * Mathf.Sin(t), z);
return direction;
}
public Vector3 RandomInsideCone2(float z) // z越大,半径越小,越收束
{
// Generate a random XY point inside a circle:
Vector3 direction = Random.insideUnitCircle;
direction.z = z; // circle is at Z units
direction = transform.TransformDirection( direction.normalized );
return direction;
// Raycast and debug
// Ray r = new Ray( transform.position, direction );
// RaycastHit hit;
// Debug.DrawLine(transform.position, transform.position + direction*20f, Color.green);
// if( Physics.Raycast( r, out hit ) ) {
// Debug.DrawLine( transform.position, hit.point , Color.red);
// }
}
DOTween
https://blog.csdn.net/qq_35361471/article/details/79353071
注意,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()
。
脚本中导入,需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->B
trigger后,在触发B->A
trigger前,应先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
。