一、MonoBehaviour 生命周期
1.1 生命周期执行顺序
Unity 中每个 MonoBehaviour 脚本从唤醒到销毁有固定的执行顺序:
1
| Awake → OnEnable → Start → FixedUpdate → Update → LateUpdate → OnDisable → OnDestroy
|
详细执行流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| 编辑器
↓
Reset(编辑器模式下点击 Reset 按钮时调用)
↓
Awake(对象加载时调用,仅一次)
↓
OnEnable(每次启用时调用,可多次)
↓
Start(第一次 Update 前调用,仅一次)
↓
物理引擎更新
├── FixedUpdate(固定时间间隔,0.02s 一次)
└── 物理碰撞检测
↓
Update(每帧调用,频率与帧率相关)
↓
LateUpdate(所有 Update 完成后调用)
↓
渲染
↓
OnDisable(对象禁用时调用)
↓
OnDestroy(对象销毁时调用)
|
1.2 各方法详解
Awake:用于初始化自身状态,不依赖其他对象。在所有对象初始化完成后调用,可安全使用 GameObject.FindWithTag。不可用于协程。
Start:在 Awake 之后、第一次 Update 之前调用。适合做依赖其他对象的初始化(如获取其他组件引用)。
OnEnable:每次对象被激活时调用。与 OnDisable 成对出现,可在同一生命周期中反复发生。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| public class Player : MonoBehaviour
{
private Rigidbody rb;
private int score;
void Awake()
{
// 初始化自身,不依赖其他对象
rb = GetComponent<Rigidbody>();
}
void Start()
{
// 依赖其他对象的初始化
score = GameManager.Instance.InitialScore;
}
void OnEnable()
{
// 每次激活时注册事件
InputManager.OnJump += Jump;
}
void OnDisable()
{
// 每次禁用时取消注册
InputManager.OnJump -= Jump;
}
}
|
1.3 Update、FixedUpdate、LateUpdate 的区别
| 方法 | 执行频率 | 用途 | 特点 |
|---|
| FixedUpdate | 固定 0.02s(可配置) | 物理相关(刚体力、碰撞) | 时间间隔固定,不受帧率影响,一帧内可能调用多次 |
| Update | 每帧一次(约 0.0167s @60FPS) | 非物理逻辑(输入检测、状态更新) | 执行间隔不固定,受帧率波动影响 |
| LateUpdate | Update 全部完成后 | 依赖其他对象更新后的逻辑(摄像机跟随) | 确保所有对象的移动和状态已更新 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| public class PlayerController : MonoBehaviour
{
private Rigidbody rb;
private float moveInput;
void Start() => rb = GetComponent<Rigidbody>();
void Update()
{
// Update:处理输入检测
moveInput = Input.GetAxis("Horizontal");
}
void FixedUpdate()
{
// FixedUpdate:处理物理移动
rb.AddForce(Vector3.right * moveInput * 10f);
}
}
public class CameraFollow : MonoBehaviour
{
public Transform target;
void LateUpdate()
{
// LateUpdate:在所有移动完成后跟随
transform.position = target.position + new Vector3(0, 5, -10);
}
}
|
二、协程(Coroutine)
2.1 协程的本质
协程是 Unity 中分段执行代码逻辑的机制。它通过 yield return 将方法拆分为多个片段,在特定条件满足后恢复执行。
协程不是多线程:协程完全运行在主线程上,只是通过分帧执行模拟"并行"效果。
协程的底层原理:基于 C# 的迭代器特性。调用 StartCoroutine() 时,Unity 将协程包装为状态机,在每帧的 Update 之后检查 yield 条件是否满足,满足则通过 MoveNext() 推动协程执行到下一个 yield 或结束。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| public class CoroutineExample : MonoBehaviour
{
void Start()
{
// 启动协程
StartCoroutine(DelaySpawnEnemy());
StartCoroutine(LoadSceneAsync());
}
// 延迟执行
IEnumerator DelaySpawnEnemy()
{
yield return new WaitForSeconds(3); // 等待3秒
Instantiate(enemyPrefab, spawnPoint.position, Quaternion.identity);
}
// 分帧加载资源
IEnumerator LoadSceneAsync()
{
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync("Level2");
while (!asyncLoad.isDone)
{
float progress = asyncLoad.progress;
UpdateLoadingUI(progress);
yield return null; // 每帧更新进度
}
}
// 序列动画
IEnumerator FadeOut(SpriteRenderer renderer)
{
Color color = renderer.color;
while (color.a > 0)
{
color.a -= Time.deltaTime * 0.5f;
renderer.color = color;
yield return null; // 每帧更新透明度
}
}
}
|
2.2 常用 yield 指令
| 指令 | 作用 |
|---|
yield return null | 等待下一帧 |
yield return new WaitForSeconds(n) | 等待 n 秒 |
yield return new WaitForEndOfFrame() | 等待帧结束 |
yield return new WaitForFixedUpdate() | 等待物理帧更新 |
yield return new WaitUntil(() => 条件) | 等待条件为 true |
yield return new WaitWhile(() => 条件) | 等待条件为 false |
yield return StartCoroutine(Other()) | 等待另一个协程完成 |
2.3 协程的注意事项
- 生命周期:协程依附于 MonoBehaviour,对象销毁时协程自动停止
- 性能:协程本身开销极低,但频繁
yield return new 会产生 GC 垃圾 - 优化:复用
WaitForSeconds 对象,避免每次创建
1
2
3
4
5
6
7
8
9
10
11
12
13
| // ❌ 每次创建新对象,产生 GC
IEnumerator Bad()
{
yield return new WaitForSeconds(1f);
}
// ✅ 缓存复用,减少 GC
private WaitForSeconds wait = new WaitForSeconds(1f);
IEnumerator Good()
{
yield return wait;
}
|
三、对象池(Object Pool)
3.1 为什么需要对象池
频繁 Instantiate 和 Destroy 会产生大量的 GC 压力和内存碎片。对象池通过复用对象来避免这个问题。典型场景:子弹、敌人、特效。
3.2 自定义对象池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| public class ObjectPool : MonoBehaviour
{
[SerializeField] private GameObject prefab;
private Queue<GameObject> pool = new Queue<GameObject>();
// 从池中获取对象
public GameObject Get()
{
if (pool.Count > 0)
{
GameObject obj = pool.Dequeue();
obj.SetActive(true);
return obj;
}
// 池中没有可用对象,创建新对象
return Instantiate(prefab);
}
// 将对象归还池中
public void Return(GameObject obj)
{
obj.SetActive(false);
pool.Enqueue(obj);
}
}
|
3.3 使用 Unity 内置对象池(UnityEngine.Pool)
Unity 提供了 ObjectPool<T> 泛型类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| using UnityEngine.Pool;
public class BulletPool : MonoBehaviour
{
[SerializeField] private GameObject bulletPrefab;
private ObjectPool<Bullet> pool;
void Start()
{
pool = new ObjectPool<Bullet>(
createFunc: () => Instantiate(bulletPrefab).GetComponent<Bullet>(),
actionOnGet: (bullet) => bullet.gameObject.SetActive(true),
actionOnRelease: (bullet) => bullet.gameObject.SetActive(false),
actionOnDestroy: (bullet) => Destroy(bullet.gameObject),
collectionCheck: true,
defaultCapacity: 10,
maxSize: 100
);
}
public Bullet GetBullet() => pool.Get();
public void ReturnBullet(Bullet bullet) => pool.Release(bullet);
}
|
核心方法:
Get():从池中获取对象Release(T):将对象放回池中
注意事项:
- 在
actionOnGet 中重置对象状态(位置、速度等) - 确保每个
Get() 都有对应的 Release()
四、动态加载资源的方式
4.1 三种加载方式对比
| 方式 | 说明 | 适用场景 |
|---|
| Instantiate | 直接实例化预制体 | 最简单,运行时动态生成 |
| Resources.Load | 从 Resources 文件夹加载 | 简单项目,但所有资源都会被打包 |
| AssetBundle | 将资源打成 AB 包 | 大型项目,支持热更新 |
| Addressables | 基于 AB 的高级管理系统 | 官方推荐,支持按需加载 |
4.2 Resources.Load
1
2
3
4
5
6
7
8
| // 同步加载
GameObject prefab = Resources.Load<GameObject>("Prefabs/Player");
Instantiate(prefab);
// 异步加载(不阻塞主线程)
ResourceRequest request = Resources.LoadAsync<GameObject>("Prefabs/Player");
yield return request;
Instantiate(request.asset);
|
Resources 的缺点:
- 所有 Resources 文件夹中的资源都会被打入安装包
- 无法减小包体
- 已过时,推荐用 Addressables
4.3 Instantiate
1
2
3
| // 最简单的动态加载
GameObject bullet = Instantiate(bulletPrefab);
GameObject enemy = Instantiate(enemyPrefab, spawnPosition, Quaternion.identity);
|
4.4 AssetBundle
1
2
3
4
5
6
7
8
| // 从服务器下载 AB 包
using (UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(url))
{
yield return request.SendWebRequest();
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
GameObject prefab = bundle.LoadAsset<GameObject>("Player");
Instantiate(prefab);
}
|
4.5 Addressables
1
2
3
4
5
6
7
8
9
10
11
12
13
| using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
// 异步加载资源
AsyncOperationHandle<GameObject> handle =
Addressables.LoadAssetAsync<GameObject>("Player");
handle.Completed += (op) =>
{
Instantiate(op.Result);
};
// 释放资源
Addressables.Release(handle);
|
Addressables 核心特性:
- 按地址加载,逻辑与物理路径解耦
- 自动管理依赖
- 支持本地和远程资源
- 引用计数自动卸载
五、UGUI 与适配
5.1 Canvas 的三种渲染模式
Screen Space - Overlay(默认):UI 始终在 3D 元素前面,与摄像机无关。
Screen Space - Camera:UI 通过指定摄像机渲染,支持 3D 效果(如粒子在 UI 前)。
World Space:UI 成为场景中的 3D 物体,可用于怪物血条、VR 开发。
5.2 Canvas Scaler 缩放模式
| 模式 | 说明 |
|---|
| Constant Pixel Size | UI 保持固定像素大小 |
| Scale With Screen Size | 根据参考分辨率动态缩放(推荐) |
| Constant Physical Size | 根据物理尺寸(英寸)缩放 |
5.3 UI 自适应方案
锚点(Anchors):定义 UI 元素相对于父容器的定位方式。
Rect Transform:UI 元素的变换组件,包含锚点、轴心、偏移量等属性。
布局组件:
Horizontal/Vertical Layout Group:自动排列子元素Content Size Fitter:根据内容动态调整大小Aspect Ratio Fitter:保持宽高比
5.4 Image 和 RawImage 的区别
| Image | RawImage |
|---|
| 支持类型 | Sprite(精灵) | Texture(原始纹理) |
| 九宫格缩放 | 支持 | 不支持 |
| 填充模式 | 支持(进度条) | 不支持 |
| UV 调整 | 不支持 | 支持 |
| 动态纹理 | 不支持 | 支持(摄像头、RenderTexture) |
1
2
3
4
5
6
7
8
9
10
11
12
13
| // RawImage 适用于动态纹理
public class ModelInUI : MonoBehaviour
{
public Camera modelCamera;
public RawImage modelDisplay;
void Start()
{
RenderTexture rt = new RenderTexture(512, 512, 24);
modelCamera.targetTexture = rt;
modelDisplay.texture = rt;
}
}
|
5.5 摇杆实现(不依赖插件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| public class JoystickController : MonoBehaviour, IDragHandler, IPointerDownHandler, IPointerUpHandler
{
[SerializeField] private RectTransform joystickBG;
[SerializeField] private RectTransform joystickHandle;
private Vector2 inputDirection;
public Vector2 InputDirection => inputDirection;
public void OnDrag(PointerEventData eventData)
{
Vector2 pos;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
joystickBG, eventData.position, eventData.pressEventCamera, out pos
);
pos = pos / joystickBG.sizeDelta; // 归一化
inputDirection = pos.magnitude > 1 ? pos.normalized : pos;
joystickHandle.anchoredPosition = inputDirection * joystickBG.sizeDelta;
}
public void OnPointerUp(PointerEventData eventData)
{
inputDirection = Vector2.zero;
joystickHandle.anchoredPosition = Vector2.zero;
}
}
// 摇杆控制角色移动
public class PlayerController : MonoBehaviour
{
public JoystickController joystick;
public float moveSpeed = 5f;
void Update()
{
Vector2 input = joystick.InputDirection;
Vector3 movement = new Vector3(input.x, 0, input.y) * moveSpeed * Time.deltaTime;
transform.Translate(movement);
}
}
|
六、鼠标交互与事件系统
6.1 射线检测(Raycast)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| public class ClickInteraction : MonoBehaviour
{
private Camera mainCamera;
void Start() => mainCamera = Camera.main;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
// 3D 物体检测
if (Physics.Raycast(ray, out hit))
{
Debug.Log("点击了: " + hit.collider.gameObject.name);
}
// 2D 物体检测
RaycastHit2D hit2D = Physics2D.Raycast(ray.origin, ray.direction);
if (hit2D.collider != null)
{
Debug.Log("点击了 2D 物体: " + hit2D.collider.name);
}
}
}
}
|
6.2 OnMouseDown 方法
1
2
3
4
5
6
7
| public class SimpleClick : MonoBehaviour
{
private void OnMouseDown()
{
Debug.Log("点击了: " + gameObject.name);
}
}
|
6.3 事件系统(Event System)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| using UnityEngine.EventSystems;
// UI 物体点击
public class UIClickHandler : MonoBehaviour, IPointerClickHandler
{
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log("点击了 UI: " + gameObject.name);
}
}
// 3D 物体点击(需添加 Physics Raycaster 到摄像机)
public class ObjectClickHandler : MonoBehaviour
{
void Start()
{
EventTrigger trigger = gameObject.AddComponent<EventTrigger>();
EventTrigger.Entry entry = new EventTrigger.Entry();
entry.eventID = EventTriggerType.PointerClick;
entry.callback.AddListener((data) => OnClick());
trigger.triggers.Add(entry);
}
void OnClick()
{
Debug.Log("点击了物体: " + gameObject.name);
}
}
|
七、委托与事件
7.1 委托(Delegate)
委托是方法的类型安全容器,支持多播(+= 注册多个方法)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // 定义委托
public delegate void DeathHandler(string message);
public class Player
{
public DeathHandler OnDeath;
public void Die()
{
OnDeath?.Invoke("玩家死亡!");
}
}
// 订阅
player.OnDeath += (msg) => Debug.Log(msg);
|
7.2 事件(Event)
事件是委托的封装,通过 event 关键字限制外部只能 += / -=,不能直接触发。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // 定义事件
public class Enemy
{
public event Action OnEnemyDeath; // 使用 Action 简化
public void Die()
{
OnEnemyDeath?.Invoke(); // 只能在类内部触发
}
}
// 订阅
enemy.OnEnemyDeath += HandleEnemyDeath;
// enemy.OnEnemyDeath?.Invoke(); // 编译错误!外部不能触发
|
委托 vs 事件的核心区别:
| 委托 | 事件 |
|---|
| 外部触发 | 可以 | 不可以 |
| 外部赋值 (=) | 可以(会覆盖其他订阅) | 不可以 |
| 使用场景 | 回调、方法参数 | 发布-订阅模式 |
八、Unity GC 与优化
8.1 GC 工作机制
Unity 的 GC 采用分代回收策略:
| 代 | 特点 |
|---|
| Gen 0 | 新分配的小对象,回收最频繁 |
| Gen 1 | Gen 0 幸存对象,回收次之 |
| Gen 2 | 长期存活对象(如单例),极少回收 |
GC 触发条件:Gen 0 空间不足时自动触发,或手动调用 System.GC.Collect()(不推荐)。
GC 执行时主线程暂停,导致帧率骤降。
8.2 常见 GC 陷阱
1
2
3
4
5
6
7
8
9
10
11
| void Update()
{
// ❌ 每帧产生新对象 → 频繁 GC
Debug.Log("Position: " + transform.position); // 字符串拼接
// ❌ 装箱
object boxed = 123;
// ❌ 每帧 GetComponent
GetComponent<Rigidbody>().AddForce(Vector3.up);
}
|
8.3 GC 优化策略
(1)减少堆内存分配:
- 使用对象池管理高频创建的对象
- 避免装箱:用泛型集合替代非泛型
- 缓存组件引用
1
2
3
4
| // ✅ 缓存组件引用
private Rigidbody rb;
void Start() => rb = GetComponent<Rigidbody>();
void Update() => rb.AddForce(Vector3.up);
|
(2)控制字符串操作:
1
2
3
4
| // ✅ 使用 StringBuilder
StringBuilder sb = new StringBuilder(256);
sb.Append("HP: ").Append(currentHp);
string result = sb.ToString();
|
(3)使用非分配 API:
1
2
3
4
5
| // ❌ 分配内存
Collider[] colliders = Physics.OverlapSphere(center, radius);
// ✅ 无分配
int count = Physics.OverlapSphereNonAlloc(center, radius, colliderBuffer);
|
(4)使用结构体代替类:
1
2
3
4
5
| public struct BulletData // 值类型,不参与 GC
{
public Vector3 Position;
public float Speed;
}
|
8.4 背包系统设计概要
数据结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // 静态数据(ScriptableObject 存储物品属性)
public abstract class ItemBase : ScriptableObject
{
public int ItemID;
public string ItemName;
public Sprite Icon;
public int MaxStack = 1;
public ItemType Type; // Weapon, Consumable, Material...
}
// 动态数据(物品实例)
[System.Serializable]
public class ItemInstance
{
public int ItemID;
public int Count;
public float Durability;
}
|
优化方向:
- 数据分离:静态属性与动态数据解耦
- 对象池:复用 UI 格子
- 虚拟列表:仅渲染可视区域内的物品
- 异步加载:图标按需加载
九、协程与异步
9.1 协程 vs async/await vs Awaitable
| 方式 | GC 开销 | 适用场景 |
|---|
| Coroutine | 有 | 简单延迟、分帧操作 |
| async/await | 有(Task 分配) | 网络请求、复杂异步 |
| Awaitable(Unity 2022+) | 无 | 性能敏感场景 |
9.2 async/await 在 Unity 中的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| using System.Threading.Tasks;
public class AsyncExample : MonoBehaviour
{
async void Start()
{
string data = await LoadDataAsync();
Debug.Log(data);
}
async Task<string> LoadDataAsync()
{
using var request = UnityWebRequest.Get("https://api.example.com");
await request.SendWebRequest();
return request.downloadHandler.text;
}
}
|
async/await 的核心价值:
- 替代回调/协程,避免"回调地狱"
- 主线程友好:await 后自动回到主线程
- 无需手动检查状态
9.3 Unity 中的多线程
Unity 主线程独占 Unity API,子线程中不能调用 GameObject.Instantiate、Transform.position 等操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| using System.Threading;
using System.Threading.Tasks;
public class ThreadExample : MonoBehaviour
{
void Start()
{
// 子线程执行耗时操作
Thread thread = new Thread(() =>
{
int result = HeavyCalculation();
// 子线程中不能调用 Unity API
});
thread.Start();
}
// Task 异步
async void LoadData()
{
string data = await Task.Run(() =>
{
return System.IO.File.ReadAllText("data.txt");
});
// 回到主线程更新 UI
Debug.Log(data);
}
}
|
Unity 自动使用的多线程:
- Job System:多线程并行计算
- Burst Compiler:将代码编译为高度优化的机器码
- 物理引擎:默认在子线程处理碰撞检测
- 资源加载:Addressables 异步加载
十、Shader 基础
10.1 Shader 的概念
Shader 是一种运行在 GPU 上的程序,用来描述物体表面的外观,控制光照与渲染效果。GPU 并行处理大量顶点和像素,Shader 就是告诉 GPU"怎么画"的指令集。
Unity 渲染管线流程:
1
| 顶点数据 → 顶点着色器(Vertex) → 几何着色器 → 光栅化 → 片段着色器(Fragment) → 输出合并
|
Unity 中的 Shader 类型:
Surface Shader:Unity 特有的简化写法,自动处理光照计算。开发者只需定义属性(颜色、纹理等),Unity 自动生成完整的 Vertex/Fragment 代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // Surface Shader 示例
Shader "Custom/SimpleSurface"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma surface surf Standard fullforwardshadows
// ... 表面函数
ENDCG
}
}
|
Vertex/Fragment Shader:提供精细控制,开发者自定义顶点和片段处理,适合复杂视觉效果(如溶解、扭曲、热浪)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| // 最简单的 Vertex/Fragment Shader
Shader "Custom/UnlitColor"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
fixed4 _Color;
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 position : SV_POSITION;
};
v2f vert(appdata v)
{
v2f o;
o.position = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return _Color;
}
ENDCG
}
}
}
|
10.2 四元数与旋转
四元数是 Unity 中表示旋转的数学工具,使用四个分量(x, y, z, w)表示三维空间中的旋转。
为什么不用欧拉角:
- 万向锁:当某个轴旋转 90 度时,另外两个轴失去自由度,导致旋转异常
- 插值不平滑:欧拉角插值会产生不自然的旋转路径
四元数的优点:
- 可进行增量旋转(旋转叠加)
- 避免万向锁
- 插值平滑(
Slerp 球形插值)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // 四元数常见操作
// 欧拉角 → 四元数
transform.rotation = Quaternion.Euler(0, 45, 0);
// 看向目标
transform.rotation = Quaternion.LookRotation(target.position - transform.position);
// 平滑旋转插值(Slerp = Spherical Linear Interpolation)
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 5f);
// 绕轴旋转
transform.rotation *= Quaternion.AngleAxis(90, Vector3.up);
// 获取当前旋转的欧拉角
Vector3 angles = transform.rotation.eulerAngles;
// 从旋转中提取方向向量
Vector3 forward = transform.rotation * Vector3.forward;
|
十一、寻路系统(NavMesh)
11.1 烘焙导航网格
NavMesh(Navigation Mesh)是 Unity 的自动寻路系统,通过将可行走区域烘焙为网格,让 AI 角色自动规划路径。
操作步骤:
- 标记场景中所有静态障碍物为
Navigation Static - 打开
Window/AI/Navigation 面板 - 调整参数:
- Agent Radius:角色半径(决定能通过的最小通道)
- Agent Height:角色高度
- Max Slope:最大可爬坡度数
- Step Height:最大可跨越台阶高度
- 点击
Bake 生成 NavMesh
11.2 NavMeshAgent 组件
NavMeshAgent 是控制角色寻路的组件,提供路径计算、移动、避障等功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| public class AIController : MonoBehaviour
{
private NavMeshAgent agent;
public Transform target;
void Start()
{
agent = GetComponent<NavMeshAgent>();
// 配置参数
agent.speed = 5f; // 移动速度
agent.angularSpeed = 120f; // 旋转速度
agent.acceleration = 8f; // 加速度
agent.stoppingDistance = 1.5f; // 停止距离(攻击范围)
agent.SetDestination(target.position);
}
void Update()
{
// 状态检测
if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance)
{
// 到达目标,执行攻击逻辑
Attack();
}
// 动态更新目标
if (target.hasChanged)
{
agent.SetDestination(target.position);
}
}
// 暂停/恢复寻路
public void Pause() => agent.isStopped = true;
public void Resume() => agent.isStopped = false;
// 判断是否到达
public bool HasReached => !agent.pathPending && agent.remainingDistance < 0.1f;
}
|
11.3 NavMeshAgent 参数详解
| 参数 | 作用 |
|---|
speed | 最大移动速度 |
angularSpeed | 旋转速度(度/秒) |
acceleration | 最大加速度 |
stoppingDistance | 距离目标多远停止 |
autoBraking | 到达时是否自动减速 |
radius | 角色半径(用于避障) |
height | 角色高度 |
obstacleAvoidanceType | 避障等级(NoObstacleAvoidance ~ All) |
11.4 动态障碍物
场景中可移动的障碍物(如门、箱子)使用 NavMeshObstacle 组件:
- 静态障碍物:在烘焙时被纳入 NavMesh
- 动态障碍物:运行时通过
NavMeshObstacle 实时阻挡Carve:启用后动态切割 NavMesh,物体移走后恢复
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| public class DynamicDoor : MonoBehaviour
{
private NavMeshObstacle obstacle;
void Start()
{
obstacle = GetComponent<NavMeshObstacle>();
obstacle.carving = true; // 启用动态切割
}
public void Open()
{
obstacle.carving = false; // 门打开,通行
// 播放开门动画
}
public void Close()
{
obstacle.carving = true; // 门关闭,阻挡
// 播放关门动画
}
}
|
11.5 多层 NavMesh(跳跃/下落)
对于不同高度的平台,Unity 支持多层 NavMesh:
- 在 Navigation 面板添加 Layer(如"Ground"、“Roof”)
- 为不同高度的物体分配不同 Layer
- AI 角色设置
agent.areaMask 控制可走区域
1
2
| // 控制 AI 可走的区域(位掩码)
agent.areaMask = (1 << NavMesh.GetAreaFromName("Ground")); // 只能走地面
|
十二、相机系统
12.1 相机参数详解
Clear Flags:决定屏幕中未渲染区域的处理方式
- Skybox:用天空盒填充(3D 场景默认)
- Solid Color:用纯色填充(2D 游戏,适合像素风)
- Depth Only:仅清除深度缓冲(用于多相机叠加,如主相机 + 武器相机)
- Don’t Clear:不清除任何内容(需谨慎使用,会产生拖影效果)
Projection:投影模式
- Perspective:透视投影,近大远小,适合 3D 游戏
Field of View(FOV):视野角度,默认 60°,FPS 游戏通常用 90°
- Orthographic:正交投影,无视距离,适合 2D 游戏、UI 相机
Size:视野高度的一半(单位:unity 单位)
Clipping Planes:
- Near:近剪裁面,过小会导致物体被"挖穿"(z-fighting)
- Far:远剪裁面,过大影响性能(深度精度下降)
进阶参数:
- Viewport Rect:定义相机渲染的屏幕区域(分屏合作游戏)
X=0, Y=0, W=0.5, H=1 → 左半屏(玩家1)X=0.5, Y=0, W=0.5, H=1 → 右半屏(玩家2)
- Culling Mask:每层 32 位掩码,选择渲染哪些 Layer 的物体
- Target Texture:将相机画面渲染到 Render Texture(小地图、监控摄像头)
12.2 多相机叠加
1
2
3
4
5
6
7
8
9
10
11
12
| public class WeaponCamera : MonoBehaviour
{
private Camera weaponCam;
void Start()
{
weaponCam = GetComponent<Camera>();
// 武器相机:只渲染武器层,不清理背景
weaponCam.clearFlags = CameraClearFlags.DepthOnly;
weaponCam.cullingMask = 1 << LayerMask.NameToLayer("Weapon");
}
}
|
12.3 相机常见实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| // 第三人称跟随
public class ThirdPersonCamera : MonoBehaviour
{
public Transform target;
public float distance = 10f;
public float height = 5f;
public float smoothSpeed = 5f;
void LateUpdate()
{
Vector3 targetPos = target.position + Vector3.up * height - target.forward * distance;
transform.position = Vector3.Lerp(transform.position, targetPos, smoothSpeed * Time.deltaTime);
transform.LookAt(target.position + Vector3.up * 2f);
}
}
// 第一人称相机
public class FirstPersonCamera : MonoBehaviour
{
public Transform playerBody;
public float mouseSensitivity = 100f;
private float xRotation = 0f;
void Start()
{
Cursor.lockState = CursorLockMode.Locked;
}
void Update()
{
float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;
xRotation -= mouseY;
xRotation = Mathf.Clamp(xRotation, -90f, 90f);
transform.localRotation = Quaternion.Euler(xRotation, 0, 0);
playerBody.Rotate(Vector3.up * mouseX);
}
}
|
十三、Unity 常用架构
13.1 传统 MonoBehaviour 架构(面向对象)
每个脚本继承 MonoBehaviour,通过组件挂载到 GameObject 上,对象独立处理逻辑。
优点:直观易用,适合中小规模项目。
缺点:
- 性能瓶颈:大量 GameObject 的 Update 逐个执行,导致主线程卡顿
- 内存碎片化:频繁创建/销毁引发 GC
- 耦合度高:对象间引用关系复杂时难以维护
1
2
3
4
5
6
7
8
9
10
| public class Player : MonoBehaviour
{
public float speed = 5f;
void Update()
{
float h = Input.GetAxis("Horizontal");
transform.Translate(h * speed * Time.deltaTime, 0, 0);
}
}
|
13.2 MVC 架构
将数据(Model)、显示(View)、逻辑(Controller) 分离,降低耦合度。
适用场景:UI 系统、网络同步、需要严格分层的项目。
1
2
3
4
5
| 用户输入 → Controller(逻辑控制)
↓
Model(数据存储)→ 数据变更通知
↓
View(界面更新)→ 反馈用户
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| // Model:纯数据,无 MonoBehaviour
[System.Serializable]
public class PlayerModel
{
public int Health = 100;
public Vector3 Position = Vector3.zero;
}
// View:只负责显示,不存数据
public class PlayerView : MonoBehaviour
{
public void UpdatePosition(Vector3 pos) => transform.position = pos;
public void UpdateHealthBar(int health) { /* UI 更新 */ }
}
// Controller:协调 Model 和 View
public class PlayerController : MonoBehaviour
{
[SerializeField] private PlayerView view;
private PlayerModel model = new PlayerModel();
public void TakeDamage(int damage)
{
model.Health -= damage;
view.UpdateHealthBar(model.Health);
}
public void Move(Vector3 delta)
{
model.Position += delta;
view.UpdatePosition(model.Position);
}
}
|
13.3 事件驱动架构
通过事件解耦模块间的通信,适合成就系统、音效触发等需要低耦合的场景。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
| // 定义事件参数类(携带数据)
public class EnemyDiedEventArgs
{
public Vector3 Position;
public EnemyType Type;
public int ScoreValue;
}
// 发布者
public class Enemy : MonoBehaviour
{
// 使用 C# Action 事件
public static event System.Action<EnemyDiedEventArgs> OnAnyEnemyDied;
void Die()
{
OnAnyEnemyDied?.Invoke(new EnemyDiedEventArgs
{
Position = transform.position,
Type = enemyType,
ScoreValue = score
});
Destroy(gameObject);
}
}
// 订阅者 1:成就系统
public class AchievementSystem : MonoBehaviour
{
private int killCount;
void OnEnable() => Enemy.OnAnyEnemyDied += OnEnemyKilled;
void OnDisable() => Enemy.OnAnyEnemyDied -= OnEnemyKilled;
void OnEnemyKilled(EnemyDiedEventArgs args)
{
killCount++;
if (killCount >= 100)
UnlockAchievement("百人斩");
}
}
// 订阅者 2:音效系统
public class AudioManager : MonoBehaviour
{
void OnEnable() => Enemy.OnAnyEnemyDied += (args) => PlayDeathSound();
void OnDisable() => Enemy.OnAnyEnemyDied -= (args) => PlayDeathSound();
}
// 订阅者 3:UI 系统
public class ScoreUI : MonoBehaviour
{
void OnEnable() => Enemy.OnAnyEnemyDied += (args) =>
UpdateScore(args.ScoreValue);
}
|
13.4 ECS 架构(Entity-Component-System)
ECS 是数据导向的架构,将面向对象的"对象 = 数据 + 行为"拆分为纯数据和纯逻辑。
为什么需要 ECS:
- 传统 OOP 中,10000 个敌人各自执行 Update,CPU 缓存命中率低
- ECS 将同类数据连续存储,CPU 遍历时按顺序读取,性能提升数倍
核心概念:
| 概念 | 说明 | 类比 OOP |
|---|
| Entity(实体) | 唯一 ID,无数据无方法 | GameObject |
| Component(组件) | 纯数据结构 | 类的字段 |
| System(系统) | 处理特定组件组合的逻辑 | 类的方法 |
Archetype 与 Chunk:
- Archetype:具有相同组件组合的 Entity 归为一类(如 Position+Velocity)
- Chunk:每个 Archetype 的数据存储在连续的内存块中(每个 Chunk 16KB)
1
2
3
4
5
6
7
| Archetype (Position + Velocity)
┌─────────────────────────────────┐
│ Chunk 1 │
│ [P1][V1] [P2][V2] [P3][V3] ... │ ← 连续内存,CPU 缓存友好
│ Chunk 2 │
│ [P11][V11][P12][V12] ... │
└─────────────────────────────────┘
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| using Unity.Entities;
using Unity.Burst;
using Unity.Jobs;
using Unity.Mathematics;
// Component:纯数据结构
public struct Position : IComponentData
{
public float3 Value;
}
public struct Velocity : IComponentData
{
public float3 Value;
}
// System:每帧处理所有具有 Position + Velocity 的 Entity
[BurstCompile]
public partial struct MovementSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
// 查询所有拥有 Position 和 Velocity 的 Entity
foreach (var (position, velocity) in
SystemAPI.Query<RefRW<Position>, RefRO<Velocity>>())
{
// 更新位置:position += velocity * deltaTime
position.ValueRW.Value += velocity.ValueRO.Value * SystemAPI.Time.DeltaTime;
}
}
}
// 创建 Entity 并添加组件
public class GameManager : MonoBehaviour
{
void Start()
{
var world = World.DefaultGameObjectInjectionWorld;
var entityManager = world.EntityManager;
// 批量创建 10000 个移动实体
for (int i = 0; i < 10000; i++)
{
Entity entity = entityManager.CreateEntity();
entityManager.AddComponentData(entity, new Position { Value = float3.zero });
entityManager.AddComponentData(entity, new Velocity { Value = new float3(1, 0, 0) });
}
}
}
|
ECS 性能优势:
| 特性 | 原理 | 效果 |
|---|
| 连续内存 | 同类型组件连续存储 | CPU 缓存命中率提高 10x |
| 多线程 Job System | 自动将 System 逻辑分发到多核 | 充分利用多核 CPU |
| Burst Compiler | C# 编译为高度优化的原生代码 | 性能提升 5-10 倍 |
对比:100000 个移动物体
| 架构 | CPU 耗时 | GC 分配 |
|---|
| MonoBehaviour | 35ms | 高频 |
| ECS + Jobs + Burst | 2ms | 几乎为零 |
十四、Unity 移动射击动画融合
14.1 动画层(Animation Layers)
实现"边跑边射击"的核心技术是动画分层。将身体分为上下两层,分别播放不同动画。
实现步骤:
在 Animator Controller 中创建两个 Layer:
- Base Layer:处理移动动画(走、跑、跳跃)
- Upper Body Layer:处理上身动画(射击、换弹、投掷)
为 Upper Body Layer 设置 Avatar Mask:
- 创建 Avatar Mask 资产
- 仅勾选上半身骨骼(Spine、Chest、Left/Right Arm、Head)
- 下半身骨骼交由 Base Layer 控制
代码控制 Layer 权重:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| public class PlayerAnimator : MonoBehaviour
{
private Animator animator;
// 动画层索引
private readonly int baseLayer = 0;
private readonly int upperLayer = 1;
void Start() => animator = GetComponent<Animator>();
void Update()
{
// Base Layer:根据速度切换走/跑
float speed = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")).magnitude;
animator.SetFloat("Speed", speed);
// Upper Body Layer:射击时启用
if (Input.GetButtonDown("Fire1"))
{
// 将 Upper Body 权重设为 1(完全覆盖上半身)
animator.SetLayerWeight(upperLayer, 1f);
animator.Play("Shoot", upperLayer, 0f);
}
// 射击结束后逐渐恢复权重为 0
if (Input.GetButtonUp("Fire1"))
{
StartCoroutine(FadeOutLayer(upperLayer, 0.2f));
}
}
IEnumerator FadeOutLayer(int layer, float duration)
{
float elapsed = 0;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
animator.SetLayerWeight(layer, Mathf.Lerp(1, 0, elapsed / duration));
yield return null;
}
animator.SetLayerWeight(layer, 0);
}
}
|
14.2 动画参数与混合
常用动画参数类型:
| 参数类型 | 方法 | 用途 |
|---|
| Float | SetFloat("Speed", 5.5f) | 移动速度、转向角度 |
| Int | SetInteger("State", 2) | 状态枚举 |
| Bool | SetBool("IsGrounded", true) | 条件切换 |
| Trigger | SetTrigger("Jump") | 一次性触发(自动复位) |
Blend Tree 混合:根据一个或多个参数平滑过渡动画(如根据速度混合 Idle → Walk → Run)。
1
2
3
4
5
6
| // 1D Blend Tree 示例:Idle(0) → Walk(2) → Run(6)
animator.SetFloat("Speed", currentSpeed);
// 2D Blend Tree 示例:根据横向和纵向输入混合
animator.SetFloat("Horizontal", moveX);
animator.SetFloat("Vertical", moveY);
|
14.3 Animator 性能优化
- 减少层数:每增加一层都会额外计算
- 减少参数数量:参数变更会触发状态机重评估
- 使用 HasExitTime:确保动画播完再过渡,避免频繁打断
- 优化 Avatar Mask:只在需要的骨骼上启用遮罩
十五、Unity 序列化
15.1 什么是序列化
序列化将对象状态转换为可存储或可传输的格式(二进制、JSON、XML),反序列化则逆向还原。
Unity 中的序列化场景:
| 场景 | 说明 |
|---|
| 场景和预制体保存 | Inspector 中修改的 public 变量自动序列化到 .unity / .prefab 文件 |
| Inspector 面板显示 | [SerializeField] 让私有变量显示在 Inspector 并序列化 |
| ScriptableObject | 创建可复用配置数据,保存为 .asset 文件 |
| JSON 存档 | 使用 JsonUtility 序列化游戏存档 |
| 网络传输 | 序列化对象状态发送到服务器或其他客户端 |
15.2 序列化规则
Unity 自动序列化以下类型:
- 基本类型:
int, float, string, bool, enum - Unity 结构体:
Vector2/3/4, Quaternion, Color, Rect, Bounds - 引用类型:
GameObject, Component, ScriptableObject(序列化引用而非内容) - 数组和
List<T>(T 必须是可序列化类型) - 自定义类/结构体:需标记
[System.Serializable]
不会被序列化的成员:
- 静态变量
- 属性(Property,有
get/set 的成员) - 委托和事件
- 接口类型
- 字典
Dictionary(需手动处理)
15.3 控制序列化的特性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| [System.Serializable]
public class SaveData
{
public int level;
public float positionX;
public List<string> unlockedItems = new List<string>();
}
public class Player : MonoBehaviour
{
[SerializeField] private int health; // 私有字段显示在 Inspector
[HideInInspector] public int debugValue; // 公开字段不在 Inspector 显示
[NonSerialized] public int tempValue; // 不被序列化(不保存)
[Range(0, 100)] public float speed; // Inspector 滑动条
[Tooltip("当前角色等级")]
public int level; // 鼠标悬停提示
[Header("战斗属性")] // 分组标题
public int attack;
public int defense;
}
// ScriptableObject(可复用配置数据)
[CreateAssetMenu(menuName = "Game/ItemData")]
public class ItemData : ScriptableObject
{
public int itemID;
public string itemName;
public Sprite icon;
public int maxStack = 1;
}
// 使用 ScriptableObject
ItemData sword = ScriptableObject.CreateInstance<ItemData>();
sword.itemName = "铁剑";
AssetDatabase.CreateAsset(sword, "Assets/Data/Items/IronSword.asset");
|
15.4 JSON 序列化(存档)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| // 存档数据结构
[System.Serializable]
public class GameSaveData
{
public int level;
public float playerHealth;
public Vector3 playerPosition;
public List<string> inventoryItems;
public Dictionary<string, int> stats; // 需要 ISerializationCallbackReceiver
}
// 保存存档
public class SaveManager : MonoBehaviour
{
public void SaveGame(GameSaveData data)
{
string json = JsonUtility.ToJson(data, true); // true = 格式化输出
File.WriteAllText(Application.persistentDataPath + "/save.json", json);
}
public GameSaveData LoadGame()
{
string path = Application.persistentDataPath + "/save.json";
if (File.Exists(path))
{
string json = File.ReadAllText(path);
return JsonUtility.FromJson<GameSaveData>(json);
}
return null;
}
}
|
JsonUtility vs Newtonsoft.Json:
| 特性 | JsonUtility | Newtonsoft.Json |
|---|
| 性能 | 快(Unity 原生) | 较慢 |
| 支持类型 | MonoBehaviour、ScriptableObject | 任意 .NET 类型 |
| 字典支持 | 不支持 | 支持 |
| 多态 | 不支持 | 支持 |
| 安装 | 内置 | 需安装 Package |
15.5 ISerializationCallbackReceiver
用于序列化前后执行自定义逻辑,典型用途是处理字典序列化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| public class Inventory : MonoBehaviour, ISerializationCallbackReceiver
{
// Dictionary 不会被 Unity 自动序列化
[NonSerialized] public Dictionary<string, int> items = new Dictionary<string, int>();
// 用列表中转存储
[SerializeField] private List<string> _keys = new List<string>();
[SerializeField] private List<int> _values = new List<int>();
// 序列化前:字典 → 列表
public void OnBeforeSerialize()
{
_keys.Clear();
_values.Clear();
foreach (var pair in items)
{
_keys.Add(pair.Key);
_values.Add(pair.Value);
}
}
// 反序列化后:列表 → 字典
public void OnAfterDeserialize()
{
items.Clear();
for (int i = 0; i < _keys.Count; i++)
items[_keys[i]] = _values[i];
}
}
|
15.6 序列化方案对比
| 方案 | 优点 | 缺点 |
|---|
| Unity 默认序列化 | 自动处理组件引用、Inspector 显示 | 不支持字典、接口 |
| JsonUtility | 轻量、易读、支持 MonoBehaviour | 性能较低、不支持多态 |
| BinaryFormatter | 高效、体积小 | 安全性差(已弃用) |
| 第三方(MessagePack) | 高性能、跨平台 | 需额外集成 |
十六、高性能开发建议
16.1 CPU 性能优化
(1)减少 Update 中的开销:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // ❌ 不好:每帧 GetComponent 和字符串拼接
void Update()
{
GetComponent<Rigidbody>().AddForce(Vector3.up);
Debug.Log("位置: " + transform.position);
}
// ✅ 好:缓存引用 + StringBuilder
private Rigidbody rb;
private StringBuilder sb = new StringBuilder(64);
void Start() => rb = GetComponent<Rigidbody>();
void Update()
{
rb.AddForce(Vector3.up);
sb.Clear();
sb.Append("位置: ").Append(transform.position);
}
|
(2)使用对象池代替频繁 Instantiate/Destroy:
- 子弹、敌人、粒子特效等高频创建的对象使用对象池
- 避免 GC 压力,降低 CPU 峰值
(3)减少 Mono 调用:
- 每帧调用 Unity API(如
transform.position)有 native/managed 边界开销 - 缓存频繁访问的 Transform、Renderer 等组件引用
(4)LOD(Level of Detail):
- 远处的物体使用低面数模型
- Unity LOD Group 组件自动切换
16.2 GPU 性能优化
(1)合批(Batching):
| 方式 | 条件 | 效果 |
|---|
| Static Batching | 标记为 Static 的物体 | 减少 Draw Call |
| Dynamic Batching | 小物体(<900顶点) | CPU 换 GPU |
| GPU Instancing | 相同 Mesh + Material 的物体 | 一次绘制多个 |
(2)减少 Draw Call:
- 使用图集(Sprite Atlas)合并纹理
- 尽可能共用材质
- 使用 Shader 变体控制功能开关
(3)纹理优化:
- 使用合适的纹理格式(ASTC/ETC2)
- 开启 Mipmap(远处用低分辨率)
- 控制最大纹理尺寸(手机 2048 足够)
16.3 内存优化
(1)资源管理:
1
2
3
4
5
6
| // 不再使用的资源手动卸载
Resources.UnloadAsset(myTexture);
Resources.UnloadUnusedAssets();
// Addressables 引用计数自动管理
Addressables.Release(handle);
|
(2)音频优化:
- 短音效用
Decompress On Load(立即播放) - 背景音乐用
Streaming(边播边加载) - 合理设置
Force To Mono(立体声转单声道减半)
(3)避免内存泄漏:
- 事件订阅后记得取消(
OnEnable 注册,OnDisable 注销) - 协程在对象销毁后自动停止
DontDestroyOnLoad 对象谨慎使用
16.4 渲染性能 Profiling 工具
| 工具 | 用途 |
|---|
| Profiler | 分析 CPU/GPU/内存瓶颈 |
| Frame Debugger | 逐帧查看 Draw Call |
| Memory Profiler | 分析内存分配详情 |
| RenderDoc | 深度 GPU 调试 |
| Physics Debugger | 碰撞体可视化 |
16.5 Unity 常用特性关键字
| 特性 | 作用 |
|---|
[SerializeField] | 私有字段显示在 Inspector |
[Range(min, max)] | 滑动条限制数值范围 |
[Tooltip("说明")] | Inspector 中鼠标悬停提示 |
[Header("标题")] | Inspector 中分组标题 |
[HideInInspector] | 公开字段不在 Inspector 显示 |
[RequireComponent(typeof(X))] | 自动添加依赖组件 |
[ExecuteInEditMode] | 编辑模式下也执行脚本 |
[DisallowMultipleComponent] | 禁止挂载多个相同组件 |
本文涵盖 Unity 引擎从基础到进阶的核心知识点,适合 Unity 开发者系统复习和快速查阅。