Unity
Unity
参考资料
- 官方手册:https://docs.unity.cn/cn/current/Manual/InstantiatingPrefabs.html
- 脚本 API :https://docs.unity.cn/cn/current/ScriptReference/MonoBehaviour.html
软件安装与设置
安装
- 官网安装 Unity Hub
设置
- 设置脚本编辑器 Edit >> Preferences >> External Tool s >> External Script Enditor
- 界面语言切换 Edit >> Preferences >> Language
快捷键
- 复制粘贴物体:Ctrl + D
- 平移 Q 、移动 W 、旋转 E 、缩放 R
创建游戏
- 官方手册:https://docs.unity.cn/cn/current/Manual/CreatingGameplay.html
- 可以直接建模,类似于 3dmax 建模,但是坐标系统不同
- 可以从其他软件建模导出 .fbx 文件,或者从 Unity 商店下载
场景 Scene
摄像机 Camera
- 在场景中至少要有一个摄像机
- 可以有多个摄像机提供双人分屏或营造高级自定义效果。
游戏对象 GameObject
- 用于表示任何可以存在于场景中的事物
- 游戏对象可以具有标签和图层
- 目的:为对象设置标记以及分类管理
- 对象的 transform 信息有相对位置和绝对位置区别
静态游戏对象
- 如果游戏对象在运行时未移动,则被称为静态游戏对象。如果游戏对象在运行时移动,则被称为动态游戏对象。
预设体与预设变体 Prefab
- 类似于图块功能,实现预设体统一修改所有实例
- 将创建的 GameObject 拖拽到项目 Assets 中生成预设体,或者 Assets 右键 >> Create >> Prefab
- 预设变体基于预设体,预设体的修改同时会修改预设变体:选中预设体右键 >> Create >> Prefab Variat
约束组件 Constraint
- Aim:旋转受约束的游戏对象以朝向关联的游戏对象。
- Look At:将受约束的游戏对象旋转到关联的游戏对象(简化的 Aim Constraint)。
- Parent:使受约束的游戏对象跟随关联的游戏对象移动和旋转。
- Position:像关联的游戏对象一样移动受约束的游戏对象。
- Rotation:像关联的游戏对象一样旋转受约束的游戏对象。
- Scale:像关联的游戏对象一样缩放受约束的游戏对象。
旋转与四元数 Quaternion
- Unity 中使用四元数来表示旋转
- 从欧拉角转换为四元数使用
Quaternion.Euler
函数 - 要将四元数转换为欧拉角使用
Quaternion.eulerAngles
函数
物理系统
角色控制器 Character control
刚体 Rigidbody
- 可以接受力和扭矩,任何游戏对象都必须包含受重力影响的刚体
- 当刚体移动速度低于规定的最小线性速度或转速时,游戏对象被物理引擎设置为“睡眠”模式,直到在受到碰撞或力将其唤醒。
- 如果通过修改
Transform
位置将静态碰撞体位置改变,将不会唤醒休眠的对象,导致错误,此时应该使用WakeUp
函数手动唤醒游戏对象
碰撞体 Collision
- 原始碰撞体类型:盒型碰撞体、球形碰撞体、胶囊碰撞体
- 复合碰撞体、网格碰撞体、静态碰撞体
- 物理材质:设置摩擦力和弹力, Assets > Create > Physic Material
- 触发器:使用 Is Trigger 属性将碰撞体设置为触发器,不再表现为碰撞
- 脚本事件:
OnCollisionEnter / OnTriggerEnter 触发器时 / 对应的 On***Stay 和 On***Exit 事件
- 静态碰撞体:拥有碰撞体而没有刚体,发生碰撞时不会移动,用于周围环境
- 刚体碰撞体:完全由物理引擎模拟,并可响应通过脚本施加的碰撞和力
- 运动刚体碰撞体:启用了刚体的 IsKinematic 属性的对象,不会响应碰撞和力,当时可以对其他对象施加力和碰撞
关节 Joints
- Character Joint:球窝关节,沿所有线性自由度约束刚体移动,并实现所有角度自由度。
- Configurable Joint:骨骼关节
- Fixed Joint:固定关节,跟随所连接到的刚体的移动
- Hinge Joint:铰链关节,绕特定轴旋转
- Spring Joint:弹簧关节
音频
资源文件可以是常规的音频文件和音轨模块文件
音轨模块文件在类似于 MIDI 文件,相比于常规音频文件更小
导入 Unity 的音频文件会被自动转为 AudioClip,其中只包含音频数据
音效:能够叠加,播放后就无法控制
使用方法:
收听者添加 Audio Listerner 组件
播放游戏对象添加 Audio Source 组件
为播放游戏对象添加脚本控制
AudioSource audio = GetComponent<AudioSource>(); // 获取组件 if(audio != null) audio.Play(); // 设置组件参数并播放 // 播放特效, shotSlip 需要先获取的音频资源 audio.PlayOneShot(shotSlip);
视频
- 导入视频剪辑并使用视频播放器 (Video Player) 组件
- 可以将视频素材直接提供给任何组件的 Texture 参数
输入控制
配置输入:Edit >> Project Settings >> Input Manager
虚拟轴被映射到控件,用户激活控件后,该轴会收到 [–1..1] 范围中的某个值。
获取输入:
// A 键被按下时 Input.GetKeyDown(KeyCode.A); // A 键处于被按下状态 Input.GetKey(KeyCode.A); // A 键被抬起时 Input.GetKeyUp(KeyCode.A); // 从虚拟轴获取水平垂直方向数值 float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical"); // 获取单点触摸 if (Input.touchCount == 1) { Touch touch = Input.GetTouch(0); } // 获取多点触摸 else { Touch[] touches = Input.touches; }
让游戏对象动起来
public class MoveTest : MonoBehaviour { private CharacterController player; // 游戏对象需要具有 CharacterController 组件 private float speed=2; // 定义移动速度 void Start() { player = GetComponent<CharacterController>(); } void Update() { float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical"); Vector3 dir = new Vector3(h, 0, v); player.SimpleMove(dir*speed); } }
脚本
- MonoBehaviour 是一个基类,所有 Unity 脚本都派生自该类。
- 该类不支持 null 条件运算符 (?.) 和 null 合并运算符 (??)。
生命周期方法
Awake : 加载脚本时调用
OnEnable : 对象启用组件并激活时调用【会多次执行】
Start : 首次调用 Update 之前调用【初始化】
Update : 每一帧调用一次,调用频率与设备帧率相关
LateUpdate : 每次调用 Update 之后调用
FixedUpdate : 以固定频率调用
OnDisable : 组件取消激活时调用
OnDestroy : 销毁脚本时调用
多个脚本执行顺序
- 默认不能控制脚本顺序,对象按生命周期方法顺序执行:所有脚本的 Awake >> 所有脚本的 Start ...
- 修改 ProjectSetting 中的 ScriptExecutionOrder 修改顺序,编号越小的先执行
变量与 Inspector
脚本中的 public 变量会出现在 Inspector 进行编辑
public string myName;
在 Inspector 中转换为My Name
转换规则:移除 m_ 、k 、_ 开头的字符,再将首字母大写,最后在大小写字母之间添加一个空格
在脚本中实例化预制件:使用场景
- 实例化飞弹和爆炸;
- 创建重复物体;
public class InstantiationExample : MonoBehaviour { // 引用预制件。在 Inspector 中,将预制件拖动到该字段中。 public GameObject myPrefab; // 该脚本将在游戏开始时简单地实例化预制件。 void Start() { // 实例化为位置 (0, 0, 0) 和零旋转。 Instantiate(myPrefab, new Vector3(0, 0, 0), Quaternion.identity); } }
事件和事件函数
- 事件函数: https://docs.unity.cn/cn/current/ScriptReference/MonoBehaviour.html
- 碰撞相关函数:OnCollisionEnter、OnCollisionStay 和 OnCollisionExit ;
- 当
UnityEvent
添加到MonoBehaviour
时,它会出现在 Inspector 中,并可添加持久回调。 UnityEvent
能从标准 .net 委托之类的代码中执行
特性
- 不要使用 .NET 库中定义的 ThreadStatic 特性;如果将其添加到 Unity 脚本,会导致崩溃。
- 对于 UnityEngine 特性,请参阅 AddComponentMenu 和同级页面
- 对于 UnityEditor 特性,请参阅 CallbackOrderAttribute 和同级页面
射线检测 Ray
if (Input.GetMouseButtonDown(0))
{
// 根据相机中心点到屏幕鼠标点绘制射线
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// 射线碰撞检查
if (Physics.Raycast(ray, out RaycastHit hitInfo))
{
Vector3 dir = new Vector3( hitInfo.point.x- transform.position.x, 0, hitInfo.point.z- transform.position.z);
player.SimpleMove(dir/dir.magnitude * speed);
}
}
协程 coroutine
- 协程是一种可以暂停执行并将控制权返回给Unity的方法,继续执行时将回到之前离开的地方继续执行。
- 协程不是线程,协程中的同步代码任然运行在主线程上。
void Update()
{
if (Input.GetKeyDown("f"))
{
StartCoroutine(Fade());
}
}
IEnumerator Fade()
{
Color c = renderer.material.color;
for (float alpha = 1f; alpha >= 0; alpha -= 0.1f)
{
c.a = alpha;
renderer.material.color = c;
yield return new WaitForSeconds(.1f); // 等待 0.1 秒
}
}
C# Job System
概念
- 用于编写与 Unity 其余部分良好交互的多线程代码
- C# 作业系统的一个重要特点是它与 Unity 内部使用的系统(Unity 的原生作业系统)相集成,与 Unity 共享工作线程
- 作业系统通过创建作业而不是线程来管理多线程代码。
- 作业系统将作业放入作业队列中以待执行。
- 作业系统会管理依赖关系并确保作业以适当的顺序执行。
- 安全系统复制数据的过程的缺点是会将作业的结果隔离到每个副本中,因此,需要将结果存储在 NativeContainer 的共享内存中。
Unity.Collections
提供了一些列集合作为NativeContainer
:NativeArray / NativeList / NativeHashMap / NativeMultiHashMap / NativeQueue
基本使用
创建作业:实现 IJob,成员变量为 blittable 类型 或 NativeContainer 类型
调度作业:实例化 Job,填充数据,调用 Schedule 方法
依赖作业:
// 将第一个作业的 Handle 传递给第二个作业 JobHandle firstJobHandle = firstJob.Schedule(); secondJob.Schedule(firstJobHandle); // 合并依赖 NativeArray<JobHandle> handles = new NativeArray<JobHandle>(numJobs, Allocator.TempJob); JobHandle jh = JobHandle.CombineDependencies(handles);
获取结果并释放资源
实例
// 创建单个浮点数的本机数组以存储结果。此示例等待作业完成
NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);
// 作业 #1 的数据
MyJob jobData = new MyJob();
jobData.a = 10;
jobData.b = 10;
jobData.result = result;
// 调度作业 #1
JobHandle firstHandle = jobData.Schedule();
// 设置作业 #2 的数据
AddOneJob incJobData = new AddOneJob();
incJobData.result = result;
// 调度作业 #2
JobHandle secondHandle = incJobData.Schedule(firstHandle);
// 等待作业 #2 完成
secondHandle.Complete();
// 访问结果
float num = result[0];
// 释放由结果数组分配的内存
result.Dispose();
public struct MyJob : IJob
{
public float a;
public float b;
public NativeArray<float> result;
public void Execute()
{
result[0] = a + b;
}
}
public struct AddOneJob : IJob
{
public NativeArray<float> result;
public void Execute()
{
result[0] = result[0] + 1;
}
}
IJobParallelFor
批处理作业,同时对数据源的数据执行 Execute 方法
public struct MyParallelJob : IJobParallelFor { [ReadOnly] public NativeArray<float> a; [ReadOnly] public NativeArray<float> b; public NativeArray<float> result; public void Execute(int i) { result[i] = a[i] + b[i]; } }
NativeArray<float> a = new NativeArray<float>(2, Allocator.TempJob); NativeArray<float> b = new NativeArray<float>(2, Allocator.TempJob); NativeArray<float> result = new NativeArray<float>(2, Allocator.TempJob); a[0] = 1.1; b[0] = 2.2; a[1] = 3.3; b[1] = 4.4; MyParallelJob jobData = new MyParallelJob(); jobData.a = a; jobData.b = b; jobData.result = result; // 调度作业,为结果数组中的每个索引执行一个 Execute 方法,且每个处理批次只处理一项 JobHandle handle = jobData.Schedule(result.Length, 1); // 等待作业完成 handle.Complete(); // 释放数组分配的内存 a.Dispose(); b.Dispose(); result.Dispose();
IJobParallelForTransform
- 专为操作变换组件而设计
要点
- 不要从作业访问静态数据,从作业访问静态数据时会绕过所有安全系统。
- 在实体组件系统 (ECS) 中会隐式刷新批次,因此没必要调用 JobHandle.ScheduleBatchedJobs。
- 无法直接更改 NativeContainer 的内容。例如,nativeArray[0]++; 不会更新 nativeArray 中的值。
- 拥有作业所需的数据后就立即在作业上调用
Schedule
,并仅在需要结果时才开始在作业上调用Complete
。 - 将 NativeContainer 类型标记为只读,使用
[ReadOnly]
来标记变量提高性能 - 不要在作业中分配托管内存,在作业中分配托管内存非常慢。
重要的类
Object
- Unity 可以引用的所有对象的基类。
- 从 Object 派生的任何公共变量都将在 Inspector 中显示为放置目标, 让您能够从 GUI 设置其值。
- 该类不支持 null 条件运算符 (?.) 和 null 合并运算符 (??)。
MonoBehaviour
- Unity 脚本的默认基类,继承了 Object 类
- 事件:脚本代码主要位于各个事件方法中,包括
Start / Update / OnClollisionEnter / OnTriggerEnter / ...
- 协程:MonoBehaviour 类允许启动、停止和管理协程,协程用于耗时代码的执行:
StartCoroutine
GameObject
- 用于表示任何可以存在于场景中的事物
- 通过
GetComponent() 或者 GetComponents()
方法可以获取当前对象的组件 - 通过
Find()
静态方法可以获取场景中的游戏对象
Transform
- 控制位置、旋转、缩放:
相对位置 localPosition 绝对位置 position
- 通过
gameObject.transform
变量可以获取到当前的变换组件 - 通过
transform.parent 或者 transform.GetChild()
可以获取gameObject
的父子级对象的变换组件 - 通过
gameObject
变量可以获取到附加当前组件的GameObject
游戏对象实例
Quaternion
尽量采用四元数直接操作旋转,不能对四元数进行直接计算,应该借助提供的旋转函数操作。
欧拉角:Quaternion.Euler(x, y, z) 绕 xyz 旋转角度
使用欧拉角操作旋转时,必须将角度保存在变量中,仅将它们作为欧拉角应用于旋转,最终仍应存储为 Quaternion。
创建旋转:
操作旋转:
Transform 类处理 Quaternion 旋转:
ScriptableObject
可独立于类实例来保存大量数据的数据容器。
减少系统中不同实例预制件的重复数据,节省内存
不能将 ScriptableObject 附加到游戏对象,应该作为项目中的资源
使用 ScriptableObject:
- 在 Editor 会话期间保存和存储数据
- 将数据保存为项目中的资源,以便在运行时使用
必须在应用程序的 Assets 文件夹中创建脚本
[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/SpawnManagerScriptableObject", order = 1)] public class SpawnManagerScriptableObject : ScriptableObject { public string prefabName; public int numberOfPrefabsToCreate; public Vector3[] spawnPoints; }
在脚本中引用 ScriptableObject
public class Spawner : MonoBehaviour { public SpawnManagerScriptableObject spawnManagerValues; }
Time
Time.time
现在距项目开始的用时(秒)Time.deltaTime
现在距上一帧完成的用时(秒)Time.timeScale
控制时间流逝速率,可用于制作慢动作效果Time.fixedDeltaTime
设置固定步长时间Time.maximumDeltaTime
设置Time.deltaTime
的上限
Mathf
- Unity 的所有三角函数都使用弧度,Mathf 提供了常用的三角函数。
- 功能:三角函数、幂和平方根、插值、限制和重复值、对数、其他函数
其他事项
- Unity API不是线程安全的,应该只在 UnitySynchronizationContext 中使用 async 和 await 任务。
- Unity 建议你不要使用多线程。
调试与分析
- Debug 类:输出调试信息
Debug.Log("I come in peace!", this.gameObject)
- 写入控制台窗口的所有内容(由 Unity 或您自己的代码)也会写入到日志文件 。
- Debug 类还提供了两种在 Scene 视图和 Game 视图中绘制线条的方法:DrawLine 和 DrawRay。