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.
- package/README.md +16 -16
- package/dist/index.mjs +20 -20
- package/package.json +2 -2
- package/templates/_shared/{ar-placement → ar}/README.md +1 -1
- package/templates/_shared/blank/README.md +7 -0
- package/templates/_shared/first-person-game/AGENTS.md +11 -0
- package/templates/_shared/first-person-game/README.md +21 -0
- package/templates/_shared/first-person-game/public/level.glb +0 -0
- package/templates/_shared/first-person-game/src/game.css +47 -0
- package/templates/_shared/first-person-game/src/game.ts +232 -0
- package/templates/_shared/{sprite-game → platformer}/README.md +1 -1
- package/templates/_shared/{sprite-game/src/sprite-game.ts → platformer/src/platformer.ts} +3 -3
- package/templates/_shared/{third-person-controller → third-person-game}/README.md +1 -1
- package/templates/_shared/{vr-starter → vr}/README.md +1 -1
- package/templates/engine/{ar-placement → ar}/index.html +2 -2
- package/templates/engine/base/package.json +2 -2
- package/templates/engine/first-person-game/index.html +26 -0
- package/templates/engine/first-person-game/src/main.ts +167 -0
- package/templates/engine/{sprite-game → platformer}/index.html +2 -2
- package/templates/engine/{sprite-game → platformer}/src/main.ts +2 -2
- package/templates/engine/{third-person-controller → third-person-game}/index.html +2 -2
- package/templates/engine/{vr-starter → vr}/index.html +2 -2
- package/templates/index.ts +14 -14
- package/templates/react/{ar-placement → ar}/src/App.tsx +1 -1
- package/templates/react/base/package.json +2 -2
- package/templates/react/first-person-game/src/App.tsx +140 -0
- package/templates/react/{sprite-game → platformer}/src/App.tsx +3 -3
- package/templates/react/{third-person-controller → third-person-game}/src/App.tsx +1 -1
- package/templates/react/{vr-starter → vr}/src/App.tsx +1 -1
- package/templates/web-components/{ar-placement → ar}/index.html +2 -2
- package/templates/web-components/base/package.json +2 -2
- package/templates/web-components/first-person-game/index.html +80 -0
- package/templates/web-components/first-person-game/src/main.ts +74 -0
- package/templates/web-components/{sprite-game → platformer}/index.html +2 -2
- package/templates/web-components/{sprite-game → platformer}/src/main.ts +2 -2
- package/templates/web-components/{third-person-controller → third-person-game}/index.html +2 -2
- package/templates/web-components/{vr-starter → vr}/index.html +2 -2
- package/templates/_shared/first-person-controller/README.md +0 -7
- package/templates/_shared/first-person-controller/src/controller.ts +0 -18
- package/templates/_shared/spinning-cube/README.md +0 -7
- package/templates/engine/first-person-controller/index.html +0 -19
- package/templates/engine/first-person-controller/src/main.ts +0 -143
- package/templates/react/first-person-controller/src/App.tsx +0 -115
- package/templates/web-components/first-person-controller/index.html +0 -163
- package/templates/web-components/first-person-controller/src/main.ts +0 -15
- /package/templates/_shared/{ar-placement → ar}/src/ar-placement.ts +0 -0
- /package/templates/_shared/{first-person-controller → first-person-game}/src/scripts.d.ts +0 -0
- /package/templates/_shared/{third-person-controller → third-person-game}/src/controller.ts +0 -0
- /package/templates/_shared/{third-person-controller → third-person-game}/src/scripts.d.ts +0 -0
- /package/templates/_shared/{vr-starter → vr}/src/vr.ts +0 -0
- /package/templates/_shared/{vr-starter → vr}/src/xr.d.ts +0 -0
- /package/templates/engine/{ar-placement → ar}/src/main.ts +0 -0
- /package/templates/engine/{spinning-cube → blank}/src/main.ts +0 -0
- /package/templates/engine/{third-person-controller → third-person-game}/src/main.ts +0 -0
- /package/templates/engine/{vr-starter → vr}/src/main.ts +0 -0
- /package/templates/react/{spinning-cube → blank}/src/App.tsx +0 -0
- /package/templates/web-components/{ar-placement → ar}/src/main.ts +0 -0
- /package/templates/web-components/{spinning-cube → blank}/index.html +0 -0
- /package/templates/web-components/{spinning-cube → blank}/src/main.ts +0 -0
- /package/templates/web-components/{third-person-controller → third-person-game}/src/main.ts +0 -0
- /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 {
|
|
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={
|
|
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>
|
|
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
|
|
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>
|
|
@@ -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>
|
|
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>
|
|
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.
|
|
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.
|
|
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>
|
|
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>
|
|
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 {
|
|
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(
|
|
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
|
|
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
|
|
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>
|
|
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>
|
|
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,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,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);
|