create-playcanvas 0.2.6 → 0.3.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 +39 -47
- package/dist/index.mjs +48 -47
- package/package.json +53 -49
- package/templates/index.ts +15 -57
- package/templates/react-ts/README.md +32 -59
- package/templates/react-ts/eslint.config.mjs +14 -0
- package/templates/react-ts/index.html +10 -10
- package/templates/react-ts/package.json +34 -27
- package/templates/react-ts/prettier.config.mjs +1 -0
- package/templates/react-ts/src/App.css +37 -49
- package/templates/react-ts/src/App.tsx +38 -41
- package/templates/react-ts/src/Scene.tsx +50 -50
- package/templates/react-ts/src/index.css +30 -52
- package/templates/react-ts/src/main.tsx +9 -8
- package/templates/react-ts/src/vite-env.d.ts +12 -0
- package/templates/react-ts/tsconfig.json +24 -5
- package/templates/react-ts/vite.config.ts +4 -4
- package/templates/vanilla-ts/README.md +31 -91
- package/templates/vanilla-ts/eslint.config.mjs +3 -0
- package/templates/vanilla-ts/index.html +10 -10
- package/templates/vanilla-ts/package.json +28 -21
- package/templates/vanilla-ts/prettier.config.mjs +1 -0
- package/templates/vanilla-ts/src/App.ts +149 -151
- package/templates/vanilla-ts/src/main.ts +7 -7
- package/templates/vanilla-ts/src/style.css +60 -93
- package/templates/vanilla-ts/src/utils.ts +9 -8
- package/templates/vanilla-ts/src/vite-env.d.ts +12 -0
- package/templates/vanilla-ts/tsconfig.json +22 -22
- package/index.js +0 -3
- package/templates/react-ts/scripts/ensure-node-version.cjs +0 -12
- package/templates/react-ts/tsconfig.app.json +0 -27
- package/templates/react-ts/tsconfig.node.json +0 -25
- package/templates/vanilla-ts/scripts/ensure-node-version.cjs +0 -12
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { StrictMode } from 'react'
|
|
2
|
-
import { createRoot } from 'react-dom/client'
|
|
3
|
-
|
|
4
|
-
import
|
|
1
|
+
import { StrictMode } from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
|
|
4
|
+
import './index.css';
|
|
5
|
+
import App from './App.tsx';
|
|
5
6
|
|
|
6
7
|
createRoot(document.getElementById('root')!).render(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
)
|
|
8
|
+
<StrictMode>
|
|
9
|
+
<App />
|
|
10
|
+
</StrictMode>
|
|
11
|
+
);
|
|
@@ -1 +1,13 @@
|
|
|
1
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
|
+
}
|
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
/* Bundler mode */
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"verbatimModuleSyntax": true,
|
|
13
|
+
"moduleDetection": "force",
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
|
+
|
|
17
|
+
/* Linting */
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"erasableSyntaxOnly": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true,
|
|
23
|
+
"noUncheckedSideEffectImports": true
|
|
24
|
+
},
|
|
25
|
+
"include": ["src", "vite.config.ts"]
|
|
7
26
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import react from '@vitejs/plugin-react';
|
|
2
|
+
import { defineConfig } from 'vite';
|
|
3
3
|
|
|
4
4
|
// https://vite.dev/config/
|
|
5
5
|
export default defineConfig({
|
|
6
|
-
|
|
7
|
-
})
|
|
6
|
+
plugins: [react()]
|
|
7
|
+
});
|
|
@@ -1,114 +1,54 @@
|
|
|
1
|
-
# PlayCanvas +
|
|
1
|
+
# PlayCanvas + TypeScript Starter
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
* **PlayCanvas** `^2.8.2` – high-performance WebGL engine
|
|
6
|
-
* **Vite** `^7` – ultra-fast dev server & bundler with HMR
|
|
7
|
-
* **TypeScript** `~5.8` – static typing out-of-the-box
|
|
8
|
-
* **ESLint** `^9` – linting with recommended TypeScript rules
|
|
9
|
-
|
|
10
|
-
---
|
|
3
|
+
A Vite-powered PlayCanvas Engine project with TypeScript, hot module replacement, ESLint and Prettier.
|
|
11
4
|
|
|
12
5
|
## Prerequisites
|
|
13
6
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
nvm install 22 # or 20.19+
|
|
18
|
-
nvm use 22
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
---
|
|
7
|
+
Node.js 22.23.2 or later.
|
|
22
8
|
|
|
23
|
-
## Getting
|
|
24
|
-
|
|
25
|
-
Clone the repo and install dependencies:
|
|
9
|
+
## Getting started
|
|
26
10
|
|
|
27
11
|
```bash
|
|
28
|
-
|
|
12
|
+
npm create playcanvas@latest playcanvas-project -- --template vanilla-ts
|
|
29
13
|
cd playcanvas-project
|
|
30
14
|
npm install
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Start the dev server with hot-module reload:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
15
|
npm run dev
|
|
37
16
|
```
|
|
38
17
|
|
|
39
|
-
Open <http://localhost:5173
|
|
40
|
-
|
|
41
|
-
---
|
|
18
|
+
Open <http://localhost:5173>. Edit `src/App.ts` to test hot module replacement.
|
|
42
19
|
|
|
43
20
|
## Scripts
|
|
44
21
|
|
|
45
|
-
| Command
|
|
46
|
-
|
|
47
|
-
| `npm run dev`
|
|
48
|
-
| `npm run build`
|
|
49
|
-
| `npm run
|
|
50
|
-
| `npm run lint`
|
|
22
|
+
| Command | Description |
|
|
23
|
+
| ------------------- | --------------------------------- |
|
|
24
|
+
| `npm run dev` | Start the Vite development server |
|
|
25
|
+
| `npm run build` | Build for production |
|
|
26
|
+
| `npm run start` | Preview the production build |
|
|
27
|
+
| `npm run lint` | Run ESLint |
|
|
28
|
+
| `npm run fmt` | Check formatting |
|
|
29
|
+
| `npm run typecheck` | Run TypeScript checks |
|
|
51
30
|
|
|
52
|
-
|
|
31
|
+
## Project structure
|
|
53
32
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
```
|
|
33
|
+
```text
|
|
57
34
|
.
|
|
58
|
-
├── public/
|
|
35
|
+
├── public/
|
|
36
|
+
│ └── environment-map.png
|
|
59
37
|
├── src/
|
|
60
|
-
│ ├──
|
|
61
|
-
│
|
|
62
|
-
│ ├──
|
|
63
|
-
│ └──
|
|
64
|
-
├──
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
### `PlayCanvasCanvas.tsx` (concept)
|
|
69
|
-
|
|
70
|
-
```tsx
|
|
71
|
-
import { useEffect, useRef } from 'react'
|
|
72
|
-
import * as pc from 'playcanvas'
|
|
73
|
-
|
|
74
|
-
export default function PlayCanvasCanvas() {
|
|
75
|
-
const canvasRef = useRef<HTMLCanvasElement>(null)
|
|
76
|
-
|
|
77
|
-
useEffect(() => {
|
|
78
|
-
const canvas = canvasRef.current!
|
|
79
|
-
const app = new pc.Application(canvas, {})
|
|
80
|
-
app.start()
|
|
81
|
-
// demo scene setup here…
|
|
82
|
-
|
|
83
|
-
return () => app.destroy() // cleanup on unmount
|
|
84
|
-
}, [])
|
|
85
|
-
|
|
86
|
-
return <canvas ref={canvasRef} style={{ width: '100%', height: '100%' }} />
|
|
87
|
-
}
|
|
38
|
+
│ ├── assets/
|
|
39
|
+
│ ├── App.ts
|
|
40
|
+
│ ├── main.ts
|
|
41
|
+
│ └── style.css
|
|
42
|
+
├── eslint.config.mjs
|
|
43
|
+
├── index.html
|
|
44
|
+
└── tsconfig.json
|
|
88
45
|
```
|
|
89
46
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## Linting & Formatting
|
|
95
|
-
|
|
96
|
-
ESLint is pre-configured with TypeScript, React hooks, React Refresh, and global browser/node globals. Adjust `eslint.config.js` as your codebase grows.
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## Deployment
|
|
101
|
-
|
|
102
|
-
Run `npm run build` to generate a static production bundle in `dist/`. Deploy the contents of that folder to any static hosting provider (Netlify, Vercel, GitHub Pages, Cloudflare Pages, etc.).
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
## Further Reading
|
|
107
|
-
|
|
108
|
-
* [PlayCanvas Engine Docs](https://developer.playcanvas.com/en/api/)
|
|
109
|
-
* [Vite Docs](https://vitejs.dev/)
|
|
110
|
-
* [TypeScript Handbook](https://www.typescriptlang.org/docs/)
|
|
47
|
+
Run `npm run build` to generate a deployable static site in `dist/`.
|
|
111
48
|
|
|
112
|
-
|
|
49
|
+
## Further reading
|
|
113
50
|
|
|
114
|
-
|
|
51
|
+
- [PlayCanvas Engine manual](https://developer.playcanvas.com/user-manual/engine/)
|
|
52
|
+
- [PlayCanvas examples](https://playcanvas.github.io/)
|
|
53
|
+
- [Vite documentation](https://vite.dev/)
|
|
54
|
+
- [TypeScript documentation](https://www.typescriptlang.org/docs/)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.ts"></script>
|
|
12
|
+
</body>
|
|
13
13
|
</html>
|
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
2
|
+
"name": "playcanvas-project",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "A PlayCanvas TypeScript 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": "^2.21.3"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@playcanvas/eslint-config": "3.0.0-beta.8",
|
|
22
|
+
"eslint": "^9.39.5",
|
|
23
|
+
"prettier": "^3.9.6",
|
|
24
|
+
"typescript": "~6.0.3",
|
|
25
|
+
"vite": "^8.2.0"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=22.23.2"
|
|
29
|
+
}
|
|
23
30
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@playcanvas/eslint-config/prettier';
|
|
@@ -1,35 +1,30 @@
|
|
|
1
|
+
import type { Layer, Texture } from 'playcanvas';
|
|
1
2
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
AssetListLoader,
|
|
25
|
-
TEXTURETYPE_RGBP
|
|
3
|
+
createGraphicsDevice,
|
|
4
|
+
AppBase,
|
|
5
|
+
AppOptions,
|
|
6
|
+
RenderComponentSystem,
|
|
7
|
+
CameraComponentSystem,
|
|
8
|
+
ScriptComponentSystem,
|
|
9
|
+
TextureHandler,
|
|
10
|
+
ContainerHandler,
|
|
11
|
+
FILLMODE_FILL_WINDOW,
|
|
12
|
+
RESOLUTION_AUTO,
|
|
13
|
+
Entity,
|
|
14
|
+
Color,
|
|
15
|
+
Vec3,
|
|
16
|
+
Picker,
|
|
17
|
+
EVENT_MOUSEMOVE,
|
|
18
|
+
EVENT_MOUSEUP,
|
|
19
|
+
TouchDevice,
|
|
20
|
+
Mouse,
|
|
21
|
+
StandardMaterial,
|
|
22
|
+
Asset,
|
|
23
|
+
AssetListLoader,
|
|
24
|
+
TEXTURETYPE_RGBP
|
|
26
25
|
} from 'playcanvas';
|
|
27
|
-
|
|
28
|
-
// @ts-expect-error - PlayCanvas ESM scripts don't have type declarations
|
|
29
|
-
import { Grid } from 'playcanvas/scripts/esm/grid.mjs';
|
|
30
|
-
|
|
31
|
-
// @ts-expect-error - PlayCanvas ESM scripts don't have type declarations
|
|
32
26
|
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
|
|
27
|
+
import { Grid } from 'playcanvas/scripts/esm/grid.mjs';
|
|
33
28
|
|
|
34
29
|
import { throttle } from './utils';
|
|
35
30
|
|
|
@@ -38,10 +33,15 @@ const DEFAULT_COLOR = new Color(0.827, 0.827, 0.827);
|
|
|
38
33
|
|
|
39
34
|
// Assets to load
|
|
40
35
|
const assets = {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
envAtlas: new Asset(
|
|
37
|
+
'env-atlas',
|
|
38
|
+
'texture',
|
|
39
|
+
{ url: '/environment-map.png' },
|
|
40
|
+
{
|
|
41
|
+
type: TEXTURETYPE_RGBP,
|
|
42
|
+
mipmaps: false
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
/**
|
|
@@ -49,134 +49,132 @@ const assets = {
|
|
|
49
49
|
* @param canvas - The canvas element
|
|
50
50
|
* @param onClick - The function to call when the user clicks on the sphere
|
|
51
51
|
*/
|
|
52
|
-
async
|
|
52
|
+
const setupApp = async (canvas: HTMLCanvasElement, onClick: () => void) => {
|
|
53
|
+
if (!canvas) {
|
|
54
|
+
throw new Error('Canvas not found');
|
|
55
|
+
}
|
|
53
56
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
+
// Create graphics device
|
|
58
|
+
const device = await createGraphicsDevice(canvas);
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
// Create app options
|
|
61
|
+
const createOptions = new AppOptions();
|
|
62
|
+
createOptions.graphicsDevice = device;
|
|
63
|
+
createOptions.mouse = new Mouse(document.body);
|
|
64
|
+
createOptions.touch = new TouchDevice(document.body);
|
|
65
|
+
createOptions.componentSystems = [RenderComponentSystem, CameraComponentSystem, ScriptComponentSystem];
|
|
66
|
+
createOptions.resourceHandlers = [TextureHandler, ContainerHandler];
|
|
60
67
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
createOptions.mouse = new Mouse(document.body);
|
|
65
|
-
createOptions.touch = new TouchDevice(document.body);
|
|
66
|
-
createOptions.componentSystems = [
|
|
67
|
-
RenderComponentSystem,
|
|
68
|
-
CameraComponentSystem,
|
|
69
|
-
ScriptComponentSystem
|
|
70
|
-
];
|
|
71
|
-
createOptions.resourceHandlers = [TextureHandler, ContainerHandler];
|
|
72
|
-
|
|
73
|
-
// Create app
|
|
74
|
-
const app = new AppBase(canvas);
|
|
75
|
-
app.init(createOptions);
|
|
76
|
-
|
|
77
|
-
// Set the canvas to fill the window
|
|
78
|
-
app.setCanvasFillMode(FILLMODE_FILL_WINDOW);
|
|
79
|
-
app.setCanvasResolution(RESOLUTION_AUTO);
|
|
80
|
-
|
|
81
|
-
// Ensure canvas is resized when window changes size
|
|
82
|
-
const resize = () => app.resizeCanvas();
|
|
83
|
-
window.addEventListener('resize', resize);
|
|
84
|
-
|
|
85
|
-
app.once('destroy', () => {
|
|
86
|
-
window.removeEventListener('resize', resize);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// Load assets
|
|
90
|
-
await new Promise<void>((resolve) => {
|
|
91
|
-
new AssetListLoader(Object.values(assets), app.assets).load(() => resolve());
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
app.start();
|
|
95
|
-
|
|
96
|
-
// Set up environment lighting (no skybox, just IBL)
|
|
97
|
-
app.scene.envAtlas = assets.envAtlas.resource;
|
|
98
|
-
const skyboxLayer = app.scene.layers.getLayerByName('Skybox');
|
|
99
|
-
if (skyboxLayer) {
|
|
100
|
-
skyboxLayer.enabled = false;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Create sphere entity
|
|
104
|
-
const sphere = new Entity('sphere');
|
|
105
|
-
sphere.setPosition(new Vec3(0, 0.5, 0));
|
|
106
|
-
|
|
107
|
-
// Create a new material
|
|
108
|
-
const material = new StandardMaterial();
|
|
109
|
-
material.diffuse.copy(DEFAULT_COLOR);
|
|
110
|
-
material.update();
|
|
111
|
-
|
|
112
|
-
sphere.addComponent('render', {
|
|
113
|
-
type: 'sphere',
|
|
114
|
-
material: material
|
|
115
|
-
});
|
|
116
|
-
app.root.addChild(sphere);
|
|
117
|
-
|
|
118
|
-
// Create camera entity
|
|
119
|
-
const camera = new Entity('camera');
|
|
120
|
-
camera.addComponent('camera', {
|
|
121
|
-
clearColor: new Color(0.09, 0.09, 0.09)
|
|
122
|
-
});
|
|
123
|
-
camera.setPosition(new Vec3(4, 1, 4));
|
|
124
|
-
app.root.addChild(camera);
|
|
125
|
-
|
|
126
|
-
// Create camera controls
|
|
127
|
-
camera.addComponent('script');
|
|
128
|
-
camera.script?.create(CameraControls);
|
|
129
|
-
|
|
130
|
-
// Create grid entity
|
|
131
|
-
const grid = new Entity('grid');
|
|
132
|
-
grid.addComponent('script');
|
|
133
|
-
grid.script?.create(Grid);
|
|
134
|
-
grid.setLocalScale(1000, 1000, 1000);
|
|
135
|
-
app.root.addChild(grid);
|
|
136
|
-
|
|
137
|
-
// Create a picker for mouse interaction
|
|
138
|
-
const picker = new Picker(app, 1, 1);
|
|
139
|
-
const worldLayer = app.scene.layers.getLayerByName('World');
|
|
140
|
-
|
|
141
|
-
const intersectsSphere = (x: number, y: number, layer: Layer): Promise<boolean> => {
|
|
142
|
-
if (!camera.camera) {
|
|
143
|
-
return Promise.resolve(false);
|
|
144
|
-
}
|
|
68
|
+
// Create app
|
|
69
|
+
const app = new AppBase(canvas);
|
|
70
|
+
app.init(createOptions);
|
|
145
71
|
|
|
146
|
-
|
|
147
|
-
|
|
72
|
+
// Set the canvas to fill the window
|
|
73
|
+
app.setCanvasFillMode(FILLMODE_FILL_WINDOW);
|
|
74
|
+
app.setCanvasResolution(RESOLUTION_AUTO);
|
|
148
75
|
|
|
149
|
-
|
|
150
|
-
|
|
76
|
+
// Ensure canvas is resized when window changes size
|
|
77
|
+
const resize = () => app.resizeCanvas();
|
|
78
|
+
window.addEventListener('resize', resize);
|
|
79
|
+
|
|
80
|
+
app.once('destroy', () => {
|
|
81
|
+
window.removeEventListener('resize', resize);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Load assets
|
|
85
|
+
await new Promise<void>((resolve) => {
|
|
86
|
+
new AssetListLoader(Object.values(assets), app.assets).load(() => resolve());
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
app.start();
|
|
90
|
+
|
|
91
|
+
// Set up environment lighting (no skybox, just IBL)
|
|
92
|
+
app.scene.envAtlas = assets.envAtlas.resource as Texture;
|
|
93
|
+
const skyboxLayer = app.scene.layers.getLayerByName('Skybox');
|
|
94
|
+
if (skyboxLayer) {
|
|
95
|
+
skyboxLayer.enabled = false;
|
|
151
96
|
}
|
|
152
97
|
|
|
153
|
-
|
|
98
|
+
// Create sphere entity
|
|
99
|
+
const sphere = new Entity('sphere');
|
|
100
|
+
sphere.setPosition(new Vec3(0, 0.5, 0));
|
|
101
|
+
|
|
102
|
+
// Create a new material
|
|
103
|
+
const material = new StandardMaterial();
|
|
104
|
+
material.diffuse.copy(DEFAULT_COLOR);
|
|
105
|
+
material.update();
|
|
154
106
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
107
|
+
sphere.addComponent('render', {
|
|
108
|
+
type: 'sphere',
|
|
109
|
+
material: material
|
|
158
110
|
});
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
material.diffuse.copy(intersects ? HOVER_COLOR : DEFAULT_COLOR);
|
|
166
|
-
document.body.style.cursor = intersects ? 'pointer' : 'default';
|
|
167
|
-
material.update();
|
|
111
|
+
app.root.addChild(sphere);
|
|
112
|
+
|
|
113
|
+
// Create camera entity
|
|
114
|
+
const camera = new Entity('camera');
|
|
115
|
+
camera.addComponent('camera', {
|
|
116
|
+
clearColor: new Color(0.09, 0.09, 0.09)
|
|
168
117
|
});
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
118
|
+
camera.setPosition(new Vec3(4, 1, 4));
|
|
119
|
+
app.root.addChild(camera);
|
|
120
|
+
|
|
121
|
+
// Create camera controls
|
|
122
|
+
camera.addComponent('script');
|
|
123
|
+
camera.script?.create(CameraControls);
|
|
124
|
+
|
|
125
|
+
// Create grid entity
|
|
126
|
+
const grid = new Entity('grid');
|
|
127
|
+
grid.addComponent('script');
|
|
128
|
+
grid.script?.create(Grid);
|
|
129
|
+
grid.setLocalScale(1000, 1000, 1000);
|
|
130
|
+
app.root.addChild(grid);
|
|
131
|
+
|
|
132
|
+
// Create a picker for mouse interaction
|
|
133
|
+
const picker = new Picker(app, 1, 1);
|
|
134
|
+
const worldLayer = app.scene.layers.getLayerByName('World');
|
|
135
|
+
|
|
136
|
+
const intersectsSphere = (x: number, y: number, layer: Layer) => {
|
|
137
|
+
if (!camera.camera) {
|
|
138
|
+
return Promise.resolve(false);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const pickerScale = 0.5;
|
|
142
|
+
picker.resize(canvas.clientWidth * pickerScale, canvas.clientHeight * pickerScale);
|
|
143
|
+
|
|
144
|
+
if (!layer) {
|
|
145
|
+
return Promise.resolve(false);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
picker.prepare(camera.camera, app.scene, [layer]);
|
|
149
|
+
|
|
150
|
+
return picker.getSelectionAsync(x * pickerScale, y * pickerScale, 1, 1).then((items) => {
|
|
151
|
+
if (items.length === 0) return false;
|
|
152
|
+
return items[0] === sphere.render?.meshInstances[0];
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// On mouse move, check if hovering over sphere and update cursor/color
|
|
157
|
+
app.mouse?.on(
|
|
158
|
+
EVENT_MOUSEMOVE,
|
|
159
|
+
throttle((event) => {
|
|
160
|
+
if (!worldLayer) return;
|
|
161
|
+
intersectsSphere(event.x, event.y, worldLayer).then((intersects) => {
|
|
162
|
+
material.diffuse.copy(intersects ? HOVER_COLOR : DEFAULT_COLOR);
|
|
163
|
+
document.body.style.cursor = intersects ? 'pointer' : 'default';
|
|
164
|
+
material.update();
|
|
165
|
+
});
|
|
166
|
+
}, 100)
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// On mouse up, check if clicked on sphere and call onClick
|
|
170
|
+
app.mouse?.on(EVENT_MOUSEUP, (event) => {
|
|
171
|
+
if (!worldLayer || !onClick) return;
|
|
172
|
+
intersectsSphere(event.x, event.y, worldLayer).then((intersects) => {
|
|
173
|
+
if (intersects) {
|
|
174
|
+
onClick();
|
|
175
|
+
}
|
|
176
|
+
});
|
|
178
177
|
});
|
|
179
|
-
|
|
180
|
-
}
|
|
178
|
+
};
|
|
181
179
|
|
|
182
180
|
export { setupApp };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import './style.css'
|
|
2
|
-
import
|
|
3
|
-
import playcanvasLogo from './assets/playcanvas.png'
|
|
4
|
-
import
|
|
1
|
+
import './style.css';
|
|
2
|
+
import { setupApp } from './App';
|
|
3
|
+
import playcanvasLogo from './assets/playcanvas.png';
|
|
4
|
+
import typescriptLogo from './assets/typescript.svg';
|
|
5
5
|
|
|
6
6
|
let count = 0;
|
|
7
7
|
|
|
@@ -33,13 +33,13 @@ document.querySelector<HTMLDivElement>('#root')!.innerHTML = `
|
|
|
33
33
|
</p>
|
|
34
34
|
</div>
|
|
35
35
|
</div>
|
|
36
|
-
|
|
36
|
+
`;
|
|
37
37
|
|
|
38
38
|
const counterElement = document.getElementById('counter')!;
|
|
39
39
|
|
|
40
40
|
const increment = () => {
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
count++;
|
|
42
|
+
counterElement.textContent = `Click Count: ${count}`;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
setupApp(document.getElementById('application-canvas') as HTMLCanvasElement, increment);
|