Appearance
PlayCanvas Tooling Evaluation (#2312)
Evaluated 2026-10-01 on farm-01 (Node 24.13.1). The React and web-components layers were scaffolded with create-playcanvas@0.9.0, installed, typechecked, built and rendered in headless Chromium in a throwaway directory outside the repo; playcanvas-sync@2.1.1 was read from its published package. No PlayCanvas account exists, so nothing touched the hosted Editor.
Verdict: archive (reference only). The tooling is good and the declarative layers work as advertised, but no BoB-managed project renders 3D (no playcanvas, three, @babylonjs/core or @react-three/fiber dependency in any project on this machine), and create-playcanvas is already a better scaffolder for a PlayCanvas app than a BoB template would be. When a project needs 3D, it runs the upstream scaffolder and then bob init, as below. Nothing goes into templates/ or the registry, and no follow-up is filed.
What was looked at
| Package | Version | Licence | Last published | Role |
|---|---|---|---|---|
playcanvas | 2.23.0 | MIT | 2026-10-01 | The engine: WebGL2/WebGPU, WebXR, glTF |
@playcanvas/react | 0.11.7 | MIT | 2026-09-25 | React components over the engine (React 18.3 or 19) |
@playcanvas/web-components | 0.27.0 | MIT | 2026-09-30 | <pc-app>, <pc-entity>… custom elements |
@playcanvas/pcui | 6.1.4 | MIT | 2026-05-14 | UI components for editor-style tools (not spiked; no AC) |
create-playcanvas | 0.9.0 | MIT | 2026-09-30 | npm create playcanvas scaffolder |
playcanvas-sync | 2.1.1 | MIT | 2026-06-22 | pcsync / pcwatch: local files ↔ PlayCanvas Editor |
All are actively maintained, as the issue says.
The spike: React and web components
Each format was generated with the default starter (a lit, spinning cube):
bash
npx create-playcanvas@0.9.0 app-react -f react -y
npx create-playcanvas@0.9.0 app-web-components -f web-components -y
npx create-playcanvas@0.9.0 app-engine -f engine -y --no-skills # baseline| React | Web components | Engine API (baseline) | |
|---|---|---|---|
| Scene code | 36-line App.tsx | 29-line index.html + 21-line main.ts | 65-line main.ts |
npm install | 25 s, 375 packages, 287 MB | 12 s, 367 packages, 281 MB | — |
tsc --noEmit | clean | clean | — |
vite build | 1.3 s | 1.2 s | — |
| JS bundle | 2,337 kB, 614 kB gzip | 2,300 kB, 589 kB gzip | 1,140 kB, 302 kB gzip |
| Headless render | cube drawn, no console errors | cube drawn, no console errors | — |
The renders were taken with Playwright's Chromium on SwiftShader (no GPU). Both screenshots show the same lit cube on the clear colour, about two seconds from navigation to first frame.
Developer experience. Both declarative forms read well. React gives a scene graph of <Entity> with <Camera>, <Light>, <Render> components and hooks such as useAppEvent('update', …) for the frame loop. Web components put the whole scene in HTML (<pc-entity position="0 0 3"><pc-camera>…), with one wrinkle: a script bundled by Vite cannot be named in markup (<pc-script-instance> only resolves scripts fetched at runtime), so the starter attaches its Rotate script from main.ts after whenReady(). TypeScript, ESLint and Prettier come configured; the generated engines field needs Node ≥ 22.23.2.
Bundle footprint is the cost. Either declarative layer roughly doubles the shipped engine: about 290–310 kB gzip on top of the 302 kB engine-only build for the same cube. Both wrappers import a large slice of the engine up front — @playcanvas/web-components statically imports about 150 engine symbols and declares sideEffects: true, because registering its custom elements is the side effect; @playcanvas/react reaches gaussian splats, gizmos, animation and Ammo physics from its component set — so the bundler cannot drop features the scene never uses. For a product viewer on a marketing page that is a real cost; for a tool or a game it is small next to the assets. A project that cares writes against the engine API, or lazy-loads the 3D view with a dynamic import().
create-playcanvas against BoB's provisioning
They do different jobs and do not overlap.
create-playcanvas | BoB | |
|---|---|---|
| Scope | One-shot app scaffold: Vite + TypeScript + lint/format + one of 12 starters in 3 formats | Ongoing tooling for any repo: .claude/ links, manifest-driven registry items, labels, ports, dev.json |
| Re-run | Never; --overwrite deletes the target's files | Idempotent, converges (bob ready, bob provision refresh) |
| Agent skills | Copies 15 @playcanvas/skills skills as real files into .claude/skills/ and .agents/skills/ (--no-skills omits them) | Symlinks provisioned registry items into .claude/ and gitignores them |
The one point of contact is .claude/skills/. The copied skills are real files, so under BoB's authored vs. derived rule they are authored project tooling: committed, never in the managed ignore block (which lists symlinks only), and untouched by bob provision. They name no collision with BoB's registry skills. So the order for a new 3D project is:
bash
npm create playcanvas@latest <name> -- -f react -y # or web-components / engine
cd <name> && git init
bob ready "$(pwd)"Keep the skills unless the duplicate .agents/skills/ copy is unwanted, in which case delete that directory — Claude Code reads only .claude/.
A BoB template would reproduce what upstream already maintains, with a version that drifts. Reuse is the right answer: point at the scaffolder.
playcanvas-sync against BoB's workflows
pcsync keeps a local directory and a branch of a project in PlayCanvas's hosted Editor in step: diff, pull, push, rename, rm, and watch (push local changes as they happen), configured by pcconfig.json or PLAYCANVAS_* variables with an API key. It only applies to projects built in the hosted Editor, and BoB has none, so the tool itself has no use here.
Its design choices, read for patterns:
- Local is authoritative. Only
pullandinitwrite local files; every other command changes the remote. BoB already holds the same rule: source repo →deploy.sh→BOB_HOME, never the reverse. - One file names the hand-managed set.
pcignore.txtlists files that a PlayCanvas merge leaves alone, and the same list is whatpcsyncmanages, so the two can't disagree. BoB's managed ignore block plays this role for provisioned links. --dry-runon every command and a multi-instance check beforewatchstarts. BoB has both (report-by-default CLIs; the live-session guard ondeploy.sh).- A documented unsupported case. Two people, one in
watchand one in the browser editor on the same branch, overwrite each other; the README says so instead of trying to merge. A fair model for stating a limit.
Nothing new to take: each pattern is already in BoB.
Recommendation
Archive. Do not adopt any package into BoB, and do not add a template.
- When a BoB project needs 3D, scaffold it with
create-playcanvasand thenbob ready, as above. Choose web components for a 3D element inside an otherwise static or server-rendered page, React when the app is already React, the engine API when bundle size matters. - Budget about 300 kB gzip for the engine and as much again for a declarative layer, or load the view lazily.
@playcanvas/skillsstays upstream in the generated project. It would only become a registry candidate if several BoB projects used PlayCanvas.
Not verified
@playcanvas/pcui(no acceptance criterion asked for it).- The other eleven starters, WebGPU, and WebXR; only the default cube was built.
- Rendering on a real GPU; the screenshots used SwiftShader.
playcanvas-syncagainst a live Editor project (no account or API key).