create-playcanvas 0.3.0 → 0.4.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.
- package/README.md +37 -25
- package/dist/index.mjs +48 -46
- package/package.json +2 -1
- package/templates/_shared/model-viewer/public/playcanvas-cube.glb +0 -0
- package/templates/{vanilla-ts → engine/base}/README.md +3 -19
- package/templates/{vanilla-ts → engine/base}/src/vite-env.d.ts +6 -0
- package/templates/{vanilla-ts → engine/interactive-sphere}/src/main.ts +1 -1
- package/templates/{vanilla-ts → engine/interactive-sphere}/src/style.css +1 -1
- package/templates/{vanilla-ts → engine/interactive-sphere}/src/utils.ts +0 -1
- package/templates/engine/model-viewer/index.html +13 -0
- package/templates/engine/model-viewer/src/main.ts +81 -0
- package/templates/engine/model-viewer/src/style.css +8 -0
- package/templates/engine/spinning-cube/index.html +13 -0
- package/templates/engine/spinning-cube/src/main.ts +65 -0
- package/templates/engine/spinning-cube/src/style.css +8 -0
- package/templates/engine/splat-viewer/index.html +13 -0
- package/templates/engine/splat-viewer/src/main.ts +74 -0
- package/templates/engine/splat-viewer/src/style.css +8 -0
- package/templates/index.ts +37 -5
- package/templates/{react-ts → react/base}/README.md +2 -19
- package/templates/{react-ts → react/base}/src/vite-env.d.ts +6 -0
- package/templates/{react-ts → react/interactive-sphere}/src/Scene.tsx +1 -2
- package/templates/react/model-viewer/index.html +13 -0
- package/templates/react/model-viewer/src/App.tsx +43 -0
- package/templates/react/model-viewer/src/index.css +9 -0
- package/templates/react/model-viewer/src/main.tsx +11 -0
- package/templates/react/spinning-cube/index.html +13 -0
- package/templates/react/spinning-cube/src/App.tsx +36 -0
- package/templates/react/spinning-cube/src/index.css +9 -0
- package/templates/react/spinning-cube/src/main.tsx +11 -0
- package/templates/react/splat-viewer/index.html +13 -0
- package/templates/react/splat-viewer/src/App.tsx +39 -0
- package/templates/react/splat-viewer/src/index.css +9 -0
- package/templates/react/splat-viewer/src/main.tsx +11 -0
- package/templates/web-components/base/README.md +66 -0
- package/templates/web-components/base/_gitignore +24 -0
- package/templates/web-components/base/eslint.config.mjs +3 -0
- package/templates/web-components/base/package.json +31 -0
- package/templates/web-components/base/prettier.config.mjs +1 -0
- package/templates/web-components/base/src/assets/playcanvas.png +0 -0
- package/templates/web-components/base/src/vite-env.d.ts +19 -0
- package/templates/web-components/base/tsconfig.json +25 -0
- package/templates/web-components/interactive-sphere/index.html +53 -0
- package/templates/web-components/interactive-sphere/src/assets/typescript.svg +1 -0
- package/templates/web-components/interactive-sphere/src/main.ts +76 -0
- package/templates/web-components/interactive-sphere/src/style.css +111 -0
- package/templates/web-components/model-viewer/index.html +34 -0
- package/templates/web-components/model-viewer/src/main.ts +26 -0
- package/templates/web-components/model-viewer/src/style.css +8 -0
- package/templates/web-components/spinning-cube/index.html +29 -0
- package/templates/web-components/spinning-cube/src/main.ts +20 -0
- package/templates/web-components/spinning-cube/src/style.css +8 -0
- package/templates/web-components/splat-viewer/index.html +31 -0
- package/templates/web-components/splat-viewer/src/main.ts +14 -0
- package/templates/web-components/splat-viewer/src/style.css +8 -0
- package/templates/vanilla-ts/public/environment-map.png +0 -0
- /package/templates/{react-ts → _shared/interactive-sphere}/public/environment-map.png +0 -0
- /package/templates/{react-ts → engine/base}/_gitignore +0 -0
- /package/templates/{vanilla-ts → engine/base}/eslint.config.mjs +0 -0
- /package/templates/{vanilla-ts → engine/base}/package.json +0 -0
- /package/templates/{react-ts → engine/base}/prettier.config.mjs +0 -0
- /package/templates/{react-ts → engine/base}/src/assets/playcanvas.png +0 -0
- /package/templates/{vanilla-ts → engine/base}/tsconfig.json +0 -0
- /package/templates/{vanilla-ts → engine/interactive-sphere}/index.html +0 -0
- /package/templates/{vanilla-ts → engine/interactive-sphere}/src/App.ts +0 -0
- /package/templates/{vanilla-ts → engine/interactive-sphere}/src/assets/typescript.svg +0 -0
- /package/templates/{vanilla-ts → react/base}/_gitignore +0 -0
- /package/templates/{react-ts → react/base}/eslint.config.mjs +0 -0
- /package/templates/{react-ts → react/base}/package.json +0 -0
- /package/templates/{vanilla-ts → react/base}/prettier.config.mjs +0 -0
- /package/templates/{vanilla-ts → react/base}/src/assets/playcanvas.png +0 -0
- /package/templates/{react-ts → react/base}/tsconfig.json +0 -0
- /package/templates/{react-ts → react/base}/vite.config.ts +0 -0
- /package/templates/{react-ts → react/interactive-sphere}/index.html +0 -0
- /package/templates/{react-ts → react/interactive-sphere}/src/App.css +0 -0
- /package/templates/{react-ts → react/interactive-sphere}/src/App.tsx +0 -0
- /package/templates/{react-ts → react/interactive-sphere}/src/assets/react.svg +0 -0
- /package/templates/{react-ts → react/interactive-sphere}/src/index.css +0 -0
- /package/templates/{react-ts → react/interactive-sphere}/src/main.tsx +0 -0
|
@@ -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 + TS</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<canvas id="application-canvas"></canvas>
|
|
11
|
+
<script type="module" src="/src/main.ts"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppBase,
|
|
3
|
+
AppOptions,
|
|
4
|
+
CameraComponentSystem,
|
|
5
|
+
Color,
|
|
6
|
+
ContainerHandler,
|
|
7
|
+
Entity,
|
|
8
|
+
FILLMODE_FILL_WINDOW,
|
|
9
|
+
LightComponentSystem,
|
|
10
|
+
RESOLUTION_AUTO,
|
|
11
|
+
RenderComponentSystem,
|
|
12
|
+
TextureHandler,
|
|
13
|
+
createGraphicsDevice
|
|
14
|
+
} from 'playcanvas';
|
|
15
|
+
|
|
16
|
+
import './style.css';
|
|
17
|
+
|
|
18
|
+
const canvas = document.getElementById('application-canvas') as HTMLCanvasElement;
|
|
19
|
+
|
|
20
|
+
const device = await createGraphicsDevice(canvas);
|
|
21
|
+
device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
|
|
22
|
+
|
|
23
|
+
const createOptions = new AppOptions();
|
|
24
|
+
createOptions.graphicsDevice = device;
|
|
25
|
+
createOptions.componentSystems = [RenderComponentSystem, CameraComponentSystem, LightComponentSystem];
|
|
26
|
+
createOptions.resourceHandlers = [TextureHandler, ContainerHandler];
|
|
27
|
+
|
|
28
|
+
const app = new AppBase(canvas);
|
|
29
|
+
app.init(createOptions);
|
|
30
|
+
app.start();
|
|
31
|
+
|
|
32
|
+
// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
|
|
33
|
+
app.setCanvasFillMode(FILLMODE_FILL_WINDOW);
|
|
34
|
+
app.setCanvasResolution(RESOLUTION_AUTO);
|
|
35
|
+
|
|
36
|
+
// Ensure canvas is resized when window changes size
|
|
37
|
+
const resize = () => app.resizeCanvas();
|
|
38
|
+
window.addEventListener('resize', resize);
|
|
39
|
+
app.on('destroy', () => {
|
|
40
|
+
window.removeEventListener('resize', resize);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Create box entity
|
|
44
|
+
const box = new Entity('cube');
|
|
45
|
+
box.addComponent('render', {
|
|
46
|
+
type: 'box'
|
|
47
|
+
});
|
|
48
|
+
app.root.addChild(box);
|
|
49
|
+
|
|
50
|
+
// Create camera entity
|
|
51
|
+
const camera = new Entity('camera');
|
|
52
|
+
camera.addComponent('camera', {
|
|
53
|
+
clearColor: new Color(0.5, 0.6, 0.9)
|
|
54
|
+
});
|
|
55
|
+
camera.setPosition(0, 0, 3);
|
|
56
|
+
app.root.addChild(camera);
|
|
57
|
+
|
|
58
|
+
// Create directional light entity
|
|
59
|
+
const light = new Entity('light');
|
|
60
|
+
light.addComponent('light');
|
|
61
|
+
light.setEulerAngles(45, 0, 0);
|
|
62
|
+
app.root.addChild(light);
|
|
63
|
+
|
|
64
|
+
// Rotate the box according to the delta time since the last frame
|
|
65
|
+
app.on('update', (dt: number) => box.rotate(10 * dt, 20 * dt, 30 * dt));
|
|
@@ -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 + TS</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<canvas id="application-canvas"></canvas>
|
|
11
|
+
<script type="module" src="/src/main.ts"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppBase,
|
|
3
|
+
AppOptions,
|
|
4
|
+
Asset,
|
|
5
|
+
AssetListLoader,
|
|
6
|
+
CameraComponentSystem,
|
|
7
|
+
Entity,
|
|
8
|
+
FILLMODE_FILL_WINDOW,
|
|
9
|
+
GSplatComponentSystem,
|
|
10
|
+
GSplatHandler,
|
|
11
|
+
RESOLUTION_AUTO,
|
|
12
|
+
ScriptComponentSystem,
|
|
13
|
+
TextureHandler,
|
|
14
|
+
createGraphicsDevice
|
|
15
|
+
} from 'playcanvas';
|
|
16
|
+
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
|
|
17
|
+
|
|
18
|
+
import './style.css';
|
|
19
|
+
|
|
20
|
+
// Hosted so the template stays small. Capture or edit your own splat at https://superspl.at, then
|
|
21
|
+
// drop the .sog or .ply in public/ and point this at it instead
|
|
22
|
+
const SPLAT_URL = 'https://developer.playcanvas.com/assets/toy-cat.sog';
|
|
23
|
+
|
|
24
|
+
const canvas = document.getElementById('application-canvas') as HTMLCanvasElement;
|
|
25
|
+
|
|
26
|
+
// Antialiasing is disabled - splat rendering is fragment bound, so MSAA costs a lot and adds little
|
|
27
|
+
const device = await createGraphicsDevice(canvas, { antialias: false });
|
|
28
|
+
device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
|
|
29
|
+
|
|
30
|
+
const createOptions = new AppOptions();
|
|
31
|
+
createOptions.graphicsDevice = device;
|
|
32
|
+
createOptions.componentSystems = [CameraComponentSystem, GSplatComponentSystem, ScriptComponentSystem];
|
|
33
|
+
|
|
34
|
+
// TextureHandler is required as well as GSplatHandler - a .sog is a container whose splat data is
|
|
35
|
+
// stored as textures, and without it the splat loads but silently renders nothing
|
|
36
|
+
createOptions.resourceHandlers = [TextureHandler, GSplatHandler];
|
|
37
|
+
|
|
38
|
+
const app = new AppBase(canvas);
|
|
39
|
+
app.init(createOptions);
|
|
40
|
+
|
|
41
|
+
// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
|
|
42
|
+
app.setCanvasFillMode(FILLMODE_FILL_WINDOW);
|
|
43
|
+
app.setCanvasResolution(RESOLUTION_AUTO);
|
|
44
|
+
|
|
45
|
+
// Ensure canvas is resized when window changes size
|
|
46
|
+
const resize = () => app.resizeCanvas();
|
|
47
|
+
window.addEventListener('resize', resize);
|
|
48
|
+
app.on('destroy', () => {
|
|
49
|
+
window.removeEventListener('resize', resize);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Load the splat before starting, so the first frame already has something in it
|
|
53
|
+
const splat = new Asset('splat', 'gsplat', { url: SPLAT_URL });
|
|
54
|
+
|
|
55
|
+
await new Promise<void>((resolve) => {
|
|
56
|
+
new AssetListLoader([splat], app.assets).load(() => resolve());
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
app.start();
|
|
60
|
+
|
|
61
|
+
// Splats are captured in their own space, so this one needs offsetting and flipping upright
|
|
62
|
+
const toyCat = new Entity('toy cat');
|
|
63
|
+
toyCat.addComponent('gsplat', { asset: splat });
|
|
64
|
+
toyCat.setPosition(0, -0.7, 0);
|
|
65
|
+
toyCat.setEulerAngles(0, 0, 180);
|
|
66
|
+
app.root.addChild(toyCat);
|
|
67
|
+
|
|
68
|
+
// Create camera entity with orbit controls
|
|
69
|
+
const camera = new Entity('camera');
|
|
70
|
+
camera.addComponent('camera');
|
|
71
|
+
camera.setPosition(0, 0, 2.5);
|
|
72
|
+
camera.addComponent('script');
|
|
73
|
+
camera.script?.create(CameraControls);
|
|
74
|
+
app.root.addChild(camera);
|
package/templates/index.ts
CHANGED
|
@@ -1,18 +1,50 @@
|
|
|
1
1
|
import colors from 'picocolors';
|
|
2
2
|
|
|
3
|
-
const { cyan, yellow } = colors;
|
|
3
|
+
const { cyan, magenta, yellow } = colors;
|
|
4
4
|
|
|
5
5
|
export const TEMPLATES = [
|
|
6
6
|
{
|
|
7
|
-
name: '
|
|
8
|
-
display: '
|
|
9
|
-
description: 'with PlayCanvas Engine',
|
|
7
|
+
name: 'engine',
|
|
8
|
+
display: 'Engine',
|
|
9
|
+
description: 'with the PlayCanvas Engine API directly',
|
|
10
10
|
color: yellow
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
|
-
name: 'react
|
|
13
|
+
name: 'react',
|
|
14
14
|
display: 'React',
|
|
15
15
|
description: 'with @playcanvas/react',
|
|
16
16
|
color: cyan
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'web-components',
|
|
20
|
+
display: 'Web Components',
|
|
21
|
+
description: 'with @playcanvas/web-components',
|
|
22
|
+
color: magenta
|
|
23
|
+
}
|
|
24
|
+
];
|
|
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 = [
|
|
30
|
+
{
|
|
31
|
+
name: 'spinning-cube',
|
|
32
|
+
display: 'Spinning Cube',
|
|
33
|
+
description: 'a rotating cube and a light'
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'interactive-sphere',
|
|
37
|
+
display: 'Interactive Sphere',
|
|
38
|
+
description: 'orbit controls, a grid and pointer picking'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'model-viewer',
|
|
42
|
+
display: 'Model Viewer',
|
|
43
|
+
description: 'a glTF model, orbit controls and a procedural sky'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'splat-viewer',
|
|
47
|
+
display: 'Splat Viewer',
|
|
48
|
+
description: 'a Gaussian splat and orbit controls'
|
|
17
49
|
}
|
|
18
50
|
];
|
|
@@ -9,13 +9,13 @@ 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 -- --template react
|
|
13
13
|
cd playcanvas-project
|
|
14
14
|
npm install
|
|
15
15
|
npm run dev
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Open <http://localhost:5173>. Edit `src
|
|
18
|
+
Open <http://localhost:5173>. Edit the files under `src/` and save to see the scene update.
|
|
19
19
|
|
|
20
20
|
## Scripts
|
|
21
21
|
|
|
@@ -28,23 +28,6 @@ Open <http://localhost:5173>. Edit `src/Scene.tsx` to test hot module replacemen
|
|
|
28
28
|
| `npm run fmt` | Check formatting |
|
|
29
29
|
| `npm run typecheck` | Run TypeScript checks |
|
|
30
30
|
|
|
31
|
-
## Project structure
|
|
32
|
-
|
|
33
|
-
```text
|
|
34
|
-
.
|
|
35
|
-
├── public/
|
|
36
|
-
│ └── environment-map.png
|
|
37
|
-
├── src/
|
|
38
|
-
│ ├── assets/
|
|
39
|
-
│ ├── App.tsx
|
|
40
|
-
│ ├── Scene.tsx
|
|
41
|
-
│ └── main.tsx
|
|
42
|
-
├── eslint.config.mjs
|
|
43
|
-
├── index.html
|
|
44
|
-
├── tsconfig.json
|
|
45
|
-
└── vite.config.ts
|
|
46
|
-
```
|
|
47
|
-
|
|
48
31
|
Run `npm run build` to generate a deployable static site in `dist/`.
|
|
49
32
|
|
|
50
33
|
## Further reading
|
|
@@ -11,3 +11,9 @@ declare module 'playcanvas/scripts/esm/grid.mjs' {
|
|
|
11
11
|
|
|
12
12
|
export const Grid: typeof Script & (new () => Script);
|
|
13
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
|
+
}
|
|
@@ -9,7 +9,6 @@ import { useEffect, useState } from 'react';
|
|
|
9
9
|
* The Scene renders a sphere with a grid and camera controls
|
|
10
10
|
*/
|
|
11
11
|
function Scene({ onClick }: SceneProps) {
|
|
12
|
-
// Track the hover state and set the color based on the hover state
|
|
13
12
|
const [hovering, setHovering] = useState(false);
|
|
14
13
|
|
|
15
14
|
// Set a material color based on the hover state
|
|
@@ -21,7 +20,7 @@ function Scene({ onClick }: SceneProps) {
|
|
|
21
20
|
// Load the environment map
|
|
22
21
|
const { asset: envMap } = useEnvAtlas('/environment-map.png');
|
|
23
22
|
|
|
24
|
-
//
|
|
23
|
+
// Change the mouse cursor based on the hover state
|
|
25
24
|
useEffect(() => {
|
|
26
25
|
document.body.style.cursor = hovering ? 'pointer' : 'default';
|
|
27
26
|
|
|
@@ -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,43 @@
|
|
|
1
|
+
import { Application, Container, Entity } from '@playcanvas/react';
|
|
2
|
+
import { Camera, Script } from '@playcanvas/react/components';
|
|
3
|
+
import { useModel } from '@playcanvas/react/hooks';
|
|
4
|
+
import { TONEMAP_ACES2 } from 'playcanvas';
|
|
5
|
+
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
|
|
6
|
+
import { ProceduralSky } from 'playcanvas/scripts/esm/sky/procedural-sky.mjs';
|
|
7
|
+
|
|
8
|
+
// Swap this for your own model - anything you drop in public/ is served from the site root
|
|
9
|
+
const MODEL_URL = '/playcanvas-cube.glb';
|
|
10
|
+
|
|
11
|
+
function Model() {
|
|
12
|
+
const { asset } = useModel(MODEL_URL);
|
|
13
|
+
|
|
14
|
+
// Don't render until the model is loaded
|
|
15
|
+
if (!asset) return null;
|
|
16
|
+
|
|
17
|
+
return <Container asset={asset} />;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function App() {
|
|
21
|
+
return (
|
|
22
|
+
<Application>
|
|
23
|
+
{/* The procedural sky is both the background and the image-based lighting, so no
|
|
24
|
+
light or environment map asset is needed. Its default luminance is scene-scale
|
|
25
|
+
bright, so turn it down to keep the model out of clipping */}
|
|
26
|
+
<Entity name="sky">
|
|
27
|
+
<Script script={ProceduralSky} luminance={0.2} />
|
|
28
|
+
</Entity>
|
|
29
|
+
|
|
30
|
+
{/* Create a camera entity with orbit controls. The sky is HDR, so it needs tone
|
|
31
|
+
mapping to bring it back into displayable range - without it the whole image blows
|
|
32
|
+
out to white */}
|
|
33
|
+
<Entity name="camera" position={[0, 0, 3]}>
|
|
34
|
+
<Camera toneMapping={TONEMAP_ACES2} />
|
|
35
|
+
<Script script={CameraControls} />
|
|
36
|
+
</Entity>
|
|
37
|
+
|
|
38
|
+
<Model />
|
|
39
|
+
</Application>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default App;
|
|
@@ -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,36 @@
|
|
|
1
|
+
import { Application, Entity } from '@playcanvas/react';
|
|
2
|
+
import { Camera, Light, Render } from '@playcanvas/react/components';
|
|
3
|
+
import { useAppEvent } from '@playcanvas/react/hooks';
|
|
4
|
+
import type { Entity as PcEntity } from 'playcanvas';
|
|
5
|
+
import { useRef } from 'react';
|
|
6
|
+
|
|
7
|
+
function Scene() {
|
|
8
|
+
const cube = useRef<PcEntity>(null);
|
|
9
|
+
|
|
10
|
+
// Rotate the cube according to the delta time since the last frame
|
|
11
|
+
useAppEvent('update', (dt: number) => cube.current?.rotate(10 * dt, 20 * dt, 30 * dt));
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<>
|
|
15
|
+
<Entity name="camera" position={[0, 0, 3]}>
|
|
16
|
+
<Camera clearColor="#8099e6" />
|
|
17
|
+
</Entity>
|
|
18
|
+
<Entity name="light" rotation={[45, 0, 0]}>
|
|
19
|
+
<Light type="directional" />
|
|
20
|
+
</Entity>
|
|
21
|
+
<Entity name="cube" ref={cube}>
|
|
22
|
+
<Render type="box" />
|
|
23
|
+
</Entity>
|
|
24
|
+
</>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function App() {
|
|
29
|
+
return (
|
|
30
|
+
<Application>
|
|
31
|
+
<Scene />
|
|
32
|
+
</Application>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default App;
|
|
@@ -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,66 @@
|
|
|
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
|
+
## Further reading
|
|
62
|
+
|
|
63
|
+
- [PlayCanvas Web Components manual](https://developer.playcanvas.com/user-manual/web-components/)
|
|
64
|
+
- [Web Components examples](https://playcanvas.github.io/web-components/examples)
|
|
65
|
+
- [Vite documentation](https://vite.dev/)
|
|
66
|
+
- [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?
|