☰
第 3 章 工具链全景:工程、Playground、Inspector 与两条导入路线
2026/10/13 10:24:22 网站建设 项目流程

实测环境:Babylon.js9.30.0、Vite 8.3.4、TypeScript 5.9.3、Windows 11、Chrome。本章所有体积/警告数字都来自本机当次运行日志(build.log、dev.log),复现命令在 3.8。
本章代码:src/chapters/ch03-toolchain.ts+ 对照实验页public/toolchain-umd.html。

学完这一章,你拥有的不是"知道有几个工具",而是一条闭环工作流:

看到一个例子 → 判断它属于哪条路线 → 搬到本地 → 跑起来 → 用 Inspector 看它在做什么 → 量出代价

后面 45 章你每天都会走这条链至少一次。


3.1 本章要做成什么样

本章画面上是一个蓝灰立方体,左下方两个按钮:

① 调试面板(Inspector) ② 打开 UMD 对照页(全局 BABYLON 对象)

下方面板打印着本章的核心结论——包体积实测表(数字见 3.8)。点 ① 右侧滑出属性树;点 ② 新标签页打开一个不用import、不用 TS、不用构建的橙色方块页面,第一屏明显更慢。

【图 3-1:Inspector 打开状态截图(人工验收时补)】
【图 3-2:UMD 对照页截图(人工验收时补)】


3.2 本书的工程结构:先看清地形

babylonjs/ ├─ index.html 实验台外壳:左侧章目录 + 画布 + HUD + 本章信息栏 ├─ vite.config.ts 端口 18000 / 0.0.0.0 / strictPort ├─ vite.bench.config.ts 体积基准专用构建(3.8) ├─ tsconfig.json strict 系列开关(第 2 章 2.3.5) ├─ src/ │ ├─ hub.ts Engine 单例 + 渲染循环 + 章切换与销毁 + HUD │ ├─ core/chapter.ts 章节契约:meta / create / ChapterHandle │ └─ chapters/chNN-*.ts 每章一个文件(本章是 ch03-toolchain.ts) ├─ bench/min.ts 第 4 章十行代码的独立副本,只用于测体积 ├─ public/ │ ├─ toolchain-umd.html 本章的全局脚本对照页 │ └─ vendor/babylon.max.js UMD 包的静态副本(8.22 MiB) ├─ tools/new-chapter.mjs 生成新章骨架 ├─ 手稿/chNN-*.md 正文 └─ docs/ 大纲、写作规范、坑总表、体积基准

三个设计决定,值得你知道为什么:

  1. 一个页面跑全书章节,而不是每章一份工程。48 份重复的package.json只会让读者在装依赖上浪费生命。代价是章节必须遵守统一契约。
  2. 章节契约是meta+create(src/core/chapter.ts)。create收到{ engine, canvas, stage, overlay, describe },返回{ scene, dispose }。hub.ts用 Vite 的import.meta.glob('./chapters/*.ts')自动发现它们——你新增一个文件,左侧目录就自动出现一章。
  3. Engine全局只有一个,每章新建Scene并在离开时dispose()。这正是第 4 章 4.7 讲的"一个页面一台引擎,多个场景"的工程实现。

新增一章只要一条命令:

npmrun new-chapter12pbr-intuition"从顶点到像素:渲染管线直觉"

3.3 开发环境三件套:dev/build/typecheck

npmrun dev# 起本地服务器,改代码即时生效npmrun build# 先 tsc --noEmit 再做生产构建,产物在 dist/npmrun typecheck# 只查类型,最快

dev和build检查的东西不一样,这是新手最容易忽略的一点:

  • vite dev用 esbuild/oxc 之类做转译,基本不做类型检查——类型错了照样能跑。
  • vite build才产出真实体积,也才会暴露 tree-shaking、动态导入分块的结果。
  • tsc --noEmit只管类型,不管能不能跑。

本书的纪律是:每章交付前三个都跑,并且build的产物大小要记进docs/体积基准.md(第 41 章的性能承诺靠这份数据,不靠感觉)。

顺便:vite.config.ts里strictPort: true是故意的。

server:{host:'0.0.0.0',port:18000,strictPort:true}
  • host: '0.0.0.0'让同网段的手机能连进来(第 43、44 章必须用它)。
  • 端口选 18000 是因为这台机器上 Windows 保留了大片区间。实测输出(netsh int ipv4 show excludedportrange protocol=tcp):2180–2779、6203–6302、50000–50059等。落在保留区里的端口,listen报的是EACCES(权限),不是EADDRINUSE(占用)——不写清楚,你会去查防火墙。
  • strictPort: true很重要:默认 Vite 端口被占会静默换端口,你在手机上输的地址就对不上了。宁可启动失败,也不要偷偷换端口。

3.4 在线工具:Playground、Sandbox、以及"版本"这个陷阱

Babylon 官方提供几个在线工具,本书把它们当验证工具而不是学习主战场。用法要点(界面细节以你打开时为准,本书只对 API 行为负责):

  • Playground:左边写代码、右边即时出画面,能分享链接。它最适合"我只想验证某一个 API 的一个参数"。第 9 章调MeshBuilder参数、第 14 章试贴图 UV 时,比在本地起工程快得多。
  • Sandbox:把一个.glb拖进浏览器,立刻看到模型、材质、动画、包围盒。第 32 章用它做导出体检——模型在 Sandbox 里就是黑的/朝向不对,那是资产问题,不是你的代码问题。这个区分能帮你省掉几小时。

陷阱:在线工具的引擎版本和你本地的不一定一致。

Playground 这类站点通常可以选版本(有的默认最新预览版)。你在 Playground 里验证成功的一段代码,回到本地9.30.0可能报错,反之亦然。本书的规矩:

  1. 从网上搬代码,先看它用的写法在本地tsc下报不报错——编译器是最便宜的版本检测器。
  2. 搬进本地后跑一次,再和 Playground 的表现对照一次,不一致就记进docs/坑总表.md(第 4 章的 4-01/4-02 就是这条流程抓出来的)。
  3. 不要假设"在线示例 = 最新版"。Babylon 大约每周一版(第 1 章 1.3.1 的 npm 时间戳实测),示例落后半年是常态。

3.5 Inspector:本书最重要的老师

Inspector 是运行时的场景检查器。它对你的价值不是"炫",而是把抽象变可见:

  • 场景树:谁是谁的父节点、谁被隐藏了(第 8 章)
  • 选中一个网格 → 看它的position / rotation / scaling / 包围盒 / 材质,还能当场拖动数值看画面变化
  • 材质面板:贴图通道、PBR 参数(第 13–17 章的每个参数都在这里试)
  • 灯光与阴影设置(第 19 章)
  • 帧统计、纹理与显存信息(第 41 章)

v9 的入口(源码@babylonjs/inspector/lib/inspector.d.ts):

exportdeclarefunctionShowInspector(scene:Scene,options?:Partial<InspectorOptions>):InspectorToken

InspectorOptions里本书常用一项:

layoutMode?:'inline'|'overlay'// 默认 overlay;inline 会把 canvas 一起嵌进面板容器

返回值InspectorToken:

{dispose():Promise<void>,isDisposed:boolean,onDisposed:IReadOnlyObservable<void>}

本章代码的完整用法:

const{ShowInspector}=awaitimport('@babylonjs/inspector')// 懒加载,见 3.7consttoken=ShowInspector(scene)awaittoken.dispose()// dispose 是异步的

为什么await import()而不是文件顶部import?因为 Inspector 是 React 实现的,包大。本章的实测分块(npm run build产物,见 3.8)能说明代价:懒加载时它不在首屏路径上,写进主入口就要背进初始包。

同包还导出两个对你有用的东西(我在lib/index.d.ts里核实的):

export{AttachDebugLayer,DetachDebugLayer}from'./legacy/debugLayer'

AttachDebugLayer(scene)会在画面左上贴一块引擎自带的调试层(帧率、绘制统计等)。它比 Inspector 轻,适合手机上快速看一眼。注意它在legacy/路径下——本书会用它,但如果哪天它消失,第一反应是换 Inspector 的统计面板(第 41 章实测)。

3.5.1 Inspector 的五件具体用途

新手常把 Inspector 当"炫技面板",实际上它是你写代码时的第二双眼睛。下面五件事是你几乎每章都会做的:

① 当场改数值看画面变化。选中一个网格 → 右侧属性栏里position / rotation / scaling都能直接拖滑块或输入数字。比如你不确定rotation.y = Math.PI / 4到底转了多少度,先在 Inspector 里手动拖到 45°,看到效果后再回代码里写死。这省掉"改一行→刷新页面→找角度"的循环。

② 确认材质通道是否真的生效。第 14–17 章的 PBR 参数(albedo/metallic/roughness/clearcoat/anisotropy)在 Inspector 的材质面板里有实时预览:贴图有没有挂上、通道值对不对、法线贴图方向反没反。很多时候"画面看起来不对"不是算法问题,而是贴图 UV 错了或通道名拼错——Inspector 一眼看出。

③ 数一数场景里到底有多少东西。Scene 标签页显示 meshes/lights/cameras/textures 的数量;Stats 标签页显示 draw calls、三角形数、顶点数。第 41 章的性能优化起点就是这里:先知道瓶颈在哪,再决定砍什么。

④ 验证光照与阴影设置。灯光列表能看到每个光源的类型、强度、颜色、范围;ShadowGenerator 面板能看到分辨率、过滤类型、级联设置。第 19 章调阴影锯齿时,你会反复在这里切过滤模式看效果。

⑤ 读纹理与显存信息。Textures 标签页列出所有加载的贴图,包括尺寸、格式、是否压缩、占用显存。第 15 章的 KTX2/DDS 压缩收益实测就靠它——肉眼看不出区别,但显存差几倍。

这五件事的共同点:它们都是"运行时真相",不是代码里的期望。你写的代码说"我挂了这张贴图",Inspector 告诉你"挂了,但 UV 全挤在左下角"——这就是它的价值。


3.6 DevTools:三处真正常用的

  1. Console:本章所有报错的第一现场。Babylon 的报错多数会说清楚(No camera defined、WebGL not supported),别跳过读原文。
  2. Network:看模型/贴图到底请求了什么、返回码是什么、多大、多久。拖模型显示不出来,八成先在这里看到 404 或 4 MB 的 PNG(第 33 章)。
  3. Performance:录制一段帧,看 JavaScript 侧的长任务出现在哪一帧;GPU 侧占用在多数浏览器里看不全,所以本书主要靠自己的数据(getDeltaTime()分布 +SceneInstrumentation,第 41 章)。

一个具体的用法示范:把第 1 章的体检表和 DevTools 对照——你在页面看到的"平均帧 16.6ms",与 Performance 面板里requestAnimationFrame之间的间隔应当吻合;如果差很多,说明瓶颈在 GPU 侧或有人在你看不到的地方做了重活。


3.7 两条导入路线,以及实测体积

路线 A:模块化 + TypeScript(本书主线)

import{Engine,Scene,MeshBuilder}from'@babylonjs/core'

路线 B:全局脚本 UMD(老教程、快速速写)

<scriptsrc="/vendor/babylon.max.js"></script><script>constengine=newBABYLON.Engine(canvas,true)constscene=newBABYLON.Scene(engine)constbox=BABYLON.MeshBuilder.CreateBox('box',{size:2},scene)// 老签名:第一个参数现在是被忽略的兼容位(第 4 章坑 4-02)camera.attachControl(canvas,true)</script>

对照页public/toolchain-umd.html就是路线 B 的完整可运行版本。BABYLON.MeshBuilder确实存在(我核实的类型声明:babylon.d.ts:58445 export var MeshBuilder: {…}),所以老代码里的这一句在 v9 仍然可用;但同文件里Mesh.CreateBox之类的旧入口被标了@deprecated,写着"Please use MeshBuilder instead"——看到Mesh.CreateXxx一律改MeshBuilder.CreateXxx。

实测数字(2026-10-11 本机)

路线产物rawgzip
UMDbabylon.max.js单文件8.22 MiB1.77 MiB
UMDbabylon.js8.22 MiB(与 max字节数完全相同,均 8,619,713 B)—
模块化(顶层导入@babylonjs/core)dist-bench/全部 JS,76 个 chunk6.76 MiB1.49 MiB

复现命令:

npminstall--save-dev babylonjs@9.30.0 npx vite build--configvite.bench.config.ts

bench/min.ts是第 4 章那十行立方体代码的独立副本,不含 Inspector、不含实验台外壳。

这三行结论是本章最该带走的东西:

  1. 只比 UMD 少约 18%(raw)/16%(gzip)。网上"tree-shaking 能砍一半以上"的说法,在顶层导入下不成立——@babylonjs/core的根入口几乎把整个引擎拉了进来。
  2. 想真省体积,要走深路径导入 + 按需动态加载。本书不在这里给具体数字,因为路径写法需要在 9.30.0 上逐条验证,留到第 41 章实测。
  3. 选 Babylon 的理由是"功能齐",不是"包小"。如果你的项目只有 10 KB 的预算,这本书帮不了你——这不是缺点,是定位。

附带发现(一条会浪费你一小时的坑)

在vite dev下直接写<script src="/node_modules/babylonjs/babylon.max.js">,实测下载量是48,581,628 字节(≈46 MiB):Vite 把它当模块处理并挂上 sourcemap。正确做法是复制成静态资源:

mkdir-ppublic/vendor&&cpnode_modules/babylonjs/babylon.max.js public/vendor/

实测/vendor/babylon.max.js回到8,619,713 字节,与磁盘文件一致。本章对照页用的就是这个路径。


3.7.1 新增一章时,运行时发生了什么

当你执行npm run new-chapter 05 my-feature "我的功能"并写完src/chapters/ch05-my-feature.ts后,运行npm run dev,实验台会发生这些事:

  1. Vite 启动,读取vite.config.ts,在端口 18000 监听(strictPort: true,见第 3 章坑 3-04)。
  2. src/hub.ts被打包,其中这一行是关键:
    constloaders=import.meta.glob('./chapters/*.ts',{eager:false})
    Vite 在构建时扫描src/chapters/目录,把所有.ts文件的路径和懒加载函数收集进loaders对象。这不是运行时行为,是编译期静态分析——所以新文件只要放在这个目录下,就会被自动发现,不需要修改hub.ts。
  3. 左侧章节列表渲染:hub.ts遍历loaders,用正则/ch(\d+)-[^/]*$/从路径提取章号(如./chapters/ch05-my-feature.ts→'05'),按章号排序后生成导航链接。
  4. 用户点击某个章节:
    • 如果当前有活跃章节,调用它的dispose()(解绑观察者、清定时器、销毁 Scene)。
    • await entry.load()懒加载目标章节模块。
    • 校验meta.id是否与文件名一致(不一致则报错,见挑战 2)。
    • 调用create(args),传入共享的engine、canvas、stage、overlay和describe函数。
    • 拿到返回的{ scene, dispose },存入全局变量current。
    • 如果这是第一次渲染,启动engine.runRenderLoop(() => current.scene.render())。
  5. HUD 每 500ms 刷新:读取scene.meshes.length、scene.lights.length等,加上engine.getFps(),更新到页面上方。

这条链路解释了三个设计选择:

  • 为什么create()只解构需要的参数:因为 hub 传给你的就是这些,多解构未使用的会触发 TS6133 报错(noUnusedParameters)。
  • 为什么不能销毁 Engine:Engine 由 hub 创建和管理,章节只管自己的 Scene。销毁 Engine 会让整个应用停摆。
  • 为什么meta.id必须与文件名一致:因为导航以文件名为准(正则提取),meta.id只是辅助校验。两者不一致说明你复制粘贴时忘了改 id,会导致导航与实际内容错位。

理解这条链路,你就理解了本书工程的核心:模块化 ≠ 多项目,而是单项目内的动态加载与隔离。


3.8 装哪些包:一张决策表

你想做装什么本书哪章
任何 3D@babylonjs/core全程
读 glb/gltf/obj 模型@babylonjs/loaders32、33
界面控件@babylonjs/gui35、36
网格地面/压印/液体等特殊材质@babylonjs/materials17、34
调试面板与节点编辑器@babylonjs/inspector3、4 起常驻
物理@babylonjs/havok(WASM,较大,第 30 章才装)30、31
老教程式<script>速写babylonjs(本工程装成 devDependency 只用于体积对照)3

版本必须齐步走。上面所有包都在同一个仓库里发版,混用不同大版本是编译期与运行期双重灾难。本书统一9.30.0,package.json里写^9.30.0;升级时整批升,升完先跑第 1 章体检章 +npm run typecheck。

顺便记录两条不用管的构建警告(你会在dev.log/build.log里看到,别去修):

Sourcemap for "@griffel/core/src/devtools/...js" points to a source file outside its package [MODULE_LEVEL_DIRECTIVE] The semantics of the module level directive "use client" in "@fluentui/..." may not be preserved

它们来自 Inspector 依赖的 Fluent UI / Griffel 包自身的 sourcemap 与指令写法,实测不影响渲染。


3.9 预期输出与自查

你看到说明
点 ① 右侧出现场景树,里面能看到box/camera/hemiInspector 通了,本章主要工具就绪
再点 ① 面板消失token.dispose()正常工作(异步,所以有轻微延迟)
点 ② 新标签出现橙色方块 + 顶部说明文字UMD 路线能跑,typeof BABYLON显示object
② 的第一屏明显比 ① 慢8.22 MiB 未压缩脚本,代价看得见
面板打印了 5 行体积实测数据与docs/体积基准.md一致(不一致说明你的 Vite/Babylon 版本不同,把差异记下来)
Inspector 打开后画面区域变小你用了layoutMode: 'inline'(它会重挂 canvas),默认overlay不会
章节左侧目录没有"第 3 章"文件名没被 glob 匹配到:必须是src/chapters/chNN-*.ts,且导出了meta与create

3.10 本章的坑

3-01|dev 模式用<script>引/node_modules下的 UMD → ≈46 MiB。Vite 会加工它并附 sourcemap。→ 复制到public/vendor/当静态资源(实测 8.22 MiB)。

3-02|babylon.js与babylon.max.js在 9.30.0 字节数相同。老书上"一个压缩一个未压缩"的说明对 v9 不再成立;压缩交给 CDN 与 gzip。

3-03|Inspector 静态导入会把几 MiB 的编辑器拖进首屏路径。实测分块里,节点几何编辑器单块 5.4 MB、NME 1.9 MB、GUI 编辑器 1.8 MB。→ 一律await import(),且只在用户点击时。

3-04|Vite 端口被 Windows 保留区间占用时报EACCES。不是权限问题也不是占用,别去关防火墙。→strictPort: true+ 避开保留区间(本机实测 2180–2779、6203–6302 被保留)。

3-05|dev通过不代表build通过,反之也一样。转译不查类型,构建才暴露分块与体积。→ 三个命令都跑。

3-06|Mesh.CreateBox这类旧入口已标@deprecated。搬老代码时优先全局替换成MeshBuilder.CreateBox。

3.10.1 工程故障排查表

现象第一怀疑对象检查步骤
页面白屏,控制台无报错runRenderLoop没启动 / Scene 为空确认create()返回了scene;在create末尾加console.log('scene created', scene)
页面白屏,控制台报No camera defined场景里没有激活相机确认scene.activeCamera不为空;或用camera.attachControl()绑定控制
模型不显示,控制台无报错相机位置/朝向不对 / 模型太大超出视锥在 Inspector 里看包围盒;把相机拉远(camera.radius *= 10)再试
模型不显示,Network 报 404glTF/贴图路径错误确认SceneLoader.Append的路径相对于public/;用绝对路径/assets/model.glb测试
帧率个位数场景太重 / 循环里有重活打开 Inspector Stats 看 draw calls;注释掉一半网格再测,定位重负载源
切章节后帧率越来越差上一章节的 Observable 没解绑确认dispose()里调用了scene.onAfterRenderObservable.remove(observer);查第 2 章坑 2-02
切章节后体检表重复打印定时器没清除确认dispose()里调用了clearInterval(timer);查第 1 章坑 1-07
npm run build失败,报Cannot resolve module导入路径错误 / 包未安装确认package.json里有对应依赖;路径用@babylonjs/core而非相对路径
手机端打不开http://IP:18000防火墙拦截 / IP 不对确认电脑 IP(ipconfig看 IPv4);Windows 防火墙放行 Node.js;手机和电脑在同一 WiFi
页面能开但 canvas 黑屏WebGL 不支持 / 硬件加速关闭看第 1 章体检表的webGLVersion;Chrome 设置里搜索"硬件加速"并开启

这张表覆盖了你前 10 章会遇到的 90% 工程问题。遇到不在表里的情况,第一反应永远是:看控制台原文,别猜。


3.11 章节挑战

  1. 把public/toolchain-umd.html里的MeshBuilder.CreateBox换成Mesh.CreateBox,看它是否还能跑,并到node_modules/babylonjs/babylon.d.ts里找到那条@deprecated注释。
  2. 把src/chapters/ch03-toolchain.ts的meta.id改成'3',刷新页面。画面照常,但错误栏会出现一行"章号不一致"。解释三件事:为什么章号以文件名为准(hub.ts用正则从文件名取 id)、为什么npm run typecheck抓不到这个不一致(meta声明为as const,类型是字面量但没有跨文件约束)、要让它在编译期就报错该怎么改src/core/chapter.ts的契约。
  3. 用AttachDebugLayer(scene)替换本章的 Inspector 按钮,比较两者在手机上的可用性与信息密度(第 43 章会用得上这个结论)。
  4. 跑一次npm run build,把dist/assets/里最大的三个 chunk 记进docs/体积基准.md。这将是你在第 41 章的优化基线。

3.12 本章小结

  1. 工作流比工具名重要:看到例子 → 判路线 → 搬本地 → 跑 → 用 Inspector 看 → 量代价。
  2. 本书用"一个页面 + 章节契约"的实验台:meta/create/dispose,import.meta.glob自动发现章节,全局一台Engine、每章一个Scene。
  3. dev只转译、build才见体积、typecheck只见类型——三者都跑是交付纪律。
  4. Inspector 的 v9 入口是ShowInspector,返回可dispose()的 token;用它请懒加载。
  5. 两条导入路线的体积差实测只有 16–18%,因为顶层导入没给 tree-shaking 留空间;Babylon 的定位是功能齐而非包小。
  6. 所有@babylonjs/*包必须齐步升版,升完先跑第 1 章体检。

3.13 术语卡

术语英文一句话
转译transpile把 TS/新语法变成浏览器能跑的 JS,不做类型检查
生产构建production build压缩 + 分块 + tree-shaking 后的产物
分块chunk构建把代码切成的可延迟加载文件
懒加载lazy loading用到时才import(),不进首屏
摇树tree-shaking把没被用到的代码从产物里剔除
UMDUniversal Module Definition挂全局对象的旧式打包形式
调试层debug layer引擎自带的画面内统计浮层
保留端口区间excluded port range系统预留、程序不可绑的端口段

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询