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.
- package/README.md +20 -27
- package/dist/index.mjs +44 -42
- package/package.json +1 -1
- package/skills/calibrate-model/references/direct-engine.md +7 -4
- package/skills/calibrate-model/references/react.md +8 -3
- package/skills/calibrate-model/references/web-components.md +7 -4
- package/skills/configure-animation/references/web-components.md +18 -9
- package/skills/inspect-glb/SKILL.md +27 -18
- package/skills/inspect-glb/scripts/inspect.mjs +179 -50
- package/templates/_features/physics/assets/public/ammo/LICENSE +21 -0
- package/templates/_features/physics/assets/public/ammo/SOURCE.md +16 -0
- package/templates/_features/physics/assets/public/ammo/ammo.js +973 -0
- package/templates/_features/physics/assets/public/ammo/ammo.wasm.js +937 -0
- package/templates/_features/physics/assets/public/ammo/ammo.wasm.wasm +0 -0
- package/templates/_features/physics/react/package.json +5 -0
- package/templates/_features/physics/react/src/sync-ammo.d.ts +4 -0
- package/templates/_shared/ar-placement/README.md +7 -0
- package/templates/_shared/ar-placement/src/ar-placement.ts +123 -0
- package/templates/_shared/first-person-controller/README.md +7 -0
- package/templates/_shared/first-person-controller/src/controller.ts +18 -0
- package/templates/_shared/first-person-controller/src/scripts.d.ts +5 -0
- package/templates/_shared/interactive-sphere/README.md +7 -0
- package/templates/_shared/model-viewer/README.md +7 -0
- package/templates/_shared/physics-playground/README.md +7 -0
- package/templates/_shared/product-configurator/ASSET_LICENSES.md +3 -0
- package/templates/_shared/product-configurator/README.md +7 -0
- package/templates/_shared/product-configurator/src/product.css +14 -0
- package/templates/_shared/scene-editor/README.md +7 -0
- package/templates/_shared/scene-editor/src/scene-editor.ts +57 -0
- package/templates/_shared/spinning-cube/README.md +7 -0
- package/templates/_shared/splat-viewer/README.md +7 -0
- package/templates/_shared/sprite-game/README.md +7 -0
- package/templates/_shared/sprite-game/src/sprite-game.ts +197 -0
- package/templates/_shared/third-person-controller/README.md +7 -0
- package/templates/_shared/third-person-controller/src/controller.ts +53 -0
- package/templates/_shared/third-person-controller/src/scripts.d.ts +5 -0
- package/templates/_shared/vr-starter/README.md +7 -0
- package/templates/_shared/vr-starter/src/vr.ts +40 -0
- package/templates/_shared/vr-starter/src/xr.d.ts +11 -0
- package/templates/engine/ar-placement/src/main.ts +77 -0
- package/templates/engine/base/.prettierignore +1 -0
- package/templates/engine/base/README.md +1 -1
- package/templates/engine/base/_npmrc +1 -0
- package/templates/engine/base/eslint.config.mjs +1 -1
- package/templates/engine/base/index.html +13 -0
- package/templates/engine/base/src/starter.css +104 -0
- package/templates/engine/first-person-controller/src/main.ts +139 -0
- package/templates/engine/interactive-sphere/src/App.ts +9 -7
- package/templates/engine/interactive-sphere/src/main.ts +2 -29
- package/templates/engine/interactive-sphere/src/style.css +18 -97
- package/templates/engine/model-viewer/src/main.ts +3 -2
- package/templates/engine/physics-playground/src/main.ts +132 -0
- package/templates/engine/product-configurator/src/main.ts +156 -0
- package/templates/engine/scene-editor/src/main.ts +90 -0
- package/templates/engine/splat-viewer/src/main.ts +4 -3
- package/templates/engine/sprite-game/src/main.ts +72 -0
- package/templates/engine/third-person-controller/src/main.ts +216 -0
- package/templates/engine/vr-starter/src/main.ts +101 -0
- package/templates/index.ts +66 -5
- package/templates/react/ar-placement/src/App.tsx +58 -0
- package/templates/react/base/.prettierignore +1 -0
- package/templates/react/base/README.md +1 -1
- package/templates/react/base/_npmrc +1 -0
- package/templates/react/base/eslint.config.mjs +1 -1
- package/templates/react/base/index.html +13 -0
- package/templates/react/base/src/main.tsx +10 -0
- package/templates/react/base/src/starter.css +104 -0
- package/templates/react/first-person-controller/src/App.tsx +115 -0
- package/templates/react/interactive-sphere/src/App.css +18 -68
- package/templates/react/interactive-sphere/src/App.tsx +1 -22
- package/templates/react/interactive-sphere/src/Scene.tsx +7 -6
- package/templates/react/interactive-sphere/src/index.css +1 -48
- package/templates/react/model-viewer/src/App.tsx +2 -2
- package/templates/react/physics-playground/src/App.tsx +85 -0
- package/templates/react/product-configurator/src/App.tsx +157 -0
- package/templates/react/scene-editor/src/App.tsx +84 -0
- package/templates/react/splat-viewer/src/App.tsx +3 -3
- package/templates/react/sprite-game/src/App.tsx +38 -0
- package/templates/react/third-person-controller/src/App.tsx +193 -0
- package/templates/react/vr-starter/src/App.tsx +99 -0
- package/templates/web-components/ar-placement/index.html +44 -0
- package/templates/web-components/ar-placement/src/main.ts +13 -0
- package/templates/web-components/base/.prettierignore +1 -0
- package/templates/web-components/base/README.md +1 -1
- package/templates/web-components/base/_npmrc +1 -0
- package/templates/web-components/base/eslint.config.mjs +1 -1
- package/templates/web-components/base/package.json +1 -1
- package/templates/web-components/base/src/starter.css +104 -0
- package/templates/web-components/first-person-controller/index.html +168 -0
- package/templates/web-components/first-person-controller/src/main.ts +15 -0
- package/templates/web-components/interactive-sphere/index.html +5 -22
- package/templates/web-components/interactive-sphere/src/main.ts +5 -4
- package/templates/web-components/interactive-sphere/src/style.css +18 -76
- package/templates/web-components/model-viewer/index.html +1 -1
- package/templates/web-components/model-viewer/src/main.ts +2 -1
- package/templates/web-components/physics-playground/index.html +51 -0
- package/templates/web-components/physics-playground/src/main.ts +32 -0
- package/templates/web-components/product-configurator/index.html +51 -0
- package/templates/web-components/product-configurator/src/main.ts +93 -0
- package/templates/web-components/scene-editor/index.html +51 -0
- package/templates/web-components/scene-editor/src/main.ts +26 -0
- package/templates/web-components/spinning-cube/src/main.ts +1 -0
- package/templates/web-components/splat-viewer/index.html +2 -2
- package/templates/web-components/splat-viewer/src/main.ts +2 -1
- package/templates/web-components/sprite-game/index.html +34 -0
- package/templates/web-components/sprite-game/src/main.ts +9 -0
- package/templates/web-components/third-person-controller/index.html +183 -0
- package/templates/web-components/third-person-controller/src/main.ts +16 -0
- package/templates/web-components/vr-starter/index.html +80 -0
- package/templates/web-components/vr-starter/src/main.ts +12 -0
- package/templates/engine/interactive-sphere/src/assets/typescript.svg +0 -1
- package/templates/react/interactive-sphere/src/assets/react.svg +0 -1
- 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());
|
package/templates/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import colors from 'picocolors';
|
|
|
2
2
|
|
|
3
3
|
const { cyan, magenta, yellow } = colors;
|
|
4
4
|
|
|
5
|
-
export const
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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;
|
|
@@ -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,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
|
-
.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
border-radius:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
<
|
|
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
|
}
|