Unity知识梗概

Unity 引擎核心知识点系统性总结,涵盖生命周期、协程、对象池、UGUI、寻路等

一、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 角色自动规划路径。

操作步骤:

  1. 标记场景中所有静态障碍物为 Navigation Static
  2. 打开 Window/AI/Navigation 面板
  3. 调整参数:
    • Agent Radius:角色半径(决定能通过的最小通道)
    • Agent Height:角色高度
    • Max Slope:最大可爬坡度数
    • Step Height:最大可跨越台阶高度
  4. 点击 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:

  1. 在 Navigation 面板添加 Layer(如"Ground"、“Roof”)
  2. 为不同高度的物体分配不同 Layer
  3. 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)

实现"边跑边射击"的核心技术是动画分层。将身体分为上下两层,分别播放不同动画。

实现步骤:

  1. 在 Animator Controller 中创建两个 Layer:

    • Base Layer:处理移动动画(走、跑、跳跃)
    • Upper Body Layer:处理上身动画(射击、换弹、投掷)
  2. 为 Upper Body Layer 设置 Avatar Mask:

    • 创建 Avatar Mask 资产
    • 仅勾选上半身骨骼(Spine、Chest、Left/Right Arm、Head)
    • 下半身骨骼交由 Base Layer 控制
  3. 代码控制 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 开发者系统复习和快速查阅。

不嘻嘻
使用 Hugo 构建
主题 Stack 由 Jimmy 设计