create-playcanvas 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +42 -25
  2. package/dist/index.mjs +49 -46
  3. package/package.json +3 -1
  4. package/skills/add-effects/SKILL.md +81 -0
  5. package/skills/apply-conventions/SKILL.md +73 -0
  6. package/skills/assemble-scene/SKILL.md +53 -0
  7. package/skills/assemble-scene/references/direct-engine.md +16 -0
  8. package/skills/assemble-scene/references/react.md +24 -0
  9. package/skills/assemble-scene/references/web-components.md +19 -0
  10. package/skills/build-app/SKILL.md +41 -0
  11. package/skills/build-app/references/direct-engine.md +12 -0
  12. package/skills/build-app/references/react.md +20 -0
  13. package/skills/build-app/references/web-components.md +55 -0
  14. package/skills/build-hud/SKILL.md +36 -0
  15. package/skills/calibrate-model/SKILL.md +65 -0
  16. package/skills/calibrate-model/references/direct-engine.md +22 -0
  17. package/skills/calibrate-model/references/react.md +27 -0
  18. package/skills/calibrate-model/references/web-components.md +19 -0
  19. package/skills/configure-animation/SKILL.md +50 -0
  20. package/skills/configure-animation/references/direct-engine.md +32 -0
  21. package/skills/configure-animation/references/react.md +32 -0
  22. package/skills/configure-animation/references/web-components.md +13 -0
  23. package/skills/find-examples/SKILL.md +79 -0
  24. package/skills/find-examples/references/direct-engine.md +15 -0
  25. package/skills/find-examples/references/react.md +17 -0
  26. package/skills/find-examples/references/web-components.md +17 -0
  27. package/skills/inspect-glb/SKILL.md +72 -0
  28. package/skills/inspect-glb/scripts/inspect.mjs +239 -0
  29. package/skills/light-scene/SKILL.md +84 -0
  30. package/skills/manage-game-state/SKILL.md +48 -0
  31. package/skills/reuse-scripts/SKILL.md +59 -0
  32. package/skills/reuse-scripts/references/direct-engine.md +41 -0
  33. package/skills/reuse-scripts/references/react.md +28 -0
  34. package/skills/reuse-scripts/references/web-components.md +27 -0
  35. package/templates/_shared/model-viewer/public/playcanvas-cube.glb +0 -0
  36. package/templates/engine/base/.prettierignore +2 -0
  37. package/templates/{vanilla-ts → engine/base}/README.md +7 -19
  38. package/templates/engine/base/eslint.config.mjs +3 -0
  39. package/templates/{vanilla-ts → engine/base}/src/vite-env.d.ts +6 -0
  40. package/templates/{vanilla-ts → engine/interactive-sphere}/src/main.ts +1 -1
  41. package/templates/{vanilla-ts → engine/interactive-sphere}/src/style.css +1 -1
  42. package/templates/{vanilla-ts → engine/interactive-sphere}/src/utils.ts +0 -1
  43. package/templates/engine/model-viewer/index.html +13 -0
  44. package/templates/engine/model-viewer/src/main.ts +81 -0
  45. package/templates/engine/model-viewer/src/style.css +8 -0
  46. package/templates/engine/spinning-cube/index.html +13 -0
  47. package/templates/engine/spinning-cube/src/main.ts +65 -0
  48. package/templates/engine/spinning-cube/src/style.css +8 -0
  49. package/templates/engine/splat-viewer/index.html +13 -0
  50. package/templates/engine/splat-viewer/src/main.ts +74 -0
  51. package/templates/engine/splat-viewer/src/style.css +8 -0
  52. package/templates/index.ts +37 -5
  53. package/templates/react/base/.prettierignore +2 -0
  54. package/templates/{react-ts → react/base}/README.md +6 -19
  55. package/templates/{react-ts → react/base}/eslint.config.mjs +1 -0
  56. package/templates/{react-ts → react/base}/src/vite-env.d.ts +6 -0
  57. package/templates/{react-ts → react/interactive-sphere}/src/Scene.tsx +1 -2
  58. package/templates/react/model-viewer/index.html +13 -0
  59. package/templates/react/model-viewer/src/App.tsx +43 -0
  60. package/templates/react/model-viewer/src/index.css +9 -0
  61. package/templates/react/model-viewer/src/main.tsx +11 -0
  62. package/templates/react/spinning-cube/index.html +13 -0
  63. package/templates/react/spinning-cube/src/App.tsx +36 -0
  64. package/templates/react/spinning-cube/src/index.css +9 -0
  65. package/templates/react/spinning-cube/src/main.tsx +11 -0
  66. package/templates/react/splat-viewer/index.html +13 -0
  67. package/templates/react/splat-viewer/src/App.tsx +39 -0
  68. package/templates/react/splat-viewer/src/index.css +9 -0
  69. package/templates/react/splat-viewer/src/main.tsx +11 -0
  70. package/templates/web-components/base/.prettierignore +2 -0
  71. package/templates/web-components/base/README.md +70 -0
  72. package/templates/web-components/base/_gitignore +24 -0
  73. package/templates/web-components/base/eslint.config.mjs +3 -0
  74. package/templates/web-components/base/package.json +31 -0
  75. package/templates/web-components/base/prettier.config.mjs +1 -0
  76. package/templates/web-components/base/src/assets/playcanvas.png +0 -0
  77. package/templates/web-components/base/src/vite-env.d.ts +19 -0
  78. package/templates/web-components/base/tsconfig.json +25 -0
  79. package/templates/web-components/interactive-sphere/index.html +53 -0
  80. package/templates/web-components/interactive-sphere/src/assets/typescript.svg +1 -0
  81. package/templates/web-components/interactive-sphere/src/main.ts +76 -0
  82. package/templates/web-components/interactive-sphere/src/style.css +111 -0
  83. package/templates/web-components/model-viewer/index.html +34 -0
  84. package/templates/web-components/model-viewer/src/main.ts +26 -0
  85. package/templates/web-components/model-viewer/src/style.css +8 -0
  86. package/templates/web-components/spinning-cube/index.html +29 -0
  87. package/templates/web-components/spinning-cube/src/main.ts +20 -0
  88. package/templates/web-components/spinning-cube/src/style.css +8 -0
  89. package/templates/web-components/splat-viewer/index.html +31 -0
  90. package/templates/web-components/splat-viewer/src/main.ts +14 -0
  91. package/templates/web-components/splat-viewer/src/style.css +8 -0
  92. package/templates/vanilla-ts/eslint.config.mjs +0 -3
  93. package/templates/vanilla-ts/public/environment-map.png +0 -0
  94. /package/templates/{react-ts → _shared/interactive-sphere}/public/environment-map.png +0 -0
  95. /package/templates/{react-ts → engine/base}/_gitignore +0 -0
  96. /package/templates/{vanilla-ts → engine/base}/package.json +0 -0
  97. /package/templates/{react-ts → engine/base}/prettier.config.mjs +0 -0
  98. /package/templates/{react-ts → engine/base}/src/assets/playcanvas.png +0 -0
  99. /package/templates/{vanilla-ts → engine/base}/tsconfig.json +0 -0
  100. /package/templates/{vanilla-ts → engine/interactive-sphere}/index.html +0 -0
  101. /package/templates/{vanilla-ts → engine/interactive-sphere}/src/App.ts +0 -0
  102. /package/templates/{vanilla-ts → engine/interactive-sphere}/src/assets/typescript.svg +0 -0
  103. /package/templates/{vanilla-ts → react/base}/_gitignore +0 -0
  104. /package/templates/{react-ts → react/base}/package.json +0 -0
  105. /package/templates/{vanilla-ts → react/base}/prettier.config.mjs +0 -0
  106. /package/templates/{vanilla-ts → react/base}/src/assets/playcanvas.png +0 -0
  107. /package/templates/{react-ts → react/base}/tsconfig.json +0 -0
  108. /package/templates/{react-ts → react/base}/vite.config.ts +0 -0
  109. /package/templates/{react-ts → react/interactive-sphere}/index.html +0 -0
  110. /package/templates/{react-ts → react/interactive-sphere}/src/App.css +0 -0
  111. /package/templates/{react-ts → react/interactive-sphere}/src/App.tsx +0 -0
  112. /package/templates/{react-ts → react/interactive-sphere}/src/assets/react.svg +0 -0
  113. /package/templates/{react-ts → react/interactive-sphere}/src/index.css +0 -0
  114. /package/templates/{react-ts → react/interactive-sphere}/src/main.tsx +0 -0
@@ -0,0 +1,9 @@
1
+ body {
2
+ margin: 0;
3
+ overflow: hidden;
4
+ }
5
+
6
+ #root {
7
+ width: 100vw;
8
+ height: 100vh;
9
+ }
@@ -0,0 +1,11 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+
4
+ import './index.css';
5
+ import App from './App.tsx';
6
+
7
+ createRoot(document.getElementById('root')!).render(
8
+ <StrictMode>
9
+ <App />
10
+ </StrictMode>
11
+ );
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/png" href="/src/assets/playcanvas.png" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>PlayCanvas + React</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,39 @@
1
+ import { Application, Entity } from '@playcanvas/react';
2
+ import { Camera, GSplat, Script } from '@playcanvas/react/components';
3
+ import { useSplat } from '@playcanvas/react/hooks';
4
+ import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
5
+
6
+ // Hosted so the template stays small. Capture or edit your own splat at https://superspl.at, then
7
+ // drop the .sog or .ply in public/ and point this at it instead
8
+ const SPLAT_URL = 'https://developer.playcanvas.com/assets/toy-cat.sog';
9
+
10
+ function Splat() {
11
+ const { asset } = useSplat(SPLAT_URL);
12
+
13
+ // Don't render until the splat is loaded
14
+ if (!asset) return null;
15
+
16
+ // Splats are captured in their own space, so this one needs offsetting and flipping upright
17
+ return (
18
+ <Entity name="toy cat" position={[0, -0.7, 0]} rotation={[0, 0, 180]}>
19
+ <GSplat asset={asset} />
20
+ </Entity>
21
+ );
22
+ }
23
+
24
+ // Antialiasing is disabled - splat rendering is fragment bound, so MSAA costs a lot and adds little
25
+ function App() {
26
+ return (
27
+ <Application graphicsDeviceOptions={{ antialias: false }}>
28
+ {/* Create a camera entity with orbit controls */}
29
+ <Entity name="camera" position={[0, 0, 2.5]}>
30
+ <Camera />
31
+ <Script script={CameraControls} />
32
+ </Entity>
33
+
34
+ <Splat />
35
+ </Application>
36
+ );
37
+ }
38
+
39
+ export default App;
@@ -0,0 +1,9 @@
1
+ body {
2
+ margin: 0;
3
+ overflow: hidden;
4
+ }
5
+
6
+ #root {
7
+ width: 100vw;
8
+ height: 100vh;
9
+ }
@@ -0,0 +1,11 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+
4
+ import './index.css';
5
+ import App from './App.tsx';
6
+
7
+ createRoot(document.getElementById('root')!).render(
8
+ <StrictMode>
9
+ <App />
10
+ </StrictMode>
11
+ );
@@ -0,0 +1,2 @@
1
+ .claude
2
+ .agents
@@ -0,0 +1,70 @@
1
+ # PlayCanvas Web Components + TypeScript Starter
2
+
3
+ A Vite-powered `@playcanvas/web-components` project with TypeScript, ESLint and Prettier. The scene is
4
+ declared as HTML custom elements in `index.html`.
5
+
6
+ ## Prerequisites
7
+
8
+ Node.js 22.23.2 or later.
9
+
10
+ ## Getting started
11
+
12
+ ```bash
13
+ npm create playcanvas@latest playcanvas-project -- --template web-components
14
+ cd playcanvas-project
15
+ npm install
16
+ npm run dev
17
+ ```
18
+
19
+ Open <http://localhost:5173>. Edit `index.html` to change the scene.
20
+
21
+ ## Scripts
22
+
23
+ | Command | Description |
24
+ | ------------------- | --------------------------------- |
25
+ | `npm run dev` | Start the Vite development server |
26
+ | `npm run build` | Build for production |
27
+ | `npm run start` | Preview the production build |
28
+ | `npm run lint` | Run ESLint |
29
+ | `npm run fmt` | Check formatting |
30
+ | `npm run typecheck` | Run TypeScript checks |
31
+
32
+ Run `npm run build` to generate a deployable static site in `dist/`.
33
+
34
+ ## How it fits together
35
+
36
+ `index.html` holds the scene: `<pc-app>` wraps a `<pc-scene>` of `<pc-entity>` elements, each with
37
+ components such as `<pc-camera>`, `<pc-light>` and `<pc-render>` attached. Entities are real DOM elements,
38
+ so `document.querySelector` reaches them and `setAttribute` changes them.
39
+
40
+ `src/main.ts` covers the two things HTML alone cannot do:
41
+
42
+ - **Attaching engine scripts.** `<pc-script name="...">` resolves scripts that `<pc-asset>` fetched at
43
+ runtime. Scripts imported through the bundler — like `CameraControls` and `Grid` here — are attached
44
+ through the entity instead, reached via `whenReady()` and the element's `.entity` property.
45
+ - **Responding to pointer input.** `<pc-app>` picks whatever is under the pointer and dispatches an
46
+ ordinary `PointerEvent` on the `<pc-entity>` it hit, so `addEventListener('pointerup', ...)` works on a
47
+ 3D entity exactly as it would on a `<button>`. Events are only generated for entities that have a
48
+ listener.
49
+
50
+ ## Editor support
51
+
52
+ The package ships a Custom Elements Manifest, which gives tag and attribute completion in `index.html`.
53
+ JetBrains IDEs pick it up automatically. For VS Code, add to `.vscode/settings.json`:
54
+
55
+ ```json
56
+ {
57
+ "html.customData": ["./node_modules/@playcanvas/web-components/dist/vscode.html-custom-data.json"]
58
+ }
59
+ ```
60
+
61
+ ## Agent skills
62
+
63
+ This project includes [`@playcanvas/skills`](https://github.com/playcanvas/skills) under `.claude/skills/` and `.agents/skills/`, so Claude Code, Codex and Cursor pick up PlayCanvas-specific workflows automatically.
64
+
65
+ ## Further reading
66
+
67
+ - [PlayCanvas Web Components manual](https://developer.playcanvas.com/user-manual/web-components/)
68
+ - [Web Components examples](https://playcanvas.github.io/web-components/examples)
69
+ - [Vite documentation](https://vite.dev/)
70
+ - [TypeScript documentation](https://www.typescriptlang.org/docs/)
@@ -0,0 +1,24 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
@@ -0,0 +1,3 @@
1
+ import typescriptConfig from '@playcanvas/eslint-config/typescript';
2
+
3
+ export default [{ ignores: ['.claude', '.agents'] }, ...typescriptConfig];
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "playcanvas-project",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "description": "A PlayCanvas Web Components project",
6
+ "type": "module",
7
+ "scripts": {
8
+ "build": "vite build",
9
+ "dev": "vite",
10
+ "fmt": "prettier --check .",
11
+ "fmt:fix": "prettier --write .",
12
+ "lint": "eslint .",
13
+ "lint:fix": "eslint . --fix",
14
+ "start": "vite preview",
15
+ "typecheck": "tsc --noEmit"
16
+ },
17
+ "dependencies": {
18
+ "@playcanvas/web-components": "^0.10.1",
19
+ "playcanvas": "^2.21.3"
20
+ },
21
+ "devDependencies": {
22
+ "@playcanvas/eslint-config": "3.0.0-beta.8",
23
+ "eslint": "^9.39.5",
24
+ "prettier": "^3.9.6",
25
+ "typescript": "~6.0.3",
26
+ "vite": "^8.2.0"
27
+ },
28
+ "engines": {
29
+ "node": ">=22.23.2"
30
+ }
31
+ }
@@ -0,0 +1 @@
1
+ export { default } from '@playcanvas/eslint-config/prettier';
@@ -0,0 +1,19 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module 'playcanvas/scripts/esm/camera-controls.mjs' {
4
+ import type { Script } from 'playcanvas';
5
+
6
+ export const CameraControls: typeof Script & (new () => Script);
7
+ }
8
+
9
+ declare module 'playcanvas/scripts/esm/grid.mjs' {
10
+ import type { Script } from 'playcanvas';
11
+
12
+ export const Grid: typeof Script & (new () => Script);
13
+ }
14
+
15
+ declare module 'playcanvas/scripts/esm/sky/procedural-sky.mjs' {
16
+ import type { Script } from 'playcanvas';
17
+
18
+ export const ProceduralSky: typeof Script & (new () => Script);
19
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "verbatimModuleSyntax": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
15
+
16
+ /* Linting */
17
+ "strict": true,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "erasableSyntaxOnly": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "noUncheckedSideEffectImports": true
23
+ },
24
+ "include": ["src"]
25
+ }
@@ -0,0 +1,53 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/png" href="/src/assets/playcanvas.png" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>PlayCanvas + Web Components</title>
8
+ </head>
9
+ <body>
10
+ <pc-app>
11
+ <!-- Materials are declared once and referenced by id -->
12
+ <pc-material id="sphere-material" diffuse="lightgrey"></pc-material>
13
+
14
+ <pc-scene>
15
+ <!-- Camera. Orbit controls are attached in src/main.ts -->
16
+ <pc-entity name="camera" position="4 1 4">
17
+ <pc-camera clear-color="#171717"></pc-camera>
18
+ </pc-entity>
19
+
20
+ <!-- Lighting comes from the environment map, which is applied in src/main.ts -->
21
+
22
+ <!-- Background grid. The grid script is attached in src/main.ts -->
23
+ <pc-entity name="grid" scale="1000 1000 1000"></pc-entity>
24
+
25
+ <!-- The sphere. pc-app dispatches pointer events onto whatever the pointer hits -->
26
+ <pc-entity name="sphere" position="0 0.5 0">
27
+ <pc-render type="sphere" material="sphere-material"></pc-render>
28
+ </pc-entity>
29
+ </pc-scene>
30
+ </pc-app>
31
+
32
+ <div class="absolute overlay">
33
+ <div class="grow">
34
+ <header>
35
+ <h1>PlayCanvas + Web Components</h1>
36
+ <a href="https://developer.playcanvas.com/user-manual/web-components/" target="_blank">
37
+ <img src="/src/assets/playcanvas.png" class="playcanvas-logo logo" alt="PlayCanvas logo" />
38
+ </a>
39
+ <a href="https://www.typescriptlang.org/" target="_blank">
40
+ <img src="/src/assets/typescript.svg" class="logo typescript" alt="TypeScript logo" />
41
+ </a>
42
+ </header>
43
+ </div>
44
+ <div>
45
+ <span id="counter" class="pill">Click Count: 0</span>
46
+ <p>Edit <code>index.html</code> and save to reload the scene</p>
47
+ </div>
48
+ <p class="read-the-docs">Click on the PlayCanvas and TypeScript logos to learn more</p>
49
+ </div>
50
+
51
+ <script type="module" src="/src/main.ts"></script>
52
+ </body>
53
+ </html>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path fill="#007ACC" d="M0 128v128h256V0H0z"></path><path fill="#FFF" d="m56.612 128.85l-.081 10.483h33.32v94.68h23.568v-94.68h33.321v-10.28c0-5.69-.122-10.444-.284-10.566c-.122-.162-20.4-.244-44.983-.203l-44.74.122l-.121 10.443Zm149.955-10.742c6.501 1.625 11.459 4.51 16.01 9.224c2.357 2.52 5.851 7.111 6.136 8.208c.08.325-11.053 7.802-17.798 11.988c-.244.162-1.22-.894-2.317-2.52c-3.291-4.795-6.745-6.867-12.028-7.233c-7.76-.528-12.759 3.535-12.718 10.321c0 1.992.284 3.17 1.097 4.795c1.707 3.536 4.876 5.649 14.832 9.956c18.326 7.883 26.168 13.084 31.045 20.48c5.445 8.249 6.664 21.415 2.966 31.208c-4.063 10.646-14.14 17.879-28.323 20.276c-4.388.772-14.79.65-19.504-.203c-10.28-1.828-20.033-6.908-26.047-13.572c-2.357-2.6-6.949-9.387-6.664-9.874c.122-.163 1.178-.813 2.356-1.504c1.138-.65 5.446-3.129 9.509-5.485l7.355-4.267l1.544 2.276c2.154 3.29 6.867 7.801 9.712 9.305c8.167 4.307 19.383 3.698 24.909-1.26c2.357-2.153 3.332-4.388 3.332-7.68c0-2.966-.366-4.266-1.91-6.501c-1.99-2.845-6.054-5.242-17.595-10.24c-13.206-5.69-18.895-9.224-24.096-14.832c-3.007-3.25-5.852-8.452-7.03-12.8c-.975-3.617-1.22-12.678-.447-16.335c2.723-12.76 12.353-21.659 26.25-24.3c4.51-.853 14.994-.528 19.424.569Z"></path></svg>
@@ -0,0 +1,76 @@
1
+ import type { EntityElement } from '@playcanvas/web-components';
2
+ import { whenReady } from '@playcanvas/web-components';
3
+ import type { Texture } from 'playcanvas';
4
+ import { Asset, TEXTURETYPE_RGBP } from 'playcanvas';
5
+ import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
6
+ import { Grid } from 'playcanvas/scripts/esm/grid.mjs';
7
+
8
+ import './style.css';
9
+
10
+ const HOVER_COLOR = 'orange';
11
+ const DEFAULT_COLOR = 'lightgrey';
12
+
13
+ // The camera is awaited as <pc-camera> rather than the <pc-entity> holding it - an entity becomes
14
+ // ready before its component children do, and CameraControls needs the camera component to exist
15
+ const [appElement, cameraComponent, grid, sphere] = await Promise.all([
16
+ whenReady('pc-app'),
17
+ whenReady('pc-camera'),
18
+ whenReady<EntityElement>('pc-entity[name="grid"]'),
19
+ whenReady<EntityElement>('pc-entity[name="sphere"]')
20
+ ]);
21
+
22
+ // This is a pre-generated environment atlas rather than an equirectangular image, so it is applied
23
+ // straight to the scene instead of through <pc-sky>
24
+ const app = appElement.app!;
25
+ const envAtlas = new Asset(
26
+ 'env-atlas',
27
+ 'texture',
28
+ { url: '/environment-map.png' },
29
+ {
30
+ type: TEXTURETYPE_RGBP,
31
+ mipmaps: false
32
+ }
33
+ );
34
+
35
+ app.assets.add(envAtlas);
36
+ await new Promise<void>((resolve) => {
37
+ envAtlas.ready(() => resolve());
38
+ app.assets.load(envAtlas);
39
+ });
40
+
41
+ // Light the scene from the environment, with the skybox itself left hidden
42
+ app.scene.envAtlas = envAtlas.resource as Texture;
43
+ const skyboxLayer = app.scene.layers.getLayerByName('Skybox');
44
+ if (skyboxLayer) {
45
+ skyboxLayer.enabled = false;
46
+ }
47
+
48
+ // Engine scripts are resolved by the bundler, so they are attached through the entity rather than
49
+ // with <pc-script name="...">, which only resolves scripts fetched at runtime by <pc-asset>
50
+ const camera = cameraComponent.closestEntity;
51
+ camera?.entity?.addComponent('script');
52
+ camera?.entity?.script?.create(CameraControls);
53
+ grid.entity?.addComponent('script');
54
+ grid.entity?.script?.create(Grid);
55
+
56
+ const material = document.querySelector('pc-material')!;
57
+ const counterElement = document.getElementById('counter')!;
58
+
59
+ let count = 0;
60
+
61
+ // pc-app picks whatever is under the pointer and dispatches a regular DOM PointerEvent on it, so
62
+ // the sphere is wired up like any other element
63
+ sphere.addEventListener('pointerenter', () => {
64
+ material.setAttribute('diffuse', HOVER_COLOR);
65
+ document.body.style.cursor = 'pointer';
66
+ });
67
+
68
+ sphere.addEventListener('pointerleave', () => {
69
+ material.setAttribute('diffuse', DEFAULT_COLOR);
70
+ document.body.style.cursor = 'default';
71
+ });
72
+
73
+ sphere.addEventListener('pointerup', () => {
74
+ count++;
75
+ counterElement.textContent = `Click Count: ${count}`;
76
+ });
@@ -0,0 +1,111 @@
1
+ :root {
2
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3
+ line-height: 1.5;
4
+ font-weight: 400;
5
+
6
+ color-scheme: light dark;
7
+ color: rgba(255, 255, 255, 0.87);
8
+ background-color: #242424;
9
+
10
+ font-synthesis: none;
11
+ text-rendering: optimizeLegibility;
12
+ -webkit-font-smoothing: antialiased;
13
+ -moz-osx-font-smoothing: grayscale;
14
+ }
15
+
16
+ header {
17
+ display: flex;
18
+ flex-direction: row;
19
+ align-items: center;
20
+ padding: 2rem;
21
+ }
22
+
23
+ a {
24
+ font-weight: 500;
25
+ color: #646cff;
26
+ pointer-events: auto;
27
+ text-decoration: inherit;
28
+ }
29
+ a:hover {
30
+ color: #535bf2;
31
+ }
32
+
33
+ body {
34
+ width: 100%;
35
+ height: 100%;
36
+ padding: 0;
37
+ margin: 0;
38
+ overflow: hidden;
39
+ }
40
+
41
+ h1 {
42
+ font-size: 2.4em;
43
+ line-height: 1.1;
44
+ opacity: 0.85;
45
+ }
46
+
47
+ .logo {
48
+ height: 2em;
49
+ padding: 0.5em;
50
+ will-change: filter;
51
+ transition: filter 300ms;
52
+ }
53
+
54
+ .playcanvas-logo {
55
+ border-radius: 100%;
56
+ }
57
+
58
+ .logo:hover {
59
+ filter: drop-shadow(0 0 2em #646cffaa);
60
+ }
61
+
62
+ .logo.typescript:hover {
63
+ filter: drop-shadow(0 0 2em #3178c6aa);
64
+ }
65
+
66
+ pc-app {
67
+ display: block;
68
+ }
69
+
70
+ .read-the-docs {
71
+ color: #888;
72
+ }
73
+
74
+ @media (prefers-color-scheme: light) {
75
+ :root {
76
+ color: #213547;
77
+ background-color: #ffffff;
78
+ }
79
+ a:hover {
80
+ color: #747bff;
81
+ }
82
+ }
83
+
84
+ .overlay {
85
+ margin: 0 auto;
86
+ text-align: center;
87
+ display: flex;
88
+ flex-direction: column;
89
+ align-items: center;
90
+ justify-content: stretch;
91
+ width: 100vw;
92
+ height: 100vh;
93
+ pointer-events: none;
94
+ }
95
+
96
+ .overlay > .grow {
97
+ flex-grow: 1;
98
+ }
99
+
100
+ .absolute {
101
+ position: absolute;
102
+ top: 0;
103
+ left: 0;
104
+ }
105
+
106
+ .pill {
107
+ background-color: #000;
108
+ color: #fff;
109
+ padding: 0.5rem 1rem;
110
+ border-radius: 0.5rem;
111
+ }
@@ -0,0 +1,34 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/png" href="/src/assets/playcanvas.png" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>PlayCanvas + Web Components</title>
8
+ </head>
9
+ <body>
10
+ <pc-app>
11
+ <!-- Swap this for your own model - anything you drop in public/ is served from the site root -->
12
+ <pc-asset id="model" src="/playcanvas-cube.glb"></pc-asset>
13
+
14
+ <pc-scene>
15
+ <!-- Camera. Orbit controls are attached in src/main.ts. The sky is HDR, so it needs
16
+ tone mapping to bring it back into displayable range - without it the whole
17
+ image blows out to white -->
18
+ <pc-entity name="camera" position="0 0 3">
19
+ <pc-camera tonemap="aces2"></pc-camera>
20
+ </pc-entity>
21
+
22
+ <!-- The procedural sky is both the background and the image-based lighting, so no
23
+ light or environment map asset is needed. The script is attached in src/main.ts -->
24
+ <pc-entity name="sky"></pc-entity>
25
+
26
+ <pc-entity name="model">
27
+ <pc-model asset="model"></pc-model>
28
+ </pc-entity>
29
+ </pc-scene>
30
+ </pc-app>
31
+
32
+ <script type="module" src="/src/main.ts"></script>
33
+ </body>
34
+ </html>
@@ -0,0 +1,26 @@
1
+ import type { EntityElement } from '@playcanvas/web-components';
2
+ import { whenReady } from '@playcanvas/web-components';
3
+ import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
4
+ import { ProceduralSky } from 'playcanvas/scripts/esm/sky/procedural-sky.mjs';
5
+
6
+ import './style.css';
7
+
8
+ // The camera is awaited as <pc-camera> rather than the <pc-entity> holding it - an entity becomes
9
+ // ready before its component children do, and CameraControls needs the camera component to exist
10
+ const [cameraComponent, sky] = await Promise.all([
11
+ whenReady('pc-camera'),
12
+ whenReady<EntityElement>('pc-entity[name="sky"]')
13
+ ]);
14
+
15
+ // Engine scripts are resolved by the bundler, so they are attached through the entity rather than
16
+ // with <pc-script name="...">, which only resolves scripts fetched at runtime by <pc-asset>
17
+ const camera = cameraComponent.closestEntity;
18
+ camera?.entity?.addComponent('script');
19
+ camera?.entity?.script?.create(CameraControls);
20
+
21
+ sky.entity?.addComponent('script');
22
+
23
+ // The sky's default luminance is scene-scale bright, so turn it down to keep the model out of clipping
24
+ sky.entity?.script?.create(ProceduralSky, {
25
+ properties: { luminance: 0.2 }
26
+ });
@@ -0,0 +1,8 @@
1
+ body {
2
+ margin: 0;
3
+ overflow: hidden;
4
+ }
5
+
6
+ pc-app {
7
+ display: block;
8
+ }
@@ -0,0 +1,29 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/png" href="/src/assets/playcanvas.png" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>PlayCanvas + Web Components</title>
8
+ </head>
9
+ <body>
10
+ <pc-app>
11
+ <pc-scene>
12
+ <pc-entity name="camera" position="0 0 3">
13
+ <pc-camera clear-color="#8099e6"></pc-camera>
14
+ </pc-entity>
15
+
16
+ <pc-entity name="light" rotation="45 0 0">
17
+ <pc-light></pc-light>
18
+ </pc-entity>
19
+
20
+ <!-- The rotate script is attached in src/main.ts -->
21
+ <pc-entity name="cube">
22
+ <pc-render type="box"></pc-render>
23
+ </pc-entity>
24
+ </pc-scene>
25
+ </pc-app>
26
+
27
+ <script type="module" src="/src/main.ts"></script>
28
+ </body>
29
+ </html>
@@ -0,0 +1,20 @@
1
+ import type { EntityElement } from '@playcanvas/web-components';
2
+ import { whenReady } from '@playcanvas/web-components';
3
+ import { Script } from 'playcanvas';
4
+
5
+ import './style.css';
6
+
7
+ class Rotate extends Script {
8
+ static scriptName = 'rotate';
9
+
10
+ update(dt: number) {
11
+ this.entity.rotate(10 * dt, 20 * dt, 30 * dt);
12
+ }
13
+ }
14
+
15
+ // Engine scripts are resolved by the bundler, so they are attached through the entity rather than
16
+ // with <pc-script name="...">, which only resolves scripts fetched at runtime by <pc-asset>
17
+ const cube = await whenReady<EntityElement>('pc-entity[name="cube"]');
18
+
19
+ cube.entity?.addComponent('script');
20
+ cube.entity?.script?.create(Rotate);
@@ -0,0 +1,8 @@
1
+ body {
2
+ margin: 0;
3
+ overflow: hidden;
4
+ }
5
+
6
+ pc-app {
7
+ display: block;
8
+ }