标签:源码分析
-
SceneMultiplayer 架构:MultiplayerAPI 与 ENet 的组合分层
Godot 高层多人 API 是三层结构:MultiplayerPeer 管字节流、SceneMultiplayer 管编解码与寻址、你的节点脚本管语义。这篇逐层拆开讲,重点解释为什么 NodePath 要做整数缓存,以及换传输层为什么只需改一行。
-
导入管线:EditorFileSystem 的扫描与 .godot/imported 目录
Godot 运行时不直接读 PNG/WAV,而是读 .godot/imported 里的转换产物。这篇讲 EditorFileSystem 的全量与增量扫描、.import 文件的字段含义,以及改一张贴图到底触发了哪些连锁反应。
-
Chaos 碰撞检测:FImplicitObject 层级与窄相位
拆解 Chaos 的碰撞几何体系:FImplicitObject 类型族、BroadPhase 到 NarrowPhase 的管线、GJK/EPA 在 Chaos 里的具体形态,源码基于 UE5.3。
-
GameplayTag 体系:层级标签的匹配为什么这么快
FGameplayTag 本质是全局注册表里的一个 32 位索引:层级关系编译期固化成父链缓存,匹配是整数比较而非字符串遍历。这篇讲注册表结构、查询机制和项目实践的坑。
-
AttributeSet 与聚合器:BaseValue/CurrentValue 为什么分开存
属性的 CurrentValue 不是一个字段而是每次查询时对全部生效 GE 的分层求值:BaseValue 管永久成长,聚合器管临时修正,分开存是为了让 GE 的撤销幂等且与施加顺序无关。
-
GDScript 类型推导与 static typing:analyzer 的双遍检查
analyzer 用两遍遍历解决"先用后声明"问题:第一遍解析继承和成员签名,第二遍才检查函数体。这篇讲两遍各自做什么、推导的边界在哪、以及类型标注为什么能让代码快一倍。
-
lambda 与 Callable 的捕获语义:拷贝还是引用
GDScript 的 lambda 捕获是"按值拷贝"——但 Variant 的拷贝语义让这句话有坑:int 是真拷贝,Array/Dictionary 是写时复制的共享,Object 是指针拷贝。这篇用三个实验把捕获规则钉死,并给出循环捕获和生命周期两个实战建议。
-
PackedScene 的序列化格式:.tscn 文本与 .scn 二进制对照
.tscn 和 .scn 装的是同一份 SceneState,只是一个用文本一个用二进制。这篇逐段解剖文本格式的四个区块,对照二进制格式的 RSCC 文件头结构,以及什么场景该选哪种。
-
组(Group)系统:SceneTree 里的组哈希表
add_to_group 背后是两份哈希表的双写,get_nodes_in_group 的遍历顺序由 HashSet 决定而不稳定——这篇把组系统的存储结构、开销和适用边界讲清楚。
-
VoxelGI 的实现与它为什么被边缘化 —— 和 SDFGI 的对比
VoxelGI 的 64³ 体素化 + mip 链 + cone tracing 实现细节,以及它在 4.x 被 SDFGI 取代的三条结构性原因。
-
AnimationMixer 的重构:AnimationPlayer/AnimationTree 的统一底座 —— 4.x 为什么多出一个基类
Godot 4 把动画播放的公共逻辑从 AnimationPlayer 里抽成了 AnimationMixer,AnimationTree 从此和 AnimationPlayer 平级。这篇讲这次重构拆出了什么、ActiveAnimation 列表怎么驱动混合求值、以及它对写动画代码的实际影响。
-
TaskGraph 与并行:NamedThread/AnyThread 的任务调度
TaskGraph 的前置依赖调度模型、NamedThread 的固定角色分工,以及引擎内并发编程的正确姿势。
-
RPC 的实现:从 @rpc 注解到字节流,一次远端调用被编码成什么样
@rpc 声明只是元数据,真正发送时函数名被压成整数索引、参数走 Variant 压缩编码。这篇跟一遍 Node::rpc 到 SceneMultiplayer 的编码链,讲清包格式、method 索引为什么必须配 checksum 校验、以及带返回值的 RPC 是怎么实现的。
-
Vulkan 后端初始化:instance、device 与 queue 的选择逻辑
vulkan_context.cpp 按四层递进初始化 Vulkan:instance 选验证层、physical device 按能力打分挑卡、logical device 凑齐扩展和队列族;读懂选择逻辑能解释大半"启动失败 / 用了核显"的问题。
-
GAS 的预测与复制:FPredictionKey 机制和属性同步怎么咬合
拆解 GAS 技能系统的预测键机制、预测回滚的实现方式,以及 AttributeSet 走 Push Model 的同步路径。
-
String/StringName 的内部表示与哈希缓存
StringName 用一张全局静态表把字符串比较变成指针比较,这篇拆 core/string/string_name.h 的结构,讲清为什么 NodePath 与信号名的比较必须走 StringName 而不是 String。
-
后处理链源码:FPostProcessing 的 Pass 顺序与设计逻辑
拆解 UE5 后处理链的 pass 顺序:每个 pass 为什么在那个位置、RDG 化后的组织方式、调优时改哪一环,源码基于 UE5.3。
-
动画资产结构:USkeleton/AnimSequence 的数据布局
从 USkeleton 的骨骼树到 AnimSequence 的压缩轨道:动画资产在内存里长什么样、压缩格式怎么权衡、Additive 和 Root Motion 存在哪,源码基于 UE5.3。
-
Nanite 光栅化与 VisBuffer:软件光栅写可见性缓存
为什么 Nanite 要拆软硬两条光栅路径、VisBuffer 里到底存了什么、材质分类 pass 怎么把几何和着色解耦。
-
日志与断言:UE_LOG/check/ensure 的实现
日志分类的编译期裁剪机制、check/ensure/verify 三档断言的级别取舍,以及 Shipping 包里它们各自剩下什么。
-
状态机源码:FAnimNode_StateMachine 的转移评估
转移规则到底什么时候求值、跨状态 Blend 怎么叠加、打断(Interruption)优先级怎么算——FAnimNode_StateMachine 的三个常被误解的机制,源码基于 UE5.3。
-
Resource 的引用计数与 ResourceCache:资源为什么不会重复加载
从 Ref 模板到 ResourceCache 全局哈希表再到 ResourceLoader 的加载链,讲清 Godot 4.x 里一份资源从磁盘到内存、再到被共享和释放的完整生命周期。
-
UBT 构建流程解析:.Target.cs/.Build.cs 如何生成编译命令
拆解 UnrealBuildTool 从 .Target.cs 到 cl/link 命令行的完整推导链,搞懂模块依赖和编译参数到底从哪来。
-
动画通知与曲线:AnimNotify 触发管线和驱动逻辑的正确姿势
AnimNotify 和动画曲线是同一条求值管线的两个出口:通知做离散事件,曲线做连续量。这篇讲触发时序、队列分发机制,以及"哪些逻辑能写进通知里"的边界。
-
Chaos Cloth 布料:XPBD 的约束求解
从 XPBD 公式到 FClothingSimulationSolver:Chaos Cloth 的粒子-约束模型、面板参数怎么映射到物理系数、自碰撞为什么这么贵,源码基于 UE5.3。
-
Chaos 约束系统:关节与 PBD 投影
从数学上拆解 Chaos 为什么用位置级求解处理关节约束:PBD/XPBD 的投影公式、刚度与迭代数的关系、swing-twist 关节流形的实现,源码基于 UE5.3。
-
Compatibility 渲染器:GLES3 的 fallback 路径
Compatibility 渲染器不走 RD,而是 Godot 3.x 血统的 GLES3 光栅器直通驱动层。这篇讲它的代码从哪来、为什么不走 RD、哪些特性被静默降级,以及 WebGL2 这个最苛刻目标带来的约束。
-
GameMode/PlayerController/Pawn 初始化链路:时序源码走读
从 UWorld::InitWorld 到 Possess,把 GameFramework 三件套的诞生顺序和每一棒的源码位置钉死。
-
descriptor set 与 UniformSet 的缓存策略:绑定资源时的哈希查表
uniform_set_create 不急着建 Vulkan descriptor set,先对完整绑定内容算哈希查缓存;命中一次查表省下 vkAllocateDescriptorSets,这是高 draw call 场景下绑定期开销的主要来源。
-
节点树的挂接与脱离:add_child() 背后做了什么
add_child 一行代码背后是命名冲突消解、树内通知级联、process 标志传播三件事,这篇读 scene/main/node.cpp 的 _add_child_nocheck 与 _propagate 家族,讲清挂接的完整成本。
-
ScriptServer 与脚本类注册:`class_name` 是怎么被全局看见的
写 `class_name Foo` 之后,整个工程任何脚本都能直接写 Foo.new()——背后是 ScriptServer 的全局类注册表。这篇讲注册时机、缓存文件格式,以及它和 ClassDB 的边界。
-
Godot Physics 2D:分离轴定理与脉冲求解器,2D 比 3D 简单在哪
Godot 的 2D 物理自研且独立于 3D:凸多边形碰撞用分离轴定理,求解器砍掉了 3D 的角速度耦合。这篇讲 SAT 的几何原理、2D 冲量求解为什么简单、以及 2D 没有 Jolt 替代的现实。
-
AnimationTree 状态机:blend tree 的求值顺序 —— 图节点的拓扑排序
AnimationTree 的 blend tree 不是每帧"跑一遍图",而是按依赖关系做拓扑排序后从叶子到根求值。这篇讲 AnimationNode 的输入输出模型、求值顺序怎么定、BlendSpace2D 的重心坐标插值,以及同步节点的时钟语义。
-
命令行解析与启动参数:--editor、--headless 的分发逻辑
godot --editor --headless 这些参数在 Main::setup() 里被一棵参数树消化,这篇读 main/main.cpp 的参数解析段,讲清参数怎么决定显示驱动、脚本服务器和 MainLoop 的选择。
-
UObject 生命周期:从 NewObject 到 ConditionalBeginDestroy
把 UObject 从出生到销毁的每个阶段拆开来,讲清各阶段允许做什么、不允许做什么。
-
Tick 机制:FTickFunction 的分组与依赖
TickGroup 为什么是这个顺序、Prerequisite 怎么在组内排序、LastDemotable 为什么不能被提升——一次把帧内时序的源码翻透。
-
委托系统源码:DECLARE_DELEGATE 到 TBaseDelegate 的实现
单播/多播/动态委托的类层次、绑定时的内存分配、以及动态委托为什么必须配 UFUNCTION。
-
Godot 4.x 源码目录速览:main/core/scene/servers/modules/editor 各管什么
第一次 clone Godot 源码的人都会被 40 多个顶层目录劝退。这篇把 4.x 的目录按"分层职责"重新组织成一张地图,并标出每层最值得先读的文件。
-
SSAO 与 SSIL 的近似算法 —— 半分辨率加 upsample 的套路
SSAO 的 spiral 采样数学、SSIL 为什么是同框架的副产品,以及半分辨率 + 深度感知 blur + bilateral upsample 的性能结构。
-
异步物理与 GT/PT 解耦:Fixed Timestep 的代价
物理跑在独立线程、以固定步长推进,渲染帧率不再拖住物理——但解耦不是免费的。这篇讲 UE5.1+ 的 AsyncPhysicsTickEnabled 架构、Dirty Properties 机制和固定步长换来的三个代价,源码基于 UE5.3。
-
Chaos Destruction:GeometryCollection 的碎裂模拟
GeometryCollection 怎么从静态簇演化成一地下刚体:Cluster 层级、字段(Field)驱动的断裂判定、损伤传播与休眠簇的启用,源码基于 UE5.3。
-
core/os 层:OS 单例如何抽象平台差异
Godot 跨六个桌面/移动/Web 平台,平台差异被压在 core/os 的 OS 基类和 platform/ 的派生实现里。这篇讲 OS::get_singleton() 的分发机制、虚函数表的覆盖策略,以及为什么"单例 + 继承"比"接口 + 工厂"更适合引擎。
-
GDScript 的内存模型:引用计数、树所有权与循环引用
GDScript 侧的对象分三类管生命周期:Object 手动、RefCounted 引用计数、Node 场景树所有权。三类之间的混用规则是循环引用泄漏的根源,这篇把不变式和常见泄漏模式列全。
-
MultiplayerSynchronizer 的属性同步:delta 压缩是怎么省带宽的
拆开 Godot 高层网络 API 的两层省带宽机制:MultiplayerSynchronizer 的 delta 同步只发变化的属性,MultiplayerAPI 的 Variant 编码把整数压到最小字节数。基于 4.2/4.3 源码与官方文档。
-
RenderSceneBuffers 与帧缓冲管理:分辨率改变时哪些资源重建了
每个视口的 color/depth/velocity 缓冲都归 RenderSceneBuffersRD 管,配置变更时整套 RID 销毁重建。这篇拆配置结构、重建的触发链与成本,以及 XR 多视角下的 buffer 布局。
-
GDExtension 与 godot-cpp:ABI 兼容层是怎么设计的
GDExtension 让 C++ 代码编译成动态库热插进引擎,且引擎升级后不用重编译。这篇讲 GDExtensionInterface 函数表的设计、版本协商机制,以及 godot-cpp 绑定层如何把 C 接口包装成 idiomatic C++。
-
-
Lumen 的 Surface Cache 与 Radiance Cache:两级缓存结构拆解
讲清 Lumen 间接光的两级缓存:Surface Cache 的 Card 图集怎么存材质,Radiance Cache 的探针怎么插值复用。
-
Control Rig 原理:RigVM 字节码执行模型
Control Rig 的图不是"每帧走一遍 UObject 节点",而是编译成 RigVM 字节码后在寄存器式虚拟机上执行——这篇讲编译管线、操作数模型和字节码执行的开销为什么比 AnimBP 低。
-
Enhanced Input:UInputAction 到 Trigger 的触发链
从原始按键到 BindAction 回调之间的四层管线:MappingContext 求值、Modifier 变换、Trigger 状态机、事件派发,逐层看源码。
-
场景切换的正确姿势:change_scene_to_file 的内部行为
change_scene_to_file 是同步拆旧建新——旧 current_scene 被立即摘除并释放,从物理回调里直接调会触发 locked 报错;这篇把 _change_scene 的每一步和延迟切换的正确写法讲清楚。
-
Variant 的内部结构:union 加类型标签怎么装下四十多种类型
Variant 是脚本与 C++ 之间的通用货币,要在固定 24 字节里装下从 int 到 Transform3D 再到 Object 指针的 40 多种类型。这篇拆 core/variant/variant.h 的内存布局,讲清 4 字节类型标签加 16 字节 union 的取舍。
-
CanvasItemRenderer:2D 渲染的批处理 —— 上千个精灵怎么合批
2D 命令怎么按状态合批、合批在哪八个断点处被打断,以及静态 canvas 的重绘缓存策略。
-
RenderingDevice 抽象层设计:RID、命令缓冲与提交模型
RenderingDevice 是 Godot 4 渲染栈里最"干净"的一层:RID 句柄、begin/end 式列表 API、submit/sync 提交模型。基于 4.3 源码拆解它怎么把 Vulkan 的复杂度藏起来,以及 4.3 拆出 RenderingDeviceDriver 的意义。
-
材质编译与 Shader Permutation:FMaterialShaderMap 的生成链路
拆解 UE5 材质编译的全链路:HLSL 怎么生成、permutation 组合爆炸怎么来的、DDC 怎么缓存编译结果,源码基于 UE5.3。
-
后处理链:tonemap/glow/SSAO 的 render pass 顺序 —— 一张像素从场景到屏幕的路
3D 场景的 HDR 画面要经过 SSAO、SDFGI、体积雾、tonemap、bloom、FSR 一串 pass 才上屏。这篇按 4.4 源码顺序拆每个 pass 的输入输出和依赖,告诉你改画质选项时帧时间花在哪。
-
Callable 与信号的关系:bind 的参数存在哪
Callable 是"可调用的东西"的统一表示,bind 的参数不是存在信号里而是存在 Callable 自己内部。这篇读 core/variant/callable.h 与 callable_bind.cpp,讲清信号发射时参数怎么拼接。
-
模块注册机制:register_module_types 如何串起所有模块
Godot 的 40 多个模块(GDScript、glTF、Jolt、mbedtls……)通过一套生成代码机制注册到引擎里。这篇讲 register_module_types.gen.cpp 的生成逻辑、三阶段初始化的分工,以及静态初始化顺序问题的规避方案。
-
ActorComponent 架构:注册、Tick 集成与组合优于继承
讲清 UActorComponent 的注册机制、Tick 集成方式,以及"组合优于继承"在 UE 里的具体落地。
-
RenderingDevice 多线程提交的限制与坑
RD 的类上挂着 _THREAD_SAFE_CLASS_,方法级互斥没问题,但"同时只能有一个活动列表"和"submit 顺序即 GPU 顺序"两条约束决定了它不能被当成自由的多线程图形 API。这篇列四条实战规则,以及每条背后的源码依据。
-
Lumen Final Gather:屏幕探针与 SDF 追踪的间接光闭环
拆解 Lumen Final Gather 的完整链路:屏幕探针怎么布置、SDF 追踪怎么取Radiance、时域累积怎么滤波,源码基于 UE5.3。
-
NOTIFICATION 全家桶:ENTER_TREE / EXIT_TREE / READY 的顺序保证
把 Node 的通知编号体系、Object::notification 的分发路径、reversed 参数的含义一次讲清,顺带解释为什么这套机制不用 C++ 虚函数。
-
序列化机制:FArchive 与 UPROPERTY 的存取框架
讲清 UE 序列化系统的分层设计,以及 SaveGame 和资产加载为什么共用同一套框架。
-
Forward+ 的 cluster shading:光源如何被分配到 cluster
Forward+ 用"屏幕 tile × 深度切片"的三维网格给每个像素预筛光源,把逐像素遍历全部光源的 O(n) 降到 O(1)。这篇给 cluster 索引的数学推导、光源影响域的 AABB 求交,以及 Godot 4.x 的具体参数来源。
-
Jolt 接入 Godot 4.4 始末:PhysicsServer3D 是怎么被替换的
Jolt 从社区 GDExtension 插件变成 4.4 的官方内置物理引擎,靠的是 PhysicsServer3DManager 的服务器注册机制。基于 4.4 源码看接入点、JoltSpace3D 的 step 流程,以及两个引擎的行为差异。
-
Node 的 owner 与持久化:场景保存时哪些节点会被写盘
owner 字段决定一个节点在保存场景时被写进哪份 PackedScene;它只影响序列化、不影响运行时查找,运行时 add_child 出来的节点 owner 为 null,天然不会被保存。
-
IoStore 与 Cook 管线:.utoc/.ucas 容器格式和 Zen Server 的协作
讲清 UE5 Cook 后资产怎么进 IoStore 容器、Zen Server 在其中扮演的角色,以及补丁 diff 的数据来源。
-
多线程基础设施:WorkerThreadPool 与 TaskGroup
WorkerThreadPool 是引擎内部所有"扔给后台干"的活儿的统一入口,这篇讲它的任务队列、Group 语义,以及为什么引擎不用 std::async 也不让业务自己起线程。
-
-
ERR_FAIL_COND 家族:引擎错误处理宏的设计逻辑
Godot 源码里看不到一个 try/catch,错误处理全靠 ERR_FAIL_COND、ERR_CONTINUE、CRASH_NOW 这三十多个宏。这篇讲这套宏的设计动机、六个变体的分工,以及"不用异常"换来的东西。
-
C# 支持:从 Mono 嵌入宿主到 .NET 6+ 的迁移
Godot 4 的 C# 支持换掉了整个技术底座:3.x 把 Mono 运行时嵌进引擎进程,4.0 起改用标准 .NET hosting API(nethost + hostfxr)。这篇讲清楚两种宿主方案的调用链差异,以及互操作 marshaling 的真实成本。
-
射线检测的调用路径:intersect_ray 一路到 broad phase
intersect_ray 看似一次函数调用,实际穿过了命令刷新、broad phase 线段剔除、逐形状窄相求交三层。这篇沿 servers/physics_3d 的源码把每一层做的事和开销讲清楚,帮你判断"这条射线到底贵在哪"。
-
-
PhysicsServer3D 抽象:为什么物理和场景树是解耦的
RigidBody3D 这样的物理节点只是 RID 的持有者,所有计算都发生在 PhysicsServer3D 服务器里,还能跑在独立线程上。这篇从接口形状、线程模型和查询纪律三个角度讲清这个抽象的动机、收益和代价。
-
ResourceLoader 的加载链:ResourceFormatLoader 的注册与选择
load("res://foo.tscn") 的第一个分叉点是 recognize 而不是扩展名:ResourceFormatLoader 列表按优先级询问"你认这个路径吗"。这篇拆加载链的每一环和自定义 loader 的接入方式。
-
TranslationServer:CSV 变 .translation 的导入与运行时查找链
Godot 的多语言不是运行时读 CSV,而是导入期把翻译表编译成二进制 .translation 资源。这篇讲导入格式的细节、TranslationServer 的 locale 回退链,以及 tr() 从调用到拿到译文经过的每一站。
-
条件复制与 COND_* 标志位全集:只给该看的人发
COND_* 每个标志位的精确判定逻辑、RepNotify 的服务端/客户端两侧语义差,以及条件过滤在 RepLayout 里的实现位置。
-
CharacterBody3D 的 move_and_slide:剩余滑动向量的推导
move_and_slide 不是黑盒魔法,它的核心是一次"碰撞 → 削速度 → 继续走"的迭代循环。这篇从向量推导出发,解释滑动方向怎么算、为什么要迭代、max_slides 和 floor snapping 各解决了什么问题。
-
内置脚本调试器:一个自研协议的断点、变量检查与远程通道
Godot 的脚本调试器不走 DAP,而是自研的一套消息协议:编辑器与运行实例之间一条 TCP 长连接,按消息名分发 JSON 负载。这篇讲断点在字节码层的实现方式、变量检查的序列化成本,以及 profiler 数据怎么同通道复用。
-
IK 节点族:TwoBoneIK/FABRIK/CCDIK 的实现对比
三种 IK 算法的数学本质和工程取舍:TwoBoneIK 的解析解、FABRIK 的前向后向迭代、CCDIK 的贪心旋转——以及在 AnimGraph 里怎么选,源码基于 UE5.3。
-
网络复制的属性同步协议:FRepLayout 增量序列化全流程
从 DOREPLIFETIME 宏到 Bunch 打包,讲清 UE 属性复制的 ChangeList 增量协议与历史状态机制。
-
Nanite 流送:Cluster Page 的按需加载与驻留管理
拆解 Nanite 流送的驻留模型:page 粒度如何选、剔除结果怎么变成加载请求、显存池满时的换出策略,源码基于 UE5.3。
-
软体物理 SoftBody3D 的现状:能用但不快的原因
SoftBody3D 基于位置的动力学把网格顶点当粒子、边当距离约束来解。这篇讲它的求解结构为什么和刚体是两个世界、CSR 稀疏矩阵在软体里扮演的角色,以及哪些场景它撑不住。
-
Chaos 岛(Island)管理:约束图分岛与并行求解
拆解 Chaos 的 island 机制:约束图怎么划分、休眠判定怎么做、为什么它是物理并行化省 CPU 的关键,源码基于 UE5.3。
-
与 GDExtension 互操作:GDExtensionInterface 函数表怎么桥接两个世界
GDExtension 不是"加载一个 DLL"那么简单:扩展和引擎之间隔着一张 C 函数表,所有跨边界调用都经它转发。这篇讲函数表的生成、调用链的开销构成,以及 godot-cpp 在表上加了什么。
-
SDFGI 原理:有向距离场全局光照 —— 级联体素与探针插值
SDFGI 用每帧体素化的 SDF 加 probe 间接口图集实现动态 GI。这篇推导 cone tracing 的步进公式、probe 烘焙与 cascade 滑动的成本结构。
-
动画混合:BlendSpace 与 Layered Blend 的采样
BlendSpace 的网格采样、分层混合的逐骨骼权重矩阵、Additive 叠加的数学——动画混合的正确性都建立在权重归一化这条公理上,源码基于 UE5.3。
-
AActor 生命周期:PostInitProperties 到 EndPlay
把 AActor 从出生到销毁的每个虚函数拆开,讲清每个阶段该干什么、不该干什么。
-
物理帧率与渲染帧率:physics_ticks_per_second 的同步语义
physics_ticks_per_second 决定物理步长,但渲染帧和物理帧之间靠什么对齐?这篇讲固定步长累加器的实现、60 物理帧配 144 渲染帧会发生什么、max_physics_steps 的兜底逻辑,以及帧率不稳时的行为。
-
Cook 流程深潜:CookByTheBook 的依赖遍历与 EditorOnly 剥离
从 CookCommandlet 入口到 CookByTheBook 的队列消费:初始包集从哪来、依赖图怎么展开、保存阶段怎么分层并行、EditorOnly 数据在哪一步被物理剥掉。
-
复制架构总览:NetDriver/Connection/Channel 三层模型
一个对象从服务端内存到客户端内存要经过哪三层对象:NetDriver 管调度、Connection 管传输、Channel 管对象状态。
-
await 协程的状态机实现:函数暂停时栈帧存在哪
GDScript 的 await 是无栈协程:OPCODE_AWAIT 把指令指针和整个栈打包进堆上的 CallState,函数当场返回一个 GDScriptFunctionState 句柄。这篇追踪信号到来前后的完整状态迁移。
-
Virtual Shadow Map:虚拟页表的阴影贴图
拆解 Virtual Shadow Map 的设计:为什么需要 16K 虚拟分辨率、页表怎么映射、缓存命中率怎么决定性能,源码基于 UE5.3。
-
call_deferred 与消息队列:延迟调用的实现与 flush 时机
call_deferred 不是定时器,是把 Callable 塞进一条全局 FIFO 消息队列;队列在一帧的固定时点上被 flush,回调运行期改树是主要用途,队列满了引擎会直接崩。
-
Lumen 硬件光追路径:HWRT 与 SWRT 的切换逻辑
对比 Lumen 的软件光追(SDF)与硬件光追(HWRT)两条路径:各自的数据结构、切换条件、质量差异和性能账本,源码基于 UE5.3。
-
RID 句柄系统:服务器对象为什么不用裸指针
RenderingServer、PhysicsServer 对外暴露的不是对象指针而是 RID——一个 64 位整数句柄。这篇讲 RID 的查表实现、为什么它能带来线程安全和热重载能力,以及 RID_Owner 的槽位复用机制。
-
阴影贴图:CSM 级联与阴影 atlas 的分配 —— 定向光阴影的分页管理
定向光的 CSM 级联怎么按距离公式切分、每级有多大,以及局部光的阴影怎么在一张 2048 atlas 里分页复用。含 bias 的单位陷阱。
-
线程化资源加载:`load_threaded_request` 的状态机与令牌
load_threaded_request 把资源加载挪到后台线程,主线程靠状态轮询拿结果。这篇讲 ThreadLoadTask 的状态机、三个公开 API 的调用契约,以及它为什么解决不了"加载瞬间的卡顿"的全部问题。
-
Chaos Vehicle:车辆物理模块结构
从 ChaosVehicleManager 到悬挂射线和轮胎摩擦模型:UE 载具系统的模块划分、关键参数背后的物理含义、以及为什么联机载具手感最难调,源码基于 UE5.3。
-
PCK 文件格式与加密导出:资源包能不能被解包
PCK 是公开格式的资源包,任何人都能解出你的脚本和贴图。这篇按字节解剖 PCK 的文件布局——为什么目录表在文件尾、GDPC magic 怎么寻址,以及 AES-256 加密导出的真实防护等级。
-
-
NodePath 的解析与缓存:get_node 每次调用都重新解析吗
NodePath 在构造时就完成了字符串切分,get_node 的成本在逐级子节点查找而不在解析;引擎不缓存查找结果,所以高频路径要用 @onready 变量把引用钉住。
-
RefCounted 引用计数与 memnew/memdelete:引擎为什么不用 shared_ptr
Godot 的资源生命周期靠 RefCounted + Ref<T> 管理,内存分配靠 memnew/memdelete 宏。这篇讲这套组合为什么故意绕开 std::shared_ptr,以及引用计数在继承体系里是怎么实现的。
-
_process 与 _physics_process 的时序关系:同一帧里谁先谁后
从 Main::iteration 的固定步长累加器出发,讲清一个渲染帧里物理迭代和 _process 的先后、delta 各自怎么算,以及 144Hz 屏配 60Hz 物理时的步数抖动和插值平滑。
-
RenderingDeviceDriver 的诞生:4.3 拆出驱动层解决了什么问题
4.3 把 RenderingDevice 里"懂 Vulkan"的部分下沉成 RenderingDeviceDriver,让上层只剩资源与命令簿记;这次拆分给 D3D12/Metal 后端铺了路,也让 barrier 推导能在驱动层统一做。
-
GameplayCue 派发机制:表现层事件与 Ability 的解耦
GameplayCue 如何从 GameplayTag 出发,经 GameplayCueManager 路由到 Static/Actor 两种表现载体,以及网络模式下本地预测的派发路径。
-
unique_name_in_owner 与 %NodePath:百分号语法糖背后的查找表
百分号写法不是路径简写,而是一套挂在 owner 上的 StringName 哈希表,查询 O(1) 且不受节点深度影响——这篇从 set_unique_name_in_owner 的注册逻辑追到 get_node_or_null 的分支。
-
Iris 复制系统:UE5.3 实验性新协议的架构思路
Iris 用 NetRefHandle 和复制片段(Replication Fragment)重构对象模型,协议层改成 tokenized 状态同步,它到底解决了老系统的什么痛。
-
DDC 派生数据缓存:GetDerivedData 的哈希键怎么构造
DDC 能成立的前提是什么、查询键由哪几段拼成、各级后端怎么串,以及为什么"配置也是键的一部分"——看懂这些才能解释缓存为什么命中或失效。
-
Area3D 的重叠检测与回调时机:body_entered 在物理帧的哪个阶段发出
Area3D 不做碰撞响应,只做"谁进来了"的记账。这篇讲 Area 与 Body 的重叠对是怎么维护的、监控回调为什么在物理步进结束后的 flush 阶段才发,以及 monitoring/monitorable 两个开关各自关掉的是哪半边。
-
CDO 与属性初始化:ClassDefaultObject 到底在干什么
讲清 ClassDefaultObject 的创建时机、复制机制,以及为什么改 CDO 能影响所有实例。
-
decal 与 fog volume 的 cluster 参与:它们怎么挤进光源剔除体系
Forward+ 渲染器把 ClusterBuilderRD 模板实例化了两次:一次给光源,一次给 decal。fog volume 走的却是另一条路——向相机空间的体雾 3D 纹理注入密度。这篇拆两者的参与方式、片元侧的消费逻辑和各自的限额行为。
-
LevelStreaming 源码:ULevelStreaming 的加载状态机
从 SetShouldBeVisible 到 Actor BeginPlay 之间隔着哪些状态、谁推动迁移、WorldComposition 到 World Partition 改了哪一层。
-
-
-
从 RD 到 RDD 的抽象开销实测
4.3 把 RenderingDevice 拆成 RD + RDD 两层后,每次资源操作多走一次虚函数。这篇用一个空操作循环和一次真实抓帧估算这笔开销的量级,结论是:调用开销在 1% 以下,真正的成本在别处。
-
-
process_priority 排序:同一帧内节点的执行顺序
process_priority 决定同一帧里 _process 的调用次序,优先级相同时按挂接顺序稳定排队——这篇讲 SceneTree 处理组的排序插入、线程组机制,以及什么时候真的需要调优先级。
-
屏障与同步:RD 怎么隐藏 Vulkan 的 pipeline barrier
Vulkan 的 pipeline barrier 是手写渲染代码时最容易出错的部分。这篇拆 Godot 4.x 的 RenderingDevice 如何记录每个资源的读写历史、在列表结束时自动推导屏障,以及它保留下来的三个显式同步口(barrier / compute_list_add_barrier / sync)。
-
SceneTree 的 MainLoop 循环:一帧到底发生了什么
从 main/main.cpp 的 Main::iteration() 一路追到 SceneTree::process(),弄清固定物理帧和渲染帧是怎么在一帧里被排好队的。
-
Vector/HashMap/LocalVector:Godot 为什么不用 STL
core/templates 下有一套自研容器,这篇对比 Vector、LocalVector、HashMap 与 STL 对应物的设计分歧,讲清拷贝语义、内存模型和 ABI 稳定性这三条不用 STL 的理由。
-
shader 编译链:GLSL 到 SPIR-V 再到 RD 反射
从 .glsl 文件进仓库到 RD 能用的 shader RID,中间隔着构建期 glslang 编译、SPIR-V 字节码打包进 .gen.h、运行期反射解析 UBO 布局三步。这篇按 Godot 4.x 的真实链路逐段讲,包括用户脚本能走的 shader_create_from_source 路径。
-
Mutex/RWLock 封装与服务器线程模式:thread_safe 编译宏的影响
Godot 的锁不是 std::mutex 的直接包装,而是一套带"可编译期消除"语义的封装,这篇讲 Mutex/RWLock 的分层、THREAD_SAFE 宏如何让锁在单线程构建里消失,以及服务器的线程模式设置。
-
UWorld 与 Level:PersistentLevel/StreamingLevel 的组织
多关卡共存时 Actor 到底归谁管:UWorld 与 ULevel 的持有关系、OwningWorld 链和流送关卡的增删全流程。
-
脚本热重载:编辑器里按保存后哪些状态丢了
运行中改脚本是编辑器最顺手也最容易踩坑的能力:GDScript 的热重载靠"新编译产物替换引用",C# 靠 collectible AssemblyLoadContext。两种路线下,正在运行的实例、静态变量、signal 连接各有不同的保留规则。
-
Window 节点与 Viewport 的关系:4.x 把窗口做成节点解决了什么
4.x 让 Window 继承 Viewport 并进入场景树,窗口从此是一等节点:多窗口管理、per-window 渲染缓冲、子窗口嵌入都能用节点语义表达,代价是 Viewport 的类职责被拉宽。
-
静态体的优化:StaticBody3D 为什么不进 solver
一万个 StaticBody3D 和一个 RigidBody3D 的物理成本差在哪?静态体不是"被 solver 跳过的刚体",而是从积分、island 构建到迭代求解都走了特殊通道。这篇讲静态体在引擎各阶段的成本分布,以及它和 kinematic 模式的区别。
-
资产加载机制:FPackageIndex 与 Linker 的解析链路
一个软引用从字符串到 UObject* 的完整路径:Package 加载、Linker 建表、FPackageIndex 解析 Import/Export 的过程。
-
GPU 内存分配子系统:内存池与 staging buffer 的上传路径
RD 的显存来自 Vulkan Memory Allocator 的池化分配,CPU 到 GPU 的上传统一走 staging buffer 中转;贴图加载慢和 device lost 两类问题,根子常在这两条路径的配额上。
-
3D 空间音频:距离衰减与多普勒,AudioStreamPlayer3D 的衰减模型
AudioStreamPlayer3D 在 mix 前把单声道源按听者位置算衰减、声像和多普勒音高偏移,再交给普通立体声混音。这篇讲距离衰减三种模式的数学形式、双声道声像的正弦平移法、以及多普勒为什么只调 playback 的 pitch 不动数据。
-
RHI 抽象层:FRHICommandList 与平台后端
为什么 UE 要把命令录成"数据"再翻译一遍、FRHICommandList 与 ID3D12GraphicsCommandList 之间隔了几层、PSO 缓存为什么绕不开。
-
NavigationServer 与物理的解耦:导航网格烘焙为什么不依赖碰撞体
NavigationServer 是独立于物理服务器的第三套查询系统:烘焙走 Recast 体素化,运行时查询走 Detour。物理碰撞体和导航网格只是"数据源的一种",两者解耦让导航可以来自任意几何。这篇讲烘焙的数据流、运行时查询路径,以及两套系统接缝处的坑。
-
渲染线程模型:GameThread/RenderThread/RHIThread 的边界
三条线程各自干什么、ENQUEUE_RENDER_COMMAND 背后是什么队列、帧同步栅栏怎么保证游戏线程不踩渲染线程。
-
GameplayEffect 执行模型:三种 DurationPolicy 与 Stacking 的完整语义
Instant/Duration/Infinite 的差别不在"有没有时间"而在"留下什么记录":这篇讲 GE 的生命周期状态机、Stacking 的聚合规则、以及 Modifier 和 Execution 的选择边界。
-
MultiplayerSpawner 的节点镜像:spawn 出来的节点怎么在两端对齐
MultiplayerSpawner 盯住 spawn_path 下的子树,谁在权威端 add_child,对端就收到一条 spawn 消息并原样重建节点。这篇讲它靠 SceneTree 信号发现节点增删、spawn 消息里到底带了哪些字段、自定义生成函数的适用场景,以及名字冲突和 spawnable 列表不一致这类典型 desync。
-
AbilityTask 机制:异步事件怎么挂进技能生命周期
AbilityTask 是"激活期内的临时订阅者":把技能挂到外部事件上(Montage 窗口、目标数据回传)而不让技能永久存活。这篇讲实例归属、ShouldBroadcastAbilityTask 的三重门和预测键在任务链上的传播。
-
GDScript 字节码指令集与 dispatch 循环:callp 函数的两千行 switch
GDScript VM 的执行核心是一个两千多行的巨型 switch:每条指令从共享的 int 流里取操作码和操作数,按地址空间下标取 Variant。这篇拆这个循环的结构、分发成本模型,以及为什么不用 computed goto 和 JIT。
-
渲染驱动多后端:Vulkan/D3D12/Metal 的统一入口
4.3 拆出 RenderingDeviceDriver 后,D3D12 和 Metal 后端在 4.4 起陆续落地。这篇讲 RDD 的职责边界、三个后端文件的翻译策略,以及渲染上下文为什么也要单独拆一层。
-
RoPE 的复数形式与实现技巧
RoPE 的实现把 query/key 的维度两两配对看成复数,乘上 e^{imθᵢ} 就完成了一次旋转。复数视角不仅省推导,还直接给出工程上最重要的三个技巧:rotate_half 等价于配对置换、cos/sin 表缓存、复数乘融进 attention kernel。
-
pause 模式:process_mode 与 SceneTree.paused——暂停游戏时哪些节点还在跑
paused 只是一道布尔闸门,真正决定节点停不停的是沿 owner 链向上解析出的有效 process_mode;物理服务器不停步、动画默认停——把这两条边界想清楚,暂停逻辑就没什么神秘的了。
-
UID 系统:uid:// 路径如何映射到文件
Godot 4 用 64 位 UID 取代路径做资源引用,移动文件不再断链。这篇讲 UID 的编码格式、三处存储位置、ResourceUID 单例的查表流程,以及复制文件导致 UID 撞车的踩坑现场。
-
RPC 实现:UFUNCTION(Server/Client) 的调用打包与丢失语义
一次 RPC 从 ProcessEvent 到 bunch 发送的路径,reliable 队列的重传时机,以及参数序列化与 WithValidation 的细节。
-
Subsystem 体系:五种 Subsystem 的生命周期差异
Engine/GameInstance/World/LocalPlayer/Editor 五种 Subsystem 各活多久、随谁销毁,以及为什么它能取代手写单例。
-
AnimBP 编译:AnimGraph 到 FAnimInstance 的生成
Designer 面板里连的节点图怎么变成每帧执行的求值栈:AnimBP 编译期的图变换、FAnimInstanceProxy 的分工、FastPath 怎么消掉反射开销,源码基于 UE5.3。
-
Node2D 与 CanvasItem:全局变换的脏标记传播
CanvasItem 用脏标记加惰性求值维护全局变换:改动只向上冒泡置位、计算只在读取时发生;理解了传播规则,才能解释为什么频繁改 transform 的 2D 游戏不必每帧全树重算矩阵。
-
GDScript 编译管线:从 tokenizer 到字节码 VM
一行 print() 从源码文本到被执行,要经过 parser、analyzer、compiler 三段管线,最后变成 GDScriptFunction 里的 int 数组字节码。基于 4.2 源码拆解每个阶段产出什么。
-
ENetConnection:可靠 UDP 的通道模型与传输模式怎么选
Godot 默认走 ENet(可靠 UDP 库),它的核心抽象是通道 + 四种发送标志。这篇讲通道之间为什么互不阻塞、Godot 三种 transfer mode 在 ENet 层的映射、MTU 与分片的边界,以及带宽节流的触发条件。
-
GPU Scene 与 SceneProxy:场景数据如何上 GPU
拆解 UE5 的场景数据通路:FPrimitiveSceneInfo 怎么注册、SceneProxy 的游戏线程/渲染线程分工、GPU Scene 大缓冲的增量更新机制。
-
Object 基类与 ClassDB:Godot 反射系统与信号槽的地基
C++ 没有反射,Godot 用 GDCLASS 宏和 ClassDB 静态注册表造了一套。这篇讲类怎么注册、方法怎么绑定、信号连接存在哪,以及 emit_signal 为什么要在发射前复制一份连接列表。
-
Nanite Cluster 剔除流程:两级 HZB 与持久线程的源码走读
拆解 Nanite 剔除流水线:DAG 按误差切割、两遍遮挡剔除、持久线程调度,源码基于 UE5.3。
-
PackedScene::instantiate:场景实例化的完整链路
一个 .tscn 文件 instantiate 成活节点树要经历取字符串表、建对象、递归子场景、刷属性、连信号五步,owner 在实例化时被重新赋值——这篇按 scene_state.cpp 的代码走一遍。
-
UHT 与 .generated.h:反射代码生成的输入输出
看懂 .generated.h 里 GENERATED_BODY 宏的展开结果,理解 UHT 把头文件翻译成注册代码的完整流水线。
-
signal 语法糖的展开:从 connect 到 emit,编译器帮你写了什么
signal 声明、.emit()、.connect() 三件套背后没有专用指令系统:声明进脚本的信号表,emit 编译成方法调用落到 Object::emit_signal,connect 生成一次性可调用对象。这篇把语法糖逐层剥掉。
-
AudioServer 的软件混音器:bus 树的实时混音
Godot 不用系统混音器,自己在音频线程里做软件混音:所有 AudioStreamPlayer 把采样累加进各自 bus 的浮点缓冲,再沿 bus 树逐层流向 Master。这篇讲 mix 步骤的遍历顺序、效果器实例的挂接方式、音频线程与主线程的锁边界。
-
Godot Physics 3D 的 step 流程:space 到 broad phase 到 solver
一次物理迭代的内部步骤:兑现排队查询、施加重力、八叉树 broad phase 出候选对、窄相生成接触流形、按 island 迭代求解。基于 servers/physics_3d 源码(4.x)逐阶段拆,附冲量求解的数学。
-
UAsset/UPackage 格式:从 Package Summary 到 Import/Export 表
拆开一个 .uasset 的二进制骨架:Package Summary、Names/Import/Export 三张表各存什么,FPackageIndex 怎么当全文件统一的指针用,EditorOnly 数据在 Cook 时怎么被剥掉。
-
Main 类启动流程拆解:从 main() 到 MainLoop 开始迭代
main/main.cpp 四千多行,但真正决定"引擎怎么活过来"的只有 setup、setup2、start 三段。这篇把启动序列按依赖关系理一遍,标出每步为什么必须在这个位置。
-
Control 的布局通知:NOTIFICATION_RESIZED 何时触发
Control 的尺寸变化分两条路:anchor 变化同步触发 RESIZED,容器重排则走延迟的 SORT_CHILDREN;两条路的时机差异决定了自定义 UI 代码该把逻辑挂在哪个通知里。
-
Node 的 _ready 与 _process 调用链源码分析
从 add_child 到 NOTIFICATION_READY,再从 set_process 的标志位到 GDVIRTUAL_CALL 进脚本虚函数,把两个最常用的生命周期回调在 4.2 源码里的完整链路走通。
-
物理调试绘制与性能开销:打开"显示碰撞形状"后引擎在干什么
编辑器里勾上 Visible Collision Shapes 后,物理数据要经过两条完全不同的通路变成屏幕上的线框:节点侧走缓存的 debug mesh,服务器侧走每帧现算的 immediate 顶点流。这篇拆解两条通路各自的开销构成。