关于 Unity
Unity 是面向 Unity 游戏开发者的开发指南,涵盖从项目搭建到发布。它将 C# 实践、架构选择和性能建议与专题参考文件结合起来。
主要功能
- 01项目结构与资源规范
- 02C# 和 MonoBehaviour 模式
- 03架构与设计模式
- 04性能分析与优化建议
- 05工作流、测试与构建实践
- 06Unity 6 与专项系统
适用场景
- 01整理不断扩大的 Unity 项目
- 02编写或审查游戏脚本
- 03排查性能问题
- 04选择游戏架构与工作流
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ juliankerignard/ unity-skills --skill unity安装后在 Claude Code 中输入/unity确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/architecture-advanced.mdarchitecture.mdworkflow.mdperformance.mdcsharp-patterns.mdsystems.mdspecialized.mdproject-structure.md详细介绍
Unity 开发指南
经过实战验证的 Unity 最佳实践,涵盖从项目设置到发布的各个环节。这里是入门指南;如需深入了解,请参阅相应的参考文件。
参考文件 — 何时阅读哪些内容
| 你正在进行的工作…… | 阅读此文件 |
|---|---|
| 整理文件夹、命名资源、设置导入选项 | references/project-structure.md |
| 编写或审查 C# 脚本 | references/csharp-patterns.md |
| 排查性能问题或进行优化 | references/performance.md |
| 设计游戏架构、模式和系统 | references/architecture.md |
| 设置 Git、测试、CI/CD 或构建 | references/workflow.md |
| 动画、2D、Shader/VFX Graph | references/specialized.md |
| 调试、本地化、无障碍、AI/Inference Engine | references/systems.md |
| 设置 UI Toolkit 或 Cinemachine | references/architecture-advanced.md |
编写代码前,请阅读相关参考文件;其中的模式可以帮你避免大量返工。
1. 项目结构
与按类型划分的文件夹相比,按功能划分的文件夹更易于扩展,因为随着项目增长,相关代码仍能放在一起:
Assets/
├── _Project/
│ ├── Scripts/
│ │ ├── Core/ # Game loop, managers, bootstrap
│ │ ├── Player/ # Controller, input, camera
│ │ ├── Enemies/ # AI, spawners, enemy types
│ │ ├── UI/ # Views, presenters, screens
│ │ ├── Data/ # ScriptableObject definitions
│ │ └── Utils/ # Extensions, helpers
│ ├── Prefabs/
│ ├── Scenes/
│ ├── ScriptableObjects/ # Data asset instances
│ ├── Art/ # Models, Textures, Materials, Animations, Shaders
│ └── Audio/ # SFX, Music
├── Plugins/ # Third-party SDKs
└── Settings/ # Render pipeline, input, quality
为什么要用 _Project/?下划线前缀会将它固定在 Assets 文件夹顶部,让你的代码与导入的包和第三方资源分开。
为什么不用 Resources/?Unity 会在启动时将 Resources/ 中的所有内容加载到内存中。对于少量小型资源以外的内容,更推荐使用 Addressables 系统;它可以按需加载,并支持远程内容分发。
有关文件夹组织、资源命名约定、导入设置、预制件/场景约定,以及如何从游戏创作活动扩展到大型项目的深入说明,请参阅 references/project-structure.md。
程序集定义
将代码拆分到程序集(.asmdef)中,是提升迭代速度最有效的方法之一。没有程序集定义时,任何脚本发生更改都会触发全部内容重新编译。使用程序集定义后,Unity 只会重新编译受影响的程序集:
Scripts/Core/ → Game.Core.asmdef
Scripts/Player/ → Game.Player.asmdef (refs: Game.Core)
Scripts/UI/ → Game.UI.asmdef (refs: Game.Core)
Scripts/Utils/ → Game.Utils.asmdef (no dependencies)
命名约定
| 元素 | 约定 | 示例 |
|---|---|---|
| 类 / 结构体 | PascalCase | PlayerController |
| 公共方法 | PascalCase | TakeDamage() |
| 私有字段 | _camelCase | _currentHealth |
| 序列化字段 | [SerializeField] + _camelCase | [SerializeField] private float _speed; |
| 接口 | I 前缀 | IDamageable |
| ScriptableObject | SO 后缀 | WeaponDataSO |
| 枚举 | PascalCase,使用单数形式 | WeaponType { Sword, Bow } |
2. 核心 C# 模式
这些模式几乎会出现在每个 Unity 项目中。如需查看完整目录(生命周期顺序、异步模式、扩展方法、编辑器脚本),请参阅 references/csharp-patterns.md。
整洁的 MonoBehaviour
结构良好的 MonoBehaviour 会遵循统一的布局:顶部是序列化配置,接着是事件、私有状态,最后是生命周期方法。这样可以一眼看清类的结构。
public class PlayerHealth : MonoBehaviour, IDamageable
{
[Header("Config")]
[SerializeField] private int _maxHealth = 100;
[SerializeField] private float _invincibilityTime = 0.5f;
[Header("Refs")]
[SerializeField] private HealthBarUI _healthBar;
public event Action<int, int> OnHealthChanged; // current, max
public event Action OnDeath;
private int _currentHealth;
private bool _isInvincible;
private void Awake() => _currentHealth = _maxHealth;
private void Start() => _healthBar?.SetMaxHealth(_maxHealth);
public void TakeDamage(int damage)
{
if (_isInvincible || _currentHealth <= 0) return;
_currentHealth = Mathf.Max(0, _currentHealth - damage);
OnHealthChanged?.Invoke(_currentHealth, _maxHealth);
if (_currentHealth <= 0) { OnDeath?.Invoke(); return; }
StartCoroutine(InvincibilityCo());
}
private IEnumerator InvincibilityCo()
{
_isInvincible = true;
yield return new WaitForSeconds(_invincibilityTime);
_isInvincible = false;
}
}
核心原则
优先使用 [SerializeField] private,而不是 public 字段。 公共字段会破坏封装——其他脚本可以随意写入,导致难以追踪的错误。[SerializeField] 能在 Inspector 中显示字段,同时让它对其他代码保持私有。
每个组件只负责一项职责。 将 PlayerMovement、PlayerHealth、PlayerCombat 分开,而不是使用一个庞大的 Player。这样你可以迭代单个系统,而不影响其他系统,也能让组件更易于复用。
在 Awake() 中缓存引用。 GetComponent、Find 和 FindObjectOfType 都是搜索操作。在运行时调用它们(尤其是在 Update 中)会浪费 CPU 周期。应在初始化时缓存一次。
优先使用 TryGetComponent<T>(),而不是 GetComponent<T>()——它会返回一个 bool,避免空引用异常,而且速度略快。
使用事件进行通信,而不是直接耦合。 当系统 A 需要告知系统 B 某件事已经发生时,使用 C# 事件或 ScriptableObject 事件通道。这样能让系统彼此独立且易于测试。
ScriptableObject 架构
ScriptableObject(SO)是存放在项目中、可跨场景持久存在的资源。它们是实现高内聚、低耦合 Unity 项目的基石。
数据容器——武器属性、敌人配置、关卡设置:
[CreateAssetMenu(menuName = "Game/Weapon Data")]
public class WeaponDataSO : ScriptableObject
{
public string weaponName;
[Range(1, 100)] public int damage = 10;
[Range(0.1f, 5f)] public float attackSpeed = 1f;
public AudioClip[] attackSounds;
}
事件通道——系统之间完全解耦的发布/订阅:
[CreateAssetMenu(menuName = "Events/Void Event")]
public class VoidEventChannelSO : ScriptableObject
{
private Action _onRaised;
public void Raise() => _onRaised?.Invoke();
public void Subscribe(Action fn) => _onRaised += fn;
public void Unsubscribe(Action fn) => _onRaised -= fn;
}
其妙处在于:发布者只需在 SO 资源上调用 Raise()。订阅者引用同一个资源并监听事件。双方都不知道对方的存在。你可以通过 Inspector 将它们连接起来。完整的模式集合(运行时集合、共享变量)请参阅 references/architecture.md。
3. 性能——基本要点
性能问题主要分为三类:CPU(每帧处理的逻辑过多)、GPU(渲染内容过多)和内存(分配导致 GC 暂停)。首要原则是先分析性能,再进行优化——在凭猜测之前,先打开 Window > Analysis > Profiler。
帧预算: 60fps 时为 16.6ms,30fps 时为 33.3ms。每超出预算 1ms,就会丢失一帧。
热路径中零分配
垃圾回收器(GC)运行时会暂停游戏。在 Update()、FixedUpdate() 或任何逐帧代码中进行分配,是导致这一问题的主要原因。
// Problematic — allocates every frame
void Update() {
var enemies = FindObjectsOfType<Enemy>(); // array allocation
string s = "HP: " + _health; // string concat
var list = new List<int>(); // list allocation
}
// Better — pre-allocate, reuse
private readonly StringBuilder _sb = new();
private readonly List<int> _reusable = new();
void Update() {
_sb.Clear().Append("HP: ").Append(_health);
_reusable.Clear();
// ... fill _reusable as needed
}
对象池
频繁调用 Instantiate() / Destroy() 会导致 GC 峰值和内存碎片。应改用对象池。Unity 6+ 开箱即提供 UnityEngine.Pool.ObjectPool<T>,也可以使用 Queue<GameObject> 自行构建。
渲染速效优化
- 对静止对象使用 Static Batching(在 Inspector 中勾选“Static”)
- 对重复网格使用 GPU Instancing(在 Material 上启用)
- 为复杂 3D 模型使用 LOD Groups
- 尽可能使用烘焙光照——实时光照开销很大,尤其是在移动设备上
- 在室内场景或复杂场景中使用 Occlusion Culling
完整的性能优化指南(物理、移动设备目标、纹理预算、性能分析工作流、Jobs/Burst)请参阅 references/performance.md。
4. 架构决策
根据项目规模选择
| 规模 | 团队 | 推荐方案 |
|---|---|---|
| Game jam / 原型 | 1-2 | 单例 + 直接引用 |
| 小型独立游戏 | 1-3 | ScriptableObject 架构 |
| 中型 | 3-10 | SO 架构 + Service Locator |
| 大型 / 长期运营服务 | 10+ | DI 框架(VContainer)+ 按需使用 ECS |
不要为项目规模不必要地过度设计。Game jam 不需要依赖注入,而一款运营 2 年的长期服务型游戏则需要。
关键模式摘要
| 模式 | 使用场景 |
|---|---|
| SO Event Channels | 跨场景的解耦通信 |
| State Machine | 玩家状态、AI、游戏流程、UI 屏幕 |
| Service Locator | 无需单例即可访问全局系统 |
| Command | 输入处理、撤销/重做、回放 |
| MVP(Model-View-Presenter) | UI 系统 |
| Object Pool | 频繁生成/销毁的对象 |
每种模式的完整实现请参阅 references/architecture.md。
5. 现代 Unity(Unity 6+)
Unity 6 是当前一代版本。最新的 LTS 版本是 Unity 6.3(2025 年 12 月),这是新项目推荐的基线版本。Unity 按季度更新(6.1 → 6.2 → 6.3 LTS → 6.4+)。
渲染管线选择
除非需要特定的 HDRP 功能(光线追踪、体积雾、高端主机级画面保真度),否则请从 URP 开始。URP 支持移动设备、VR、2D 和中端 PC。Unity 正在转向统一渲染器(共用 Render Graph 后端),以衔接 URP 和 HDRP——但目前,在项目中途切换渲染管线仍然很麻烦。请尽早决定。
Addressables
在生产环境中用 Addressables 替代 Resources.Load()。Addressables 可按需加载资源、支持异步加载,并支持远程内容分发。请牢记:用完后务必释放句柄,否则会造成内存泄漏。
新输入系统
如果项目面向多个平台或输入设备,请使用新的 Input System 包。它通过一个统一的抽象层(Input Actions 资源)处理键盘、游戏手柄、触控和 XR 输入。
Unity 6 中的 API 变更
为提高可读性,多个 API 更改了名称:
Rigidbody.velocity→Rigidbody.linearVelocity(Rigidbody2D 同样适用)Rigidbody.angularVelocity仍然保留,但请留意弃用警告- Cinemachine 2.x → Cinemachine 3.x(
CinemachineVirtualCamera→CinemachineCamera,组件名称有显著变化——参见 architecture.md §10)
Unity 6.3 LTS 亮点
- Box2D v3 底层 2D 物理 API(多线程、确定性、可视化调试)——可与现有 API 并行运行,最终将取而代之
- Platform Toolkit——用于跨 PS5/Xbox/Switch/Steam/Android/iOS 管理账户、成就和存档数据的统一 API
- Mesh LOD——在编辑器中为静态网格和蒙皮网格自动生成 LOD
- Shader Graph——支持地形着色器、8 组纹理坐标,以及模板浏览器
- Scriptable Audio Pipeline——通过 Burst 编译的 C# 处理器扩展音频链
- UI Toolkit 新增自定义着色器、滤镜、SVG(现已成为核心模块)和增强的世界空间支持
- 2D Renderer 现在可在 2D URP 中与精灵一同渲染 Mesh Renderer 和 Skinned Mesh Renderer
- Sprite Atlas Analyser 工具,用于查找图集打包效率问题
DOTS / ECS
当你需要处理数千个相似实体时(大规模模拟、群体 AI、服务器端多人游戏),ECS 才能发挥优势。对于大多数活跃实体少于几百个的独立游戏和中型项目,传统的 MonoBehaviour 方式更简单,也足以满足需求。如果使用 DOTS,请保留 Unity.Mathematics 类型,使用 EntityCommandBuffer 进行结构性更改,并结合使用 ECS(逻辑)与 GameObjects(创作/渲染)。Unity 的路线图计划在未来版本中进一步统一 ECS。
Netcode for GameObjects
对于多人游戏:从第一天起就采用服务器权威架构。在服务器上验证所有输入,使用 NetworkVariable<T> 同步状态,并应用客户端预测以提升响应感。仅同步增量,尽量减少带宽使用。
6. 常见反模式
| 反模式 | 不良影响 | 更好的做法 |
|---|---|---|
臃肿的 GameManager 类 | 难以维护,所有内容彼此耦合 | 拆分为职责明确的系统 |
在 Update 中使用 Find*() | 每帧都进行线性搜索 | 在 Awake 中缓存,使用事件 |
| 到处使用公共字段 | 缺乏封装,难以追踪错误 | 使用 [SerializeField] private |
| 基于字符串的动画参数 | 拼写错误会导致静默失败 | 缓存 Animator.StringToHash() 的结果 |
| 深层继承 | 结构僵化,难以重构 | 组合 + 接口 |
| 硬编码魔法数字 | 无法在不重新编译的情况下调整 | 使用 ScriptableObjects 或 const |
7. 快速决策参考
| 决策 | 默认选择 | 在以下情况下考虑替代方案…… |
|---|---|---|
| 渲染管线 | URP | 需要光线追踪、体积雾 → HDRP |
| 资源加载 | Addressables | 小型项目,资源少于 20 个 → Resources |
| 输入 | New Input System | 仅限编辑器的工具,无需游戏手柄 → 旧版 Input |
| 异步 | Awaitable(Unity 6+) | Unity 6 之前的版本 → UniTask 或 Coroutines |
| 多人游戏 | Netcode for GameObjects | MMO/大规模项目 → 专用服务端 + ECS |
| UI(菜单、HUD) | UI Toolkit | 世界内 UI 或旧版项目 → uGUI Canvas |
| 摄像机 | Cinemachine | 非常简单的固定摄像机 → 手动脚本 |
| 物理 | 内置 3D/2D Physics | 刚体超过 10K → DOTS Physics |
| 粒子(少于 100 个) | Shuriken(Particle System) | 粒子超过 1000 个或需要 GPU 特效 → VFX Graph |
| 粒子(1000 个以上) | VFX Graph | 不支持计算着色器的移动平台 → Shuriken |
| 本地化 | Unity Localization package | 小型 Game Jam 项目 → 硬编码字符串 |
| 版本控制 | Git + LFS | 大型团队、二进制文件较多 → Perforce / Plastic |
| 动画混合 | Blend Trees | 无需混合的离散状态 → 简单过渡 |
| 2D 物理 | 内置 Physics2D | 需要高性能/确定性 → Box2D v3 低级 API(6.3+) |
| 跨平台服务 | Platform Toolkit(6.3+) | 较旧的 Unity 版本 → 按平台手动使用 SDK |