create-playcanvas 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -27
- package/dist/index.mjs +44 -42
- package/package.json +1 -1
- package/skills/calibrate-model/references/direct-engine.md +7 -4
- package/skills/calibrate-model/references/react.md +8 -3
- package/skills/calibrate-model/references/web-components.md +7 -4
- package/skills/configure-animation/references/web-components.md +18 -9
- package/skills/inspect-glb/SKILL.md +27 -18
- package/skills/inspect-glb/scripts/inspect.mjs +179 -50
- package/templates/_features/physics/assets/public/ammo/LICENSE +21 -0
- package/templates/_features/physics/assets/public/ammo/SOURCE.md +16 -0
- package/templates/_features/physics/assets/public/ammo/ammo.js +973 -0
- package/templates/_features/physics/assets/public/ammo/ammo.wasm.js +937 -0
- package/templates/_features/physics/assets/public/ammo/ammo.wasm.wasm +0 -0
- package/templates/_features/physics/react/package.json +5 -0
- package/templates/_features/physics/react/src/sync-ammo.d.ts +4 -0
- package/templates/_shared/ar-placement/README.md +7 -0
- package/templates/_shared/ar-placement/src/ar-placement.ts +123 -0
- package/templates/_shared/first-person-controller/README.md +7 -0
- package/templates/_shared/first-person-controller/src/controller.ts +18 -0
- package/templates/_shared/first-person-controller/src/scripts.d.ts +5 -0
- package/templates/_shared/interactive-sphere/README.md +7 -0
- package/templates/_shared/model-viewer/README.md +7 -0
- package/templates/_shared/physics-playground/README.md +7 -0
- package/templates/_shared/product-configurator/ASSET_LICENSES.md +3 -0
- package/templates/_shared/product-configurator/README.md +7 -0
- package/templates/_shared/product-configurator/src/product.css +14 -0
- package/templates/_shared/scene-editor/README.md +7 -0
- package/templates/_shared/scene-editor/src/scene-editor.ts +57 -0
- package/templates/_shared/spinning-cube/README.md +7 -0
- package/templates/_shared/splat-viewer/README.md +7 -0
- package/templates/_shared/sprite-game/README.md +7 -0
- package/templates/_shared/sprite-game/src/sprite-game.ts +197 -0
- package/templates/_shared/third-person-controller/README.md +7 -0
- package/templates/_shared/third-person-controller/src/controller.ts +53 -0
- package/templates/_shared/third-person-controller/src/scripts.d.ts +5 -0
- package/templates/_shared/vr-starter/README.md +7 -0
- package/templates/_shared/vr-starter/src/vr.ts +40 -0
- package/templates/_shared/vr-starter/src/xr.d.ts +11 -0
- package/templates/engine/ar-placement/src/main.ts +77 -0
- package/templates/engine/base/.prettierignore +1 -0
- package/templates/engine/base/README.md +1 -1
- package/templates/engine/base/_npmrc +1 -0
- package/templates/engine/base/eslint.config.mjs +1 -1
- package/templates/engine/base/index.html +13 -0
- package/templates/engine/base/src/starter.css +104 -0
- package/templates/engine/first-person-controller/src/main.ts +139 -0
- package/templates/engine/interactive-sphere/src/App.ts +9 -7
- package/templates/engine/interactive-sphere/src/main.ts +2 -29
- package/templates/engine/interactive-sphere/src/style.css +18 -97
- package/templates/engine/model-viewer/src/main.ts +3 -2
- package/templates/engine/physics-playground/src/main.ts +132 -0
- package/templates/engine/product-configurator/src/main.ts +156 -0
- package/templates/engine/scene-editor/src/main.ts +90 -0
- package/templates/engine/splat-viewer/src/main.ts +4 -3
- package/templates/engine/sprite-game/src/main.ts +72 -0
- package/templates/engine/third-person-controller/src/main.ts +216 -0
- package/templates/engine/vr-starter/src/main.ts +101 -0
- package/templates/index.ts +66 -5
- package/templates/react/ar-placement/src/App.tsx +58 -0
- package/templates/react/base/.prettierignore +1 -0
- package/templates/react/base/README.md +1 -1
- package/templates/react/base/_npmrc +1 -0
- package/templates/react/base/eslint.config.mjs +1 -1
- package/templates/react/base/index.html +13 -0
- package/templates/react/base/src/main.tsx +10 -0
- package/templates/react/base/src/starter.css +104 -0
- package/templates/react/first-person-controller/src/App.tsx +115 -0
- package/templates/react/interactive-sphere/src/App.css +18 -68
- package/templates/react/interactive-sphere/src/App.tsx +1 -22
- package/templates/react/interactive-sphere/src/Scene.tsx +7 -6
- package/templates/react/interactive-sphere/src/index.css +1 -48
- package/templates/react/model-viewer/src/App.tsx +2 -2
- package/templates/react/physics-playground/src/App.tsx +85 -0
- package/templates/react/product-configurator/src/App.tsx +157 -0
- package/templates/react/scene-editor/src/App.tsx +84 -0
- package/templates/react/splat-viewer/src/App.tsx +3 -3
- package/templates/react/sprite-game/src/App.tsx +38 -0
- package/templates/react/third-person-controller/src/App.tsx +193 -0
- package/templates/react/vr-starter/src/App.tsx +99 -0
- package/templates/web-components/ar-placement/index.html +44 -0
- package/templates/web-components/ar-placement/src/main.ts +13 -0
- package/templates/web-components/base/.prettierignore +1 -0
- package/templates/web-components/base/README.md +1 -1
- package/templates/web-components/base/_npmrc +1 -0
- package/templates/web-components/base/eslint.config.mjs +1 -1
- package/templates/web-components/base/package.json +1 -1
- package/templates/web-components/base/src/starter.css +104 -0
- package/templates/web-components/first-person-controller/index.html +168 -0
- package/templates/web-components/first-person-controller/src/main.ts +15 -0
- package/templates/web-components/interactive-sphere/index.html +5 -22
- package/templates/web-components/interactive-sphere/src/main.ts +5 -4
- package/templates/web-components/interactive-sphere/src/style.css +18 -76
- package/templates/web-components/model-viewer/index.html +1 -1
- package/templates/web-components/model-viewer/src/main.ts +2 -1
- package/templates/web-components/physics-playground/index.html +51 -0
- package/templates/web-components/physics-playground/src/main.ts +32 -0
- package/templates/web-components/product-configurator/index.html +51 -0
- package/templates/web-components/product-configurator/src/main.ts +93 -0
- package/templates/web-components/scene-editor/index.html +51 -0
- package/templates/web-components/scene-editor/src/main.ts +26 -0
- package/templates/web-components/spinning-cube/src/main.ts +1 -0
- package/templates/web-components/splat-viewer/index.html +2 -2
- package/templates/web-components/splat-viewer/src/main.ts +2 -1
- package/templates/web-components/sprite-game/index.html +34 -0
- package/templates/web-components/sprite-game/src/main.ts +9 -0
- package/templates/web-components/third-person-controller/index.html +183 -0
- package/templates/web-components/third-person-controller/src/main.ts +16 -0
- package/templates/web-components/vr-starter/index.html +80 -0
- package/templates/web-components/vr-starter/src/main.ts +12 -0
- package/templates/engine/interactive-sphere/src/assets/typescript.svg +0 -1
- package/templates/react/interactive-sphere/src/assets/react.svg +0 -1
- package/templates/web-components/interactive-sphere/src/assets/typescript.svg +0 -1
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
|
|
20
|
+
|
|
21
|
+
import './starter.css';
|
|
22
|
+
|
|
23
|
+
const COLORS = [new Color(0.16, 0.68, 0.9), new Color(0.95, 0.34, 0.22), new Color(0.98, 0.68, 0.18)];
|
|
24
|
+
const TYPES = ['box', 'sphere', 'capsule', 'cylinder'] as const;
|
|
25
|
+
const SCALES: [number, number, number][] = [
|
|
26
|
+
[0.9, 0.9, 0.9],
|
|
27
|
+
[0.9, 0.9, 0.9],
|
|
28
|
+
[0.7, 1.1, 0.7],
|
|
29
|
+
[0.8, 1.1, 0.8]
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
WasmModule.setConfig('Ammo', {
|
|
33
|
+
glueUrl: '/ammo/ammo.wasm.js',
|
|
34
|
+
wasmUrl: '/ammo/ammo.wasm.wasm',
|
|
35
|
+
fallbackUrl: '/ammo/ammo.js'
|
|
36
|
+
});
|
|
37
|
+
await new Promise<void>((resolve) => {
|
|
38
|
+
WasmModule.getInstance('Ammo', () => resolve());
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
document.body.insertAdjacentHTML(
|
|
42
|
+
'beforeend',
|
|
43
|
+
'<div class="hud"><section class="panel"><h1>Physics Playground</h1><p>Rigid bodies, collisions and runtime spawning.</p><div class="controls"><button id="spawn">Spawn object</button><button id="reset">Reset</button></div></section></div>'
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const canvas = document.getElementById('application-canvas') as HTMLCanvasElement;
|
|
47
|
+
const device = await createGraphicsDevice(canvas);
|
|
48
|
+
const options = new AppOptions();
|
|
49
|
+
options.graphicsDevice = device;
|
|
50
|
+
options.componentSystems = [
|
|
51
|
+
RenderComponentSystem,
|
|
52
|
+
CameraComponentSystem,
|
|
53
|
+
LightComponentSystem,
|
|
54
|
+
ScriptComponentSystem,
|
|
55
|
+
CollisionComponentSystem,
|
|
56
|
+
RigidBodyComponentSystem
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const app = new AppBase(canvas);
|
|
60
|
+
app.init(options);
|
|
61
|
+
app.start();
|
|
62
|
+
app.setCanvasFillMode(FILLMODE_FILL_WINDOW);
|
|
63
|
+
app.setCanvasResolution(RESOLUTION_AUTO);
|
|
64
|
+
app.systems.rigidbody!.gravity.set(0, -9.81, 0);
|
|
65
|
+
|
|
66
|
+
const materials = COLORS.map((color) => {
|
|
67
|
+
const material = new StandardMaterial();
|
|
68
|
+
material.diffuse = color;
|
|
69
|
+
material.gloss = 0.45;
|
|
70
|
+
material.update();
|
|
71
|
+
return material;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const floorMaterial = new StandardMaterial();
|
|
75
|
+
floorMaterial.diffuse = new Color(0.18, 0.2, 0.24);
|
|
76
|
+
floorMaterial.gloss = 0.2;
|
|
77
|
+
floorMaterial.update();
|
|
78
|
+
|
|
79
|
+
const floor = new Entity('floor');
|
|
80
|
+
floor.setLocalScale(10, 0.2, 10);
|
|
81
|
+
floor.setPosition(0, -0.1, 0);
|
|
82
|
+
floor.addComponent('render', { type: 'box', material: floorMaterial });
|
|
83
|
+
floor.addComponent('collision', { type: 'box', halfExtents: new Vec3(5, 0.1, 5) });
|
|
84
|
+
floor.addComponent('rigidbody', { type: 'static' });
|
|
85
|
+
app.root.addChild(floor);
|
|
86
|
+
|
|
87
|
+
const bodies: Entity[] = [];
|
|
88
|
+
const spawn = () => {
|
|
89
|
+
const i = bodies.length;
|
|
90
|
+
const type = TYPES[i % TYPES.length];
|
|
91
|
+
const entity = new Entity(type);
|
|
92
|
+
entity.setPosition(((i % 4) - 1.5) * 1.1, 0.7 + Math.floor(i / 4) * 1.1, (Math.floor(i / 4) - 1) * 1.4);
|
|
93
|
+
entity.setEulerAngles(i * 13, i * 29, 0);
|
|
94
|
+
entity.setLocalScale(...SCALES[i % SCALES.length]);
|
|
95
|
+
entity.addComponent('render', { type, material: materials[i % materials.length] });
|
|
96
|
+
entity.addComponent('collision', {
|
|
97
|
+
type,
|
|
98
|
+
halfExtents: new Vec3(0.5, 0.5, 0.5),
|
|
99
|
+
radius: 0.5
|
|
100
|
+
});
|
|
101
|
+
entity.addComponent('rigidbody', { type: 'dynamic', mass: 1, restitution: 0.35 });
|
|
102
|
+
app.root.addChild(entity);
|
|
103
|
+
bodies.push(entity);
|
|
104
|
+
};
|
|
105
|
+
const reset = () => {
|
|
106
|
+
bodies.splice(0).forEach((entity) => entity.destroy());
|
|
107
|
+
for (let i = 0; i < 12; i++) spawn();
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const camera = new Entity('camera');
|
|
111
|
+
camera.setPosition(10, 8, 10);
|
|
112
|
+
camera.lookAt(0, 2, 0);
|
|
113
|
+
camera.addComponent('camera', { clearColor: new Color(0.05, 0.07, 0.11) });
|
|
114
|
+
camera.addComponent('script');
|
|
115
|
+
camera.script!.create(CameraControls, { properties: { sceneSize: 10 } });
|
|
116
|
+
app.root.addChild(camera);
|
|
117
|
+
|
|
118
|
+
const light = new Entity('light');
|
|
119
|
+
light.addComponent('light', {
|
|
120
|
+
type: 'directional',
|
|
121
|
+
intensity: 2.5,
|
|
122
|
+
castShadows: true,
|
|
123
|
+
shadowBias: 0.2,
|
|
124
|
+
normalOffsetBias: 0.05
|
|
125
|
+
});
|
|
126
|
+
light.setEulerAngles(45, 35, 0);
|
|
127
|
+
app.root.addChild(light);
|
|
128
|
+
|
|
129
|
+
document.getElementById('spawn')!.onclick = spawn;
|
|
130
|
+
document.getElementById('reset')!.onclick = reset;
|
|
131
|
+
reset();
|
|
132
|
+
window.addEventListener('resize', () => app.resizeCanvas());
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { ContainerResource, RenderComponent } from 'playcanvas';
|
|
2
|
+
import {
|
|
3
|
+
AppBase,
|
|
4
|
+
AppOptions,
|
|
5
|
+
Asset,
|
|
6
|
+
AssetListLoader,
|
|
7
|
+
CameraComponentSystem,
|
|
8
|
+
Color,
|
|
9
|
+
ContainerHandler,
|
|
10
|
+
CylinderGeometry,
|
|
11
|
+
Entity,
|
|
12
|
+
FILLMODE_FILL_WINDOW,
|
|
13
|
+
LightComponentSystem,
|
|
14
|
+
Mesh,
|
|
15
|
+
MeshInstance,
|
|
16
|
+
RenderComponentSystem,
|
|
17
|
+
RESOLUTION_AUTO,
|
|
18
|
+
ScriptComponentSystem,
|
|
19
|
+
StandardMaterial,
|
|
20
|
+
TextureHandler,
|
|
21
|
+
TONEMAP_ACES2,
|
|
22
|
+
Vec2,
|
|
23
|
+
Vec3,
|
|
24
|
+
createGraphicsDevice
|
|
25
|
+
} from 'playcanvas';
|
|
26
|
+
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
|
|
27
|
+
import { ProceduralSky } from 'playcanvas/scripts/esm/sky/procedural-sky.mjs';
|
|
28
|
+
|
|
29
|
+
import './product.css';
|
|
30
|
+
import './starter.css';
|
|
31
|
+
|
|
32
|
+
const PRODUCT_URL = 'https://developer.playcanvas.com/assets/lambo.glb';
|
|
33
|
+
const PAINTS = [
|
|
34
|
+
{ name: 'Crimson', color: '#d6293e', shade: '#9b1731' },
|
|
35
|
+
{ name: 'Arctic', color: '#d8e4e9', shade: '#8fa8b5' },
|
|
36
|
+
{ name: 'Volt', color: '#9acb34', shade: '#3c742d' }
|
|
37
|
+
];
|
|
38
|
+
const CAMERA_FOCUS = new Vec3(0, 0.8, 0);
|
|
39
|
+
const CAMERA_PITCH = new Vec2(-85, -4);
|
|
40
|
+
|
|
41
|
+
document.body.insertAdjacentHTML(
|
|
42
|
+
'beforeend',
|
|
43
|
+
`<div class="hud"><section class="panel"><h1>Product Configurator</h1><p>Choose a paint finish, then drag to orbit.</p><div class="controls variants" id="paints">${PAINTS.map(({ name, color }, i) => `<button aria-pressed="${i === 0}" data-color="${color}"><span class="finish" style="--color:${color}"></span>${name}</button>`).join('')}</div></section></div>`
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const canvas = document.getElementById('application-canvas') as HTMLCanvasElement;
|
|
47
|
+
const device = await createGraphicsDevice(canvas);
|
|
48
|
+
device.maxPixelRatio = Math.min(device.maxPixelRatio, 2);
|
|
49
|
+
|
|
50
|
+
const options = new AppOptions();
|
|
51
|
+
options.graphicsDevice = device;
|
|
52
|
+
options.componentSystems = [RenderComponentSystem, CameraComponentSystem, LightComponentSystem, ScriptComponentSystem];
|
|
53
|
+
options.resourceHandlers = [TextureHandler, ContainerHandler];
|
|
54
|
+
|
|
55
|
+
const app = new AppBase(canvas);
|
|
56
|
+
app.init(options);
|
|
57
|
+
app.setCanvasFillMode(FILLMODE_FILL_WINDOW);
|
|
58
|
+
app.setCanvasResolution(RESOLUTION_AUTO);
|
|
59
|
+
|
|
60
|
+
const product = new Asset('car', 'container', { url: PRODUCT_URL });
|
|
61
|
+
await new Promise<void>((resolve) => new AssetListLoader([product], app.assets).load(() => resolve()));
|
|
62
|
+
|
|
63
|
+
app.start();
|
|
64
|
+
|
|
65
|
+
const car = (product.resource as ContainerResource).instantiateRenderEntity();
|
|
66
|
+
car.setLocalEulerAngles(-90, -25, 0);
|
|
67
|
+
app.root.addChild(car);
|
|
68
|
+
app.root.syncHierarchy();
|
|
69
|
+
|
|
70
|
+
const meshes = (car.findComponents('render') as RenderComponent[]).flatMap((render) => render.meshInstances);
|
|
71
|
+
const bounds = meshes[0].aabb.clone();
|
|
72
|
+
meshes.slice(1).forEach((mesh) => bounds.add(mesh.aabb));
|
|
73
|
+
const scale = 5 / Math.max(bounds.halfExtents.x * 2, bounds.halfExtents.z * 2);
|
|
74
|
+
car.setLocalScale(scale, scale, scale);
|
|
75
|
+
app.root.syncHierarchy();
|
|
76
|
+
bounds.copy(meshes[0].aabb);
|
|
77
|
+
meshes.slice(1).forEach((mesh) => bounds.add(mesh.aabb));
|
|
78
|
+
car.setPosition(-bounds.center.x, 0.03 - bounds.getMin().y, -bounds.center.z);
|
|
79
|
+
|
|
80
|
+
const body = meshes.filter((mesh) => mesh.material.name === 'material');
|
|
81
|
+
const panels = meshes.filter((mesh) => mesh.material.name === 'material_1');
|
|
82
|
+
const paint = body[0].material.clone() as StandardMaterial;
|
|
83
|
+
const shade = panels[0].material.clone() as StandardMaterial;
|
|
84
|
+
body.forEach((mesh) => (mesh.material = paint));
|
|
85
|
+
panels.forEach((mesh) => (mesh.material = shade));
|
|
86
|
+
paint.diffuse.fromString(PAINTS[0].color);
|
|
87
|
+
shade.diffuse.fromString(PAINTS[0].shade);
|
|
88
|
+
paint.update();
|
|
89
|
+
shade.update();
|
|
90
|
+
|
|
91
|
+
const sky = new Entity('sky');
|
|
92
|
+
sky.addComponent('script');
|
|
93
|
+
sky.script!.create(ProceduralSky, { properties: { luminance: 0.18 } });
|
|
94
|
+
app.root.addChild(sky);
|
|
95
|
+
|
|
96
|
+
const camera = new Entity('camera');
|
|
97
|
+
camera.setPosition(5.5, 3.2, 6.5);
|
|
98
|
+
camera.lookAt(CAMERA_FOCUS);
|
|
99
|
+
camera.addComponent('camera', { fov: 40, toneMapping: TONEMAP_ACES2, renderSceneColorMap: true });
|
|
100
|
+
camera.addComponent('script');
|
|
101
|
+
camera.script!.create(CameraControls, {
|
|
102
|
+
properties: {
|
|
103
|
+
sceneSize: 6.2,
|
|
104
|
+
focusPoint: CAMERA_FOCUS,
|
|
105
|
+
pitchRange: CAMERA_PITCH,
|
|
106
|
+
enableFly: false,
|
|
107
|
+
enablePan: false
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
app.root.addChild(camera);
|
|
111
|
+
|
|
112
|
+
const ground = new Entity('studio-ground');
|
|
113
|
+
const podium = new StandardMaterial();
|
|
114
|
+
podium.diffuse = new Color(0.3, 0.32, 0.34);
|
|
115
|
+
podium.metalness = 0.15;
|
|
116
|
+
podium.gloss = 0.65;
|
|
117
|
+
podium.update();
|
|
118
|
+
|
|
119
|
+
ground.setPosition(0, -0.08, 0);
|
|
120
|
+
ground.addComponent('render', {
|
|
121
|
+
meshInstances: [
|
|
122
|
+
new MeshInstance(
|
|
123
|
+
Mesh.fromGeometry(
|
|
124
|
+
device,
|
|
125
|
+
new CylinderGeometry({ radius: 4, height: 0.16, heightSegments: 1, capSegments: 128 })
|
|
126
|
+
),
|
|
127
|
+
podium
|
|
128
|
+
)
|
|
129
|
+
],
|
|
130
|
+
castShadows: false,
|
|
131
|
+
receiveShadows: true
|
|
132
|
+
});
|
|
133
|
+
app.root.addChild(ground);
|
|
134
|
+
|
|
135
|
+
const light = new Entity('light');
|
|
136
|
+
light.addComponent('light', {
|
|
137
|
+
type: 'directional',
|
|
138
|
+
intensity: 2.5,
|
|
139
|
+
castShadows: true,
|
|
140
|
+
shadowBias: 0.2,
|
|
141
|
+
normalOffsetBias: 0.05
|
|
142
|
+
});
|
|
143
|
+
light.setEulerAngles(45, -145, 0);
|
|
144
|
+
app.root.addChild(light);
|
|
145
|
+
|
|
146
|
+
document.querySelectorAll<HTMLButtonElement>('#paints button').forEach((button, i, buttons) => {
|
|
147
|
+
button.onclick = () => {
|
|
148
|
+
paint.diffuse.fromString(PAINTS[i].color);
|
|
149
|
+
shade.diffuse.fromString(PAINTS[i].shade);
|
|
150
|
+
paint.update();
|
|
151
|
+
shade.update();
|
|
152
|
+
buttons.forEach((item, j) => (item.ariaPressed = String(i === j)));
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
window.addEventListener('resize', () => app.resizeCanvas());
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppBase,
|
|
3
|
+
AppOptions,
|
|
4
|
+
CameraComponentSystem,
|
|
5
|
+
Color,
|
|
6
|
+
Entity,
|
|
7
|
+
FILLMODE_FILL_WINDOW,
|
|
8
|
+
LightComponentSystem,
|
|
9
|
+
RenderComponentSystem,
|
|
10
|
+
RESOLUTION_AUTO,
|
|
11
|
+
ScriptComponentSystem,
|
|
12
|
+
StandardMaterial,
|
|
13
|
+
createGraphicsDevice
|
|
14
|
+
} from 'playcanvas';
|
|
15
|
+
import { Grid } from 'playcanvas/scripts/esm/grid.mjs';
|
|
16
|
+
|
|
17
|
+
import { SceneEditor } from './scene-editor';
|
|
18
|
+
import './starter.css';
|
|
19
|
+
|
|
20
|
+
document.body.insertAdjacentHTML(
|
|
21
|
+
'beforeend',
|
|
22
|
+
'<div class="hud"><section class="panel"><h1>Scene Editor</h1><p>Select an object, then translate, rotate or scale it.</p><div class="controls"><button data-target="0" aria-pressed="true">Cube</button><button data-target="1">Sphere</button></div><div class="controls"><button data-mode="translate" aria-pressed="true">Move</button><button data-mode="rotate">Rotate</button><button data-mode="scale">Scale</button></div></section></div>'
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const canvas = document.getElementById('application-canvas') as HTMLCanvasElement;
|
|
26
|
+
const device = await createGraphicsDevice(canvas);
|
|
27
|
+
const options = new AppOptions();
|
|
28
|
+
options.graphicsDevice = device;
|
|
29
|
+
options.componentSystems = [RenderComponentSystem, CameraComponentSystem, LightComponentSystem, ScriptComponentSystem];
|
|
30
|
+
|
|
31
|
+
const app = new AppBase(canvas);
|
|
32
|
+
app.init(options);
|
|
33
|
+
app.start();
|
|
34
|
+
app.setCanvasFillMode(FILLMODE_FILL_WINDOW);
|
|
35
|
+
app.setCanvasResolution(RESOLUTION_AUTO);
|
|
36
|
+
|
|
37
|
+
app.scene.ambientLight = new Color(0.25, 0.28, 0.34);
|
|
38
|
+
|
|
39
|
+
const coral = new StandardMaterial();
|
|
40
|
+
coral.diffuse = new Color(1, 0.32, 0.27);
|
|
41
|
+
coral.update();
|
|
42
|
+
const cyan = new StandardMaterial();
|
|
43
|
+
cyan.diffuse = new Color(0.14, 0.68, 0.88);
|
|
44
|
+
cyan.update();
|
|
45
|
+
|
|
46
|
+
const cube = new Entity('cube');
|
|
47
|
+
cube.addComponent('render', { type: 'box', material: coral });
|
|
48
|
+
cube.setPosition(-1.2, 0.5, 0);
|
|
49
|
+
app.root.addChild(cube);
|
|
50
|
+
|
|
51
|
+
const sphere = new Entity('sphere');
|
|
52
|
+
sphere.addComponent('render', { type: 'sphere', material: cyan });
|
|
53
|
+
sphere.setPosition(1.2, 0.5, 0);
|
|
54
|
+
app.root.addChild(sphere);
|
|
55
|
+
|
|
56
|
+
const grid = new Entity('grid');
|
|
57
|
+
grid.addComponent('script');
|
|
58
|
+
grid.script!.create(Grid);
|
|
59
|
+
grid.setLocalScale(12, 12, 12);
|
|
60
|
+
app.root.addChild(grid);
|
|
61
|
+
|
|
62
|
+
const camera = new Entity('camera');
|
|
63
|
+
camera.addComponent('camera', { clearColor: new Color(0.04, 0.06, 0.09) });
|
|
64
|
+
camera.setPosition(4, 3.2, 5);
|
|
65
|
+
camera.lookAt(0, 0.5, 0);
|
|
66
|
+
app.root.addChild(camera);
|
|
67
|
+
|
|
68
|
+
const light = new Entity('light');
|
|
69
|
+
light.addComponent('light', {
|
|
70
|
+
type: 'directional',
|
|
71
|
+
intensity: 2.5,
|
|
72
|
+
castShadows: true,
|
|
73
|
+
shadowBias: 0.2,
|
|
74
|
+
normalOffsetBias: 0.05
|
|
75
|
+
});
|
|
76
|
+
light.setEulerAngles(45, 30, 0);
|
|
77
|
+
app.root.addChild(light);
|
|
78
|
+
|
|
79
|
+
const editor = new Entity('editor');
|
|
80
|
+
editor.addComponent('script');
|
|
81
|
+
editor.script!.create(SceneEditor, {
|
|
82
|
+
properties: {
|
|
83
|
+
camera,
|
|
84
|
+
targets: [cube, sphere],
|
|
85
|
+
layer: app.scene.layers.getLayerByName('World')
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
app.root.addChild(editor);
|
|
89
|
+
|
|
90
|
+
window.addEventListener('resize', () => app.resizeCanvas());
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
Asset,
|
|
5
5
|
AssetListLoader,
|
|
6
6
|
CameraComponentSystem,
|
|
7
|
+
Color,
|
|
7
8
|
Entity,
|
|
8
9
|
FILLMODE_FILL_WINDOW,
|
|
9
10
|
GSplatComponentSystem,
|
|
@@ -67,8 +68,8 @@ app.root.addChild(toyCat);
|
|
|
67
68
|
|
|
68
69
|
// Create camera entity with orbit controls
|
|
69
70
|
const camera = new Entity('camera');
|
|
70
|
-
camera.addComponent('camera');
|
|
71
|
-
camera.setPosition(0, 0, 2.
|
|
71
|
+
camera.addComponent('camera', { clearColor: new Color(0.56, 0.58, 0.62) });
|
|
72
|
+
camera.setPosition(0, 0, 2.8);
|
|
72
73
|
camera.addComponent('script');
|
|
73
|
-
camera.script?.create(CameraControls);
|
|
74
|
+
camera.script?.create(CameraControls, { properties: { sceneSize: 2 } });
|
|
74
75
|
app.root.addChild(camera);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppBase,
|
|
3
|
+
AppOptions,
|
|
4
|
+
CameraComponentSystem,
|
|
5
|
+
Color,
|
|
6
|
+
Entity,
|
|
7
|
+
FILLMODE_FILL_WINDOW,
|
|
8
|
+
RenderComponentSystem,
|
|
9
|
+
RESOLUTION_AUTO,
|
|
10
|
+
ScriptComponentSystem,
|
|
11
|
+
SpriteComponentSystem,
|
|
12
|
+
StandardMaterial,
|
|
13
|
+
createGraphicsDevice
|
|
14
|
+
} from 'playcanvas';
|
|
15
|
+
|
|
16
|
+
import { SpriteGame } from './sprite-game';
|
|
17
|
+
import './starter.css';
|
|
18
|
+
|
|
19
|
+
document.body.insertAdjacentHTML(
|
|
20
|
+
'beforeend',
|
|
21
|
+
'<div class="hud"><section class="panel"><h1>Sprite Game</h1><p>Use A/D or the arrow keys to move and Space to jump.</p></section></div>'
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const canvas = document.getElementById('application-canvas') as HTMLCanvasElement;
|
|
25
|
+
const device = await createGraphicsDevice(canvas);
|
|
26
|
+
device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
|
|
27
|
+
|
|
28
|
+
const options = new AppOptions();
|
|
29
|
+
options.graphicsDevice = device;
|
|
30
|
+
options.componentSystems = [RenderComponentSystem, CameraComponentSystem, SpriteComponentSystem, ScriptComponentSystem];
|
|
31
|
+
|
|
32
|
+
const app = new AppBase(canvas);
|
|
33
|
+
app.init(options);
|
|
34
|
+
app.start();
|
|
35
|
+
app.setCanvasFillMode(FILLMODE_FILL_WINDOW);
|
|
36
|
+
app.setCanvasResolution(RESOLUTION_AUTO);
|
|
37
|
+
|
|
38
|
+
const camera = new Entity('camera');
|
|
39
|
+
camera.addComponent('camera', {
|
|
40
|
+
projection: 1,
|
|
41
|
+
orthoHeight: 4.4,
|
|
42
|
+
clearColor: new Color(0.36, 0.73, 0.9)
|
|
43
|
+
});
|
|
44
|
+
camera.setPosition(0, 0, 10);
|
|
45
|
+
app.root.addChild(camera);
|
|
46
|
+
|
|
47
|
+
const dirtMaterial = new StandardMaterial();
|
|
48
|
+
dirtMaterial.emissive = new Color(0.45, 0.28, 0.16);
|
|
49
|
+
dirtMaterial.update();
|
|
50
|
+
const grassMaterial = new StandardMaterial();
|
|
51
|
+
grassMaterial.emissive = new Color(0.28, 0.62, 0.34);
|
|
52
|
+
grassMaterial.update();
|
|
53
|
+
|
|
54
|
+
const dirt = new Entity('dirt');
|
|
55
|
+
dirt.addComponent('render', { type: 'box', material: dirtMaterial });
|
|
56
|
+
dirt.setLocalScale(20, 2.6, 1);
|
|
57
|
+
dirt.setPosition(0, -3.39, 0);
|
|
58
|
+
app.root.addChild(dirt);
|
|
59
|
+
|
|
60
|
+
const grass = new Entity('grass');
|
|
61
|
+
grass.addComponent('render', { type: 'box', material: grassMaterial });
|
|
62
|
+
grass.setLocalScale(20, 0.18, 1);
|
|
63
|
+
grass.setPosition(0, -2, 0);
|
|
64
|
+
app.root.addChild(grass);
|
|
65
|
+
|
|
66
|
+
const game = new Entity('game');
|
|
67
|
+
game.setPosition(0, -2.15, 0);
|
|
68
|
+
game.addComponent('script');
|
|
69
|
+
game.script!.create(SpriteGame);
|
|
70
|
+
app.root.addChild(game);
|
|
71
|
+
|
|
72
|
+
window.addEventListener('resize', () => app.resizeCanvas());
|
|
@@ -0,0 +1,216 @@
|
|
|
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 { addThirdPersonController } from './controller';
|
|
21
|
+
import './starter.css';
|
|
22
|
+
|
|
23
|
+
const BLOCK_SCALE: [number, number, number] = [2.4, 1, 2.4];
|
|
24
|
+
const BLOCK_HALF_EXTENTS: [number, number, number] = [1.2, 0.5, 1.2];
|
|
25
|
+
const BLOCKS: [number, number, number][] = [
|
|
26
|
+
[-4.5, 0.5, -3.5],
|
|
27
|
+
[4.2, 0.5, -2.8],
|
|
28
|
+
[-3.7, 0.5, 3.2],
|
|
29
|
+
[3.8, 0.5, 3.6]
|
|
30
|
+
];
|
|
31
|
+
const TREES: [number, number, number][] = [
|
|
32
|
+
[-7, -6, 1.1],
|
|
33
|
+
[6.5, -6.5, 1.25],
|
|
34
|
+
[-7.5, 5, 0.9],
|
|
35
|
+
[7, 5.5, 1.15],
|
|
36
|
+
[-2.2, -8, 0.85]
|
|
37
|
+
];
|
|
38
|
+
const PINE_LAYERS: [number, number, number][] = [
|
|
39
|
+
[1.9, 2.1, 1.8],
|
|
40
|
+
[2.65, 1.6, 1.55],
|
|
41
|
+
[3.3, 1.1, 1.4]
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
WasmModule.setConfig('Ammo', {
|
|
45
|
+
glueUrl: '/ammo/ammo.wasm.js',
|
|
46
|
+
wasmUrl: '/ammo/ammo.wasm.wasm',
|
|
47
|
+
fallbackUrl: '/ammo/ammo.js'
|
|
48
|
+
});
|
|
49
|
+
await new Promise<void>((resolve) => {
|
|
50
|
+
WasmModule.getInstance('Ammo', () => resolve());
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
document.body.insertAdjacentHTML(
|
|
54
|
+
'beforeend',
|
|
55
|
+
'<div class="hud"><section class="panel"><h1>Third-Person Controller</h1><p>Click the scene, then use WASD to move, mouse to orbit and Space to jump.</p></section></div>'
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const canvas = document.getElementById('application-canvas') as HTMLCanvasElement;
|
|
59
|
+
const device = await createGraphicsDevice(canvas);
|
|
60
|
+
const options = new AppOptions();
|
|
61
|
+
options.graphicsDevice = device;
|
|
62
|
+
options.componentSystems = [
|
|
63
|
+
RenderComponentSystem,
|
|
64
|
+
CameraComponentSystem,
|
|
65
|
+
LightComponentSystem,
|
|
66
|
+
ScriptComponentSystem,
|
|
67
|
+
CollisionComponentSystem,
|
|
68
|
+
RigidBodyComponentSystem
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const app = new AppBase(canvas);
|
|
72
|
+
app.init(options);
|
|
73
|
+
app.start();
|
|
74
|
+
app.setCanvasFillMode(FILLMODE_FILL_WINDOW);
|
|
75
|
+
app.setCanvasResolution(RESOLUTION_AUTO);
|
|
76
|
+
app.scene.ambientLight = new Color(0.28, 0.32, 0.38);
|
|
77
|
+
|
|
78
|
+
const groundMaterial = new StandardMaterial();
|
|
79
|
+
groundMaterial.diffuse = new Color(0.2, 0.4, 0.18);
|
|
80
|
+
groundMaterial.update();
|
|
81
|
+
const blockMaterial = new StandardMaterial();
|
|
82
|
+
blockMaterial.diffuse = new Color(0.34, 0.35, 0.3);
|
|
83
|
+
blockMaterial.update();
|
|
84
|
+
|
|
85
|
+
const ground = new Entity('ground');
|
|
86
|
+
ground.setLocalScale(20, 0.2, 20);
|
|
87
|
+
ground.setPosition(0, -0.1, 0);
|
|
88
|
+
ground.addComponent('render', { type: 'box', material: groundMaterial });
|
|
89
|
+
ground.addComponent('collision', { type: 'box', halfExtents: new Vec3(10, 0.1, 10) });
|
|
90
|
+
ground.addComponent('rigidbody', { type: 'static' });
|
|
91
|
+
app.root.addChild(ground);
|
|
92
|
+
|
|
93
|
+
for (const [i, position] of BLOCKS.entries()) {
|
|
94
|
+
const block = new Entity(`block-${i}`);
|
|
95
|
+
block.setPosition(...position);
|
|
96
|
+
block.setLocalScale(...BLOCK_SCALE);
|
|
97
|
+
block.setEulerAngles(0, i * 25, 0);
|
|
98
|
+
block.addComponent('render', { type: 'box', material: blockMaterial });
|
|
99
|
+
block.addComponent('collision', { type: 'box', halfExtents: new Vec3(...BLOCK_HALF_EXTENTS) });
|
|
100
|
+
block.addComponent('rigidbody', { type: 'static' });
|
|
101
|
+
app.root.addChild(block);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const wood = new StandardMaterial();
|
|
105
|
+
wood.diffuse = new Color(0.34, 0.18, 0.08);
|
|
106
|
+
wood.update();
|
|
107
|
+
const leaves = new StandardMaterial();
|
|
108
|
+
leaves.diffuse = new Color(0.08, 0.3, 0.12);
|
|
109
|
+
leaves.update();
|
|
110
|
+
const tips = new StandardMaterial();
|
|
111
|
+
tips.diffuse = new Color(0.12, 0.4, 0.16);
|
|
112
|
+
tips.update();
|
|
113
|
+
const prop = (
|
|
114
|
+
name: string,
|
|
115
|
+
position: [number, number, number],
|
|
116
|
+
scale: [number, number, number],
|
|
117
|
+
material: StandardMaterial
|
|
118
|
+
) => {
|
|
119
|
+
const entity = new Entity(name);
|
|
120
|
+
entity.setPosition(...position);
|
|
121
|
+
entity.setLocalScale(...scale);
|
|
122
|
+
entity.addComponent('render', { type: 'box', material });
|
|
123
|
+
entity.addComponent('collision', {
|
|
124
|
+
type: 'box',
|
|
125
|
+
halfExtents: new Vec3(scale[0] / 2, scale[1] / 2, scale[2] / 2)
|
|
126
|
+
});
|
|
127
|
+
entity.addComponent('rigidbody', { type: 'static' });
|
|
128
|
+
app.root.addChild(entity);
|
|
129
|
+
};
|
|
130
|
+
for (const [i, [x, z, size]] of TREES.entries()) {
|
|
131
|
+
prop(`tree-${i}-trunk`, [x, size * 0.9, z], [size * 0.38, size * 1.8, size * 0.38], wood);
|
|
132
|
+
for (const [j, [y, width, height]] of PINE_LAYERS.entries()) {
|
|
133
|
+
const crown = new Entity(`tree-${i}-crown-${j}`);
|
|
134
|
+
crown.setPosition(x, size * y, z);
|
|
135
|
+
crown.setLocalScale(size * width, size * height, size * width);
|
|
136
|
+
crown.addComponent('render', { type: 'cone', material: j === 1 ? tips : leaves });
|
|
137
|
+
app.root.addChild(crown);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
prop('fallen-log', [0, 0.35, 6], [3.8, 0.7, 0.7], wood);
|
|
141
|
+
prop('stump', [-1.8, 0.4, -2.2], [0.9, 0.8, 0.9], wood);
|
|
142
|
+
|
|
143
|
+
const player = new Entity('player');
|
|
144
|
+
player.setPosition(0, 1.1, 0);
|
|
145
|
+
player.addComponent('collision', {
|
|
146
|
+
type: 'capsule',
|
|
147
|
+
radius: 0.5,
|
|
148
|
+
height: 2.4,
|
|
149
|
+
linearOffset: new Vec3(0, 0.1, 0)
|
|
150
|
+
});
|
|
151
|
+
player.addComponent('rigidbody', { type: 'dynamic', mass: 70, angularFactor: Vec3.ZERO });
|
|
152
|
+
app.root.addChild(player);
|
|
153
|
+
|
|
154
|
+
const shirt = new StandardMaterial();
|
|
155
|
+
shirt.diffuse = new Color(0.15, 0.55, 0.9);
|
|
156
|
+
shirt.update();
|
|
157
|
+
const skin = new StandardMaterial();
|
|
158
|
+
skin.diffuse = new Color(0.78, 0.53, 0.35);
|
|
159
|
+
skin.update();
|
|
160
|
+
const pants = new StandardMaterial();
|
|
161
|
+
pants.diffuse = new Color(0.18, 0.29, 0.52);
|
|
162
|
+
pants.update();
|
|
163
|
+
|
|
164
|
+
const model = new Entity('character');
|
|
165
|
+
model.setLocalPosition(0, 0.2, 0);
|
|
166
|
+
const part = (
|
|
167
|
+
parent: Entity,
|
|
168
|
+
name: string,
|
|
169
|
+
position: [number, number, number],
|
|
170
|
+
scale: [number, number, number],
|
|
171
|
+
material: StandardMaterial
|
|
172
|
+
) => {
|
|
173
|
+
const entity = new Entity(name);
|
|
174
|
+
entity.addComponent('render', { type: 'box', material });
|
|
175
|
+
entity.setLocalPosition(...position);
|
|
176
|
+
entity.setLocalScale(...scale);
|
|
177
|
+
parent.addChild(entity);
|
|
178
|
+
};
|
|
179
|
+
const limb = (
|
|
180
|
+
name: string,
|
|
181
|
+
position: [number, number, number],
|
|
182
|
+
offset: number,
|
|
183
|
+
scale: [number, number, number],
|
|
184
|
+
material: StandardMaterial
|
|
185
|
+
) => {
|
|
186
|
+
const joint = new Entity(name);
|
|
187
|
+
joint.setLocalPosition(...position);
|
|
188
|
+
model.addChild(joint);
|
|
189
|
+
part(joint, `${name}-mesh`, [0, offset, 0], scale, material);
|
|
190
|
+
};
|
|
191
|
+
part(model, 'body', [0, 0, 0], [0.56, 0.88, 0.38], shirt);
|
|
192
|
+
part(model, 'head', [0, 0.76, 0], [0.52, 0.6, 0.52], skin);
|
|
193
|
+
limb('left-arm', [-0.38, 0.44, 0], -0.44, [0.18, 0.88, 0.32], shirt);
|
|
194
|
+
limb('right-arm', [0.38, 0.44, 0], -0.44, [0.18, 0.88, 0.32], shirt);
|
|
195
|
+
limb('left-leg', [-0.15, -0.44, 0], -0.43, [0.24, 0.86, 0.34], pants);
|
|
196
|
+
limb('right-leg', [0.15, -0.44, 0], -0.43, [0.24, 0.86, 0.34], pants);
|
|
197
|
+
player.addChild(model);
|
|
198
|
+
|
|
199
|
+
const camera = new Entity('camera');
|
|
200
|
+
camera.addComponent('camera', { clearColor: new Color(0.48, 0.72, 0.9) });
|
|
201
|
+
camera.setPosition(0, 3, 6);
|
|
202
|
+
app.root.addChild(camera);
|
|
203
|
+
addThirdPersonController(player, camera, model);
|
|
204
|
+
|
|
205
|
+
const light = new Entity('light');
|
|
206
|
+
light.addComponent('light', {
|
|
207
|
+
type: 'directional',
|
|
208
|
+
intensity: 2.5,
|
|
209
|
+
castShadows: true,
|
|
210
|
+
shadowBias: 0.2,
|
|
211
|
+
normalOffsetBias: 0.05
|
|
212
|
+
});
|
|
213
|
+
light.setEulerAngles(45, 30, 0);
|
|
214
|
+
app.root.addChild(light);
|
|
215
|
+
|
|
216
|
+
window.addEventListener('resize', () => app.resizeCanvas());
|