技术博客

UE5 源码目录地图:Runtime/Programs/Developer 各层放什么

第 1 / 100 篇 · 2025-10-13 · ue5 · 源码结构模块划分入门

从 Epic 的 GitHub 仓库把 UE5 拉下来,Engine/Source 下面 300 多个模块、几千万行代码,直接 grep 全文等于自杀。读 UE 源码的第一步不是读代码,而是记住这张地图——因为 UE 的目录划分不是随意的文件归类,它和构建系统(UBT 按目录边界切模块)、加载系统(每个模块是一个 DLL)严格一一对应。搞懂目录就是搞懂模块,搞懂模块才知道一段代码归谁管。

Engine/Source 的四大分区

源码树根下只有四个一级目录,分区的依据是代码在什么阶段运行、进不进最终的 Shipping 包:

Engine/Source/
├── Runtime/     # 运行时模块:游戏打包后真正运行的代码
├── Editor/      # 编辑器专属模块:只在 -editor 模式下加载
├── Developer/   # 开发期工具模块:编辑器用,但不属于编辑器框架本身
├── Programs/    # 独立可执行程序:UBT、UHT、UnrealPak 等
└── ThirdParty/  # 第三方库的源码副本(zlib、Vulkan、nvcloth……)

这个四分法背后是一条强约束:依赖只能向下,不能回头。Runtime 模块不能引用 Editor 模块的头文件,Editor 模块也不能依赖 Programs。UBT 在解析 .Build.cs 的依赖时会检查这个层级,Runtime 模块里 include 一个 Editor 头文件会直接报构建错误。所以判断一个功能代码该去哪找,先问"这个功能打包后还在不在"——在就去 Runtime,不在就去 Editor。

Runtime:真正的引擎

Runtime/ 是引擎本体,里面的层级比顶层四分法更实用。按依赖深度从底到顶:

  • Core:Engine/Source/Runtime/Core,不依赖任何其他 UE 模块的底座。容器(Containers/)、字符串(Containers/StringConv.h 等)、内存分配(HAL/)、数学库(Math/)、日志断言都在这里。Core 不依赖 UObject 系统,所以它能被 Programs 里的 UBT 这种纯命令行工具用。
  • CoreUObject:Runtime/CoreUObject,UObject 反射、GC、序列化、包加载。它依赖 Core,是整个对象模型的地基。
  • Engine:Runtime/Engine,最大的一个模块,AActor/UWorld/GameFramework/物理接口/渲染高层调度全塞在这里。新手常犯的错是什么都去 Engine 里找,其实 Engine 只是"游戏运行时"层,渲染的真正实现在更靠右的模块。
  • 渲染线:RHI(硬件抽象)→ RenderCore(渲染通用结构、RDG)→ Renderer(Nanite/Lumen/后处理的具体 Pass 实现)。查渲染问题别在 Engine 里翻 FScene,直接去 Runtime/Renderer/Private。
  • 平台抽象:每个模块的 Public/、Private/、Classes/ 之外,平台特化代码散在各处但遵循同一约定——HAL 定义接口,*Platform*(如 WindowsPlatform、ApplePlatform)实现。查平台行为先搜 F*Platform* 前缀。
Runtime/ 依赖方向(单向):

Core ← CoreUObject ← Engine ← GameplayTasks / AIModule / ...
                     ↘ RenderCore ← Renderer

一个值得记住的检索结论:模块内 Private/ 的头文件对其他模块不可见。UBT 只把模块的 Public/ 和 Classes/ 加进外部 include 路径。所以你在另一个模块里 #include 某个头文件报 "file not found" 时,先确认这个头文件是不是在 Private/ 下——如果是,说明模块作者就没打算让你用,正确姿势是找它 Public 的接口,或者干脆换个思路,而不是手动改 include 路径去硬闯。

Editor 与 Developer 的微妙分界

这两个目录的功能在"不进 Shipping"上是一致的,区别在定位:

  • Editor/:编辑器框架本身。UnrealEd(关卡编辑器核心)、BlueprintGraph(K2 节点)、PropertyEditor(属性面板)、DetailCustomizations。改编辑器行为、写 Detail 面板定制,代码归这里。
  • Developer/:给编辑器和其他工具用的开发期基础设施。AssetTools、MessageLog、SourceControl、DerivedDataCache、Settings。可以理解为"编辑器用的 Runtime 库"。

实际影响在 .Build.cs 里:模块类型字段(ModuleType)决定了一个新模块该编进哪个目标。你写的插件如果声明成 Editor 类型却在 Runtime 目标里被引用,Cook 时会直接挂掉。

Programs:被低估的阅读入口

Programs/ 下是独立编译的 exe,不是引擎模块。这份名单本身就值得背下来,因为它们是你日常使用频率最高的工具链:

  • UnrealBuildTool:UBT 本体,所有构建的调度者(下一篇专门讲)。
  • UnrealHeaderTool:UHT,反射代码生成器。
  • UnrealPak:打包/解包 pak 文件,命令行直接可用。
  • ShaderCompileWorker:shader 编译器进程,材质编译卡顿时 stat shadercompiling 对的就是它。
  • UnrealInsights、UnrealFrontend 等。

Programs 的模块只依赖 Core 和少量 Developer 模块,代码量小、没有 UObject 包袱,是入门 UE 代码风格的最好样本。想理解 TMap 怎么用、日志怎么打,去看 UnrealPak 的源码比看 Engine 清爽得多。

ThirdParty 与 BuildConfiguration

ThirdParty/ 里每个库都有一个 .Build.cs(如 zlib.Build.cs),UBT 靠这些文件把第三方库也纳入模块依赖体系——PublicDependencyModuleNames.Add("zlib") 就能用上。注意引擎用的是自带版本的第三方库,和你系统里装的版本无关,别去链接系统的 OpenSSL。

Engine/Source/UE5/(部分版本是 Engine/Source/Developer 同级或 BuildConfiguration)其实不算第五大区,但有一个文件必须知道:Engine/Config/BaseEngine.ini 和 Engine/Source/Programs/UnrealBuildTool/Configuration/——全局构建规则(哪些警告当错误、默认 C++ 标准)在这里定义,不在任何模块里。

项目侧目录:和引擎侧的镜像关系

游戏项目的目录和引擎是镜像的,理解映射关系后找文件很快:

MyProject/
├── MyProject.uproject        # 模块清单(等价于引擎的模块声明)
├── Source/
│   ├── MyProject/
│   │   ├── MyProject.Build.cs      # 模块依赖声明
│   │   └── *.cpp / *.h
│   ├── MyProject.Target.cs         # 游戏目标
│   └── MyProjectEditor.Target.cs   # 编辑器目标
├── Binaries/          # 编译产物(模块 DLL)
├── Intermediate/      # UHT 生成物、编译中间文件、UnrealBuildTool 缓存
├── DerivedDataCache/  # DDC,资产的派生数据缓存
└── Saved/             # 日志、截图、自动保存、Cook 产物

三个实战结论:

  1. 改完 .Build.cs 不生效时,先删 Intermediate/ 再让 UBT 重新生成工程文件。UBT 有依赖缓存,缓存键没带全所有输入时会出现"改了依赖没重新编译"的灵异现象。
  2. Binaries/ 里 DLL 的命名暴露了模块系统:UE5Editor-Engine.dll、UE5Editor-MyProject.dll,一个模块一个 DLL(Monolithic 打包时才会全部静态链接成一个 exe)。加 -hotreload 后缀的是热重载产物,看到 UE5Editor-MyProject-1234.dll 这种带数字的,说明热重载发生过且老 DLL 没释放。
  3. .generated.h 不在你的 Source 里,在 Intermediate/Build/<Platform>/<Config>/Inc/<ModuleName>/ 下。调 UHT 相关的问题(宏说明符不生效、生成代码报错)要去这里看展开结果,源文件里看不到真相。

Engine/Source 分区与依赖方向(依赖只能向左下流动) Runtime Core CoreUObject Engine RHI / RenderCore Renderer Gameplay 模块群 进 Shipping 包 Editor UnrealEd / BlueprintGraph PropertyEditor -editor 模式才加载 Developer AssetTools / MessageLog DerivedDataCache 编辑器的基础设施库 Programs UBT / UHT UnrealPak / SCW 独立 exe,只用 Core ThirdParty 每个库带 .Build.cs 纳入模块依赖体系 你的项目 Source/*.Build.cs Binaries/ (DLL) Intermediate/ Saved/ 日志 模块结构镜像引擎 游戏模块依赖 Runtime(禁止反向)

检索套路:从现象反推模块

最后给一套我自己定位代码的流程,比全文搜索快一个数量级:

  1. 先定分区:功能打包后还存在吗?存在 → Runtime,不存在 → Editor/Developer,是独立工具 → Programs。
  2. 再定模块:用类名前缀判断。F 开头的纯数据结构大概率在 Core 或本模块;U 开头在 CoreUObject 或 Engine;渲染相关带 RHI/RDG/Scene 字样的去渲染线。
  3. 模块内定文件:UE 的目录命名高度约定化,Private/<子系统名>/<功能>.cpp。查 Nanite 就去 Renderer/Private/Nanite/,查序列化就去 CoreUObject/Private/UObject/。
  4. 验证归属:打开文件看一眼文件头注释和 .Build.cs 的依赖,确认这个模块确实依赖了你以为的上游——依赖关系是最可靠的"代码导航图",比任何文档都新。

这套流程的本质是把"读代码"转化成"读构建配置"。UE 的模块边界划得很硬,.Build.cs 里的 PublicDependencyModuleNames 就是这个模块对外承诺的能力清单——顺着依赖图走,基本不会迷路。下一篇讲 UBT 怎么把这些 .Build.cs 翻译成实际的编译命令。