create-playcanvas 0.5.1 → 0.6.1

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 (113) hide show
  1. package/README.md +20 -27
  2. package/dist/index.mjs +44 -42
  3. package/package.json +1 -1
  4. package/skills/calibrate-model/references/direct-engine.md +7 -4
  5. package/skills/calibrate-model/references/react.md +8 -3
  6. package/skills/calibrate-model/references/web-components.md +7 -4
  7. package/skills/configure-animation/references/web-components.md +18 -9
  8. package/skills/inspect-glb/SKILL.md +27 -18
  9. package/skills/inspect-glb/scripts/inspect.mjs +179 -50
  10. package/templates/_features/physics/assets/public/ammo/LICENSE +21 -0
  11. package/templates/_features/physics/assets/public/ammo/SOURCE.md +16 -0
  12. package/templates/_features/physics/assets/public/ammo/ammo.js +973 -0
  13. package/templates/_features/physics/assets/public/ammo/ammo.wasm.js +937 -0
  14. package/templates/_features/physics/assets/public/ammo/ammo.wasm.wasm +0 -0
  15. package/templates/_features/physics/react/package.json +5 -0
  16. package/templates/_features/physics/react/src/sync-ammo.d.ts +4 -0
  17. package/templates/_shared/ar-placement/README.md +7 -0
  18. package/templates/_shared/ar-placement/src/ar-placement.ts +123 -0
  19. package/templates/_shared/first-person-controller/README.md +7 -0
  20. package/templates/_shared/first-person-controller/src/controller.ts +18 -0
  21. package/templates/_shared/first-person-controller/src/scripts.d.ts +5 -0
  22. package/templates/_shared/interactive-sphere/README.md +7 -0
  23. package/templates/_shared/model-viewer/README.md +7 -0
  24. package/templates/_shared/physics-playground/README.md +7 -0
  25. package/templates/_shared/product-configurator/ASSET_LICENSES.md +3 -0
  26. package/templates/_shared/product-configurator/README.md +7 -0
  27. package/templates/_shared/product-configurator/src/product.css +14 -0
  28. package/templates/_shared/scene-editor/README.md +7 -0
  29. package/templates/_shared/scene-editor/src/scene-editor.ts +57 -0
  30. package/templates/_shared/spinning-cube/README.md +7 -0
  31. package/templates/_shared/splat-viewer/README.md +7 -0
  32. package/templates/_shared/sprite-game/README.md +7 -0
  33. package/templates/_shared/sprite-game/src/sprite-game.ts +197 -0
  34. package/templates/_shared/third-person-controller/README.md +7 -0
  35. package/templates/_shared/third-person-controller/src/controller.ts +53 -0
  36. package/templates/_shared/third-person-controller/src/scripts.d.ts +5 -0
  37. package/templates/_shared/vr-starter/README.md +7 -0
  38. package/templates/_shared/vr-starter/src/vr.ts +40 -0
  39. package/templates/_shared/vr-starter/src/xr.d.ts +11 -0
  40. package/templates/engine/ar-placement/src/main.ts +77 -0
  41. package/templates/engine/base/.prettierignore +1 -0
  42. package/templates/engine/base/README.md +1 -1
  43. package/templates/engine/base/_npmrc +1 -0
  44. package/templates/engine/base/eslint.config.mjs +1 -1
  45. package/templates/engine/base/index.html +13 -0
  46. package/templates/engine/base/src/starter.css +104 -0
  47. package/templates/engine/first-person-controller/src/main.ts +139 -0
  48. package/templates/engine/interactive-sphere/src/App.ts +9 -7
  49. package/templates/engine/interactive-sphere/src/main.ts +2 -29
  50. package/templates/engine/interactive-sphere/src/style.css +18 -97
  51. package/templates/engine/model-viewer/src/main.ts +3 -2
  52. package/templates/engine/physics-playground/src/main.ts +132 -0
  53. package/templates/engine/product-configurator/src/main.ts +156 -0
  54. package/templates/engine/scene-editor/src/main.ts +90 -0
  55. package/templates/engine/splat-viewer/src/main.ts +4 -3
  56. package/templates/engine/sprite-game/src/main.ts +72 -0
  57. package/templates/engine/third-person-controller/src/main.ts +216 -0
  58. package/templates/engine/vr-starter/src/main.ts +101 -0
  59. package/templates/index.ts +66 -5
  60. package/templates/react/ar-placement/src/App.tsx +58 -0
  61. package/templates/react/base/.prettierignore +1 -0
  62. package/templates/react/base/README.md +1 -1
  63. package/templates/react/base/_npmrc +1 -0
  64. package/templates/react/base/eslint.config.mjs +1 -1
  65. package/templates/react/base/index.html +13 -0
  66. package/templates/react/base/src/main.tsx +10 -0
  67. package/templates/react/base/src/starter.css +104 -0
  68. package/templates/react/first-person-controller/src/App.tsx +115 -0
  69. package/templates/react/interactive-sphere/src/App.css +18 -68
  70. package/templates/react/interactive-sphere/src/App.tsx +1 -22
  71. package/templates/react/interactive-sphere/src/Scene.tsx +7 -6
  72. package/templates/react/interactive-sphere/src/index.css +1 -48
  73. package/templates/react/model-viewer/src/App.tsx +2 -2
  74. package/templates/react/physics-playground/src/App.tsx +85 -0
  75. package/templates/react/product-configurator/src/App.tsx +157 -0
  76. package/templates/react/scene-editor/src/App.tsx +84 -0
  77. package/templates/react/splat-viewer/src/App.tsx +3 -3
  78. package/templates/react/sprite-game/src/App.tsx +38 -0
  79. package/templates/react/third-person-controller/src/App.tsx +193 -0
  80. package/templates/react/vr-starter/src/App.tsx +99 -0
  81. package/templates/web-components/ar-placement/index.html +44 -0
  82. package/templates/web-components/ar-placement/src/main.ts +13 -0
  83. package/templates/web-components/base/.prettierignore +1 -0
  84. package/templates/web-components/base/README.md +1 -1
  85. package/templates/web-components/base/_npmrc +1 -0
  86. package/templates/web-components/base/eslint.config.mjs +1 -1
  87. package/templates/web-components/base/package.json +1 -1
  88. package/templates/web-components/base/src/starter.css +104 -0
  89. package/templates/web-components/first-person-controller/index.html +168 -0
  90. package/templates/web-components/first-person-controller/src/main.ts +15 -0
  91. package/templates/web-components/interactive-sphere/index.html +5 -22
  92. package/templates/web-components/interactive-sphere/src/main.ts +5 -4
  93. package/templates/web-components/interactive-sphere/src/style.css +18 -76
  94. package/templates/web-components/model-viewer/index.html +1 -1
  95. package/templates/web-components/model-viewer/src/main.ts +2 -1
  96. package/templates/web-components/physics-playground/index.html +51 -0
  97. package/templates/web-components/physics-playground/src/main.ts +32 -0
  98. package/templates/web-components/product-configurator/index.html +51 -0
  99. package/templates/web-components/product-configurator/src/main.ts +93 -0
  100. package/templates/web-components/scene-editor/index.html +51 -0
  101. package/templates/web-components/scene-editor/src/main.ts +26 -0
  102. package/templates/web-components/spinning-cube/src/main.ts +1 -0
  103. package/templates/web-components/splat-viewer/index.html +2 -2
  104. package/templates/web-components/splat-viewer/src/main.ts +2 -1
  105. package/templates/web-components/sprite-game/index.html +34 -0
  106. package/templates/web-components/sprite-game/src/main.ts +9 -0
  107. package/templates/web-components/third-person-controller/index.html +183 -0
  108. package/templates/web-components/third-person-controller/src/main.ts +16 -0
  109. package/templates/web-components/vr-starter/index.html +80 -0
  110. package/templates/web-components/vr-starter/src/main.ts +12 -0
  111. package/templates/engine/interactive-sphere/src/assets/typescript.svg +0 -1
  112. package/templates/react/interactive-sphere/src/assets/react.svg +0 -1
  113. package/templates/web-components/interactive-sphere/src/assets/typescript.svg +0 -1
@@ -0,0 +1,101 @@
1
+ import {
2
+ AppBase,
3
+ AppOptions,
4
+ CameraComponentSystem,
5
+ Color,
6
+ Entity,
7
+ FILLMODE_FILL_WINDOW,
8
+ LightComponentSystem,
9
+ RenderComponentSystem,
10
+ RESOLUTION_AUTO,
11
+ ScriptComponentSystem,
12
+ StandardMaterial,
13
+ XrManager,
14
+ createGraphicsDevice
15
+ } from 'playcanvas';
16
+
17
+ import './starter.css';
18
+ import { setupVr } from './vr';
19
+
20
+ document.body.insertAdjacentHTML(
21
+ 'beforeend',
22
+ '<div class="hud"><section class="panel"><h1>VR Starter</h1><p id="xr-status">Checking VR support…</p><div class="controls"><button id="xr-button" disabled>Enter VR</button></div></section></div>'
23
+ );
24
+
25
+ const canvas = document.getElementById('application-canvas') as HTMLCanvasElement;
26
+ const device = await createGraphicsDevice(canvas);
27
+ const options = new AppOptions();
28
+ options.graphicsDevice = device;
29
+ options.componentSystems = [RenderComponentSystem, CameraComponentSystem, LightComponentSystem, ScriptComponentSystem];
30
+ options.xr = XrManager;
31
+
32
+ const app = new AppBase(canvas);
33
+ app.init(options);
34
+ app.start();
35
+ app.setCanvasFillMode(FILLMODE_FILL_WINDOW);
36
+ app.setCanvasResolution(RESOLUTION_AUTO);
37
+ app.scene.ambientLight = new Color(0.32, 0.34, 0.38);
38
+
39
+ const material = (color: Color) => {
40
+ const value = new StandardMaterial();
41
+ value.diffuse = color;
42
+ value.update();
43
+ return value;
44
+ };
45
+ const floorMaterial = material(new Color(0.38, 0.4, 0.43));
46
+ const wallMaterial = material(new Color(0.24, 0.26, 0.29));
47
+ const cyan = material(new Color(0.12, 0.7, 0.86));
48
+ const coral = material(new Color(1, 0.35, 0.3));
49
+ const yellow = material(new Color(1, 0.75, 0.2));
50
+
51
+ const floor = new Entity('floor');
52
+ floor.addComponent('render', { type: 'plane', material: floorMaterial });
53
+ floor.setLocalScale(10, 1, 10);
54
+ app.root.addChild(floor);
55
+
56
+ const part = (
57
+ name: string,
58
+ type: 'box' | 'sphere' | 'cone',
59
+ position: [number, number, number],
60
+ scale: [number, number, number],
61
+ value: StandardMaterial
62
+ ) => {
63
+ const entity = new Entity(name);
64
+ entity.addComponent('render', { type, material: value });
65
+ entity.setPosition(...position);
66
+ entity.setLocalScale(...scale);
67
+ app.root.addChild(entity);
68
+ };
69
+ part('back-wall', 'box', [0, 2, -3.5], [8, 4, 0.2], wallMaterial);
70
+ part('left-wall', 'box', [-4, 2, 0], [0.2, 4, 7], wallMaterial);
71
+ part('right-wall', 'box', [4, 2, 0], [0.2, 4, 7], wallMaterial);
72
+ part('cyan-panel', 'box', [-2.4, 2.4, -3.35], [1.3, 1.3, 0.12], cyan);
73
+ part('coral-panel', 'box', [0, 2.4, -3.35], [1.3, 1.3, 0.12], coral);
74
+ part('yellow-panel', 'box', [2.4, 2.4, -3.35], [1.3, 1.3, 0.12], yellow);
75
+ [-2.4, 0, 2.4].forEach((x, i) => {
76
+ part(`pedestal-${i}`, 'box', [x, 0.45, -1.6], [1, 0.9, 1], wallMaterial);
77
+ });
78
+ part('sphere', 'sphere', [-2.4, 1.35, -1.6], [0.7, 0.7, 0.7], cyan);
79
+ part('cube', 'box', [0, 1.35, -1.6], [0.7, 0.7, 0.7], coral);
80
+ part('cone', 'cone', [2.4, 1.35, -1.6], [0.8, 0.9, 0.8], yellow);
81
+
82
+ const rig = new Entity('xr-rig');
83
+ const camera = new Entity('camera');
84
+ camera.addComponent('camera', { clearColor: new Color(0.16, 0.18, 0.22) });
85
+ camera.setLocalPosition(0, 1.6, 4);
86
+ rig.addChild(camera);
87
+ app.root.addChild(rig);
88
+ setupVr(app, rig, camera);
89
+
90
+ const light = new Entity('light');
91
+ light.addComponent('light', {
92
+ type: 'directional',
93
+ intensity: 2,
94
+ castShadows: true,
95
+ shadowBias: 0.2,
96
+ normalOffsetBias: 0.05
97
+ });
98
+ light.setEulerAngles(45, 30, 0);
99
+ app.root.addChild(light);
100
+
101
+ window.addEventListener('resize', () => app.resizeCanvas());
@@ -2,7 +2,7 @@ import colors from 'picocolors';
2
2
 
3
3
  const { cyan, magenta, yellow } = colors;
4
4
 
5
- export const TEMPLATES = [
5
+ export const FORMATS = [
6
6
  {
7
7
  name: 'engine',
8
8
  display: 'Engine',
@@ -23,28 +23,89 @@ export const TEMPLATES = [
23
23
  }
24
24
  ];
25
25
 
26
- // Every template holds a `base` directory of shared tooling plus one directory per boilerplate,
27
- // which is copied over the top. Assets a boilerplate needs in every template live once in
28
- // `_shared/<boilerplate>` and are seeded in first. The first entry is the default.
29
- export const BOILERPLATES = [
26
+ export const FEATURES: Record<string, Record<string, string[]>> = {
27
+ physics: {
28
+ engine: ['assets'],
29
+ react: ['react'],
30
+ 'web-components': ['assets']
31
+ }
32
+ };
33
+
34
+ // every format holds a base plus one directory per starter; the first starter is the default
35
+ export const STARTERS = [
30
36
  {
31
37
  name: 'spinning-cube',
32
38
  display: 'Spinning Cube',
39
+ category: 'Basics',
33
40
  description: 'a rotating cube and a light'
34
41
  },
35
42
  {
36
43
  name: 'interactive-sphere',
37
44
  display: 'Interactive Sphere',
45
+ category: 'Basics',
38
46
  description: 'orbit controls, a grid and pointer picking'
39
47
  },
40
48
  {
41
49
  name: 'model-viewer',
42
50
  display: 'Model Viewer',
51
+ category: 'Viewers',
43
52
  description: 'a glTF model, orbit controls and a procedural sky'
44
53
  },
45
54
  {
46
55
  name: 'splat-viewer',
47
56
  display: 'Splat Viewer',
57
+ category: 'Viewers',
48
58
  description: 'a Gaussian splat and orbit controls'
59
+ },
60
+ {
61
+ name: 'product-configurator',
62
+ display: 'Product Configurator',
63
+ category: 'Viewers',
64
+ description: 'product variants, materials and orbit controls'
65
+ },
66
+ {
67
+ name: 'physics-playground',
68
+ display: 'Physics Playground',
69
+ category: 'Games',
70
+ description: 'rigid bodies, collisions, spawning and reset',
71
+ features: ['physics']
72
+ },
73
+ {
74
+ name: 'first-person-controller',
75
+ display: 'First-Person Controller',
76
+ category: 'Games',
77
+ description: 'first-person movement, mouse look and jumping',
78
+ features: ['physics']
79
+ },
80
+ {
81
+ name: 'third-person-controller',
82
+ display: 'Third-Person Controller',
83
+ category: 'Games',
84
+ description: 'an animated character, follow camera and locomotion',
85
+ features: ['physics']
86
+ },
87
+ {
88
+ name: 'sprite-game',
89
+ display: 'Sprite Game',
90
+ category: 'Games',
91
+ description: 'an orthographic camera, sprite animation and input'
92
+ },
93
+ {
94
+ name: 'scene-editor',
95
+ display: 'Scene Editor',
96
+ category: 'Tools',
97
+ description: 'selection and translate, rotate and scale gizmos'
98
+ },
99
+ {
100
+ name: 'vr-starter',
101
+ display: 'VR Starter',
102
+ category: 'XR',
103
+ description: 'immersive VR, controllers and locomotion'
104
+ },
105
+ {
106
+ name: 'ar-placement',
107
+ display: 'AR Placement',
108
+ category: 'XR',
109
+ description: 'mobile AR, hit testing and object placement'
49
110
  }
50
111
  ];
@@ -0,0 +1,58 @@
1
+ import { Application, Entity } from '@playcanvas/react';
2
+ import { Camera, Light, Render, Script } from '@playcanvas/react/components';
3
+ import { useMaterial } from '@playcanvas/react/hooks';
4
+ import type { Entity as PcEntity } from 'playcanvas';
5
+ import { useState } from 'react';
6
+
7
+ import { ArPlacement } from './ar-placement';
8
+ import './starter.css';
9
+
10
+ function App() {
11
+ const [camera, setCamera] = useState<PcEntity | null>(null);
12
+ const [preview, setPreview] = useState<PcEntity | null>(null);
13
+ const surface = useMaterial({ diffuse: '#474d57' });
14
+ const cyan = useMaterial({ diffuse: '#1ab3e6' });
15
+ const coral = useMaterial({ diffuse: '#ff5926' });
16
+
17
+ return (
18
+ <>
19
+ <Application graphicsDeviceOptions={{ alpha: true, xrCompatible: true }}>
20
+ <Entity name="camera" ref={setCamera} position={[3, 2.4, 4]} rotation={[-20, 37, 0]}>
21
+ <Camera clearColor="#0d141f" />
22
+ </Entity>
23
+ <Entity name="light" rotation={[45, 30, 0]}>
24
+ <Light type="directional" intensity={2} />
25
+ </Entity>
26
+ <Entity name="preview" ref={setPreview}>
27
+ <Entity name="surface" scale={[4, 0.15, 4]}>
28
+ <Render type="cylinder" material={surface} />
29
+ </Entity>
30
+ <Entity name="box" position={[0, 0.65, 0]}>
31
+ <Render type="box" material={cyan} />
32
+ </Entity>
33
+ <Entity name="reticle" position={[0, 0.12, 0]} scale={[1.5, 0.03, 1.5]}>
34
+ <Render type="cylinder" material={coral} />
35
+ </Entity>
36
+ </Entity>
37
+ {camera && preview && (
38
+ <Entity name="placement">
39
+ <Script script={ArPlacement} camera={camera} preview={preview} />
40
+ </Entity>
41
+ )}
42
+ </Application>
43
+ <div className="hud">
44
+ <section className="panel">
45
+ <h1>AR Placement</h1>
46
+ <p id="xr-status">Checking AR support…</p>
47
+ <div className="controls">
48
+ <button id="xr-button" disabled>
49
+ Start AR
50
+ </button>
51
+ </div>
52
+ </section>
53
+ </div>
54
+ </>
55
+ );
56
+ }
57
+
58
+ export default App;
@@ -1,2 +1,3 @@
1
1
  .claude
2
2
  .agents
3
+ public/ammo
@@ -9,7 +9,7 @@ Node.js 22.23.2 or later.
9
9
  ## Getting started
10
10
 
11
11
  ```bash
12
- npm create playcanvas@latest playcanvas-project -- --template react
12
+ npm create playcanvas@latest playcanvas-project -- --format react
13
13
  cd playcanvas-project
14
14
  npm install
15
15
  npm run dev
@@ -0,0 +1 @@
1
+ registry=https://registry.npmjs.org/
@@ -2,7 +2,7 @@ import reactConfig from '@playcanvas/eslint-config/react';
2
2
  import typescriptConfig from '@playcanvas/eslint-config/typescript';
3
3
 
4
4
  export default [
5
- { ignores: ['.claude', '.agents'] },
5
+ { ignores: ['.claude', '.agents', 'public/ammo'] },
6
6
  ...typescriptConfig,
7
7
  ...reactConfig,
8
8
  {
@@ -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 Starter</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,10 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+
4
+ import App from './App';
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>
10
+ );
@@ -0,0 +1,104 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ html,
6
+ body,
7
+ #root,
8
+ #application,
9
+ pc-app,
10
+ canvas {
11
+ width: 100%;
12
+ height: 100%;
13
+ margin: 0;
14
+ overflow: hidden;
15
+ }
16
+
17
+ body {
18
+ background: #11151c;
19
+ color: #f8fafc;
20
+ font-family: Inter, ui-sans-serif, system-ui, sans-serif;
21
+ }
22
+
23
+ canvas,
24
+ pc-app {
25
+ display: block;
26
+ }
27
+
28
+ .hud {
29
+ position: fixed;
30
+ inset: 0;
31
+ z-index: 10;
32
+ padding: 20px;
33
+ pointer-events: none;
34
+ }
35
+
36
+ .panel {
37
+ width: min(360px, calc(100vw - 40px));
38
+ padding: 16px;
39
+ border: 1px solid #ffffff24;
40
+ border-radius: 14px;
41
+ background: #0b1019d9;
42
+ box-shadow: 0 12px 40px #0008;
43
+ backdrop-filter: blur(12px);
44
+ pointer-events: auto;
45
+ }
46
+
47
+ h1 {
48
+ margin: 0 0 6px;
49
+ font-size: 18px;
50
+ }
51
+
52
+ p {
53
+ margin: 6px 0 0;
54
+ color: #aeb8c8;
55
+ font-size: 13px;
56
+ line-height: 1.45;
57
+ }
58
+
59
+ .controls {
60
+ display: flex;
61
+ flex-wrap: wrap;
62
+ gap: 8px;
63
+ margin-top: 14px;
64
+ }
65
+
66
+ button {
67
+ padding: 8px 11px;
68
+ border: 1px solid #ffffff24;
69
+ border-radius: 9px;
70
+ background: #263247;
71
+ color: inherit;
72
+ cursor: pointer;
73
+ }
74
+
75
+ button:hover,
76
+ button[aria-pressed='true'] {
77
+ border-color: #41b6e6;
78
+ background: #334867;
79
+ }
80
+
81
+ button:disabled {
82
+ cursor: not-allowed;
83
+ opacity: 0.5;
84
+ }
85
+
86
+ .swatch {
87
+ width: 32px;
88
+ height: 32px;
89
+ padding: 0;
90
+ border-radius: 50%;
91
+ background: var(--color);
92
+ }
93
+
94
+ .crosshair {
95
+ position: fixed;
96
+ top: 50%;
97
+ left: 50%;
98
+ width: 16px;
99
+ height: 16px;
100
+ border: 2px solid #fff;
101
+ border-radius: 50%;
102
+ translate: -50% -50%;
103
+ box-shadow: 0 0 4px #000;
104
+ }
@@ -0,0 +1,115 @@
1
+ import { Application, Entity } from '@playcanvas/react';
2
+ import { Camera, Collision, Light, Render, RigidBody } from '@playcanvas/react/components';
3
+ import { useMaterial, usePhysics } from '@playcanvas/react/hooks';
4
+ import type { Entity as PcEntity, StandardMaterial } from 'playcanvas';
5
+ import { useEffect, useRef } from 'react';
6
+
7
+ import { addFirstPersonController } from './controller';
8
+ import './starter.css';
9
+
10
+ function Block({
11
+ name,
12
+ position,
13
+ scale,
14
+ material
15
+ }: {
16
+ name: string;
17
+ position: [number, number, number];
18
+ scale: [number, number, number];
19
+ material: StandardMaterial;
20
+ }) {
21
+ return (
22
+ <Entity name={name} position={position} scale={scale}>
23
+ <Render type="box" material={material} />
24
+ <Collision type="box" halfExtents={[scale[0] / 2, scale[1] / 2, scale[2] / 2]} />
25
+ <RigidBody type="static" />
26
+ </Entity>
27
+ );
28
+ }
29
+
30
+ function Scene() {
31
+ const player = useRef<PcEntity>(null);
32
+ const camera = useRef<PcEntity>(null);
33
+ const { isPhysicsLoaded } = usePhysics();
34
+ const floor = useMaterial({ diffuse: '#474d52', emissive: '#0e0f10', gloss: 0.2 });
35
+ const wall = useMaterial({ diffuse: '#6b737a', emissive: '#151718', gloss: 0.25 });
36
+ const blue = useMaterial({ diffuse: '#154a7a', emissive: '#040f18', gloss: 0.4 });
37
+ const green = useMaterial({ diffuse: '#2e6638', emissive: '#09140b', gloss: 0.2 });
38
+ const wood = useMaterial({ diffuse: '#7a451a', emissive: '#180e05', gloss: 0.2 });
39
+ const metal = useMaterial({ diffuse: '#475461', emissive: '#0e1113', gloss: 0.5 });
40
+
41
+ useEffect(() => {
42
+ if (isPhysicsLoaded && player.current && camera.current) {
43
+ return addFirstPersonController(player.current, camera.current);
44
+ }
45
+ }, [isPhysicsLoaded]);
46
+
47
+ return (
48
+ <>
49
+ <Entity name="light" rotation={[50, 30, 0]}>
50
+ <Light type="directional" intensity={2.5} castShadows shadowBias={0.2} normalOffsetBias={0.05} />
51
+ </Entity>
52
+ <Entity name="fill" rotation={[-25, -140, 0]}>
53
+ <Light type="directional" intensity={0.8} />
54
+ </Entity>
55
+ <Block name="floor" position={[0, -0.1, 0]} scale={[18, 0.2, 18]} material={floor} />
56
+ <Block name="back-wall" position={[0, 1.5, -9]} scale={[18, 3, 0.3]} material={wall} />
57
+ <Block name="front-wall" position={[0, 1.5, 9]} scale={[18, 3, 0.3]} material={wall} />
58
+ <Block name="left-wall" position={[-9, 1.5, 0]} scale={[0.3, 3, 18]} material={wall} />
59
+ <Block name="right-wall" position={[9, 1.5, 0]} scale={[0.3, 3, 18]} material={wall} />
60
+ <Block name="blue-crate" position={[-2, 0.75, -3]} scale={[1.5, 1.5, 1.5]} material={blue} />
61
+ <Block name="green-crate" position={[3, 0.5, 1]} scale={[2.5, 1, 1]} material={green} />
62
+ <Block name="cargo-left-0" position={[-5.5, 0.75, -5.5]} scale={[2, 1.5, 2]} material={wood} />
63
+ <Block name="cargo-left-1" position={[-5.5, 2, -5.5]} scale={[1.5, 1, 1.5]} material={blue} />
64
+ <Block name="cargo-right-0" position={[5.3, 0.6, -4.8]} scale={[2.4, 1.2, 2]} material={green} />
65
+ <Block name="cargo-right-1" position={[5.3, 1.7, -4.8]} scale={[1.5, 1, 1.5]} material={wood} />
66
+ <Block name="cargo-center" position={[0, 0.6, -6.5]} scale={[2.2, 1.2, 1.8]} material={blue} />
67
+ {[-3.5, 0, 3.5].flatMap((z, i) => [
68
+ <Block
69
+ key={`left-${i}`}
70
+ name={`shelf-left-post-${i}`}
71
+ position={[-6.8, 1.4, z]}
72
+ scale={[0.25, 2.8, 0.25]}
73
+ material={metal}
74
+ />,
75
+ <Block
76
+ key={`right-${i}`}
77
+ name={`shelf-right-post-${i}`}
78
+ position={[6.8, 1.4, z]}
79
+ scale={[0.25, 2.8, 0.25]}
80
+ material={metal}
81
+ />
82
+ ])}
83
+ <Block name="shelf-left-low" position={[-6.8, 0.65, 0]} scale={[1.1, 0.18, 7.5]} material={metal} />
84
+ <Block name="shelf-left-high" position={[-6.8, 2.1, 0]} scale={[1.1, 0.18, 7.5]} material={metal} />
85
+ <Block name="shelf-right-low" position={[6.8, 0.65, 0]} scale={[1.1, 0.18, 7.5]} material={metal} />
86
+ <Block name="shelf-right-high" position={[6.8, 2.1, 0]} scale={[1.1, 0.18, 7.5]} material={metal} />
87
+ <Block name="pallet" position={[-2.5, 0.15, 2.5]} scale={[3, 0.3, 2]} material={wood} />
88
+ <Entity name="player" ref={player} position={[0, 1, 5]}>
89
+ <Collision type="capsule" radius={0.45} height={1.8} />
90
+ <RigidBody type="dynamic" mass={80} angularFactor={[0, 0, 0]} />
91
+ <Entity name="camera" ref={camera} position={[0, 0.65, 0]}>
92
+ <Camera clearColor="#61a3db" fov={80} />
93
+ </Entity>
94
+ </Entity>
95
+ </>
96
+ );
97
+ }
98
+
99
+ function App() {
100
+ return (
101
+ <>
102
+ <Application usePhysics>
103
+ <Scene />
104
+ </Application>
105
+ <div className="hud">
106
+ <section className="panel">
107
+ <h1>First-Person Controller</h1>
108
+ <p>Click the scene, then use WASD, mouse look and Space to jump.</p>
109
+ </section>
110
+ </div>
111
+ </>
112
+ );
113
+ }
114
+
115
+ export default App;
@@ -1,75 +1,25 @@
1
- #root {
2
- display: flex;
3
- flex-direction: column;
4
- height: 100vh;
5
- width: 100vw;
6
- }
7
-
8
- header {
9
- display: flex;
10
- flex-direction: row;
11
- align-items: center;
12
- padding: 2rem;
13
- }
14
-
15
- .absolute {
16
- position: absolute;
17
- top: 0;
18
- left: 0;
19
- }
20
-
21
1
  .full-bleed {
22
2
  width: 100vw;
23
3
  height: 100vh;
24
4
  overflow: hidden;
25
5
  }
26
6
 
27
- .logo {
28
- height: 2em;
29
- padding: 0.5em;
30
- will-change: filter;
31
- transition: filter 300ms;
32
- }
33
-
34
- .playcanvas-logo {
35
- border-radius: 100%;
36
- }
37
-
38
- .logo:hover {
39
- filter: drop-shadow(0 0 2em #646cffaa);
40
- }
41
-
42
- .logo.react:hover {
43
- filter: drop-shadow(0 0 2em #61dafbaa);
44
- }
45
-
46
- .read-the-docs {
47
- color: #888;
48
- }
49
-
50
- .overlay {
51
- margin: 0 auto;
52
- text-align: center;
53
- display: flex;
54
- flex-direction: column;
55
- align-items: center;
56
- justify-content: stretch;
57
- width: 100vw;
58
- height: 100vh;
59
- pointer-events: none;
60
- }
61
-
62
- .overlay a {
63
- pointer-events: auto;
64
- }
65
-
66
- .overlay > .grow {
67
- flex-grow: 1;
68
- }
69
-
70
- .pill {
71
- background-color: #000;
72
- color: #fff;
73
- padding: 0.5rem 1rem;
74
- border-radius: 0.5rem;
7
+ .counter {
8
+ position: fixed;
9
+ bottom: 22px;
10
+ left: 50%;
11
+ z-index: 2;
12
+ margin: 0;
13
+ padding: 9px 13px;
14
+ border: 1px solid #ffffff24;
15
+ border-radius: 999px;
16
+ background: #07101bd9;
17
+ color: #f8fafc;
18
+ font:
19
+ 650 12px Inter,
20
+ ui-sans-serif,
21
+ system-ui,
22
+ sans-serif;
23
+ translate: -50% 0;
24
+ backdrop-filter: blur(10px);
75
25
  }
@@ -1,8 +1,6 @@
1
1
  import { Application } from '@playcanvas/react';
2
2
  import { useState } from 'react';
3
3
 
4
- import playcanvasLogo from './assets/playcanvas.png';
5
- import reactLogo from './assets/react.svg';
6
4
  import './App.css';
7
5
  import Scene from './Scene';
8
6
 
@@ -16,26 +14,7 @@ function App() {
16
14
  <Scene onClick={() => setCount((count) => count + 1)} />
17
15
  </Application>
18
16
  </div>
19
- <div className="absolute overlay">
20
- <div className="grow">
21
- <header>
22
- <h1>PlayCanvas + React</h1>
23
- <a href="https://developer.playcanvas.com/user-manual/react/" target="_blank" rel="noreferrer">
24
- <img src={playcanvasLogo} className="playcanvas-logo logo" alt="PlayCanvas logo" />
25
- </a>
26
- <a href="https://react.dev" target="_blank" rel="noreferrer">
27
- <img src={reactLogo} className="logo react" alt="React logo" />
28
- </a>
29
- </header>
30
- </div>
31
- <div>
32
- <span className="pill">Click Count: {count}</span>
33
- <p>
34
- Edit <code>src/Scene.tsx</code> and save to test HMR
35
- </p>
36
- </div>
37
- <p className="read-the-docs">Click on the PlayCanvas and React logos to learn more</p>
38
- </div>
17
+ <p className="counter">Click count: {count}</p>
39
18
  </>
40
19
  );
41
20
  }