How to Develop a Game
Unity
PublishDate: 2025-06-01 | CreateDate: 2025-06-01 | LastModify: 2025-06-01 | Creator:ljf12825

引擎整体架构

引擎中有什么,系统如何被设计出来(静态结构),系统模块关系、职责划分、调用层级

Unity Player(C++引擎内核)

Unity Player是Unity的心脏,它是引擎的底层C++实现,是多个底层模块的统称,负责把游戏逻辑变成实际在不同平台上运行的程序

定位与作用

它本身不处理脚本逻辑,脚本逻辑最终会通过Scripting Runtime(Mono / IL2CPP)调用Player的C++接口

核心模块

  1. 渲染子系统
  1. 物理子系统
  1. 音频子系统
  1. 输入系统
  1. 资源与内存管理
  1. 线程与任务调度

与Scripting Runtime的关系

Scripting Runtime(Mono/IL2CPP)

这是Unity中连接“脚本逻辑”和Unity Player的桥梁,属于中间层运行时系统\

角色与定位

Scripting Runtime负责

它是Unity脚本世界的虚拟机,Unity提供两种实现方式

Mono&IL2CPP

托管内存与GC机制

无论是Mono还是IL2CPP,脚本层都运行在托管内存环境

Unity自2019起采用了增量式GC(Incremental GC),缓解了帧冻结问题

脚本与C++交互机制(Bindings)

NativeLayer to ScriptLayer

脚本中transform.position = new Vector3(1, 0, 0)
实际上是:C#对象 -> IL2CPP桥接 -> C++原生Transform -> 改变底层数据

[C#脚本] 
    编译为IL
[Mono / IL2CPP Runtime]
    调用桥接接口
[C++ Engine Core (Unity Player)]
   
[底层系统渲染 / 物理 / 音频 / 输入 ...]

C#与C++之间的调用成本(Interop Overhead)

这是Unity性能优化的一个“隐形税收点”
当C#调用C++时会产生上下文切换和封送(marshalling)开销,比如:

Unity为此推出了Generated Bindings系统,让IL2CPP能自动生成“零封送”接口,大幅减少了调用开销。这也是Unity越来越接近原生引擎性能的关键

平台抽象层(Platform Abstraction Layer, PAL)

决定了跨平台能力和性能底线的部分

定位与作用

平台抽象层是Unity C++引擎中介于引擎通用逻辑和具体平台实现之间的中间层
它的核心使命是:屏蔽操作系统、硬件、API差异,给上层(Unity Player / Scripting Runtime)提供统一接口
Unity的跨平台本质上就是靠这层在做“同一接口,不同平台不同实现”

[C# 脚本层]
     
[Scripting Runtime (Mono/IL2CPP)]
     
[Unity Player (C++ 引擎核心)]
     
[平台抽象层Platform Abstraction Layer]
     
[操作系统 / 硬件 / 平台SDK]

比如脚本调用File.Open()

PAL的主要模块

  1. 图形抽象层(GfxDevice Layer)
  1. 文件系统抽象(FileSystem Layer)
  1. 输入抽象(Input Layer)
  1. 音频抽象(Audio Layer)
  1. 线程与任务系统(Threading & Job System Layer)
  1. 网络抽象(Network Layer)
  1. 时间与计时系统(Time Layer)

平台特化(Platform Specific Layer)

PAL之下,Unity针对每个平台都有特化实现,比如

Runtime/PlatformDependent/Win/
Runtime/PlatformDependent/Android/
Runtime/PlatformDependent/iOS/
Runtime/PlatformDependent/Linux/

这些目录中是

Unity编译时通过宏开关(如UNITY_ANDROID, UNITY_WIN, UNITY_IOS)选择不同文件编译入引擎

对游戏开发者的意义

虽然普通开发者看不到PAL,但它影响深远

Unity的核心结构是一种“三明治模型”: C#托管逻辑(上层)<-> C++引擎内核(中层)<-> 系统平台接口(底层) 这种设计的关键价值是可移植性与扩展性。C++层保持跨平台的引擎逻辑一致性,而PAL层“翻译”系统调用。这样Unity能再不同设备上跑同一逻辑,而脚本开发者甚至无需关心平台差异

渲染管线(Build-in / URP / HDRP)

渲染管线负责把场景数据(模型、光照、材质、相机)转换成图像像素的整个过程

渲染管线本质

渲染管线的本质是一条”数据流“

场景(Scene)-> 摄像机(Camera)-> 可见物体(Renderer)-> Shader -> GPU渲染 -> 屏幕图像

它定义了:

Unity之所以能输出不同风格(卡通、写实、移动端轻量、高端HDR),本质就是在更换渲染管线实现

三代渲染管线的变化

Unity目前存在三种主流渲染管线

管线类型名称特点适用场景
Build-in Pipeline内置管线(传统)固定流程,难以自定义老项目、教学、移动端轻量
URP通用渲染管线(Universal Render Pipeline)可扩展、跨平台优化移动端、Switch、主机中端
HDRP高清渲染管线(High Definition Render Pipeline)高保真、PBR、物理级光照高端PC、主机、电影级项目

Build-in Pipeline(传统内置管线)

架构特点:

优点:

缺点:

Build-in就是“Unity替你写好了渲染管线,你只能在Shader里打补丁”

URP(Universal Render Pipeline)

URP是Unity为现代设备设计的“可编程渲染管线”,基于Scriptable Render Pipeline(SPR)架构

架构核心:

C#层:RenderPipelineAsset / RenderPipeline
C++层:Unity Player 渲染接口
GPU层:ShaderHLSL

工作机制:

  1. Unity在启动时加载URP的Pipeline Asset
  2. C#代码通过RenderPipeline控制每一帧的渲染过程
    • 过滤可见对象
    • 设置RenderTarget
    • 执行渲染Pass
  3. URP内部执行优化
    • SRP Batcher(批处理优化)
    • GPU Instancing(实例化)
    • Forward + 渲染(更高效的多光源渲染)
    • Shader Variant剔除

特点:

适用:
移动游戏、中等规格主机、VR、独立游戏

URP是“为性能和兼容性平衡而生的现代可编程管线”

HDRP(High Definition Render Pipeline)

HDRP是Unity面向高端平台的高保真管线,核心是物理化渲染(Physically-Based Rendering)

核心特性:

架构上:

HDRP是“写实画面的终极方案”,但代价是性能和开发复杂度

Scriptalbe Render Pipeline(SRP)的统一思想

URP和HDRP都基于同一个框架:SRP
SRP让Unity的渲染管线“从硬编码” -> “脚本可编程”
可以写出自定义管线

核心类:

RenderPipelineAsset // 管线资源配置
RenderPipeline // 每帧执行逻辑
RenderPass / RenderFeature //扩展模块

这意味着:

SRP的出现让Unity从“游戏引擎”进化成“渲染框架”

可拔插渲染框架思想(RenderGraph + GPU Driven Rendering)的趋势:

Unity正在逐渐把SRP向RenderGraph体系演化,用命令图(Command Graph)组织GPU Pass,未来会更接近Unreal的RDG或自研引擎的GPU-driven架构

渲染正在从“指令式”走向“数据驱动 + 并行调度”

物理与动画子系统

物理子系统(Physics)

物理子系统负责模拟真实世界的力学行为,包括刚体、碰撞、关节、角色控制器等

核心组成

  1. 刚体(Rigidbody)
    • 表示一个受力的物体
    • 属性:质量、阻力、重力、速度等
    • 控制方式:
      • 通过物理力(AddForce)驱动
      • 直接修改位置(会跳过物理)
  2. 碰撞器(Collider)
    • 定义物体形状,用于检测碰撞
    • 类型:
      • 基本形状:Box, Sphere, Capsule
      • MeshCollider(复杂模型)
  3. 物理引擎核心
  1. 场景集成
    • 每帧固定更新(FixedUpdate)物理世界
    • 与渲染循环独立,保证模拟稳定性
    • 支持多进程计算(Job System + Burst优化)

脚本层交互

C#层的Rigidbody, Collider等都是托管对象,内部绑定对应C++的原生PhysX对象

Rigidbody rb = GetComponent<Rigidbody>();
rb.AddForce(Vector3.up * 10f);

动画子系统(Animation)

动画子系统负责角色、物体的运动表现,包括骨骼动画、Blend、State Machine等

核心模块

  1. Animator/Animation组件
    • Animator使用Mecanim系统,支持状态机和Blend Tree
    • Animation是旧动画系统,主要用于简单动画
  2. 骨骼动画(Skinned Mesh)
    • Mesh通过骨骼(Bones)驱动顶点变形
    • 支持GPU skinning加速
  3. 状态机(Animator Controller)
    • 控制动画播放逻辑(Idle -> Walk -> Run)
    • 支持条件转换(Triggers, Booleans, Floats)
    • 每帧更新在Update/AnimatorUpdate模式下执行
  4. Blend Tree
    • 混合多种动画,实现平滑过渡
    • 用于复杂动作组合,如走跑切换、方向移动
  5. IK/动态调整
    • Inverse Kinematics(逆向运动学)
    • 让角色手、脚自然接触地面或环境

脚本层交互

Animator anim = GetComponent<Animator>();
anim.SetFloat("Speed", 1.0f);
anim.SetTrigger("Jump");

物理与动画的协作

  1. Rigidbody与Animator
    • Animator默认不影响物理(“根运动 Root Motion”可选)
    • Root Motion开启时,动画驱动Rigidbody位置
    • Root Motion关闭时,脚本驱动Rigidbody位置,而Animator只负责骨骼动画
  2. 碰撞与动画
    • 动画过程中可以触发碰撞事件
    • 游戏逻辑可以根据碰撞/触发器调整动画状态

总结

Editor扩展层

这是Unity架构中连接用户编辑操作和底层引擎的部分
它不影响游戏运行时逻辑,但对于提高生产效率、开发工具、定制工作流至关重要

定位与作用

Editor扩展层 = “Unity的可编程工作台”,开发者可以改造编辑器来提升效率或定制工作流

核心模块

  1. Editor Window/Editor GUI
    • 自定义窗口、面板、工具栏
    • 基于IMGUI或UIElements绘制界面
    • 可以访问Unity对象(GameObject、ScriptableObject、Asset)
  2. Inspector / Property Drawer
    • Inspector窗口显示对象属性
    • PropertyDrawer允许自定义组件属性的显示和编辑方式
  3. Editor Utility
    • 文件操作、资源管理、序列化、日志、调试工具
    • ScriptableWizard、MenuItem、EditorCoroutine
  4. Scene View/Gizmos
    • 直接在Scene视图绘制辅助信息
    • 自定义Gizmos显示逻辑,如碰撞体、路径、AI节点
  5. Custom Asset Pipeline
    • 自定义资源导入器(ScriptedInporter)
    • 自动处理特定资源格式、生成AssetBundle、Addressable资源
  6. Play Mode Simulation
    • 编辑器模式下模拟运行
    • PlayMode下的资源加载、脚本执行、物理和动画系统都调用底层Player

脚本层交互

Editor扩展层完全在C#托管环境,与游戏运行层使用不同运行上下文:

它们都通过Scripting Runtime桥接C++引擎,但编辑器扩展有额外API层用于编辑器操作

using UnityEditor;
using UnityEngine;

public class MyWindow : EditorWindow
{
  [MenuItem("Tools/My Window")]
  static void ShowWindow() => GetWindow<MyWindow>("My Window");

  void OnGUI()
  {
    if (GUILayout.Button("Create Cube"))
    {
      GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
      Undo.RegisterCreateObjectUndo(cube, "Create Cube");
    }
  }
}

Editor扩展与工具链

总结

Editor扩展层是“可编程编辑器 + 工具接口”,是Unity生产力和自动化的核心

运行时结构

运行时结构指的是————程序在运行过程中各个模块之间的组织方式、生命周期、以及数据和控制流的关系
它回答了三个问题:

Unity在游戏启动后会维持一个完整的“虚拟世界”的组织体系
当游戏从可执行文件启动时,Unity会依次初始化引擎模块、加载场景、实例化对象、执行脚本、维护帧循环,再到最终退出。

GameObject & Component系统

Unity的运行时世界由GameObject和Component构成

在C++层,每个GameObject对应一个GameObject结构体实例,内部维护组件列表(C#层表现为GetComponentAddComponent接口)
脚本层的组件(MonoBehaviour)只是托管包装,实际行为逻辑通过绑定机制连接C++对象

Unity世界 = 对象树(Transform Hierarchy)+ 组件系统(Behavior Modules)

Scene & Asset管理

场景(Scene)是对象的集合

资源加载有三种主要路径:

Scene系统本质是“对象状态的快照”,运行时通过反序列化重建整个世界

生命周期管理

Unity的运行时生命周期可以分为三个层面:
引擎启动层面
RuntimeInitialize -> 初始化Player -> 初始化模块(渲染、物理、音频、输入等) -> 加载第一个场景

对象层面
每个MonoBehaviour实例会经历:
Awake() -> OnEnable() -> Start() -> Update()/FixedUpdate()/LateUpdate() -> OnDisable() -> OnDestroy()
C++对象(Transform、Renderer等)在销毁时会触发托管对象解绑(GCHandle释放)

帧循环层面
每一帧:

  1. 处理输入(InputSystem)
  2. 执行物理更新(FixedUpdate)
  3. 调用脚本逻辑(Update/LateUpdate)
  4. 执行动画系统
  5. 渲染提交(Camera.Render)
  6. 提交GPU命令 -> 显示输出

生命周期系统维持了Unity世界的“持续运作”,是时间与逻辑的主线

脚本绑定与执行机制

Unity脚本运行在托管层(Mono/IL2CPP),核心机制是托管对象 <-> 原生对象绑定(Binding)

运行时流程:

  1. C#编译为IL字节码
  2. Mono或IL2CPP执行
  3. 脚本调用C# API,如transform.position
  4. 通过绑定接口(InternalCall或Generated Bindings)访问C++对象
  5. 修改底层引擎数据

TransformRigidbodyRenderer等对象其实是C#包装 + C++实体
脚本层操作的是“代理”,数据变更最终反映在C++引擎核心中

脚本驱动引擎,而引擎执行结果再反馈回脚本世界

内存模型与GC管理

Unity的运行时有双层内存体系

| 层级 | 语言 | 管理方式 | 内容 | | 托管层 | C#(Mono/IL2CPP) | GC | 脚本对象、数据逻辑 | | 原生层 | C++(Unity Player) | 手动分配/释放 | 渲染资源、物理数据、引擎核心 |

C#层对象通过GCHandleNativeArray等结构与原生内存交互
GC采用增量式GC(Incremental GC),分帧执行,降低卡顿

高性能开发要点:

GC是Unity运行时世界的“代谢系统”,要控制频率、减少毒素

系统协作与调度(System Collaboration & Scheduling)

系统协作与调度决定了每个子系统何时启动、如何交互、谁先谁后、怎样同步

核心理念:PlayerLoop驱动的分层世界

Unity运行时的根本调度机制就是一个多层嵌套的循环体系:PlayerLoop
它是整个运行时的“心跳”

while (ApplicationRunning)
{
    EarlyUpdate();   // 输入、时间、任务调度
    FixedUpdate();   // 物理系统(定时器驱动)
    Update();        // 脚本与AI逻辑
    LateUpdate();    // 动画、摄像机、后处理
    RenderFrame();   // 渲染提交与同步
}

这是真实存在于Unity C++层的PlayerLoopSystem结构

PlayerLoopSystem
{
  type;
  subSystemList[];
  updateDelegate;
}

每一帧中,Unity会遍历这些系统树,依序调用每个系统注册的更新函数
这样,不同模块可以通过注册PlayerLoopSystem来参与调度,而不需要硬编码耦合
这就是Unity架构的关键哲学:可重组的帧调度系统

时间维度的分层协作(MonoBehaviour生命周期)

系统之间的协作链

以一次典型帧为例,系统之间的数据与依赖链如下

InputSystem
  
ScriptSystem (MonoBehaviour.Update)
  
PhysicsSystem (FixedUpdate)
  
AnimationSystem
  
TransformSystem (同步位置)
  
CameraSystem (确定视角矩阵)
  
RenderSystem (生成渲染命令)
  
GPU (执行渲染)

这条链并非严格线性,而是通过 **任务系统(Job System)和依赖图(Dependency Graph)**并行调度;例如:动画计算,粒子模拟,骨骼蒙皮都可以异步执行,只要它们不依赖同一份数据

数据同步与Job调度

Unity的Job System是运行时的多线程调度核心
它的工作原理大致是:

TransformJob
 ├─依赖-> AnimationJob
 └─依赖-> PhysicsJob

这意味着:在执行TransformSystem之前,动画与物理系统必须先完成它们的计算。这种模型的优势是高并行性和低耦合,但也要求架构层具备明确的数据所有权与生命周期管理

渲染与主循环的协同

渲染系统是调度中的特殊角色
Unity在渲染时会:

  1. 收集场景数据(Cilling)
  2. 生成Draw Call队列(CommandBuffer)
  3. 提交给渲染线程(Graphics Jobs)
  4. GPU异步执行(Command Buffer)

这部分采用“双缓冲帧同步”

CPU: Frame N+1 在准备中
GPU: Frame N 在渲染中

这种机制确保GPU和CPU并行工作,不互相阻塞
Unity通过Fence同步点控制帧节奏,使物理、脚本、渲染三者稳定配合

PlayerLoop的可扩展性

开发者可以通过PlayerLoop.SetPlayerLoop()修改Unity的帧调度表
例如插入自定义系统

var loop = playerLoop.GetCurrentPlayerLoop();
InsertSystemAfter(ref loop, typeof(UnityEngine.PlayerLoop.Update), typeof(MyCustomSystem));
PlayerLoop.SetPlayerLoop(loop);

这意味着:Unity的调度系统是开放的
这也是DOTS、Entity Component System、以及SRP(Scriptable Render Pipeline)能独立运作的根本原因

数据流动

数据流动是Unity引擎中的核心机制,Unity的本质是“数据驱动的引擎”,数据流贯穿编辑器与运行时
在Unity中,数据流动是指游戏或应用在运行时,从输入到输出的数据如何在不同系统和模块之间传递。每一帧的数据流动涉及了多个层级,从外部输入、逻辑计算,到最终的渲染输出

输入到处理

1. 输入数据流

首先,游戏的输入来源通常包括键盘、鼠标、触摸屏、手柄等设备。输入系统是最早处理数据的系统

数据流动:

// 示例:新Input System获取按键输入
if (Keyboard.current.spaceKey.isPressed)
{
  // 做相应处理
}

2. 脚本逻辑处理

在接收到输入数据后,游戏的脚本逻辑(通常是C#脚本中的Update()函数)会对这些输入做出反应,并根据输入更新游戏的状态

数据流动:

void Update()
{
  if (Keyboard.current.spaceKey.wasPressedThisFrame)
  {
      // 执行跳跃
      character.Jump();
  }
}

从脚本到物理引擎

1. 数据流:脚本 -> 物理系统

物理系统通常由刚体(Rigidbody)、碰撞器(Collider)等组成,这些对象会根据物理规则(如重力、力的作用等)更新其状态。Unity的物理系统在固定时间间隔内运行,以FixedUpdate()为周期执行,这与普通的Update()不同,FixedUpdate()的执行周期是固定的,而Update()是根据帧率变化的

数据流动:

void FixedUpdate()
{
  // 根据输入给刚体施加一个力
  if (Keyboard.current.wKey.isPressed)
    rigidbody.AddForce(Vector3.forward * moveSpeed)
}

2. 物理系统的输出

物理系统会基于这些输入,计算出每个物体的新状态,并将结果传递回到脚本层。脚本可以进一步读取这些物理计算的结果并进行决策

数据流动:

从物理到渲染:渲染数据流动

数据流:物理与渲染

物理系统计算完成后,需要把物体的位置、旋转、缩放等变换传递给渲染系统,以便在屏幕上正确显示

数据流动:

渲染过程

异步任务与Job系统

在Unity中,许多繁重的计算任务(如物理模拟、路径计算、光照计算等)会通过Job System来异步执行。数据流动不仅限于主线程,很多计算任务会被分配到后台线程去执行,计算的结果会在主线程进行合并

Job System的数据流动

// 使用Job系统计算物理位置
NativeArray<Vector3> positions = new NativeArray<Vector3>(numObjects, Allocator.TempJob);
MyJob job = new MyJob 
{
  positions = positions
};
JobHandle handle = job.Schedule();
handle.Complete(); // 等待任务完成

数据流动的优化:GC与内存管理

Unity的内存管理和数据流动中的GC管理对于性能优化至关重要。游戏中创建和销毁大量对象会导致频繁的GC暂停,影响帧率

通过NativeArray, Job System和Burst Compiler等工具,Unity使得原生内存管理更加高效,从而减少GC暂停,提高性能

开发者通过掌握数据流的路径和优化点,可以在游戏开发中提升性能、增强稳定性、以及打造更加高效的游戏系统

设计哲学与扩展性

Unity作为一个游戏引擎,其设计哲学是追求灵活性、可扩展性和跨平台兼容性。它通过简化复杂性、提供清晰的API接口,以及在多种平台上提供一致的体验,使开发者能够专注于创造内容,而不是底层实现

抽象与暴露最小接口

Unity的设计哲学的核心之一就是抽象复杂性,同时暴露最小的接口给开发者。这一哲学体现在以下方面

1. 高层次抽象

2. 最小接口

3. 跨平台支持

Unity的另一个重要设计哲学是跨平台。开发者只需要编写一次代码,Unity就会自动处理跨平台的兼容问题,减少平台特有的差异。无论是PC、移动端、Web,还是VR/AR设备,Unity都提供了平台抽象层(PAL),让游戏的核心逻辑保持一致,开发者只需关注游戏的业务逻辑,而无需关心具体平台的底层细节

模块化与解耦

Unity的设计不仅追求易用性,同时注重模块化和解耦。这使得Unity在扩展性方面表现得尤为强大,尤其是对于大型项目的开发

1. 模块化架构

Unity引擎通过多个独立的子系统(渲染、物理、输入、音频、资源管理等)来实现功能的分离。这些模块化的设计允许开发者可以有选择地使用某些子系统,同时还能够在不同的模块之间进行定制化扩展\

2. 解耦的系统架构

游戏的物理系统、动画系统、输入系统等都能独立运行,并且可以彼此解耦,每个系统都负责自身的逻辑,可以独立开发和优化

数据驱动与可扩展性

Unity的数据驱动设计和可扩展性让它能够轻松应对多样化的开发需求,同时支持丰富的第三方插件和系统的集成

数据驱动设计

灵活的插件扩展机制

Asset Pipeline与扩展性

简化开发过程与自定义

Unity的设计哲学强调开发者体验,力求简化游戏开发过程,同时允许开发者在需要时进行自定义和深度修改

可视化编辑与脚本编程

扩展与自定义的自由度

Unity提供大量的扩展点,开发者可以根据需求自定义引擎的各个部分,例如渲染、物理、输入、GUI系统的行为。它的开源性质(如SRP)使得开发者可以灵活定制和优化性能,满足特定的项目需求

代码生成与反射

Unity通过提供反射(Reflection)功能和代码生成,使得开发者可以动态地调整程序行为。例如,使用[SerializeField]等特性,开发者可以让私有字段在编辑器中可见,便于调整游戏数据,进一步增强了Unity的可定制性