Skip to content

PlayCanvas Tooling Evaluation

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.

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.

Each format was generated with the default starter (a lit, spinning cube):

Terminal window
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

Section titled “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:

Terminal window
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.

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 pull and init write 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.txt lists files that a PlayCanvas merge leaves alone, and the same list is what pcsync manages, so the two can’t disagree. BoB’s managed ignore block plays this role for provisioned links.
  • --dry-run on every command and a multi-instance check before watch starts. BoB has both (report-by-default CLIs; the live-session guard on deploy.sh).
  • A documented unsupported case. Two people, one in watch and 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.

Archive. Do not adopt any package into BoB, and do not add a template.

  • When a BoB project needs 3D, scaffold it with create-playcanvas and then bob 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/skills stays upstream in the generated project. It would only become a registry candidate if several BoB projects used PlayCanvas.
  • @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-sync against a live Editor project (no account or API key).