首页 / 资讯中心 / 文章详情

游戏引擎的前世今生:从架构演进到工程实践的核心原理

游戏引擎的前世今生:从架构演进到工程实践的核心原理 ★ FEATURED ARTICLE
刚合上这本书的时候脑子里绕来绕去的就一句话原来“引擎”这个东西不应该先问它有多少功能而该先问它出现在哪里解决了什么问题。过去几年我一直在和游戏引擎打交道写过编辑器扩展调过渲染流程也曾在凌晨一点对着某个引擎版本号怀疑人生。但因为项目一个接一个很少有整段时间去系统梳理“引擎为什么长成今天这个样子”。这次读完《游戏引擎原理与实践聊聊游戏引擎的前世今生》最大的收获反而不是某个具体接口怎么用而是搞清楚了引擎演进背后的逻辑。这本书适合两类人一类是刚入行、被各种引擎名字绕晕的新手另一类是用过 Unity、Unreal 或 Godot但说不清这些引擎“凭什么这么设计”的从业者。它不是一本纯粹的 API 手册而是把引擎当成一个“伴随游戏工业一起长大的产物”从硬件限制、团队分工、商业模式、技术选型多个角度串起来讲。所以这篇笔记我不会去做目录式的摘抄而是把书里和实际工作能对得上的部分拆开揉碎再结合我自己踩过的坑把“前世今生”和“能用得上”这两件事接起来。1. 游戏引擎的前世从“共用代码”到“独立产品”1.1 引擎的起源先有游戏后有引擎最后才有“复用”现在提起游戏引擎很多人都默认它应该是一个装着渲染、物理、音频、动画、UI、资源管线的“全家桶”。但书里把这个问题问得很直接为什么早期游戏没有引擎答案其实很朴素——因为当时的游戏足够简单简单到每个项目都可以从零开始写。早期做游戏的人更像是在做一个“非常复杂的实验室项目”代码是跟着具体游戏走的。你做一个横板跳跃就把跳跃物理写在主循环里再做一个横板跳跃不好意思之前的代码在哪个目录、依赖了哪些平台 API可能连作者自己都说不清。这种情况持续了相当长一段时间。真正让“引擎”这个概念浮出水面的是复用需求的集中爆发。同一个团队连续做了几个相似玩法的游戏后自然会发现平台初始化、主循环、精灵绘制、按键映射、音效播放这些代码在每个项目里都是重复劳动。有经验的开发者开始把这些共用模块抽出来做成内部共享代码库。这个阶段的“引擎”说难听点就是一个“常用代码文件夹”说好听点叫“内部框架”。这一段历史给我最大的触动是引擎的起源不是某个天才一拍脑袋的设计而是“重复劳动太多之后的自然抽象”。理解这一点再看后面那些复杂架构思路会顺很多。因为引擎里每一个大系统几乎都是为了解决某个反复出现的问题才被塞进去的。渲染系统是为了不重写 GPU 交互物理系统是为了不重写碰撞检测资源系统是为了不重写加载卸载——本质上都是“复用”两个字。1.2 早期引擎的形态代码授权、改源代码、以及“引擎即游戏”书里有一段讲 id Software 和《毁灭战士》《雷神之锤》的授权模式读着特别有画面感。那时候卖引擎不叫“卖引擎”叫“授权源代码”。买家拿到的不只是一个可调用的库而是一整套游戏源码包括关卡数据、美术资源、游戏逻辑。商用团队拿过去之后第一件事经常是把里的游戏换皮然后按自己的玩法改代码。这种模式放到今天是很难想象的但它塑造了一代引擎的基本思维引擎和游戏是绑定的引擎不是独立产品而是“强力游戏改出来的兄弟版本”。所以你会看到当初 Quake 引擎的代码里仍然保留着大量从游戏逻辑里带出来的痕迹。引擎代码和游戏逻辑的边界是模糊的你想把“做游戏的逻辑”和“引擎的底层设施”彻底切开并不是一件理所当然的事。这个历史细节对现在的启示是别把引擎看得太神圣。同为“引擎”有些是真的通用平台有些只是某一个成功项目的外壳。书里提醒读者要区分“技术上的引擎”和“商业营销上的引擎”。这个区分在实际选型时特别有用团队买引擎换引擎之前先想清楚我要的是底层渲染能力还是编辑器工作流还是跨平台发布能力不同引擎的“前世”决定了它们各自最强的那个点都不一样。1.3 引擎命名背后的隐喻为什么叫“引擎”这本书还花了不少篇幅聊“引擎”这个叫法本身。我印象很深的是它把引擎比作汽车的动力总成——你踩油门的时候不会去关心气缸怎么点火、曲轴怎么转你只关心车往前走。游戏引擎其实也一样它的目标是隐藏底层复杂度让开发者的创作意图能直接转化为画面和玩法。但这个隐喻有一个容易被忽略的反面引擎只解决“怎么跑起来”不解决“往哪里跑”。一辆车的引擎再强方向盘和导航也得靠人。很多团队在项目立项时把“用哪个引擎”当成第一决策反而忽略了玩法设计、关卡结构、内容管线这些真正决定项目成败的东西。书里没有直接说教但通过这些历史案例读者会很自然得出这个结论。我后来在做技术选型评审时也习惯先问一句这个项目的风险到底在技术上还是内容上2. 游戏引擎的今生架构演进正在改变整个开发方式2.1 数据驱动让策划不再和代码“排期”从早期引擎走到现代引擎最核心的变化之一就是“数据驱动”。这个词听上去很抽象但书里举了一个非常接地气的例子。假如你想调整一个怪物的血量。在硬编码时代你要找到怪物类改一个数字重新编译跑一遍流程。如果游戏逻辑和引擎代码还耦合在一起改一次数值可能要等整个工程重新构建。而在数据驱动的引擎里怪物的属性是一份配置文件或编辑器里的一个字段策划改完保存甚至不用重新启动游戏就能看效果。这个变化表面上只是“把数字从代码里挪到配置里”实际上改变了团队协作方式。策划不用再等程序改完才能做数值验证程序也不用被琐碎的平衡性调整打断。书里把这个过程称为“把创意的控制权交还给内容创作者”。数据驱动架构越彻底团队里的人越能从“等代码”中解放出来。但我在实际项目中见过反面的坑数据驱动如果做得太激进配置项会爆炸。一个简单的 NPC 有几十上百个字段策划面对一个巨大的编辑器面板根本不知道哪些字段有实际作用最后反而更依赖程序口头讲解。所以书里虽然推崇数据驱动但也提醒了“配置即代码”的复杂度问题。好的引擎会做 Schema 约束、默认值、分组折叠让“数据驱动”不至于变成“数据灾难”。2.2 渲染管线的演进从“固定管线”到“可编程管线”这本书讲渲染演进的时候没有堆砌复杂术语而是用了一个特别清楚的对比。早期的图形 API 就像一间“固定菜单的餐馆”你只能在 GPU 提供好的几种渲染效果里选比如开雾、开光照、开纹理但你不能定义“我要一种只有自己能想出来的混合方式”。后来 GPU 硬件进步API 从固定管线变成了可编程管线相当于餐馆把厨房也开放了你只要按接口提交一段小代码ShaderGPU 就按你的方式处理顶点和像素。读完这个对比很多以前的困惑一下子通了。为什么现在引擎都强调 Shader因为可编程管线把“画成什么样”的最终决定权交还给了开发者。引擎负责把场景数据喂到 GPU而 Shader 负责定义视觉风格。书里还把渲染管线的演进和硬件周期放在一起讲比如为什么 GPU 的 Unified Shader 架构会和引擎的物理渲染PBR走得这么近因为底层硬件越来越倾向于“统一处理”引擎的渲染框架也随之收敛到一套类似理论。对做项目的人而言这一段的实用价值在于理解“引擎的渲染特性和硬件高度绑定”。你不需要从零实现一个 PBR 模型但要明白为什么 PBR 现在这么流行、为什么有的老旧自定义 Shader 在新 GPU 上会出问题。这些判断不是靠背 API 能得来的而是靠理解渲染管线的历史。2.3 编辑器与引擎分离引擎从“代码库”变成了“软件产品”另一条让我产生很强共鸣的演进线是编辑器的成熟。早期的引擎几乎不给外部团队提供编辑器你要调整场景多半是手动改文件、重新编译、运行再看效果。这种做法现在的开发者很难接受但当时它是主流。后来商业引擎为了降低使用门槛开始把“所见即所得”的编辑器作为重要卖点。编辑器这层变化本质上是把引擎从“程序员专用的库”变成了“团队通用的产品”。美术可以直接在场景里摆放模型、调光、看效果策划可以直接拖 UI、配对话、设触发事件程序则负责扩展编辑器工具把团队内部的工作流嵌进去。这跟我自己用 Unity 和 Godot 的体验完全一致大多数项目时间其实不是花在如理解引擎原理而是在编辑器里打磨资源与配置。书里提醒了一个容易忽略的问题编辑器本身也是引擎的一部分引擎离开编辑器很多时候连资源都打不开。所以现代引擎的评价维度至少得有三层运行时性能、编辑器体验、工作流扩展性。只看渲染跑分来决定用哪个引擎很可能在资源管线上栽跟头。这个观点我特别认同而且我在好几个团队里见过类似的选型失误。3. 游戏引擎原理剖析一帧画面背后的三角平衡3.1 一帧的完整链路输入、逻辑、渲染的循环读书的过程中我把“游戏引擎到底每时每刻在做什么”这个问题重新捋了一遍。无论是 2D 还是 3D无论是 Unity、Unreal、Godot 还是自研引擎核心循环都逃不开下面这个结构while (游戏运行中) { 1. 处理输入键盘/鼠标/手柄/触屏 2. 更新游戏逻辑角色移动、技能判定、AI思考 3. 渲染当前画面计算可见物、提交绘制指令 4. 处理音频、网络、物理等其他系统 }这个循环通常被称为“游戏主循环”或“帧循环”。书里讲得更细一帧的时间不是固定不变的它会受硬件性能、场景复杂度、外部负载影响而波动。因此引擎在每一帧开始时要先读取当前时间计算上一帧的耗时再决定这一帧的逻辑步长。如果一帧画得太慢游戏时间推进就会变慢画面就“卡”了。很多新手自学引擎时会先去看某个渲染特效怎么做这其实不是最好的路径。先把“一帧是什么”理解清楚你才知道为什么很多性能优化最后都归结为“减少每帧的重复计算”。比如把静态物体合并到一张 Mesh、把频繁调用的查询缓存起来、把不需要每帧更新的系统降频执行这些都是围绕“帧循环”的优化思路。没有这个框架你看到的所有优化技巧都是散的点拼不起来。3.2 资源管理再强的机器也会被“全程加载”拖垮书中有一章专门讲资源生命周期我几乎是带着冷汗读完的。因为我在开发中确实有过不止一次“为什么内存越用越多、最后直接卡死”的惨痛经历。资源管理的本质很简单一个游戏里有大量模型、贴图、音频、动画、预设体你不能指望所有内容都一次性塞进内存。引擎需要有一套机制决定“什么时候加载”“什么时候卸载”“内存不够时怎么办”。书里给出的核心模型是“引用计数 显式卸载”两套经典策略。引用计数好理解一个资源被场景引用引用数加一引用释放引用数减一归零时就进入可卸载状态。显式卸载则更粗暴引擎提供一个接口让开发者手动告诉它“这个场景已经不看这张贴图了你可以丢掉它”。现代引擎还会做流式加载根据摄像机位置预加载可能即将进入视野的大世界区块。我总结的教训是引擎的资源管理再智能也不能替代开发者的预算意识。你在设计游戏时就应该清楚“这个地图峰值内存大约多少”“同时最多有多少角色在场上”“UI 会不会承载大量高分辨率图片”。很多线上问题像长时间游玩后内存膨胀、切场景卡顿根因都是资源生命周期设计不清晰而不是引擎本身不够好。书里说的那句“资源管理是引擎里的隐形骨架”我举双脚赞成。3.3 逻辑更新与帧率稳定tick 的取舍这本书还有一个我很喜欢的小节讲“游戏逻辑的更新频率”。以前用 Unity 的时候很多人只知道有 Update 和 FixedUpdate但不明白为什么既要有“每帧调用”又要“固定时间间隔调用”。书里把这个差异说明白了渲染帧率会随硬件波动但物理计算、网络同步这类系统往往希望以稳定步长推进否则物理模拟会漂移。用一个生活类比来说你一边小跑一边用相机拍照。跑得快拍得多跑得慢拍得少但人实际位置变化应该用“秒”来衡量而不是用“拍了几张照片”来衡量。如果每帧都推进固定距离帧率高时角色会瞬移得慢帧率低时角色反倒跨大步结果就是不同电脑上体验完全不一样。所以引擎会把逻辑更新分成两类一个是跟随渲染帧的实时更新一个是以固定时间间隔推进的模拟更新。我很多时候排查“为什么低配机器上角色容易穿墙”这类问题最后都指向 FixedUpdate 步长和物理插值没配合好。书里给出的建议是理解引擎默认步长按项目需求调整如果做竞技类游戏逻辑和同步更是要严格按固定 tick 来设计。这一块的原理属于“不读源码也能遇到的坑”但读了原理之后再回头看会清楚非常多。4. 阅读笔记里的“干货”书里不会明说的经验与教训4.1 好引擎是“适合团队”的不是“跑分最高”的书里回顾了很多公司自研引擎的故事也写了商业引擎的兴衰但我从中提取到的最实用判断标准一句话引擎选择的本质是团队风险偏好和匹配度。比如一些小团队做 2D 网络游戏非要去上一个大而全的重型引擎结果编辑器复杂、发布流程长、服务端 Canvas 搭建繁琐反而是简单直接、上手平滑的轻量引擎更合适。反过来做开放世界大作还在一味追求上手简单结果后续扩展处处受限这种“简单”就成了假象。书里反复强调“没有最好的引擎只有最适合项目的引擎”这句话听起来像正确的废话但结合前面那些历史案例你能真正感受到选错技术栈的代价。我的实操经验是做技术选型时先列出“项目未来半年一定会碰到的 20 个技术需求”然后拿每个引擎去试而不是让团队对着功能清单空想。比如需要大世界流式加载就做一个原型场景测需要大量 UI 复杂交互就把 UI 工作流跑一遍。跑分和画面演示都是是骡子是马拉出来遛遛只有真正按自己项目流程跑过才知道匹配度怎么样。4.2 谈扩展点设计从 BepInEx 能注入哪些引擎说起读完引擎架构的演进之后再回头看社区里经常讨论的 BepInEx 注入问题思路会特别清晰。BepInEx 本质上是一个针对基于 .NET/Mono 运行时的游戏模组加载框架。它能对游戏进行注入靠的不是“万能破解”而是利用了引擎的托管运行时特性只要游戏是用带 .NET 脚本后端的引擎制作运行时里就有可供外部模块挂接的层BepInEx 就能把代码和配置插进去。很多人会问“BepInEx 能注入哪些游戏引擎”我按引擎类型做了一个实际可参考的对照引擎类型BepInEx 支持情况实际表现UnityMono 后端打包支持最好大量独立游戏、中小型 Steam 游戏都能用社区教程也多UnityIL2CPP 后端打包原生不支持需额外转换工具链大型商业 Unity 游戏多为 IL2CPP直接注入难度显著上升GodotMono / .NET 版本有一定支持余地必须开启 .NET 版本、用 C# 编写逻辑纯 GDScript 和不带 .NET 的导出包不行纯 C 引擎Unreal、自研引擎不支持没有 .NET 托管层不存在“按托管逻辑注入”的切入点这个对照表背后的原理其实就是引擎架构的“封闭”与“开放”问题。一个引擎的扩展性越好越应该提供官方支持的插件、脚本、模块接口如果把逻辑全部编译到原生层、没有任何脚本运行时那社区就只能走更复杂的原生注入路线。我自己的感受是项目在选引擎的时候要提前想好“后续需不需要支持 Mod 生态”。如果一定需要尽量选托管层开放、文档明确、社区方案成熟的引擎。这比后期被迫“逆向”要省太多力气。4.3 跨平台与字符集从 Godot 游戏乱码聊起字符集另一个让很多玩家和开发者都崩溃的问题是“Godot 引擎游戏乱码”。这表面上看是一个小 bug背后暴露的其实是引擎在跨语言、跨平台开发时的字符集处理机制。书里没有直接用 Godot 举例但对字体与编码的原理讲得很透完全可以对应上。先说说乱码最常见的原因通常有两个层面。第一是字体层面默认字体或项目自带的字体文件不包含中文字形于是所有汉字渲染出来都是方块或空白。第二是文本编码层面你把翻译文本放在 CSV、JSON 或其他配置文件里但保存成 GBK 编码而引擎默认按 UTF-8 读取于是读出来的字符串就变成了乱码。针对 Godot我给经常遇到乱码的朋友一个排查清单可以按顺序走先看游戏内文字到底是一堆“口口”还是完全乱码。如果是口口说明文字被正常读取了但字形缺失需要给项目设置一个包含中文的字体文件或在 Godot 4 里配置系统字体回退。如果是完全不认识的乱码符号那大概率是文件编码问题。用文本编辑器重新把 CSV/JSON 保存成 UTF-8最好带 BOM避免和 Godot 的编码检测产生歧义。还需要检查是不是把文本输出到了控制台Windows 控制台的代码页和引擎内部编码不一致也会显示乱码这种情况游戏画面不一定有问题。5. 常见问题与排查技巧实录5.1 中文文本在游戏里变乱码排查三步走既然前面提到了 Godot 乱码这里就把通用排查方法再写细一点。其实不只是 Godot很多引擎遇到中文乱码都可以用同一个“三步走”来定位省得瞎试。第一步判断文本来源。看这串乱码是来自界面上的静态文本、剧情对话还是运行时动态拼接的字符串。静态文本乱码多半是字体或资源导入阶段的编码问题动态文本乱码则可能是代码文件本身的编码没统一或者说字符串拼接时把十六进制字节错误转换了。第二步检查文件编码。每个引擎对文本文件都有默认编码假设最常见的规则是“UTF-8 无 BOM”。如果你用 Windows 记事本保存成 ANSI 中文或者用 Excel 导出的 CSV 是本地代码页编码引擎读进来之后就会得到一堆 Unicode 替换符。这一步排查手段很简单用 VS Code 或 Notepad 打开文件看右下角显示的编码种类统一转换成 UTF-8 再测试。第三步检查字体回退。如果你确认文本内容是正确的但显示为方块问题就出在“字形覆盖”上。引擎渲染文字时会先看指定字体是否包含这个字符没有就查回退字体。没有回退策略就直接画一个空心方块。解决方式是为项目准备一个覆盖常用中文范围的字体或者在 Theme 里配置系统字体回退。这一步在 Godot 里尤其常见默认字体对中文支持不全换上兼容字体立刻恢复正常。5.2 MOD 注入失败先看引擎再找原因回到 BepInEx 的话题很多玩家问“能不能注入某个游戏”其实我们可以把“注入失败”分成几种常见原因和引擎类型没有直接关系但和项目构建方式关系很大。第一种游戏根本不是托管脚本引擎打包的。比如 Unity 项目使用 IL2CPP 发布或者干脆就是纯 C 引擎BepInEx 找不到一个托管入口自然无法注入。这种情况先查游戏的根目录如果能看到 Managed 文件夹或一堆 DLL那多半是 Mono 后端有机会如果只有一个主程序 exe 和一堆 native 库就别折腾了。第二种版本不匹配。BepInEx 的版本、游戏当前的运行库版本、以及你装的插件版本三者之间可能互相冲突。我建议先只放一个最小的配置插件测试能跑通再逐步加内容。很多“注入后游戏黑屏”的问题都出在某插件和游戏内部 API 冲突而不是 BepInEx 本身。第三种启动时的执行顺序问题。BepInEx 是在游戏启动早期通过环境变量或预加载 DLL 机制挂进去的如果启动器有其他保护措施、或者游戏有自己的 DLL 校验就会失败。对于非官方 Mod 支持的游戏本来就存在被游戏自身安全机制拦下来的可能这和引擎本身无关。排查时要看 BepInEx 的日志文件路径通常在 BepInEx/LogOutput.log按日志提示定位远远好过瞎试。我把这个经验也反向用到了项目开发里如果一个引擎能很轻松地被社区做 MOD那么它的开放性确实很强如果一个引擎把所有东西都焊死那它的扩展性上限就需要提前评估。引擎的实际开放程度在最开始设计架构时就基本注定了。5.3 引擎升级后的项目“瘫痪”回滚与迁移策略还有一种项目里非常常见的灾难引擎版本一升级项目突然打不开或者构建出来的包行为异常。这本书里讲了引擎版本演进带来新功能但我更想分享实际项目里如何应对升级风险。我的一条铁律是引擎升级前必须先完整备份当前项目状态并且记录当前引擎版本、插件版本、关键配置。不是说升级一定会出问题而是出了问题之后“回滚”必须是一件低成本的、不需要思考的事。我见过很多团队在升级后才发现自定义编辑器脚本报错但旧版本已经被覆盖想退回都找不到入口最后只能临时改代码又忙又乱。引擎大版本升级的迁移顺序也有讲究。先在小范围分支上做兼容性修复比如项目的代码、Shader、资源导入规则分别调整。不要一次性把项目中所有内容都迁完再测试那样出错时很难定位是哪一块引入的问题。书里其实也暗示了引擎迭代和项目迭代之间的“节奏错位”引擎是持续接入新硬件的但游戏项目在某个阶段反而需要稳定所以工程上要刻意控制升级节奏。我的习惯是给项目设一个“引擎版本冻结期”比如项目进入 alpha 之后除非有修复关键 bug 的需求否则不轻易升级引擎。内容研发阶段可以用新版本的新特性但真的要冻结保稳定时就不能让引擎的“发展需求”干扰项目的“交付需求”。6. 给入行者的实践建议6.1 想深入引擎先学会“复读”推荐阅读路径如果你也打算系统学习游戏引擎我建议不只盯着某一款引擎的操作教程看而是先读原理类书籍。我把这类阅读称为“复读”——不是让你一遍一遍重复读同一页而是从不同角度反复回到同一个核心问题直到答案变成直觉。书单方面《游戏引擎架构》是我反复翻的一本它把引擎的模块拆得很细适合建立整体认知《游戏编程模式》讲的是各种设计模式在游戏中的应用适合理解“为什么引擎代码长那样”再加上像《游戏引擎原理与实践聊聊游戏引擎的前世今生》这类结合演进史的书能把技术和行业背景接起来。读这三类书信息密度一点也不比刷几个视频低。但我不建议一上来就啃大部头。我的个人路径是先在一个实际引擎里做出一个小游戏体会“原来我都需要哪些模块”再回去读原理书很多晦涩的章节就有代入感了。带着问题读书效率会高好几倍。6.2 读引擎源码的第一课别从渲染开始如果想进一步去读开源引擎比如 Godot 或更小型的自定义引擎很多新手的第一个冲动是去看渲染模块因为视觉上最刺激。但以我的经验这不是最佳入口。渲染模块涉及 GPU 硬件、各种图形 API、Shader 编译对初学者来说变量太多很容易陷进去出不来。我更推荐从“主循环”和“场景结构”入口。找引擎里的主循环代码看看一帧的流程是怎么组织的再看场景系统怎么管理一堆游戏对象怎么处理组件。这两个部分奠定了对引擎世界的直接理解之后再往渲染、物理这些方向伸展时你会清楚它们作为什么角色挂在哪个环节上。读源码的时候别追求把每一行都看懂。先画出“引擎启动之后按什么顺序创建哪些系统”的脑图再标出“一帧内哪些系统被调用”基本就能形成骨架。源码阅读难的从来不是某个函数而是你不知道“这一段代码是被谁调用的、为什么在这里”。主循环就是最大的那根主线抓住它就不会迷路。6.3 个人心得引擎学习的“主线”最后说一点自己的体会。我越来越觉得学习游戏引擎的过程不是一条从小到大“背功能和接口”的直线而是一条“反复追问自己为什么”的循环。每当你遇到一个引擎功能比如光照烘焙、LOD、资源热更、动画状态机都不妨追问三个问题它解决的是什么问题它为什么用这种方式解决换成另一种方案会带来什么代价这三个问题其实正好对应了这本书讲“前世今生”的核心方法——用历史的眼光看待技术选择。我见过不少开发者刚入行时热切地收集各种引擎快捷键、小技巧过阵子发现这些碎片知识过时也快换来换去最后什么都没留下。反倒是那些能讲清楚“为什么引擎这样设计”的人在新引擎层出不穷的环境里总能快速迁移。原理是迁移能力的底座是你学了这一套换到下一套还能通用的部分。我现在做新项目时还留着一个小习惯每天抽一点时间对照着引擎的实际行为和书里的原理去验证。比如自己做一个很小的多场景切换顺便给资源加载做一次内存分析或者给 UI 加一个本地化文本检验一下引擎对编码的处理。这些小实验成本极低但比单纯读书记得牢得多。希望这篇阅读笔记能成为你的起点带着“为什么”去翻开任何一本引擎书或任何一份引擎源码你会发现过去那些看不见的东西其实一直决定着你未来的走向。
阅读完成 · 觉得有帮助?
咨询建站