为什么你的代码执行顺序总是"不听话"?深度解析 Script Execution Order 背后的机制与坑点
适合人群:Unity 初中级开发者 | 预计阅读时间:16分钟
我能帮你什么:彻底理解 Unity 脚本执行顺序、避免常见的初始化陷阱、掌握正确的依赖管理方式
🎯 一、开篇:这些执行顺序问题你遇到过吗?
- 为什么 GameManager 的
Awake晚于 Player 的Awake执行? - 设置了 Script Execution Order 但有时还是不生效?
Awake、OnEnable、Start的执行顺序到底是什么?- 为什么场景中的 A 对象先初始化,B 对象后初始化,但下次加载顺序反了?
- DontDestroyOnLoad 的对象执行顺序有什么特殊规律?
如果你也被这些问题困扰,这篇文章将为你揭开 Unity 脚本执行顺序的神秘规律。
📖 二、原理研究:Unity 脚本生命周期的底层机制
2.1 生活类比:餐厅的开业流程
想象一个餐厅的开业流程:
1. Awake(装修阶段):
- 厨房安装设备(无论是否营业,先把东西准备好)
- 所有餐厅的装修同时进行
2. OnEnable(开门营业):
- 打开店门,开始接客
- 可能多次开关门(启用/禁用)
3. Start(第一天营业前的培训):
- 只在第一次开门时进行员工培训
- 之后再开门就不需要了
4. Update(每天的营业时间):
- 每天重复的日常业务
关键洞察:
Awake是构造阶段,所有对象的 Awake 都执行完后,才会执行 OnEnableOnEnable可能多次触发(每次 SetActive(true) 或场景加载)Start只执行一次,在第一次 Update 之前
2.2 Unity 脚本生命周期完整流程
[场景加载开始]
↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第一阶段:构造与初始化(按脚本顺序)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
↓
【Awake】所有启用的脚本(批量执行)
├─ 按照 Script Execution Order 排序
├─ 如果没有设置顺序,按照以下规则:
│ ├─ 1. 场景中对象的加载顺序(不可靠)
│ ├─ 2. Hierarchy 中的顺序(不可靠)
│ └─ 3. 组件添加顺序(不可靠)
└─ ⚠️ 所有 Awake 执行完才进入下一阶段
↓
【OnEnable】所有启用的脚本(批量执行)
├─ 同样按照 Script Execution Order 排序
├─ 可能多次触发:
│ ├─ 场景加载时
│ ├─ SetActive(true)
│ ├─ enabled = true
│ └─ 组件被添加时
└─ ⚠️ 所有 OnEnable 执行完才进入下一阶段
↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第二阶段:首次更新前(按脚本顺序)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
↓
【Start】所有启用的脚本(批量执行)
├─ 按照 Script Execution Order 排序
├─ 只执行一次(即使 OnEnable 多次触发)
├─ 在第一次 FixedUpdate/Update 之前
└─ ⚠️ 惰性执行:只有在首次需要时才调用
↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第三阶段:每帧更新(循环)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
↓
【FixedUpdate】(固定时间步)
└─ 每 0.02 秒(默认)执行一次
↓
【Update】(每帧)
└─ 每帧执行一次
↓
【LateUpdate】(每帧,在 Update 后)
└─ 用于相机跟随等逻辑
↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第四阶段:销毁(按脚本顺序)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
↓
【OnDisable】
├─ SetActive(false)
├─ enabled = false
├─ 组件被移除
└─ 对象被销毁前
↓
【OnDestroy】
└─ 对象真正销毁时
源码路径:
Unity/Runtime/Export/Scripting/MonoBehaviour.bindings.csUnity/Runtime/Mono/MonoBehaviourManager.cpp
2.3 Script Execution Order 的底层实现
Unity 内部逻辑(简化):
// MonoBehaviourManager.cpp (C++ 层伪代码)
void ExecuteAwake()
{
// 1. 收集所有需要执行 Awake 的脚本
List<MonoBehaviour> components = GetAllActiveMonoBehaviours();
// 2. 按照 Script Execution Order 排序
components.Sort((a, b) =>
{
int orderA = GetScriptExecutionOrder(a.GetType());
int orderB = GetScriptExecutionOrder(b.GetType());
if (orderA != orderB)
return orderA - orderB; // 按照设置的顺序
// 如果没有设置顺序,使用默认规则(不确定)
return GetDefaultOrder(a, b);
});
// 3. 依次调用 Awake
foreach (var component in components)
{
if (!component.hasAwakeCalled)
{
component.Awake();
component.hasAwakeCalled = true;
}
}
}
int GetScriptExecutionOrder(Type type)
{
// 查找编辑器设置的执行顺序
// Edit → Project Settings → Script Execution Order
// 默认值:0
return m_ScriptExecutionOrderMap.GetValueOrDefault(type, 0);
}
关键规则:
-
顺序值(Order Value):
- 负数(-32000 到 -1):在默认脚本之前执行
- 0(默认):正常执行顺序
- 正数(1 到 32000):在默认脚本之后执行
-
相同顺序的脚本:
- 执行顺序不确定(依赖于场景加载、Hierarchy 顺序等)
- ⚠️ 不要依赖相同顺序脚本之间的执行先后
-
跨场景的脚本:
- DontDestroyOnLoad 的对象会保持原有顺序
- 新场景加载的对象会按照新场景的规则执行
2.4 常见的执行顺序陷阱
陷阱1:Awake 中访问其他脚本的字段
// ❌ 错误示例
public class Player : MonoBehaviour
{
public int health = 100;
void Awake()
{
// 初始化玩家
}
}
public class GameManager : MonoBehaviour
{
public Player player;
void Awake()
{
// ⚠️ 危险:player 可能还没有执行 Awake
Debug.Log($"Player health: {player.health}");
// ⚠️ 更危险:如果 Player.Awake 中初始化了其他引用
// player.weapon.Attack(); // NullReferenceException
}
}
原因:
- 如果 GameManager 的 Awake 先执行,Player 的 Awake 还没执行
- Player 的字段虽然有默认值(100),但如果 Awake 中有初始化逻辑,就可能出错
解决方案:
// ✅ 方案1:使用 Start(保证所有 Awake 都执行完了)
public class GameManager : MonoBehaviour
{
public Player player;
void Start() // 改为 Start
{
Debug.Log($"Player health: {player.health}"); // 安全
}
}
// ✅ 方案2:设置 Script Execution Order
// Edit → Project Settings → Script Execution Order
// Player: -100
// GameManager: 0 (默认)
// ✅ 方案3:延迟初始化
public class GameManager : MonoBehaviour
{
public Player player;
void Awake()
{
StartCoroutine(DelayedInit());
}
IEnumerator DelayedInit()
{
yield return null; // 等待一帧(所有 Awake/OnEnable 都执行完)
Debug.Log($"Player health: {player.health}");
}
}
陷阱2:OnEnable 多次触发导致的问题
// ❌ 错误示例
public class EventListener : MonoBehaviour
{
void OnEnable()
{
GameEvents.OnPlayerDeath += HandlePlayerDeath;
}
void HandlePlayerDeath()
{
Debug.Log("Player died!");
}
}
// ⚠️ 问题:如果 OnEnable 被调用多次(SetActive 切换)
// 会重复订阅事件,导致 HandlePlayerDeath 被调用多次!
解决方案:
// ✅ 正确做法:OnEnable 和 OnDisable 配对使用
public class EventListener : MonoBehaviour
{
void OnEnable()
{
GameEvents.OnPlayerDeath += HandlePlayerDeath;
}
void OnDisable()
{
GameEvents.OnPlayerDeath -= HandlePlayerDeath; // 解绑
}
void HandlePlayerDeath()
{
Debug.Log("Player died!");
}
}
陷阱3:Start 的惰性执行
// ❌ 错误示例
public class LateInitializer : MonoBehaviour
{
void Start()
{
Debug.Log("LateInitializer started");
}
}
public class GameManager : MonoBehaviour
{
void Awake()
{
GameObject obj = new GameObject("LateObject");
obj.AddComponent<LateInitializer>();
// ⚠️ 此时 LateInitializer.Start 还没执行!
// 因为 Start 是惰性执行的,在第一次 Update 前才调用
}
}
解决方案:
// ✅ 方案1:使用 Awake 而不是 Start
public class LateInitializer : MonoBehaviour
{
void Awake() // 改为 Awake(立即执行)
{
Debug.Log("LateInitializer awake");
}
}
// ✅ 方案2:手动触发初始化
public class LateInitializer : MonoBehaviour
{
private bool isInitialized = false;
public void Initialize()
{
if (isInitialized) return;
isInitialized = true;
Debug.Log("Manual initialization");
}
void Start()
{
Initialize();
}
}
public class GameManager : MonoBehaviour
{
void Awake()
{
GameObject obj = new GameObject("LateObject");
var component = obj.AddComponent<LateInitializer>();
component.Initialize(); // 手动调用
}
}
🧪 三、实验验证:通过实验理解执行顺序
实验1:测试基本生命周期顺序
using UnityEngine;
public class LifecycleTest : MonoBehaviour
{
[SerializeField] private string objectName = "Object";
void Awake()
{
Log("Awake");
}
void OnEnable()
{
Log("OnEnable");
}
void Start()
{
Log("Start");
}
void FixedUpdate()
{
if (Time.frameCount <= 2)
Log($"FixedUpdate (Frame {Time.frameCount})");
}
void Update()
{
if (Time.frameCount <= 2)
Log($"Update (Frame {Time.frameCount})");
}
void LateUpdate()
{
if (Time.frameCount <= 2)
Log($"LateUpdate (Frame {Time.frameCount})");
}
void OnDisable()
{
Log("OnDisable");
}
void OnDestroy()
{
Log("OnDestroy");
}
void Log(string message)
{
Debug.Log($"[{objectName}] {message} - Time: {Time.time:F3}");
}
}
实验步骤:
- 创建 3 个空对象:A、B、C
- 分别添加
LifecycleTest脚本 - 设置 objectName 为 “A”、“B”、“C”
- 运行游戏,观察 Console 输出
实验结果:
[A] Awake - Time: 0.000
[B] Awake - Time: 0.000
[C] Awake - Time: 0.000
[A] OnEnable - Time: 0.000
[B] OnEnable - Time: 0.000
[C] OnEnable - Time: 0.000
[A] Start - Time: 0.000
[B] Start - Time: 0.000
[C] Start - Time: 0.000
[A] FixedUpdate (Frame 0) - Time: 0.000
[B] FixedUpdate (Frame 0) - Time: 0.000
[C] FixedUpdate (Frame 0) - Time: 0.000
[A] Update (Frame 1) - Time: 0.000
[B] Update (Frame 1) - Time: 0.000
[C] Update (Frame 1) - Time: 0.000
[A] LateUpdate (Frame 1) - Time: 0.000
[B] LateUpdate (Frame 1) - Time: 0.000
[C] LateUpdate (Frame 1) - Time: 0.000
结论:
- 所有 Awake → 所有 OnEnable → 所有 Start
- FixedUpdate → Update → LateUpdate(每帧循环)
实验2:测试 Script Execution Order
using UnityEngine;
// 脚本1:Manager(应该最先执行)
public class ManagerScript : MonoBehaviour
{
public static ManagerScript Instance;
void Awake()
{
Instance = this;
Debug.Log("[1] ManagerScript.Awake");
}
void Start()
{
Debug.Log("[1] ManagerScript.Start");
}
}
// 脚本2:System(应该第二执行)
public class SystemScript : MonoBehaviour
{
void Awake()
{
Debug.Log("[2] SystemScript.Awake");
// 访问 Manager
if (ManagerScript.Instance != null)
Debug.Log(" ✓ Manager 已初始化");
else
Debug.LogError(" ✗ Manager 还未初始化!");
}
}
// 脚本3:Player(应该最后执行)
public class PlayerScript : MonoBehaviour
{
void Awake()
{
Debug.Log("[3] PlayerScript.Awake");
}
}
实验步骤:
- 创建 3 个空对象,分别添加上述脚本
- 不设置 Script Execution Order,运行游戏
- 观察输出顺序(可能是随机的)
- 设置 Script Execution Order:
- Edit → Project Settings → Script Execution Order
- ManagerScript: -100
- SystemScript: 0
- PlayerScript: 100
- 再次运行,观察输出
实验结果:
设置前(随机顺序):
[3] PlayerScript.Awake
[1] ManagerScript.Awake
[2] SystemScript.Awake
✗ Manager 还未初始化!← 出错!
设置后(正确顺序):
[1] ManagerScript.Awake
[2] SystemScript.Awake
✓ Manager 已初始化
[3] PlayerScript.Awake
实验3:测试运行时实例化的执行顺序
using UnityEngine;
public class DynamicSpawnTest : MonoBehaviour
{
[SerializeField] private GameObject prefab;
void Awake()
{
Debug.Log("[Host] Awake");
}
void Start()
{
Debug.Log("[Host] Start - 开始实例化 Prefab");
GameObject instance = Instantiate(prefab);
instance.name = "DynamicObject";
Debug.Log("[Host] Start - 实例化完成");
}
}
// 在 Prefab 上的脚本
public class PrefabScript : MonoBehaviour
{
void Awake()
{
Debug.Log($"[{name}] Awake");
}
void OnEnable()
{
Debug.Log($"[{name}] OnEnable");
}
void Start()
{
Debug.Log($"[{name}] Start");
}
}
实验结果:
[Host] Awake
[Host] Start - 开始实例化 Prefab
[DynamicObject] Awake ← 立即执行
[DynamicObject] OnEnable ← 立即执行
[Host] Start - 实例化完成
[DynamicObject] Start ← 延迟到下一帧的 Update 前
结论:
Instantiate会立即触发 Awake 和 OnEnable- Start 会延迟到下一次更新循环前执行
🏗️ 四、最佳实践:五大设计模式
模式1:单例模式(正确的初始化顺序)
using UnityEngine;
public class GameManager : MonoBehaviour
{
private static GameManager s_Instance;
public static GameManager Instance
{
get
{
if (s_Instance == null)
{
// 运行时查找(性能开销,但保证能找到)
s_Instance = FindObjectOfType<GameManager>();
if (s_Instance == null)
{
Debug.LogError("场景中没有 GameManager!");
}
}
return s_Instance;
}
}
void Awake()
{
// 检查是否已存在实例
if (s_Instance != null && s_Instance != this)
{
Debug.LogWarning("场景中有多个 GameManager,销毁多余的");
Destroy(gameObject);
return;
}
s_Instance = this;
DontDestroyOnLoad(gameObject);
Initialize();
}
void Initialize()
{
Debug.Log("GameManager 初始化");
// 初始化逻辑
}
void OnDestroy()
{
if (s_Instance == this)
{
s_Instance = null;
}
}
}
优点:
- 保证唯一实例
- 支持跨场景访问
- 避免多次初始化
模式2:依赖注入模式(解耦初始化顺序)
using UnityEngine;
// 接口定义
public interface IHealthSystem
{
int GetHealth();
void TakeDamage(int damage);
}
// 实现类
public class HealthSystem : MonoBehaviour, IHealthSystem
{
[SerializeField] private int maxHealth = 100;
private int currentHealth;
void Awake()
{
currentHealth = maxHealth;
}
public int GetHealth() => currentHealth;
public void TakeDamage(int damage)
{
currentHealth = Mathf.Max(0, currentHealth - damage);
}
}
// 使用者(不依赖具体类,只依赖接口)
public class Player : MonoBehaviour
{
private IHealthSystem healthSystem;
// 依赖注入(通过 Inspector 或代码)
public void InjectDependencies(IHealthSystem health)
{
healthSystem = health;
}
void Start()
{
// 如果没有注入,尝试自动获取
if (healthSystem == null)
{
healthSystem = GetComponent<IHealthSystem>();
}
Debug.Log($"Player health: {healthSystem.GetHealth()}");
}
}
// 初始化器(负责注入依赖)
public class GameInitializer : MonoBehaviour
{
[SerializeField] private Player player;
[SerializeField] private HealthSystem healthSystem;
void Awake()
{
// 手动注入依赖(完全控制初始化顺序)
player.InjectDependencies(healthSystem);
}
}
优点:
- 解耦依赖关系
- 便于测试(可以注入 Mock 对象)
- 完全控制初始化顺序
模式3:事件驱动模式(避免执行顺序问题)
using UnityEngine;
using System;
public class GameEvents : MonoBehaviour
{
// 全局事件
public static event Action OnGameReady;
public static event Action<int> OnScoreChanged;
void Start()
{
// 所有初始化完成后,广播事件
OnGameReady?.Invoke();
}
public static void TriggerScoreChanged(int newScore)
{
OnScoreChanged?.Invoke(newScore);
}
}
// 使用者:不依赖执行顺序
public class UIManager : MonoBehaviour
{
void OnEnable()
{
// 订阅事件
GameEvents.OnGameReady += HandleGameReady;
GameEvents.OnScoreChanged += HandleScoreChanged;
}
void OnDisable()
{
// 取消订阅
GameEvents.OnGameReady -= HandleGameReady;
GameEvents.OnScoreChanged -= HandleScoreChanged;
}
void HandleGameReady()
{
Debug.Log("UI: Game is ready");
// 初始化 UI
}
void HandleScoreChanged(int score)
{
Debug.Log($"UI: Score changed to {score}");
}
}
优点:
- 完全解耦(不需要知道其他系统的存在)
- 不依赖执行顺序
- 易于扩展
模式4:初始化状态机模式
using UnityEngine;
using System.Collections;
public class GameInitializer : MonoBehaviour
{
private enum InitState
{
None,
InitializingManagers,
LoadingResources,
InitializingSystems,
Ready
}
private InitState currentState = InitState.None;
void Start()
{
StartCoroutine(InitializeGame());
}
IEnumerator InitializeGame()
{
// 阶段1:初始化管理器
currentState = InitState.InitializingManagers;
Debug.Log("[Init] 初始化管理器...");
GameManager.Instance.Initialize();
AudioManager.Instance.Initialize();
yield return null; // 等待一帧
// 阶段2:加载资源
currentState = InitState.LoadingResources;
Debug.Log("[Init] 加载资源...");
yield return StartCoroutine(LoadResources());
// 阶段3:初始化游戏系统
currentState = InitState.InitializingSystems;
Debug.Log("[Init] 初始化游戏系统...");
PlayerSystem.Instance.Initialize();
EnemySystem.Instance.Initialize();
yield return null;
// 阶段4:完成
currentState = InitState.Ready;
Debug.Log("[Init] 初始化完成!");
// 广播事件
GameEvents.OnGameReady?.Invoke();
}
IEnumerator LoadResources()
{
// 模拟资源加载
yield return new WaitForSeconds(1f);
}
}
优点:
- 清晰的初始化流程
- 可以显示加载进度
- 易于调试
模式5:Lazy Initialization(惰性初始化)
using UnityEngine;
public class ResourceManager : MonoBehaviour
{
private static ResourceManager s_Instance;
public static ResourceManager Instance
{
get
{
if (s_Instance == null)
{
// 惰性创建(第一次访问时才创建)
GameObject obj = new GameObject("ResourceManager");
s_Instance = obj.AddComponent<ResourceManager>();
DontDestroyOnLoad(obj);
}
return s_Instance;
}
}
// 惰性加载的资源缓存
private GameObject _playerPrefab;
public GameObject PlayerPrefab
{
get
{
if (_playerPrefab == null)
{
// 第一次访问时才加载
_playerPrefab = Resources.Load<GameObject>("Prefabs/Player");
}
return _playerPrefab;
}
}
// 使用示例
public void SpawnPlayer()
{
Instantiate(PlayerPrefab); // 自动触发加载
}
}
优点:
- 不依赖初始化顺序
- 按需加载,节省资源
- 代码简洁
📊 五、技术图表
图表1:生命周期方法执行时间线
时间线(场景加载 → 运行 → 销毁):
0ms ┌─────────────────────────────────────┐
│ [场景加载] │
│ - 创建 GameObject │
│ - 添加 Component │
10ms ├─────────────────────────────────────┤
│ [Awake 批量执行] │
│ - Script A.Awake() │
│ - Script B.Awake() │
│ - Script C.Awake() │
15ms ├─────────────────────────────────────┤
│ [OnEnable 批量执行] │
│ - Script A.OnEnable() │
│ - Script B.OnEnable() │
│ - Script C.OnEnable() │
20ms ├─────────────────────────────────────┤
│ [首次 Update 前] │
│ [Start 批量执行] │
│ - Script A.Start() │
│ - Script B.Start() │
│ - Script C.Start() │
25ms ├─────────────────────────────────────┤
│ [游戏循环开始] │
│ │
│ Frame 1: │
│ FixedUpdate → Update → LateUpdate │
│ │
│ Frame 2: │
│ FixedUpdate → Update → LateUpdate │
│ │
│ ... (持续运行) │
│ │
?ms ├─────────────────────────────────────┤
│ [对象销毁/禁用] │
│ - OnDisable() │
│ - OnDestroy() │
└─────────────────────────────────────┘
图表2:Script Execution Order 工作机制
脚本执行顺序决策树:
所有启用的 MonoBehaviour
↓
按照 Script Execution Order 排序
↓
┌─────────────────────────────────────┐
│ Order: -32000 到 -1 │ ← 最先执行
│ - ManagerScript (-100) │
│ - ConfigScript (-50) │
├─────────────────────────────────────┤
│ Order: 0 (默认) │ ← 默认脚本
│ - PlayerScript │
│ - EnemyScript │
│ - UIScript │
│ ⚠️ 这些脚本之间的顺序不确定 │
├─────────────────────────────────────┤
│ Order: 1 到 32000 │ ← 最后执行
│ - CameraController (100) │
│ - DebugScript (1000) │
└─────────────────────────────────────┘
↓
依次调用 Awake/OnEnable/Start
🎓 六、总结与延伸
核心要点回顾
-
生命周期顺序:
- 所有 Awake → 所有 OnEnable → 所有 Start → Update 循环
- Awake 是构造阶段,Start 是初始化阶段
- OnEnable 可能多次触发,Start 只执行一次
-
Script Execution Order:
- 用于控制脚本的执行先后
- 负数先执行,正数后执行,默认为 0
- 相同顺序的脚本执行顺序不确定
-
常见陷阱:
- Awake 中访问其他对象的引用(可能未初始化)
- OnEnable 多次触发导致重复订阅
- Start 的惰性执行特性
-
最佳实践:
- 使用单例模式管理全局对象
- 使用依赖注入解耦初始化顺序
- 使用事件驱动避免顺序依赖
- 使用初始化状态机管理复杂初始化流程
- 使用惰性初始化延迟加载
调试技巧
// 调试工具:打印所有脚本的执行顺序
#if UNITY_EDITOR
using UnityEditor;
[InitializeOnLoad]
public static class ScriptExecutionOrderDebugger
{
static ScriptExecutionOrderDebugger()
{
EditorApplication.playModeStateChanged += OnPlayModeChanged;
}
static void OnPlayModeChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.EnteredPlayMode)
{
PrintScriptExecutionOrder();
}
}
static void PrintScriptExecutionOrder()
{
Debug.Log("===== Script Execution Order =====");
foreach (MonoScript script in MonoImporter.GetAllRuntimeMonoScripts())
{
if (script.GetClass() != null && script.GetClass().IsSubclassOf(typeof(MonoBehaviour)))
{
int order = MonoImporter.GetExecutionOrder(script);
if (order != 0) // 只显示设置了顺序的
{
Debug.Log($"{script.GetClass().Name}: {order}");
}
}
}
}
}
#endif
延伸阅读清单
-
官方文档
-
深度文章
-
本系列其他文章
- 《ScriptableObject 的底层存储与序列化机制》
- 《PlayerLoop 与自定义更新机制》
💡 互动思考题
- 如果你的项目有 100 个脚本,应该如何管理它们的初始化顺序?
- Awake 和 Start 的选择原则是什么?什么时候用 Awake,什么时候用 Start?
- 如何设计一个完全不依赖执行顺序的初始化系统?
欢迎在评论区分享你的初始化设计经验!
系列完结:恭喜你完成了 Unity 底层与原理向的全部 7 篇文章!
系列回顾:
- ScriptableObject 底层存储与序列化机制
- Addressables vs AssetBundle 设计哲学
- Unity 资源依赖关系与打包陷阱
- Unity 序列化机制的黑盒解析
- Unity 内存模型(Managed/Native 分布)详解
- PlayerLoop 与自定义更新机制
- Script Execution Order 背后的机制与坑点
作者:[胡利光] | Unity 技术博主
专注于 Unity 底层原理与架构设计
如果这个系列对你有帮助,欢迎点赞、收藏、转发!
期待在评论区看到你的学习心得和项目经验分享!
2548

被折叠的 条评论
为什么被折叠?



