create-playcanvas 0.16.1 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +16 -16
  2. package/dist/index.mjs +20 -20
  3. package/package.json +2 -2
  4. package/templates/_shared/{ar-placement → ar}/README.md +1 -1
  5. package/templates/_shared/blank/README.md +7 -0
  6. package/templates/_shared/first-person-game/AGENTS.md +11 -0
  7. package/templates/_shared/first-person-game/README.md +21 -0
  8. package/templates/_shared/first-person-game/public/level.glb +0 -0
  9. package/templates/_shared/first-person-game/src/game.css +47 -0
  10. package/templates/_shared/first-person-game/src/game.ts +232 -0
  11. package/templates/_shared/{sprite-game → platformer}/README.md +1 -1
  12. package/templates/_shared/{sprite-game/src/sprite-game.ts → platformer/src/platformer.ts} +3 -3
  13. package/templates/_shared/{third-person-controller → third-person-game}/README.md +1 -1
  14. package/templates/_shared/{vr-starter → vr}/README.md +1 -1
  15. package/templates/engine/{ar-placement → ar}/index.html +2 -2
  16. package/templates/engine/base/package.json +2 -2
  17. package/templates/engine/first-person-game/index.html +26 -0
  18. package/templates/engine/first-person-game/src/main.ts +167 -0
  19. package/templates/engine/{sprite-game → platformer}/index.html +2 -2
  20. package/templates/engine/{sprite-game → platformer}/src/main.ts +2 -2
  21. package/templates/engine/{third-person-controller → third-person-game}/index.html +2 -2
  22. package/templates/engine/{vr-starter → vr}/index.html +2 -2
  23. package/templates/index.ts +14 -14
  24. package/templates/react/{ar-placement → ar}/src/App.tsx +1 -1
  25. package/templates/react/base/package.json +2 -2
  26. package/templates/react/first-person-game/src/App.tsx +140 -0
  27. package/templates/react/{sprite-game → platformer}/src/App.tsx +3 -3
  28. package/templates/react/{third-person-controller → third-person-game}/src/App.tsx +1 -1
  29. package/templates/react/{vr-starter → vr}/src/App.tsx +1 -1
  30. package/templates/web-components/{ar-placement → ar}/index.html +2 -2
  31. package/templates/web-components/base/package.json +2 -2
  32. package/templates/web-components/first-person-game/index.html +80 -0
  33. package/templates/web-components/first-person-game/src/main.ts +74 -0
  34. package/templates/web-components/{sprite-game → platformer}/index.html +2 -2
  35. package/templates/web-components/{sprite-game → platformer}/src/main.ts +2 -2
  36. package/templates/web-components/{third-person-controller → third-person-game}/index.html +2 -2
  37. package/templates/web-components/{vr-starter → vr}/index.html +2 -2
  38. package/templates/_shared/first-person-controller/README.md +0 -7
  39. package/templates/_shared/first-person-controller/src/controller.ts +0 -18
  40. package/templates/_shared/spinning-cube/README.md +0 -7
  41. package/templates/engine/first-person-controller/index.html +0 -19
  42. package/templates/engine/first-person-controller/src/main.ts +0 -143
  43. package/templates/react/first-person-controller/src/App.tsx +0 -115
  44. package/templates/web-components/first-person-controller/index.html +0 -163
  45. package/templates/web-components/first-person-controller/src/main.ts +0 -15
  46. /package/templates/_shared/{ar-placement → ar}/src/ar-placement.ts +0 -0
  47. /package/templates/_shared/{first-person-controller → first-person-game}/src/scripts.d.ts +0 -0
  48. /package/templates/_shared/{third-person-controller → third-person-game}/src/controller.ts +0 -0
  49. /package/templates/_shared/{third-person-controller → third-person-game}/src/scripts.d.ts +0 -0
  50. /package/templates/_shared/{vr-starter → vr}/src/vr.ts +0 -0
  51. /package/templates/_shared/{vr-starter → vr}/src/xr.d.ts +0 -0
  52. /package/templates/engine/{ar-placement → ar}/src/main.ts +0 -0
  53. /package/templates/engine/{spinning-cube → blank}/src/main.ts +0 -0
  54. /package/templates/engine/{third-person-controller → third-person-game}/src/main.ts +0 -0
  55. /package/templates/engine/{vr-starter → vr}/src/main.ts +0 -0
  56. /package/templates/react/{spinning-cube → blank}/src/App.tsx +0 -0
  57. /package/templates/web-components/{ar-placement → ar}/src/main.ts +0 -0
  58. /package/templates/web-components/{spinning-cube → blank}/index.html +0 -0
  59. /package/templates/web-components/{spinning-cube → blank}/src/main.ts +0 -0
  60. /package/templates/web-components/{third-person-controller → third-person-game}/src/main.ts +0 -0
  61. /package/templates/web-components/{vr-starter → vr}/src/main.ts +0 -0
@@ -0,0 +1,140 @@
1
+ import { Application, Entity } from '@playcanvas/react';
2
+ import { Camera, Collision, Light, RigidBody, Script } from '@playcanvas/react/components';
3
+ import { useAppEvent, useModel, usePhysics } from '@playcanvas/react/hooks';
4
+ import type { Entity as PcEntity } from 'playcanvas';
5
+ import { SHADOW_PCF5_32F, TONEMAP_ACES2 } from 'playcanvas';
6
+ import { ProceduralSky } from 'playcanvas/scripts/esm/sky/procedural-sky.mjs';
7
+ import { useCallback, useRef, useState } from 'react';
8
+
9
+ import { CARDS, Game } from './game';
10
+ import type { GameEvents, GameState } from './game';
11
+ import './game.css';
12
+ import './starter.css';
13
+
14
+ // Swap this for your own level - files in public/ are served next to index.html, so use a ./ path.
15
+ // The game finds its nodes by name: a mesh called `level` to look at, one called `collision` to
16
+ // stand on, any number called `orb-…` to collect, and one called `spawn` where the player starts
17
+ const LEVEL_URL = './level.glb';
18
+
19
+ function Scene({
20
+ onState,
21
+ onJoystick
22
+ }: {
23
+ onState: GameEvents['game:state'];
24
+ onJoystick: GameEvents['joystick:left'];
25
+ }) {
26
+ const [sun, setSun] = useState<PcEntity | null>(null);
27
+ const [player, setPlayer] = useState<PcEntity | null>(null);
28
+ const [camera, setCamera] = useState<PcEntity | null>(null);
29
+ const { asset: level } = useModel(LEVEL_URL);
30
+ const { isPhysicsLoaded } = usePhysics();
31
+
32
+ useAppEvent<GameEvents>('game:state', onState);
33
+ useAppEvent<GameEvents>('joystick:left', onJoystick);
34
+
35
+ return (
36
+ <>
37
+ {/* The sun is a directional light that the sky keeps pointing the way its own sun does.
38
+ Its shadows reach 45 m, far enough to cross the yard from any corner, split into
39
+ four cascades because one shadow map over that distance is too coarse up close */}
40
+ <Entity name="sun" ref={setSun}>
41
+ <Light
42
+ type="directional"
43
+ intensity={2}
44
+ castShadows
45
+ shadowDistance={45}
46
+ numCascades={4}
47
+ shadowResolution={2048}
48
+ shadowType={SHADOW_PCF5_32F}
49
+ shadowBias={0.2}
50
+ normalOffsetBias={0.05}
51
+ />
52
+ </Entity>
53
+
54
+ {/* The procedural sky is both the background and the ambient light. Its default
55
+ luminance is scene-scale bright, so turn it down to keep the yard out of clipping */}
56
+ <Entity name="sky">
57
+ {sun && <Script script={ProceduralSky} luminance={0.3} azimuth={150} elevation={45} sunLight={sun} />}
58
+ </Entity>
59
+
60
+ {/* The sky lights the shade blue. A warm light from the other side, with no shadows of
61
+ its own, stands in for the sunlight the ground bounces back, so the shade stays soft
62
+ and the sand warm */}
63
+ <Entity name="fill" rotation={[60, -30, 0]}>
64
+ <Light type="directional" intensity={0.7} color="#ffdbb3" />
65
+ </Entity>
66
+
67
+ {/* The player is a capsule the physics moves, with the camera at eye height. The game
68
+ places it at the level's spawn point and gives it a first-person controller */}
69
+ <Entity name="player" ref={setPlayer}>
70
+ <Collision type="capsule" radius={0.45} height={1.8} />
71
+ <RigidBody type="dynamic" mass={80} angularFactor={[0, 0, 0]} />
72
+ {/* The sky is HDR, so the camera needs tone mapping to bring it back into
73
+ displayable range */}
74
+ <Entity name="camera" ref={setCamera} position={[0, 0.65, 0]}>
75
+ <Camera fov={80} toneMapping={TONEMAP_ACES2} />
76
+ </Entity>
77
+ </Entity>
78
+
79
+ {/* The game needs the level loaded, physics running and the player in the scene */}
80
+ {level && isPhysicsLoaded && player && camera && (
81
+ <Entity name="game">
82
+ <Script script={Game} level={level} player={player} camera={camera} />
83
+ </Entity>
84
+ )}
85
+ </>
86
+ );
87
+ }
88
+
89
+ function App() {
90
+ const [hud, setHud] = useState<{ state: GameState; found: number; total: number; seconds: number }>();
91
+ const joystick = useRef<HTMLDivElement>(null);
92
+ const knob = useRef<HTMLDivElement>(null);
93
+
94
+ // The HUD follows the game's state
95
+ const onState = useCallback<GameEvents['game:state']>((state, found, total, seconds) => {
96
+ setHud({ state, found, total, seconds });
97
+ }, []);
98
+
99
+ // A thumb on the left of the screen steers, and the circle and knob show where it is. They move
100
+ // with every touch event, so they are placed directly rather than through React state
101
+ const onJoystick = useCallback<GameEvents['joystick:left']>((baseX, baseY, knobX, knobY) => {
102
+ if (!joystick.current || !knob.current) return;
103
+ joystick.current.hidden = knob.current.hidden = baseX < 0;
104
+ joystick.current.style.left = `${baseX}px`;
105
+ joystick.current.style.top = `${baseY}px`;
106
+ knob.current.style.left = `${knobX}px`;
107
+ knob.current.style.top = `${knobY}px`;
108
+ }, []);
109
+
110
+ const card = hud && CARDS[hud.state];
111
+
112
+ return (
113
+ <>
114
+ <Application usePhysics>
115
+ <Scene onState={onState} onJoystick={onJoystick} />
116
+ </Application>
117
+ <div className="hud">
118
+ {hud && (
119
+ <header className="status">
120
+ <span>
121
+ {hud.found} / {hud.total}
122
+ </span>
123
+ <span>{hud.seconds}s</span>
124
+ </header>
125
+ )}
126
+ {card && (
127
+ <section className="panel card">
128
+ <h1>{card.title}</h1>
129
+ <p>{card.hint}</p>
130
+ </section>
131
+ )}
132
+ <div className="crosshair" />
133
+ <div className="joystick" ref={joystick} hidden />
134
+ <div className="joystick-knob" ref={knob} hidden />
135
+ </div>
136
+ </>
137
+ );
138
+ }
139
+
140
+ export default App;
@@ -2,7 +2,7 @@ import { Application, Entity } from '@playcanvas/react';
2
2
  import { Camera, Render, Script } from '@playcanvas/react/components';
3
3
  import { useMaterial } from '@playcanvas/react/hooks';
4
4
 
5
- import { SpriteGame } from './sprite-game';
5
+ import { Platformer } from './platformer';
6
6
  import './starter.css';
7
7
 
8
8
  function Scene() {
@@ -21,7 +21,7 @@ function Scene() {
21
21
  <Render type="box" material={grass} />
22
22
  </Entity>
23
23
  <Entity name="game" position={[0, -2.15, 0]}>
24
- <Script script={SpriteGame} />
24
+ <Script script={Platformer} />
25
25
  </Entity>
26
26
  </>
27
27
  );
@@ -35,7 +35,7 @@ function App() {
35
35
  </Application>
36
36
  <div className="hud">
37
37
  <section className="panel">
38
- <h1>Sprite Game</h1>
38
+ <h1>Platformer</h1>
39
39
  <p>Use A/D or the arrow keys to move and Space to jump.</p>
40
40
  </section>
41
41
  </div>
@@ -185,7 +185,7 @@ function App() {
185
185
  </Application>
186
186
  <div className="hud">
187
187
  <section className="panel">
188
- <h1>Third-Person Controller</h1>
188
+ <h1>Third-Person Game</h1>
189
189
  <p>Click the scene, then use WASD to move, mouse to orbit and Space to jump.</p>
190
190
  </section>
191
191
  </div>
@@ -83,7 +83,7 @@ function App() {
83
83
  </Application>
84
84
  <div className="hud">
85
85
  <section className="panel">
86
- <h1>VR Starter</h1>
86
+ <h1>Virtual Reality</h1>
87
87
  <p id="xr-status">Checking VR support…</p>
88
88
  <div className="controls">
89
89
  <button id="xr-button" disabled>
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <link rel="icon" type="image/png" href="/src/assets/playcanvas.png" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>AR Placement</title>
7
+ <title>Augmented Reality</title>
8
8
  </head>
9
9
  <body>
10
10
  <pc-app alpha>
@@ -34,7 +34,7 @@
34
34
  </pc-app>
35
35
  <div class="hud">
36
36
  <section class="panel">
37
- <h1>AR Placement</h1>
37
+ <h1>Augmented Reality</h1>
38
38
  <p id="xr-status">Checking AR support…</p>
39
39
  <div class="controls"><button id="xr-button" disabled>Start AR</button></div>
40
40
  </section>
@@ -16,13 +16,13 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@playcanvas/web-components": "^0.30.0",
19
- "playcanvas": "^2.24.0"
19
+ "playcanvas": "^2.24.1"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@playcanvas/eslint-config": "3.0.0",
23
23
  "@playcanvas/inspector": "^0.5.0",
24
24
  "eslint": "^9.39.5",
25
- "prettier": "^3.10.0",
25
+ "prettier": "^3.10.1",
26
26
  "typescript": "~6.0.3",
27
27
  "vite": "^8.3.8"
28
28
  },
@@ -0,0 +1,80 @@
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>First-Person Game</title>
8
+ </head>
9
+ <body>
10
+ <pc-app>
11
+ <!-- Swap this for your own level - files in public/ are served next to index.html, so use a ./ path.
12
+ The game finds its nodes by name: a mesh called `level` to look at, one called
13
+ `collision` to stand on, any number called `orb-…` to collect, and one called `spawn`
14
+ where the player starts -->
15
+ <pc-asset id="level" src="./level.glb"></pc-asset>
16
+
17
+ <!-- Physics runs in Ammo, a WebAssembly module -->
18
+ <pc-wasm name="Ammo" glue="./ammo/ammo.wasm.js" wasm="./ammo/ammo.wasm.wasm"></pc-wasm>
19
+
20
+ <pc-scene>
21
+ <!-- The sun is a directional light that the sky keeps pointing the way its own sun does.
22
+ Its shadows reach 45 m, far enough to cross the yard from any corner, split into
23
+ four cascades because one shadow map over that distance is too coarse up close -->
24
+ <pc-entity name="sun">
25
+ <pc-light
26
+ type="directional"
27
+ intensity="2"
28
+ cast-shadows
29
+ shadow-distance="45"
30
+ num-cascades="4"
31
+ shadow-resolution="2048"
32
+ shadow-type="pcf5-32f"
33
+ shadow-bias="0.2"
34
+ normal-offset-bias="0.05"
35
+ ></pc-light>
36
+ </pc-entity>
37
+
38
+ <!-- The procedural sky is both the background and the ambient light. The script is
39
+ attached in src/main.ts -->
40
+ <pc-entity name="sky"></pc-entity>
41
+
42
+ <!-- The sky lights the shade blue. A warm light from the other side, with no shadows of
43
+ its own, stands in for the sunlight the ground bounces back, so the shade stays soft
44
+ and the sand warm -->
45
+ <pc-entity name="fill" rotation="60 -30 0">
46
+ <pc-light type="directional" intensity="0.7" color="#ffdbb3"></pc-light>
47
+ </pc-entity>
48
+
49
+ <!-- The player is a capsule the physics moves, with the camera at eye height. The game
50
+ places it at the level's spawn point and gives it a first-person controller -->
51
+ <pc-entity name="player">
52
+ <pc-collision type="capsule" radius="0.45" height="1.8"></pc-collision>
53
+ <pc-rigid-body type="dynamic" mass="80" angular-factor="0 0 0"></pc-rigid-body>
54
+ <!-- The sky is HDR, so the camera needs tone mapping to bring it back into
55
+ displayable range -->
56
+ <pc-entity name="camera" position="0 0.65 0">
57
+ <pc-camera fov="80" tonemap="aces2"></pc-camera>
58
+ </pc-entity>
59
+ </pc-entity>
60
+
61
+ <!-- The game is attached in src/main.ts -->
62
+ <pc-entity name="game"></pc-entity>
63
+ </pc-scene>
64
+ </pc-app>
65
+ <div class="hud">
66
+ <header class="status">
67
+ <span id="orbs"></span>
68
+ <span id="time"></span>
69
+ </header>
70
+ <section class="panel card" id="card">
71
+ <h1 id="card-title"></h1>
72
+ <p id="card-hint"></p>
73
+ </section>
74
+ <div class="crosshair"></div>
75
+ <div class="joystick" id="joystick" hidden></div>
76
+ <div class="joystick-knob" id="joystick-knob" hidden></div>
77
+ </div>
78
+ <script type="module" src="/src/main.ts"></script>
79
+ </body>
80
+ </html>
@@ -0,0 +1,74 @@
1
+ import type { AssetElement, EntityElement } from '@playcanvas/web-components';
2
+ import { whenReady } from '@playcanvas/web-components';
3
+ import type { Asset } from 'playcanvas';
4
+ import { ProceduralSky } from 'playcanvas/scripts/esm/sky/procedural-sky.mjs';
5
+
6
+ import { CARDS, Game } from './game';
7
+ import type { GameEvents } from './game';
8
+ import './game.css';
9
+ import './starter.css';
10
+
11
+ // The HUD follows the game's state
12
+ const hud = {
13
+ orbs: document.getElementById('orbs')!,
14
+ time: document.getElementById('time')!,
15
+ card: document.getElementById('card')!,
16
+ title: document.getElementById('card-title')!,
17
+ hint: document.getElementById('card-hint')!,
18
+ joystick: document.getElementById('joystick')!,
19
+ knob: document.getElementById('joystick-knob')!
20
+ };
21
+
22
+ const onState: GameEvents['game:state'] = (state, found, total, seconds) => {
23
+ const card = CARDS[state];
24
+ hud.orbs.textContent = `${found} / ${total}`;
25
+ hud.time.textContent = `${seconds}s`;
26
+ hud.card.hidden = !card;
27
+ if (card) {
28
+ hud.title.textContent = card.title;
29
+ hud.hint.textContent = card.hint;
30
+ }
31
+ };
32
+
33
+ // A thumb on the left of the screen steers, and the circle and knob show where it is
34
+ const onJoystick: GameEvents['joystick:left'] = (baseX, baseY, knobX, knobY) => {
35
+ hud.joystick.hidden = hud.knob.hidden = baseX < 0;
36
+ hud.joystick.style.left = `${baseX}px`;
37
+ hud.joystick.style.top = `${baseY}px`;
38
+ hud.knob.style.left = `${knobX}px`;
39
+ hud.knob.style.top = `${knobY}px`;
40
+ };
41
+
42
+ const { app } = await whenReady('pc-app');
43
+ app!.on('game:state', onState);
44
+ app!.on('joystick:left', onJoystick);
45
+
46
+ // The player is awaited as its components rather than the <pc-entity> holding them - an entity
47
+ // becomes ready before its component children do, and the game needs its rigid body to exist
48
+ await Promise.all([
49
+ whenReady('pc-entity[name="player"] > pc-collision'),
50
+ whenReady('pc-entity[name="player"] > pc-rigid-body'),
51
+ whenReady('pc-entity[name="sun"] > pc-light'),
52
+ whenReady('pc-camera')
53
+ ]);
54
+ const [level, sun, sky, player, camera, game] = await Promise.all([
55
+ whenReady<AssetElement>('pc-asset#level'),
56
+ whenReady<EntityElement>('pc-entity[name="sun"]'),
57
+ whenReady<EntityElement>('pc-entity[name="sky"]'),
58
+ whenReady<EntityElement>('pc-entity[name="player"]'),
59
+ whenReady<EntityElement>('pc-entity[name="camera"]'),
60
+ whenReady<EntityElement>('pc-entity[name="game"]')
61
+ ]);
62
+
63
+ // Engine scripts are resolved by the bundler, so they are attached through the entity rather than
64
+ // with <pc-script-instance name="...">, which only resolves scripts fetched at runtime by <pc-asset>.
65
+ // The sky's default luminance is scene-scale bright, so turn it down to keep the yard out of clipping
66
+ sky.entity!.addComponent('script');
67
+ sky.entity!.script!.create(ProceduralSky, {
68
+ properties: { luminance: 0.3, azimuth: 150, elevation: 45, sunLight: sun.entity }
69
+ });
70
+
71
+ game.entity!.addComponent('script');
72
+ game.entity!.script!.create(Game, {
73
+ properties: { level: level.asset as Asset<'container'>, player: player.entity, camera: camera.entity }
74
+ });
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <link rel="icon" type="image/png" href="/src/assets/playcanvas.png" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>Sprite Game</title>
7
+ <title>Platformer</title>
8
8
  </head>
9
9
  <body>
10
10
  <pc-app>
@@ -25,7 +25,7 @@
25
25
  </pc-app>
26
26
  <div class="hud">
27
27
  <section class="panel">
28
- <h1>Sprite Game</h1>
28
+ <h1>Platformer</h1>
29
29
  <p>Use A/D or the arrow keys to move and Space to jump.</p>
30
30
  </section>
31
31
  </div>
@@ -1,9 +1,9 @@
1
1
  import type { EntityElement } from '@playcanvas/web-components';
2
2
  import { whenReady } from '@playcanvas/web-components';
3
3
 
4
- import { SpriteGame } from './sprite-game';
4
+ import { Platformer } from './platformer';
5
5
  import './starter.css';
6
6
 
7
7
  const game = await whenReady<EntityElement>('pc-entity[name="game"]');
8
8
  game.entity!.addComponent('script');
9
- game.entity!.script!.create(SpriteGame);
9
+ game.entity!.script!.create(Platformer);
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <link rel="icon" type="image/png" href="/src/assets/playcanvas.png" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>Third-Person Controller</title>
7
+ <title>Third-Person Game</title>
8
8
  </head>
9
9
  <body>
10
10
  <pc-app>
@@ -184,7 +184,7 @@
184
184
  </pc-app>
185
185
  <div class="hud">
186
186
  <section class="panel">
187
- <h1>Third-Person Controller</h1>
187
+ <h1>Third-Person Game</h1>
188
188
  <p>Click the scene, then use WASD to move, mouse to orbit and Space to jump.</p>
189
189
  </section>
190
190
  </div>
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <link rel="icon" type="image/png" href="/src/assets/playcanvas.png" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>VR Starter</title>
7
+ <title>Virtual Reality</title>
8
8
  </head>
9
9
  <body>
10
10
  <pc-app>
@@ -70,7 +70,7 @@
70
70
  </pc-app>
71
71
  <div class="hud">
72
72
  <section class="panel">
73
- <h1>VR Starter</h1>
73
+ <h1>Virtual Reality</h1>
74
74
  <p id="xr-status">Checking VR support…</p>
75
75
  <div class="controls"><button id="xr-button" disabled>Enter VR</button></div>
76
76
  </section>
@@ -1,7 +0,0 @@
1
- ## First-Person Controller
2
-
3
- A physics-backed player controller, camera and simple obstacle course.
4
-
5
- ### Controls
6
-
7
- Click the scene, use WASD to move, move the mouse to look and press Space to jump.
@@ -1,18 +0,0 @@
1
- import type { Entity, RigidBodyComponentSystem } from 'playcanvas';
2
- import { FirstPersonController } from 'playcanvas/scripts/esm/first-person-controller.mjs';
3
-
4
- export const addFirstPersonController = (player: Entity, camera: Entity) => {
5
- (player.rigidbody!.system as RigidBodyComponentSystem).gravity.set(0, -18, 0);
6
- if (!player.script) player.addComponent('script');
7
- player.script!.create(FirstPersonController, {
8
- properties: {
9
- camera,
10
- jumpForce: 650,
11
- speedGround: 55
12
- }
13
- });
14
-
15
- return () => {
16
- player.script?.destroy('firstPersonController');
17
- };
18
- };
@@ -1,7 +0,0 @@
1
- ## Spinning Cube
2
-
3
- A rotating cube, a camera and a directional light. It is the smallest PlayCanvas scene in the catalog.
4
-
5
- ### Controls
6
-
7
- The scene runs automatically; no input is required.
@@ -1,19 +0,0 @@
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>First-Person Controller</title>
8
- </head>
9
- <body>
10
- <canvas id="application-canvas"></canvas>
11
- <div class="hud">
12
- <section class="panel">
13
- <h1>First-Person Controller</h1>
14
- <p>Click the scene, then use WASD, mouse look and Space to jump.</p>
15
- </section>
16
- </div>
17
- <script type="module" src="/src/main.ts"></script>
18
- </body>
19
- </html>
@@ -1,143 +0,0 @@
1
- import {
2
- AppBase,
3
- AppOptions,
4
- CameraComponentSystem,
5
- CollisionComponentSystem,
6
- Color,
7
- Entity,
8
- FILLMODE_FILL_WINDOW,
9
- LightComponentSystem,
10
- RenderComponentSystem,
11
- RESOLUTION_AUTO,
12
- RigidBodyComponentSystem,
13
- ScriptComponentSystem,
14
- StandardMaterial,
15
- Vec3,
16
- WasmModule,
17
- createGraphicsDevice
18
- } from 'playcanvas';
19
-
20
- import { addFirstPersonController } from './controller';
21
- import './starter.css';
22
-
23
- WasmModule.setConfig('Ammo', {
24
- glueUrl: './ammo/ammo.wasm.js',
25
- wasmUrl: './ammo/ammo.wasm.wasm'
26
- });
27
- await new Promise<void>((resolve) => {
28
- WasmModule.getInstance('Ammo', () => resolve());
29
- });
30
-
31
- const canvas = document.getElementById('application-canvas') as HTMLCanvasElement;
32
-
33
- const device = await createGraphicsDevice(canvas);
34
- device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
35
-
36
- const createOptions = new AppOptions();
37
- createOptions.graphicsDevice = device;
38
- createOptions.componentSystems = [
39
- RenderComponentSystem,
40
- CameraComponentSystem,
41
- LightComponentSystem,
42
- ScriptComponentSystem,
43
- CollisionComponentSystem,
44
- RigidBodyComponentSystem
45
- ];
46
-
47
- const app = new AppBase(canvas);
48
- app.init(createOptions);
49
- app.start();
50
-
51
- // Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
52
- app.setCanvasFillMode(FILLMODE_FILL_WINDOW);
53
- app.setCanvasResolution(RESOLUTION_AUTO);
54
-
55
- // Ensure canvas is resized when window changes size
56
- const resize = () => app.resizeCanvas();
57
- window.addEventListener('resize', resize);
58
- app.on('destroy', () => {
59
- window.removeEventListener('resize', resize);
60
- });
61
-
62
- const material = (color: Color) => {
63
- const result = new StandardMaterial();
64
- result.diffuse = color;
65
- result.emissive = new Color(color.r * 0.2, color.g * 0.2, color.b * 0.2);
66
- result.update();
67
- return result;
68
- };
69
-
70
- const floor = material(new Color(0.28, 0.3, 0.32));
71
- const wall = material(new Color(0.42, 0.45, 0.48));
72
- const blue = material(new Color(0.08, 0.29, 0.48));
73
- const green = material(new Color(0.18, 0.4, 0.22));
74
- const wood = material(new Color(0.48, 0.27, 0.1));
75
- const metal = material(new Color(0.28, 0.33, 0.38));
76
-
77
- const block = (
78
- name: string,
79
- position: [number, number, number],
80
- scale: [number, number, number],
81
- blockMaterial: StandardMaterial
82
- ) => {
83
- const entity = new Entity(name);
84
- entity.setPosition(...position);
85
- entity.setLocalScale(...scale);
86
- entity.addComponent('render', { type: 'box', material: blockMaterial });
87
- entity.addComponent('collision', {
88
- type: 'box',
89
- halfExtents: new Vec3(scale[0] / 2, scale[1] / 2, scale[2] / 2)
90
- });
91
- entity.addComponent('rigidbody', { type: 'static' });
92
- app.root.addChild(entity);
93
- };
94
-
95
- block('floor', [0, -0.1, 0], [18, 0.2, 18], floor);
96
- block('back-wall', [0, 1.5, -9], [18, 3, 0.3], wall);
97
- block('front-wall', [0, 1.5, 9], [18, 3, 0.3], wall);
98
- block('left-wall', [-9, 1.5, 0], [0.3, 3, 18], wall);
99
- block('right-wall', [9, 1.5, 0], [0.3, 3, 18], wall);
100
- block('blue-crate', [-2, 0.75, -3], [1.5, 1.5, 1.5], blue);
101
- block('green-crate', [3, 0.5, 1], [2.5, 1, 1], green);
102
- block('cargo-left-0', [-5.5, 0.75, -5.5], [2, 1.5, 2], wood);
103
- block('cargo-left-1', [-5.5, 2, -5.5], [1.5, 1, 1.5], blue);
104
- block('cargo-right-0', [5.3, 0.6, -4.8], [2.4, 1.2, 2], green);
105
- block('cargo-right-1', [5.3, 1.7, -4.8], [1.5, 1, 1.5], wood);
106
- block('cargo-center', [0, 0.6, -6.5], [2.2, 1.2, 1.8], blue);
107
- for (const [i, z] of [-3.5, 0, 3.5].entries()) {
108
- block(`shelf-left-post-${i}`, [-6.8, 1.4, z], [0.25, 2.8, 0.25], metal);
109
- block(`shelf-right-post-${i}`, [6.8, 1.4, z], [0.25, 2.8, 0.25], metal);
110
- }
111
- block('shelf-left-low', [-6.8, 0.65, 0], [1.1, 0.18, 7.5], metal);
112
- block('shelf-left-high', [-6.8, 2.1, 0], [1.1, 0.18, 7.5], metal);
113
- block('shelf-right-low', [6.8, 0.65, 0], [1.1, 0.18, 7.5], metal);
114
- block('shelf-right-high', [6.8, 2.1, 0], [1.1, 0.18, 7.5], metal);
115
- block('pallet', [-2.5, 0.15, 2.5], [3, 0.3, 2], wood);
116
-
117
- const player = new Entity('player');
118
- player.setPosition(0, 1, 5);
119
- player.addComponent('collision', { type: 'capsule', radius: 0.45, height: 1.8 });
120
- player.addComponent('rigidbody', { type: 'dynamic', mass: 80, angularFactor: Vec3.ZERO });
121
- app.root.addChild(player);
122
-
123
- const camera = new Entity('camera');
124
- camera.addComponent('camera', { clearColor: new Color(0.38, 0.64, 0.86), fov: 80 });
125
- camera.setLocalPosition(0, 0.65, 0);
126
- player.addChild(camera);
127
- addFirstPersonController(player, camera);
128
-
129
- const light = new Entity('light');
130
- light.addComponent('light', {
131
- type: 'directional',
132
- intensity: 2.5,
133
- castShadows: true,
134
- shadowBias: 0.2,
135
- normalOffsetBias: 0.05
136
- });
137
- light.setEulerAngles(50, 30, 0);
138
- app.root.addChild(light);
139
-
140
- const fill = new Entity('fill');
141
- fill.addComponent('light', { type: 'directional', intensity: 0.8 });
142
- fill.setEulerAngles(-25, -140, 0);
143
- app.root.addChild(fill);