《游戏引擎实现原理》9.3 选型与自研的判断标准

前两节对照了「各家怎么做」,这一节回答「你该怎么选」。选型不是挑一个引擎名字,而是决定「哪些成本由你自己扛」。本节给出三条判断维度——差异化落在引擎的哪一层、团队与时间账、平台与维护年限——并把它写成一条可执行的规则:输入项目画像,输出建议与理由。

9.3 选型与自研的判断标准

前两节一直在做对照:9.1 把「同一件事各家怎么做」拆成单一坐标轴,9.2 把「同一个特性各家走哪条路」摊开。但读者读到这里,心里真正的问题其实是另一个:那我到底该用现成引擎,还是自己写一个?

这个问题没有普适答案,但它有判断标准。本节的任务就是把「该用现成的还是自研」从一句口号,变成一套能说清理由、能复查、甚至能写成代码的判据。核心心法只有一句:选型不是挑一个引擎名字,而是决定哪些成本由你自己扛。9.2 已经证明——每条实现路径的代价都要落在某个人头上;自研,就是主动把那部分代价揽到自己身上。

本节给出三条判断维度,最后把它们写成一条可执行的规则。

要先说清楚一点:本节不推销任何一种选择。现成引擎和自研各有各的适用场景,说「自研更好」或「现成引擎更省」都是错的——错在它们都把「对谁而言」这个前提丢掉了。本节要做的,是把「对谁而言」重新装回判断里,让每个决定都能说出一条可复查的理由。

这一节和 9.1、9.2 是配套的:前两节教你怎么看,这一节教你怎么选。看是前提,选是结果——看不清代价落点的人,选出来的往往是「别人说好」的答案,而不是「适合自己」的答案。

维度一:差异化落在引擎的哪一层

第一条维度问的是:你的游戏,独特之处到底在引擎的哪一层。引擎是分层的,9.1 已经见过 helio 的分层(Platform < Kernel < Render < Subsystem < Game)。差异化落在越上层,现成引擎越可能覆盖;落在越下层,自研才越划算。

差异化所在层典型需求现成引擎能否覆盖建议
表层(玩法 / 关卡 / UI)独特的战斗手感、关卡设计、UI 表现能,用脚本与配置即可现成引擎
中层(渲染特性 / 物理 / 网络模型)特定的光照模型、自定义碰撞、回滚网络部分能,靠插件或扩展点现成引擎 + 扩展
底层(内存布局 / 调度 / 帧图)自研 ECS 存储、任务调度、帧图别名很难,往往要改到引擎内核自研

这张表把 9.2 的四条路径又串了一遍:资源句柄、帧图、脚本绑定、资产序列化,恰好分别落在底层与中层。如果你的差异化只停在表层,那 9.2 里那些「代价记在谁头上」的讨论对你意义不大——现成引擎已经替你付过账了。真正需要读这一章的人,是差异化落在中层或底层、不得不关心这些机制的人。

把这张表记成一句话:越靠近内核的差异化,越值得自研;越靠近玩法的差异化,越不值得。 玩法层的差异化能用脚本与配置表达,不需要动引擎;内核层的差异化,现成引擎往往只给一个很窄的扩展口,绕不开就只能自己写。

一个常见的误判是把「想自己控制」当成「必须自己写」。想控制渲染流程,不等于要从零写一个渲染器——先问「现成引擎有没有扩展点」。Godot 有 GDExtension、Bevy 有插件与 system、Defold 有原生扩展;只有在扩展点也绕不开、非得改内核时,自研才成立。

这条维度还有一个好处:它把「自研」这个词的粒度拆细了。现实里极少有「整套引擎全自研」或「整套全现成」,更常见的是逐层选择——玩法层用现成引擎、渲染层加扩展、只在某一两个最底层的模块自研。所以别问「要不要自研」,问「哪一层要自研」,答案会清楚得多。

维度二:团队与时间账

这条维度最容易被「热情」盖过去。技术人天然倾向于「自己写更爽」,但项目不是靠爽不爽运行的——它靠的是「能不能在预期时间内交付一个能跑的东西」。所以这一节先把账算清,再谈感情。

第二条维度更现实:就算差异化真的落在底层,你有没有人、有没有时间自己写。引擎不是写完就能用的,一个能出产品的引擎要包含构建、测试、性能、跨端、调试工具——正是本书卷四这一整卷的内容。这些工作量,跟「实现一个特性」完全不是一个量级。

因素倾向现成引擎倾向自研
引擎工程师0~2 人3 人以上,且长期稳定
上线时间6 个月内18 个月以上
目标平台1~3 个4 个以上,且要自己控降级
计划维护年限1~2 年5 年以上
已有引擎经验无团队做过引擎或深度改过引擎

这张表要连着读,不能只看一行。一个 5 人团队想自研,如果只做 6 个月的原型,且差异化在表层,那自研必然亏;反过来,一个 50 人团队做 5 年长线项目、差异化在底层,现成引擎反而会成为天花板。「有没有引擎工程师」通常是最硬的一条——没有它,其他条件再好也白搭,因为自研的第一个坎往往不是渲染,而是工具链。

还有一条隐性成本常被漏算:自研引擎的维护成本是持续的。现成引擎由社区或厂商分摊维护,你只需跟版本;自研引擎的每一处 bug、每一次平台适配、每一个新 API,都只有你自己修。本书卷四整卷讲的构建、测试、性能、跨端,其实就是这份维护成本的具体清单。

把这条维度也读成「成本记账」会更清楚:选现成引擎,是把引擎的研发与维护成本外包给厂商或社区,你付出的是「控制权」与「适配成本」;选自研,是把这些成本内包,你付出的是「人力」与「时间」。两者没有谁更省,只有「你的团队更擅长付哪一种」。一个有引擎工程师的团队,付人力成本更划算;一个没有的团队,付适配成本更划算。

维度三:退出成本与锁定

第三条维度问的是:一旦选了这条路,将来换掉的代价有多大。这条维度常被忽略,但它在长线项目里往往是决定性的——因为它决定了你对「需求变化」这件事的定价。

选择锁定程度换掉的代价
用现成引擎做表层玩法低,玩法多是数据与脚本小,资产与数据可移植
用现成引擎 + 深度扩展中,扩展绑定引擎 API中,扩展层要重写
自研引擎高,整套工具链自持大,基本等于重来

退出成本的现实意义在于:它让你对不确定性定价。如果一个项目的需求还在剧烈变化,选一条退出成本低的路,等于给自己留了反悔的余地;如果需求已经稳定、且确信要长期投入,退出成本高一点也可以接受,因为你不打算退出。

helio 的处境很典型:它为「讲清原理」而自研,退出成本极高,但它的项目目标从一开始就锁死在「教学」上,不会中途改成「做商业游戏」——所以高退出成本在这里不构成风险。反过来,一个还在探玩法的团队如果选了高锁定的路,一旦方向要改,沉没成本会把团队拖住。锁定的代价,和项目的不确定性成正比。

这条维度还提醒我们:「要不要自研」和「能不能自研」是两个问题。前两条维度回答「能不能」,第三条维度回答「要不要」——一个项目可能在能力上完全能自研,但因为需求太不确定,理性的选择反而是先用现成引擎。

三条维度不是等权的

维度一、二、三摆在一起,很容易被误读成「三条各占三分之一」。实际上它们的权重差别很大,而且否决顺序是固定的。

  • 维度一是一票否决:差异化停在表层,无论团队多强、时间多长,自研都是浪费。它决定「要不要谈自研」。
  • 维度二是硬门槛:差异化够深但没人,也轮不到自研。它决定「谈不谈得成」。
  • 维度三是调节项:前两条都过关后,退出成本才用来微调——需求不稳就压一压自研的冲动,需求已定就放宽。

换句话说,三条维度不是并列打分,而是串联过滤:先过第一道,再谈第二道,最后用第三道微调。可运行工具里的 recommend() 正是按这个顺序写的三道闸,而不是加权求和——这又一次呼应了 9.1 里「综合评分表会抹掉前提」的批评。

可运行产物:把判断写成可执行的规则

三条维度的讨论如果只停在表格上,仍然容易被「拍脑袋」绕过——因为表格不会替你把「深度 4 分、工程师 2 人」这种情况算出一个结论。所以本节把三条维度写成一条纯函数规则:输入项目画像,输出建议与理由。规则本身不含任何厂商宣传,只编码正文讨论的阈值。

// file: tools/engine-choice.mjs
// tier: L1
// verify: node tools/engine-choice.mjs
//
// 第 9 章:选型与自研的判断标准。把「用现成引擎 / 现成引擎 + 扩展 / 自研」
// 写成一条可复核的规则,而不是一句口号。纯函数,输入项目画像,输出建议与理由。
// 规则里的阈值来自正文讨论的四个维度,不是厂商宣传。

/** 项目画像。四维打分,均为 0..5 或计数。 */
function recommend(profile) {
  const reasons = [];
  const deep = profile.differentiationDepth >= 4; // 差异化是否落在渲染/物理/内存最深处
  const hasEngineTeam = profile.engineEngineers >= 3;
  const widePlatforms = profile.platformCount >= 4;
  const longLived = profile.yearsToMaintain >= 5;

  // 第一道闸:差异化不够深 → 现成引擎就能覆盖,自研是浪费。
  if (profile.differentiationDepth <= 2) {
    reasons.push(`差异化深度 ${profile.differentiationDepth}/5:现成引擎已覆盖`);
    return { choice: '现成引擎', reasons };
  }
  // 第二道闸:差异够深,但没有引擎工程师 → 也先别自研。
  if (deep && !hasEngineTeam) {
    reasons.push('差异化深但引擎工程师不足 3 人,自研会先死在工具链上');
    reasons.push('先用现成引擎 + 插件把玩法验证掉');
    return { choice: '现成引擎 + 扩展', reasons };
  }
  // 第三道闸:差异够深、有人、且要长期维护 → 自研才划算。
  if (deep && hasEngineTeam && (longLived || widePlatforms)) {
    reasons.push(`差异化深度 ${profile.differentiationDepth}/5 落在引擎最深处`);
    reasons.push(`引擎工程师 ${profile.engineEngineers} 人,维护 ${profile.yearsToMaintain} 年`);
    if (widePlatforms) reasons.push(`目标平台 ${profile.platformCount} 个,跨端降级要自己控`);
    return { choice: '自研', reasons };
  }
  // 其余:深度中等或团队/时间紧 → 现成引擎 + 扩展,把力气花在差异点上。
  reasons.push('差异化中等,不值得从零造轮子');
  if (widePlatforms) reasons.push('平台多,但可以靠引擎的多端导出兜住');
  return { choice: '现成引擎 + 扩展', reasons };
}

const profiles = [
  { name: '2 人 · 2D 休闲 · 8 周', differentiationDepth: 1, engineEngineers: 0, platformCount: 2, yearsToMaintain: 1 },
  { name: '8 人 · 3D 独立 · 18 月', differentiationDepth: 3, engineEngineers: 1, platformCount: 3, yearsToMaintain: 3 },
  { name: '50 人 · 开放世界 · 5 年', differentiationDepth: 5, engineEngineers: 8, platformCount: 5, yearsToMaintain: 6 },
  { name: '5 人 · 网页玩法原型 · 深渲染', differentiationDepth: 4, engineEngineers: 1, platformCount: 2, yearsToMaintain: 2 },
];

let ok = true;
for (const p of profiles) {
  const r = recommend(p);
  console.log(`${p.name.padEnd(28)} -> ${r.choice}`);
  for (const why of r.reasons) console.log(`    · ${why}`);
}

// 断言:三个典型画像必须给出符合预期的建议
const byName = Object.fromEntries(profiles.map((p) => [p.name, recommend(p).choice]));
ok =
  byName['2 人 · 2D 休闲 · 8 周'] === '现成引擎' &&
  byName['50 人 · 开放世界 · 5 年'] === '自研' &&
  byName['5 人 · 网页玩法原型 · 深渲染'] === '现成引擎 + 扩展' &&
  ok;
console.log(ok ? 'ASSERT OK' : 'ASSERT FAIL');
process.exit(ok ? 0 : 1);

本机实跑(node tools/engine-choice.mjs):

2 人 · 2D 休闲 · 8 周            -> 现成引擎
    · 差异化深度 1/5:现成引擎已覆盖
8 人 · 3D 独立 · 18 月           -> 现成引擎 + 扩展
    · 差异化中等,不值得从零造轮子
50 人 · 开放世界 · 5 年            -> 自研
    · 差异化深度 5/5 落在引擎最深处
    · 引擎工程师 8 人,维护 6 年
    · 目标平台 5 个,跨端降级要自己控
5 人 · 网页玩法原型 · 深渲染           -> 现成引擎 + 扩展
    · 差异化深但引擎工程师不足 3 人,自研会先死在工具链上
    · 先用现成引擎 + 插件把玩法验证掉
ASSERT OK

四个画像跑出三种结论,正好覆盖三条路线。注意第三个画像(50 人开放世界)被推到「自研」,不是因为它「更高级」,而是因为它同时满足了「差异在底层 + 有引擎团队 + 长期维护 + 平台多」四个条件;第四个画像(5 人深渲染)差异化虽深,但引擎工程师只有 1 人,规则把它拦在了「现成引擎 + 扩展」——这正是维度二里「有没有引擎工程师通常是最硬一条」的体现。

这个规则有一个刻意的性质:它的每一行输出都能追到一条维度。「差异化深度 1/5:现成引擎已覆盖」对应维度一;「引擎工程师不足 3 人」对应维度二;「跨端降级要自己控」同时牵涉维度二的平台行与维度三的锁定。规则不是黑箱,谁不认同某个结论,我们能立刻指出是哪条阈值有分歧,而不是吵总分。

还有一点值得强调:这条规则不给出「最优解」,只给出「可辩护解」。同一个画像,换个团队、换个时点,阈值就可能不一样——规则的价值不是替你拍板,而是把你拍板时的理由显式化,让它可被讨论、可被复查、可被修正。

顺带一提,规则里的 differentiationDepth 是一个 0~5 的主观分——它没法自动测量,只能由团队自己打。这不是缺陷,而是诚实:差异化深度本来就是一个判断,把它写成显式参数,比假装它可测要好。

什么时候必须自研,什么时候绝不自研

把两条维度合起来,能划出两块很清楚的区域:

  • 必须(或值得)自研:差异化落在引擎底层,团队有 3 名以上引擎工程师,项目要做 5 年以上或跨 4 个以上平台,且现成引擎的扩展点确实绕不开。这种项目里,现成引擎会成为天花板,自研换来的是对核心环节的完全掌控。
  • 绝不自研:团队小、周期短、差异化停在表层。这种项目里自研是最贵的错误——不是技术错误,是商业错误。本书反复强调的「工程化」不是让你什么都自己写,而是让你清楚每一行自己写的代码要付出什么。

两条区域之间还有一大片中间地带:差异化在中层、团队只有 1~2 名引擎工程师、项目 2~3 年。这片地带的答案通常是「现成引擎 + 扩展」——把引擎当底座,只在真正的差异点上写扩展。这也是大多数团队实际所处的区间,工具里前两道闸之后的所有分支基本都落在这里。

规则里那三道闸的顺序是有讲究的:先看差异化够不够深(不够深就直接否掉自研),再看有没有人(有人才有资格谈自研),最后才看时间与平台(长期或多平台才回本)。这个顺序把「最硬的否决条件」放在前面,避免被后面好看的指标带着走。

补一句:这三条维度的阈值(3 名工程师、5 年、4 个平台)是可调的经验值,不是圣旨。不同团队、不同项目,阈值可以不一样;重要的是阈值必须显式写出来、能被复查,而不是藏在直觉里。工具把它们写成常量,就是为了让它们可讨论、可推翻。

一张可带走的选型清单

把三条维度整理成一份可以带走的清单,照着问一遍,答案基本就出来了:

  1. 我的差异化在哪一层?在表层 → 现成引擎,别犹豫。
  2. 我有没有 3 名以上、能长期留下的引擎工程师?没有 → 别自研。
  3. 项目要活几年、跨几个平台?都少 → 别自研。
  4. 现成引擎的扩展点能不能绕开?能 → 用扩展,别自研。
  5. 需求稳定吗?不稳 → 优先选退出成本低的路。
  6. 前五条都指向自研,且确信要做 5 年以上 → 那就自研,但把维护成本预算先算够。

这份清单的顺序就是三道闸的顺序。它的价值不在于「替你决定」,而在于逼你把每个决定都说成一条能复查的理由。清单之外没有魔法——它只是把 9.1 和 9.2 教你的「看清代价落点」用在了决策上。

一个具体的算账例子

抽象的三条维度,落到一个具体项目上会更清楚。假设一个 6 人团队,要做一款 2D 动作游戏,核心差异是「非常精确的打击判定与帧数据」,计划 14 个月上线 PC 与 Switch 两个平台,团队里有 1 名做过引擎的程序员。

  • 维度一:打击判定属于玩法层,但「精确到帧」会牵动固定步长、输入采样、动画与物理的同步——已经摸到了引擎中层。
  • 维度二:只有 1 名引擎工程师,14 个月,2 个平台——三个指标都不支持自研。
  • 维度三:玩法还没验证,需求大概率还会变,退出成本越低越好。

三条维度合起来,答案很清楚:用现成引擎 + 扩展。把固定步长与帧数据的处理写成一个扩展模块,别去自研整个引擎。这个结论和工具对第四个画像(5 人深渲染)的判断一致——差异化够深但人不够,规则会把它拦在自研之外。

反过来,如果这个团队有 5 名引擎工程师、计划做 5 年、要上 5 个平台,答案就会翻到「自研」。同一种需求,因为团队与时间不同,最优路径完全不同——这正是本节反复强调的「对谁而言」。

三个常见误区

判断标准立好之后,回头看看最常听到的三个误区,它们都是把「对谁而言」丢掉之后产生的:

  • 误区一:「自研更可控,所以更好」。可控是有代价的:你要自己修每一处 bug、自己适配每一个平台。如果差异化不在底层,「可控」换来的是无处可用的自由。
  • 误区二:「现成引擎够用,所以永远不用自研」。够用是相对的。如果差异化正好落在引擎内核,而扩展点又绕不开,现成引擎会变成天花板——这时「够用」是假的。
  • 误区三:「先自研,不行再换现成的」。自研的退出成本最高,中途切换基本等于重来。正确顺序恰好相反:先用现成引擎验证玩法,确认差异化真的在底层,再考虑自研内核。

三个误区指向同一条纪律:先看清代价落点,再决定谁付账。这也正是 9.1、9.2 两节反复在做的事。

选型不是一次性决定

最后一个容易被忽略的点:选型不是拍一次板就完事的。团队规模会变、目标平台会变、需求会变,同一个项目在第二年可能就该重新跑一遍这三条维度。

  • 团队从 3 人扩到 30 人,维度二的「有没有引擎工程师」答案会变。
  • 从 PC 单平台扩到移动加主机,维度二的平台行与维度三的锁定都会变。
  • 玩法验证失败、转向另一个方向,维度一的差异化层次会变。

所以本节给出的不是一张「查一次就有答案」的表,而是一套定期复查的方法。稳妥的做法是把它写进项目的里程碑评审:每当团队规模、目标平台或核心玩法发生实质变化,就重跑一次判断。这条纪律和卷四其他章节一样——不追求一次性正确,追求的是变化发生时能立刻知道该重看哪一条。

三条维度与两条方法

本节是第 9 章的收尾,把本章的两条主线收拢一下:

  • 两条方法(9.1、9.2 教的):单一坐标轴、实现路径。它们让你看清各家在同一件事上的分岔与代价落点。
  • 三条维度(本节教的):差异化层次、团队与时间、退出成本。它们让你决定这些代价由谁扛。

两条方法解决「怎么看」,三条维度解决「怎么选」。缺了前者,选型会变成拍脑袋;缺了后者,看清了也没法落地。这也是本章把 9.1、9.2 放在 9.3 之前的原因——先学会看,再学选。

回到本章开头那句话:选型不是挑一个引擎名字,而是决定哪些成本由你自己扛。三条维度就是三把尺子,量的是同一件事:你愿意、也有能力,为哪些成本付账。

回到《微光岛》

最后把判断标准用回本书贯穿的项目。《微光岛》(glimmer-isle)是一本教学书的贯穿项目,它选择自研 helio,恰恰是因为它满足「必须自研」的画像:差异化落在引擎底层(这本书要演示存储、调度、帧图、序列化的实现原理),有足够的引擎工程师(作者),要做长期维护(一整套书稿),且目标环境要求自己控降级(WebGPU / WebGL2 双后端)。换句话说,helio 不是为了「炫技」而自研,而是因为它的项目目标本身就是「把引擎原理讲清楚」——差异化就在底层,自研是唯一选择。

这也解释了一个常见困惑:为什么本书不直接用 Godot 或 Bevy 来讲?因为用现成引擎,你会学到「怎么用引擎」,学不到「引擎怎么实现」。这正是 9.1、9.2 两节存在的意义——它们把 helio 的实现摆到坐标轴上,让你看清它和另外三家的分岔,而不是让你以为「只有 helio 这一条路」。

需要特别澄清的是:《微光岛》自研 helio,是一个「为了教学」的特例,不是一份「商业项目该这么做」的推荐。真实项目里,同样的三条维度往往会把结论推向「现成引擎 + 扩展」。如果你读完本章的收获是「原来我也该自己写引擎」,那多半是读反了;正确的收获应该是「原来每个选择背后都有一笔账,我能说清自己这笔账是怎么算的」。选型综述类文章,站内 /posts/game/engine/ 讲得更贴近实操,本章与它是互补而非替代。

下一章是本书的最后一章:把三卷 helio 的原理做一次全景回顾,然后说说接下来还能往哪走。

阅读导航:上一节:9.2 同一个特性在不同引擎里的实现路径 · 下一节:10.1 引擎实现原理全景回顾 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「engine」更多文章

  1. 《游戏引擎实现原理》工程与实战 目录
  2. 《游戏引擎实现原理》9.2 同一个特性在不同引擎里的实现路径
  3. 《游戏引擎实现原理》9.1 单一坐标轴对照