《小王子》16 天做成能逛的 3D 世界:littleprince.dev 创建全拆解

第一次看到 littleprince.dev,我以为又是一个"AI 生成的游戏"转发链接:一张漂亮的截图,点进去是渲染视频,或者要下载 2 GB 客户端。结果浏览器直接进去了——沙漠、一架坏掉的飞机、能走动的第三人称视角,八章故事一关一关往下走,中英法德西葡日韩八种语言。

它比"AI 做了个游戏"这句话复杂得多。作者 Sac(@Saccc_c)在 X 上公布时说的是 16 天、Claude Opus 5.5、Tripo、Blender,媒体转了一圈,故事就停在这里了。但真正值得拆的是两件事:这 16 天的流水线是怎么组织的,以及一个 Unity 项目要变成"打开网址就能玩"的东西,中间还隔着多少工程。

我把站点下载下来读了它的加载器和构建产物。后半部分那些代码不是推测——是线上正在跑的字节。

打开网址,究竟发生了什么

首页先是一段极简的加载界面:星空、Preparing the scene · 0% 的进度条,读完给一个 Begin the Journey。

在它背后,页面做了四件事:

  1. 检测 navigator.gpu。没有 WebGPU 就不进 Unity,直接给一段"你的浏览器还不支持"的说明——它没有为 WebGL2 做降级包。
  2. 拉起一个纯 JS 的加载器 loading.*.js(内容哈希命名),装上缓存策略、下载计量、内容校验、坏缓存自愈。
  3. 从 Build/ 拉 framework.js.br + wasm.br,注册 Unity 实例。
  4. 首屏只要 shared 资源组,八章的资产按章节按需流式加载。

页面里连 devicePixelRatio 上限、指针锁定(进入游戏后光标消失、Esc 才交还)、后台标签页降帧这些都已经写死。这不是一个"能跑就行"的 demo,是当产品在调。

16 天的创建路径:能确认的与不能确认的

先把事实边界划清。16 天、Opus 5.5、Tripo、Blender、Unity 全部来自作者自述与媒体转述(36 氪、机器之心一类的报道),站点本身不提供制作日志。作者说过会开源整个游戏并写详细流程,截至我写这篇时还没发布。所以下面这条流水线是从成品反推的——作品本身能证明的部分,我标出来;只有作者说过的部分,我说明来源。

MERMAID_BLOCK_0

关键的一步不是"用 AI 建模",而是先切片。如果只有一个"小王子星球"的整体场景,16 天不可能完成,因为所有资产必须一次性到位、一次性加载、一次性调光。切成八章之后,每一章是一个可以独立验收、独立打包、独立加载的单元,工作量才能并行摊开。

第一刀:把一本书切成八个能独立成立的关卡

从发布产物能直接读出这八章的边界。资源包的命名前缀就是章节键:b612、earth、king、lamp、counting、geo、theater、bar。大致对应:B612 星球、地球沙漠、国王的星球、点灯人的星球、商人(数星星)的星球、地理学家的星球、王庭、酒馆那一段。

这八个名字里有三个(theater、bar、部分 geo)在原著里只是"小王子拜访的隔壁星球"的一两段话,游戏把它们扩成了可走动的场景。这是改编的必要动作:原著给的是情绪和隐喻,游戏必须给出空间、路径、可交互物和时长。 每一章至少要回答四个问题——玩家从哪进来、走哪条线、看什么、在哪结束。

同时,八章共用一个 shared 资源组(50 个包):天空盒、植被材质、字体、通用音频、UI。这个划分很关键:shared 是首屏必下的,章节包是"走到哪下到哪"的。切片的第二条边界不是叙事,是加载边界。

资产线:Tripo 出坯,Blender 收口

Tripo 这类图生 3D / 文生 3D 工具,产出的是"形似":轮廓、大色块、基本体积关系。它能几分钟给你一个星球、一座塔、一架飞机,但拿不到游戏能用的资产——面数不可控、UV 是乱的、材质是一张烘焙图、部件不分组、法线不统一、缩放没有单位。

所以这条线必然是 Tripo 出坯 → Blender 收口:

  • 重拓扑到目标面数,按章节的相机距离决定 LOD;
  • UV 重排、贴图烘焙,把生成模型那张"万能贴图"拆成 albedo/normal/roughness;
  • 按功能拆部件(可交互的门、可拾取的道具、会动的角色骨骼);
  • 统一单位与朝向,给 Unity 导出 glTF/FBX;
  • 做碰撞体和导航网格的简化代理。

从成品能看出来这套流程确实走通了:场景里的物体有明确的近景/远景分层,材质在低光环境下没有出现"生成模型常见的塑料感",角色有骨骼动画而不是整体硬直。这些都不是 Tripo 一键能给的,是 Blender 里一刀一刀修出来的。 16 天里最重的体力活大概就在这一段,而不是写代码。

引擎线:Unity + WebGPU + Addressables

选 Unity 而不是 Three.js / Godot,理由在成品里很明显:需要成熟的第三人称控制器、动画状态机、地形与植被、音频混音、UI 系统,以及一套能把 2 GB 资产拆包流式加载的资源管线。手写 Three.js 到不了这个完成度,16 天更不可能。

为什么是 WebGPU 而不是更稳的 WebGL2?因为在浏览器里,这个项目的瓶颈在 draw call 和带宽,不在兼容性:大片植被、体积雾、大量带透明通道的粒子,WebGL2 的绑定开销和扩展缺失会直接把帧率压到不能看。WebGPU 有更低的 CPU 侧提交成本、原生 compute pass、更现代的纹理格式支持,正好对上"一个人硬做 3D 场景"的需求。代价是放弃了不支持 WebGPU 的浏览器,而且这个取舍是在 2026 年 10 月做的——那时 WebGPU 的覆盖面仍然有限。这是典型的"用兼容性换表现力",只有在"免费、个人作品、不做商业承诺"的前提下才成立。

打包方式是 Unity WebGPU + Addressables。页面里的 Unity 配置是标准结构,但每个参数都被认真选过:

var buildUrl = "Build";
var config = {
  dataUrl: buildUrl + "/ad81cd047a09dcd0497b2e9d14616bb2.data.br",
  frameworkUrl: buildUrl + "/e28e52d4bf2b7b05c1b407ec6f064005.framework.js.br",
  codeUrl: buildUrl + "/135779e5fc13a65d8f56ccacd0d63be2.wasm.br",
  streamingAssetsUrl: "StreamingAssets",
  companyName: "LittlePrince",
  // the cache identity, not the title: UnityCache is named UnityCache_<company>_<product>;
  // it stays "小王子" since the title became 《小王子:星辰之旅》(2026-10-13),
  // or every returning player would download it all again
  productName: "小王子",
  productVersion: LPWebLoading.cacheVersion,
  cacheControl: LPWebLoading.cacheControl,
  preRun: [function (module) {
    LPWebLoading.installCacheReader(module);
    LPWebLoading.installCrcEviction(module);
  }],
  // Cap the render target at 2x so high-DPI screens do not pay for 3x/4x pixels.
  devicePixelRatio: Math.min(window.devicePixelRatio || 1, 2),
  // Persist the save (journey.json) to the browser's IndexedDB between visits.
  autoSyncPersistentDataPath: true,
};

LPWebLoading.installDownloadMeter();
LPWebLoading.installLargeHeapGuard();

var script = document.createElement("script");
script.src = buildUrl + "/6b3bafb3a2a035f26594a4eb5466318a.loader.js";
script.onload = function () {
  // onload also fires for a file that did not define the loader: emptied by a blocking
  // extension, or an HTML error page in its place.
  if (typeof createUnityInstance !== "function") {
    showMessage("missing");
    return;
  }
  createUnityInstance(canvas, config, setProgress /* 第三个参数是进度回调 */);
};

productName: "小王子" 这一行值得单独说。注释写得很清楚:它是缓存标识,不是标题。 Unity 的浏览器缓存库叫 UnityCache_<company>_<product>,2026-10-13 标题改成《小王子:星辰之旅》时,这个字段故意没跟着改——改了,所有老玩家的缓存全部失效,1.7 GB 重下。标题是给人看的,缓存键是给机器看的,两者必须分开。

productVersion: LPWebLoading.cacheVersion 同理:版本不是手写的字面量,直接引用加载器里的常量,保证"页面上写的版本"和"实际生效的缓存版本"不可能不一致。

这一段有三个补丁。preRun 里塞进了缓存读取器和坏缓存驱逐;installLargeHeapGuard() 在 loader 脚本插入之前就装上(因为要赶在 Unity 定义 WebGPU 方法之前拦截)。这些事 Unity 自己都不做,而这个项目必须做——后面两节全是它们的来龙去脉。

还有一个细节:script.onload 里检查 createUnityInstance 是不是真的存在。注释说的是现实:广告拦截器或钱包扩展会把脚本换成空文件,甚至替换成一个 HTML 错误页,而 onload 照样会触发。页面前面还专门拦了 chrome-extension://、moz-extension:// 抛出的全局错误——Phantom 和 MetaMask 抢 window.ethereum,抛出的异常会被 Unity loader 当成自己的错误,导致 createUnityInstance 被拒、页面显示"加载失败"。这些不是游戏 bug,是"浏览器里住着别人的代码"这个事实。

AI 在这条流水线里的位置

把"Opus 5.5"理解成"AI 写了整个游戏"会误判这套工作流的真实形态。更准确的描述是:一个有明确验收标准的人类导演,配一个能连续执行长任务的编码代理。

分工大致是三层:

  • 人类定不可外包的东西:八章怎么切、每章的情绪落点、镜头与节奏、美术方向、哪些资产必须重做。这些判断错了,后面全白干。
  • AI 做可验证的执行:Unity 里 C# 控制器与状态机、Shader 参数、Addressables 分组、加载器这套浏览器补丁、构建脚本、i18n 注入、CI。共同点是都有客观验收信号——编译过没过、帧率、包体积、加载耗时、控制台有没有报错。
  • 资产交给专用工具:Tripo 出形,Blender 修形。AI 在这两步的作用是查文档、写批处理脚本、生成变体,不是替代建模。

这条分工解释了为什么 16 天能成立:AI 压缩的是"写代码 + 查文档 + 改一遍再跑"的循环,压缩不了审美判断和手工修模。 项目里最贵的两个东西——场景的艺术方向、八章的叙事节奏——仍然是人的时间。

把"AI 能干什么"再具体一点,可以分成"有客观信号"和"没有客观信号"两类:

有客观信号,可以交给 AI 连续执行 没有客观信号,必须人来判断
编译与打包是否通过 这颗星球的色调对不对
帧率、包体积、首屏耗时 这一章的节奏是快了还是拖了
加载器校验、缓存命中率 哪个场景值得做成可探索空间
Shader 参数是否报错 角色的动作像不像"人"
八语文案 key 是否齐全 法文版那句诗的语感

左边这一列的共同点是失败会立刻可见:编译报错、帧率掉到 30、包大了 20 MB、key 缺失。AI 可以在这类任务上连续跑几百轮而不需要人盯着。右边一列的共同点是没有 oracle——你得自己看一眼,觉得不对,再改。16 天的真实含义是:左边那半被压缩了,右边那半没有。

另一个容易被忽略的点:加载器里那些中文注释写的全是"2026-10-12 王庭 CRC 事故,WEB_LOAD.md 第四节"。这说明项目持续维护了一份故障档案,把每次线上事故变成一条可复用的防御规则。这种习惯不是 AI 给的,是人给的;但 AI 让"把事故写成代码"这件事的成本降到了一次对话。

打包线:115 个包,2111 MB

页面里内联了一份完整的包清单和体积表,这是拆解这个项目最好的入口。当前版本的数据:

指标 数值
Addressables 包数 115
解码后总量 2111.2 MB
线上传输总量(Brotli) 1696.2 MB
压缩比 1.22
最大单包 geo_scenes_all 171.2 MB
次大单包 theater_scenes_all 152.9 MB
shared 组 50 个包

压缩比只有 1.22,很多人会意外。原因是这些包的主体是已经熵编码过的纹理和网格——GPU 压缩纹理(ASTC/BC)、顶点数据、音频,本身接近随机分布,Brotli 再压榨不出多少。真正能压的是文本和未压缩的中间数据。所以"2 GB 的游戏"在网页上并不会变成"200 MB",这个预期必须一开始就有。

媒体最初报道的体积是约 1.97 GB,一个月后我看清单已经到 2111 MB——内容在长。这也是为什么所有哈希都必须由构建生成,不能手写。

分组结构也直接说明了加载策略:

shared_*                      50 个包(首屏共用)
earth_hires_*                  9
counting_hires_*               7
earth_* / bar_hires_*          各 6
theater_hires_* / b612_hires_* / geo_hires_*  各 5
theater_* / b612_* / lamp_*    各 2

hires 命名说明同一章分了近景高清包和基础包:走到哪儿加载哪一级细节,不是一次性把 171 MB 的场景包全塞给浏览器。 这是 Addressables 存在的全部理由。

加载时序:进站、走章、回头

把上面这些包按时间轴排开,一次完整游玩的加载路径大概是这样(章节顺序按原著的拜访顺序示意,游戏内实际编排可能不同):

MERMAID_BLOCK_1

三个设计点:

首屏不碰章节包。 标题界面只需要 shared 和第一章的基础资源,其余全部延后。否则"打开网址到能点开始"这段时间会被第一个 100 MB 拖死。

跨章复用靠 shared,不靠重复打包。 50 个 shared 包是全局常驻——天空、地面材质、植被、通用音效、UI、字体。八章各自的包只装"这一章独有"的东西。这是 Addressables 分组的核心纪律:共享资源进共享组,否则同一个石头会在八个包里各存一遍。

回头是缓存命中,不是重新下载。 章节包一旦落进浏览器缓存(并且通过了上一节那套内容校验),第二次走回头路是本地读取。这也是为什么加载器要把缓存当资产来经营——它的失效逻辑直接决定"二周目体验"。

页面上的进度条就是这条时序的可视化:onProgress 回调拿到 0..1,加载器把当前在下的包名映射成章节名,再叠一个实时速率(installDownloadMeter)。注释里提到构建机上流式 SHA-256 能跑到约 69 MB/s——这意味着校验成本接近下载成本,所以进度条必须把校验算进去,否则会出现"下完了卡在 99%"的体感。

一次 CRC 事故,换来三层防御

这个项目最有意思的工程细节,来自一次真实事故:2026-10-12 的"王庭 CRC 事故"。加载器注释把根因写得比很多技术博客都清楚:

Unity 的 UnityCache 以包的 URL 为键、immutable 时不再校验,而包文件名里的哈希按构建输入算,同名可能不同字节。缓存里的旧字节会一直 CRC 失败、玩家永远进不去,直到缓存被清。

翻译成时间线:构建产物的文件名哈希并不保证内容不变;浏览器把这当成不可变资源永久缓存;某次部署后同名不同字节;于是老玩家的缓存里躺着坏字节,Unity 每次 CRC 校验都失败,点"重新进入"也没用。

修复不是清一次缓存,而是三层:

第一层,下载时就校验,不让坏字节落盘。 每个章节包在页面里都带一个内容标记 LPWebBundleContent[name],值等于原始字节 sha256 的前 16 位(构建脚本 compress_bundles.mjs 写入,fingerprint_page.py 注入页面)。加载器边下边算,流结束时比对,不符就让流报错:

var tag = contentTag(name), hasher = tag ? workerHasher() : null;
var counted = new ReadableStream({
  pull: function (controller) {
    return reader.read().then(function (chunk) {
      if (chunk.done) {
        var finished = hasher ? hasher.finish() : Promise.resolve(null);
        return finished.then(function (hex) {
          if (hex !== null && hex !== tag) {
            // 长度收齐但内容不对:让流报错,Unity 什么都不会存
            var mismatch = new Error("Bundle content mismatch: " + name);
            fail(mismatch); controller.error(mismatch); return;
          }
          controller.close();
        });
      }
      if (hasher) hasher.update(chunk.value);
      controller.enqueue(chunk.value);
    });
  },
});

这段代码的选择很讲究。浏览器没有"下载完再验"的免费机会——crypto.subtle.digest 要整块 buffer;而这里要的是"边到边算、长度一收齐就判"。所以它自己实现了一份流式 SHA-256,用 Worker 跑(Blob URL 内联创建),注释里还留了性能预算:~69 MB/s on the build machine,要求"每块 JS 开销必须超过 50 MB/s",并有 loading.test.cjs 做基准测试。它没有用"应该够快"下结论,而是把阈值和测量方法写在注释里。

第二层,给缓存条目写内容标记,标记不符就当未命中。 这一层解决的是"事故之前已经存进浏览器的坏缓存":

function loadRequest(input) {
  return original.apply(owner, arguments).then(function (hit) {
    if (!hit || !hit.response) return hit;
    var name = bundleName(input);
    var tag = contentTag(name);
    // 标记缺失或不符:当作未命中,loader 重新下载、覆盖存储
    if (tag && (!hit.metaData || hit.metaData.lpContent !== tag)) return undefined;
    // ...
  });
}

注意注释里那句取舍:"上线后所有老玩家的包都没有标记,会各重新下载一次,这是有意的:它修好同名不同字节的坏缓存。" 宁愿让所有人多下一次,也不留一个永远进不去的状态。而且标记写进 lpContent 之后,以后内容变了标记自然不同,不需要再改 CACHE_VERSION——这就把"缓存失效"从人工操作变成了数据属性。

第三层,中毒缓存的运行时自愈。 万一坏包已经在 Cache 里、且标记写成了一致的(比如标记写入失败),还有兜底:拦截 Unity 打到 console.error 的那对消息,删掉对应的缓存条目。

function wrapped() {
  var text = Array.prototype.join.call(arguments, " ");
  if (/CRC Mismatch/i.test(text)) crcSeen = true;
  if (crcSeen) {
    var m = text.match(/Will not load AssetBundle '([^']+)'/);
    if (m) { crcSeen = false; evict(m[1]); }
  }
  return original.apply(this, arguments);
}

crcSeen 这个状态位是必需的:Unity 先打 CRC Mismatch、再打 Will not load AssetBundle '<url>',两行消息才拼出"哪个包坏了"。evict 只删那一个包的 Cache 条目和 RequestMetaDataStore 行,异步、不阻塞,失败也只是留给下次重试。

三层防御,三种时机:下载时(流式校验)、命中时(标记比对)、失败后(错误拦截自愈)。这是我在这个项目里看到的最成熟的一段工程——它不是"加了校验",是把缓存当成一个可能已被污染的外部系统来对待。

缓存版本与长度对齐:两个不显眼但致命的细节

CACHE_VERSION 的注释值得全文抄一遍,因为它解释了一个反直觉的约定:

// 缓存版本锚点:固定为切换时线上正在用的 productVersion "0.1.0+4a120fa1"
// (2026-10-11 02:59 部署的八章英文排版正式包)。Unity loader 的 cleanUpCache 会删掉
// version 与当前 productVersion 不同的缓存条目,锚在线上值上,老玩家已缓存的
// 章节包在切换后照样命中。此后这个值永远不随构建变化,不要改成新构建的提交号。
var CACHE_VERSION = "0.1.0+4a120fa1";

Unity 的 cleanUpCache 会删掉版本号不匹配的缓存条目。如果把 CACHE_VERSION 跟着新构建改,每次发布都会把老玩家辛苦下载的 1.7 GB 全删掉。所以它被钉死在切换时的线上值,永远不动;内容变化交给上一节的内容标记处理。"缓存版本"和"构建版本"在这里被明确拆成两个概念——这是很多人会踩的坑,而且踩了以后表现为"每次更新都要重新下几个 G"。

第二个细节是长度对齐。Unity 拿到 Brotli 响应时,会猜解码后的长度(常见做法是按 2 倍估),然后按错误长度分配 buffer、再整块拷贝一次。这个项目直接把解码后的精确长度塞进 Content-Length,并删掉 Content-Encoding:

var headers = new Headers(response.headers);
headers.set("Content-Length", String(expected));
headers.delete("Content-Encoding");
// Keep the original stream and metadata. No cache rewrite, clone, or full-body buffer.
hit.response = new Response(response.body, {
  status: response.status, statusText: response.statusText, headers: headers
});

省下的是一整份 100 MB 级 buffer 的分配与拷贝,在手机上就是"能进"和"崩掉"的区别。注释里那句 No cache rewrite, clone, or full-body buffer 说明作者很清楚:这类优化最容易写成"顺手 clone 一份",而 clone 会把省下的内存吃回去。

首屏与页面层:一堆 Python 小工具

页面不是手写的一份 HTML。从注释能看到一条构建链:tools/shared/ui/make_favicon.py 生成图标、fingerprint_page.py 给资源打内容指纹、tools/shared/web/make_page_i18n.py 注入八语文案。

注入的结果就是页面开头那段极长的字符串表——八种语言、同一组 key、顺序固定(英、法、西、德、葡、中、日、韩)。同一个机制还注入了包体积表、内容标记、构建版本。页面本身是一份"构建产物",不是一个需要手工同步状态的源文件。 这也是为什么页面上显示的版本、加载器里的锚点、包的哈希能始终一致:它们出自同一次构建。

八种语言不是把文案堆进一个 JSON 就完事。UI 里的 Preparing the scene · 这类字符串带尾随空格和分隔符,翻译时改了语序就得连带调整拼接逻辑;日期、数字、书名引号在法文和日文里的排版规则都不同。这些如果靠"运行时查表 + 手工拼字符串",八种语言 × 几十个界面元素很快就会失控。构建期注入的代价是改一句文案要重跑一次页面构建,收益是页面上不存在"某个语言少了一句"的状态——对一个人维护的项目,这个取舍是对的:宁可多跑一次脚本,也不要线上出现半截英文。

顺带一个细节:darkreader-lock 之类的 meta 也被写进去了。这意味着作者考虑过"用户装了深色模式插件,会不会把游戏的 UI 配色搞坏"。这种细节不在需求文档里,只在真的被人用过之后才会出现。

浏览器兼容:Firefox 的 2 GB 墙

第三个补丁最冷门。Unity 的 WebGPU 后端在 Firefox 上会撞到一个 WebIDL 限制:ArrayBuffer 视图不能超过 2 GB − 1 字节。正常路径不会碰到,但大场景 + 高分辨率纹理很容易踩到。项目的做法是在 Unity 初始化之前包一层 WebGPU 方法,遇到超大视图就先切片再传(以下为删节版,实际包了 9 个入口):

var LARGE_BUFFER = 0x7fffffff;   // 2 GB − 1: Firefox's WebIDL ArrayBuffer-view limit
function installLargeHeapGuard() {
  var ua = (root.navigator && root.navigator.userAgent) || "";
  if (ua.indexOf("Firefox/") < 0) return;   // only Firefox has the limit
  function oversized(view) {
    return !!(view && view.buffer && typeof view.buffer.byteLength === "number" &&
      view.buffer.byteLength > LARGE_BUFFER);
  }
  guard(root.GPUQueue, "writeBuffer", function (a) {
    var data = a[2];
    if (!oversized(data)) return null;
    var dataOffset = a[3] || 0;
    var size = a.length > 4 && a[4] != null ? a[4] : data.byteLength - dataOffset;
    return [a[0], a[1], data.slice(dataOffset, dataOffset + size), 0, size];
  });
}

setBindGroup、writeBuffer、writeTexture、setImmediateData 四个入口全包了一遍,而且只在 Firefox 生效,其他浏览器完全不走这条路径。guard 里还有 prototype[name].lpLargeHeap 的幂等标记,防止重复包装。

这段代码的价值不在于技术难度,而在于它证明作者真的在各种浏览器里把游戏玩通了,而不是只在开发机上跑过。一个 16 天的项目能做到这一层,说明测试不是最后补的。

这套做法的坑

一、WebGPU 的兼容面比想象中窄。 没有 WebGL2 降级包,等于放弃了相当一部分设备和浏览器。这是明确取舍:Unity 的 WebGPU 后端在那时还不算成熟,同时维护两套构建会让 16 天的排期直接翻倍。

二、2 GB 的体量注定不是"随手点开"。 首屏要下 shared 组,每进一章要下几十到上百 MB。在有线网络和好手机上体验不错;弱网下会明显感到"分段进入"。省流量的做法(更低分辨率包、可跳过的高清包)目前没有看到。

三、内容版权是悬着的。 《小王子》原著在多数地区已进入公有领域(法国有例外,且"小王子"相关商标、形象版权仍在多方手里)。这个项目是免费、非商业的个人作品,风险不高;一旦商业化,IP 就是第一道墙。转发和引用时也别把"AI 生成"当成"无版权"。

四、"16 天"不等于"16 天从零到成品"。 省掉的是写代码和查文档的时间,没省掉审美、修模、测浏览器、以及事故之后的返工。把它当成"AI 让游戏开发变成 16 天"会得出错误结论:它证明的是"一个人的产出上限被抬高了一个量级",不是"门槛消失了"。

五、制作流程还没公开。 说好的开源和详细教程截至现在没发。这意味着上面关于 Tripo/Blender 的具体步骤仍然是反推,不是作者确认的 SOP。

成本账

  • 时间:作者自述 16 天。按成品复杂度看,这个数字成立的前提是"全职、有 Unity 经验、AI 承担编码与文档"。
  • 资产体积:2111 MB 解码后、1696 MB 传输。托管成本取决于 CDN 流量,不便宜,但纯静态、无后端。
  • 人力:一个人。没有团队分工,所以每一层都必须自己收敛——这也是为什么八章切得那么干净。
  • 学习成本:想复刻的人需要三样东西同时具备:Unity 场景与 Addressables 的熟练度、Blender 的修模能力、以及把"加载器/缓存/校验"当正经工程来做的意识。第三样最稀缺。

如果想复刻,最小可行路径

不要从"做一个 2 GB 的开放世界"开始。按这个项目的顺序倒着做:

  1. 先切一章。选一个空间最小、情绪最清楚的场景(点灯人的星球只有一盏灯和一个人),做完整闭环:进门、走动、交互、结束。
  2. 用 Tripo 出 3 个坯,在 Blender 里只做三件事:重拓扑、UV/烘焙、拆部件。其他都先不碰。
  3. Unity 里只装这一章,用 Addressables 把它单独打成一个组,量出真实包体积。
  4. 自己写加载器,别直接用 Unity 默认的那套。至少做到:内容哈希命名、immutable 只给哈希资源、下载后校验、缓存标记。
  5. 在真机上测:iOS Safari、Android Chrome、桌面 Firefox。这一步会发现上面那些补丁里至少两个是你也需要。
  6. 最后才做八章。第一章验证的是管线,不是内容。

Littleprince.dev 最值得学的不是"用 AI 做游戏",而是它把"一个人能维护的产品级交付"这条线,从资产、引擎、加载、缓存、兼容性一路拉通了。16 天里真正被 AI 省掉的,是这条线上那些"知道要做但懒得做"的部分——而它偏偏全做了。

参考

如果只能保留一章,你会留哪一颗星球?我大概会留点灯人的——一分钟一次日落,是整本书里最像游戏机制的一段。


作者: itech001
来源: 公众号:AI人工智能时代(the-ai-era)
网站: https://www.theaiera.top/
关注每日最新AI新闻和技术博客,主页有更多的文章的AI 技术参考:https://www.theaiera.top

本文首发于 AI 人工智能时代,转载请注明出处。

posted @ 2026-11-07 10:10  iTech  阅读(5)  评论(0)    收藏  举报