create-playcanvas 0.9.0 → 0.10.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/package.json +2 -2
- package/skills/apply-conventions/SKILL.md +3 -3
- package/skills/assemble-scene/SKILL.md +1 -1
- package/skills/build-app/SKILL.md +2 -0
- package/skills/configure-animation/SKILL.md +1 -1
- package/skills/inspect-runtime/SKILL.md +41 -0
- package/skills/inspect-runtime/scripts/devtools-hook.js +23 -0
- package/skills/override-shader-chunks/SKILL.md +1 -1
- package/skills/reduce-draw-calls/SKILL.md +4 -4
- package/skills/verify-pixels/SKILL.md +1 -1
- package/templates/_shared/third-person-controller/src/controller.ts +25 -3
- package/templates/engine/base/README.md +4 -0
- package/templates/engine/base/package.json +3 -2
- package/templates/engine/base/src/inspector.css +33 -0
- package/templates/engine/base/src/inspector.ts +38 -0
- package/templates/engine/base/tsconfig.json +1 -1
- package/templates/engine/base/vite.config.ts +16 -0
- package/templates/engine/interactive-sphere/src/App.ts +2 -2
- package/templates/engine/model-viewer/src/main.ts +1 -2
- package/templates/engine/product-configurator/src/main.ts +6 -7
- package/templates/engine/third-person-controller/src/main.ts +10 -5
- package/templates/react/base/README.md +4 -0
- package/templates/react/base/package.json +4 -3
- package/templates/react/base/src/inspector.css +33 -0
- package/templates/react/base/src/inspector.ts +38 -0
- package/templates/react/base/vite.config.ts +12 -1
- package/templates/react/product-configurator/src/App.tsx +6 -6
- package/templates/react/third-person-controller/src/App.tsx +5 -2
- package/templates/web-components/base/README.md +4 -0
- package/templates/web-components/base/package.json +4 -3
- package/templates/web-components/base/src/inspector.css +33 -0
- package/templates/web-components/base/src/inspector.ts +38 -0
- package/templates/web-components/base/tsconfig.json +1 -1
- package/templates/web-components/base/vite.config.ts +16 -0
- package/templates/web-components/interactive-sphere/src/main.ts +1 -2
- package/templates/web-components/product-configurator/src/main.ts +5 -6
- package/templates/web-components/third-person-controller/index.html +17 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-playcanvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Scaffold a PlayCanvas project",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"playcanvas",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@clack/prompts": "^1.8.1",
|
|
46
46
|
"@playcanvas/eslint-config": "3.0.0-beta.8",
|
|
47
|
-
"@types/node": "22.20.
|
|
47
|
+
"@types/node": "22.20.5",
|
|
48
48
|
"eslint": "^9.39.5",
|
|
49
49
|
"mri": "^1.2.0",
|
|
50
50
|
"picocolors": "^1.1.1",
|
|
@@ -44,6 +44,6 @@ Check version-sensitive APIs against installed `playcanvas` declarations.
|
|
|
44
44
|
|
|
45
45
|
## Verify in the running app
|
|
46
46
|
|
|
47
|
-
Offline values cannot prove facing, poses, or placement.
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
Offline values cannot prove facing, poses, or placement. Read values from the running app with
|
|
48
|
+
`inspect-runtime`, and judge visuals only from screenshots you have inspected. A saved file path is
|
|
49
|
+
not evidence.
|
|
@@ -20,7 +20,7 @@ once; if the tuning record is missing, calibrate first.
|
|
|
20
20
|
## Check placement
|
|
21
21
|
|
|
22
22
|
After a rendered frame, measure each support or attachment at the object's actual position, using
|
|
23
|
-
a mount point, raycast, or geometry sample
|
|
23
|
+
a mount point, raycast, or geometry sample read through `inspect-runtime`. A positive gap floats; a negative gap penetrates.
|
|
24
24
|
Overlapping world AABBs between unrelated roots are a failure unless two different views prove the
|
|
25
25
|
overlap is empty space.
|
|
26
26
|
|
|
@@ -19,6 +19,8 @@ reference: [direct Engine](references/direct-engine.md), [React](references/reac
|
|
|
19
19
|
`canvas.width / canvas.clientWidth`.
|
|
20
20
|
- Rigid bodies stay inert until Ammo loads through `WasmModule`. Copy the loader from the
|
|
21
21
|
`physics/falling-shapes` example with `find-examples`.
|
|
22
|
+
- `devtools` defaults to `true`, so debugging tools can find the app (`inspect-runtime`). Set it to
|
|
23
|
+
`false` only to hide a production build.
|
|
22
24
|
|
|
23
25
|
## Update loop
|
|
24
26
|
|
|
@@ -20,7 +20,7 @@ Get clip and joint names from `inspect-glb`; never guess them.
|
|
|
20
20
|
## Prove playback
|
|
21
21
|
|
|
22
22
|
An active state or advancing state time does not prove motion. Sample two non-root joints over
|
|
23
|
-
several rendered frames at idle and at both ends of each blend tree. Zero joint motion means a
|
|
23
|
+
several rendered frames (`inspect-runtime`) at idle and at both ends of each blend tree. Zero joint motion means a
|
|
24
24
|
binding, playback, or blend-weight failure.
|
|
25
25
|
|
|
26
26
|
## Diagnose
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: inspect-runtime
|
|
3
|
+
description: Use when reading state from a running PlayCanvas app, to reach its live Application through the Engine devtools hook without changing app code.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Inspect a running app
|
|
7
|
+
|
|
8
|
+
From Engine 2.23, every app announces itself to a hook at
|
|
9
|
+
`globalThis[Symbol.for('playcanvas.inspector')]`. Read live state through it instead of adding
|
|
10
|
+
`window.app` or debug code to the app.
|
|
11
|
+
|
|
12
|
+
## Attach
|
|
13
|
+
|
|
14
|
+
- Resolve `scripts/devtools-hook.js` relative to this skill and inject it as an init script that
|
|
15
|
+
runs before any page script. A hook defined later misses existing apps; reload instead.
|
|
16
|
+
- Page code then reads `globalThis.__playcanvasApps`, a `Map` from each live app to
|
|
17
|
+
`{ version, revision, protocol }`. `app.init()` adds the entry and `app.destroy()` removes it.
|
|
18
|
+
- The script chains a hook defined before it, such as a devtools extension's.
|
|
19
|
+
- No entry means the Engine predates 2.23, the hook loaded late, or the app set
|
|
20
|
+
`devtools: false`. The Engine swallows errors thrown by the hook.
|
|
21
|
+
- In 2.23.0 only the debug build (`playcanvas/debug`, `build/playcanvas.dbg.*`) announces apps; the
|
|
22
|
+
default import never calls the hook. Later 2.23 patches fix every build.
|
|
23
|
+
|
|
24
|
+
## Read state
|
|
25
|
+
|
|
26
|
+
- Read after `frameend`, so values reflect a rendered frame.
|
|
27
|
+
- Return plain numbers and strings to the caller, not Engine objects.
|
|
28
|
+
- Useful reads: `app.root.findByName()` and `findByTag()`, world positions and
|
|
29
|
+
`meshInstance.aabb`, `app.stats.drawCallCount` and `frameTime`, and
|
|
30
|
+
`entity.anim.baseLayer.activeState`.
|
|
31
|
+
- Pause with `app.timeScale = 0`; `update` still runs with a zero `dt`. Step one frame by restoring
|
|
32
|
+
the scale, then zeroing it again on the next `frameend`.
|
|
33
|
+
- Revert anything you change to take a measurement.
|
|
34
|
+
|
|
35
|
+
## Inspector panel
|
|
36
|
+
|
|
37
|
+
`@playcanvas/inspector` adds a panel for people: `new Inspector(app)` shows the hierarchy, assets,
|
|
38
|
+
materials, shaders, textures, render targets, frame graph, and physics. It must import the app's
|
|
39
|
+
own `playcanvas` package, so add it only to development builds. It draws over the canvas and
|
|
40
|
+
applies view overrides, so destroy it before `verify-pixels` captures. Stepping through draws
|
|
41
|
+
needs the debug build.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Records every PlayCanvas app (Engine 2.23+) in globalThis.__playcanvasApps, a Map from app to
|
|
2
|
+
// { version, revision, protocol }. Run it before any page script; apps created earlier are missed.
|
|
3
|
+
// Plain script with no imports or exports, so it runs as an init script, a <script>, or an import.
|
|
4
|
+
{
|
|
5
|
+
const key = Symbol.for('playcanvas.inspector');
|
|
6
|
+
const prev = globalThis[key];
|
|
7
|
+
const apps = (globalThis.__playcanvasApps ??= new Map());
|
|
8
|
+
|
|
9
|
+
// chain a hook defined earlier, such as a devtools extension's, and install only once
|
|
10
|
+
if (!prev?.playcanvasApps) {
|
|
11
|
+
globalThis[key] = {
|
|
12
|
+
playcanvasApps: apps,
|
|
13
|
+
register(app, info) {
|
|
14
|
+
apps.set(app, info);
|
|
15
|
+
prev?.register?.(app, info);
|
|
16
|
+
},
|
|
17
|
+
unregister(app) {
|
|
18
|
+
apps.delete(app);
|
|
19
|
+
prev?.unregister?.(app);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -8,7 +8,7 @@ description: Use when a PlayCanvas StandardMaterial needs custom shading its pro
|
|
|
8
8
|
Overriding a `StandardMaterial` chunk keeps fog, tone mapping, shadows, skinning, and instancing
|
|
9
9
|
intact. Use a `ShaderMaterial` only when the look needs its own vertex and fragment pair.
|
|
10
10
|
|
|
11
|
-
- Set `material.shaderChunksVersion` to the installed major.minor, such as `'2.
|
|
11
|
+
- Set `material.shaderChunksVersion` to the installed major.minor, such as `'2.23'`. Chunk names
|
|
12
12
|
and contracts change between versions, so read the installed chunk source, not memory.
|
|
13
13
|
- Set chunks with `material.getShaderChunks(SHADERLANGUAGE_GLSL).set(name, source)` and the WGSL
|
|
14
14
|
equivalent, then call `material.update()`. For every material on a device, use
|
|
@@ -5,10 +5,10 @@ description: Use when a PlayCanvas app has too many draw calls or is CPU-bound,
|
|
|
5
5
|
|
|
6
6
|
# Cut draw calls
|
|
7
7
|
|
|
8
|
-
Measure first with `app.stats.
|
|
9
|
-
profiler builds. Also halve the pixel ratio and call `app.resizeCanvas()`:
|
|
10
|
-
cost is fill rate, not draw calls. Stop at the first rung that meets the
|
|
11
|
-
before-and-after counts.
|
|
8
|
+
Measure first with `app.stats.drawCallCount` or `MiniStats`; the per-pass `app.stats.drawCalls`
|
|
9
|
+
breakdown fills only in profiler builds. Also halve the pixel ratio and call `app.resizeCanvas()`:
|
|
10
|
+
if frame time drops, the cost is fill rate, not draw calls. Stop at the first rung that meets the
|
|
11
|
+
budget and report before-and-after counts.
|
|
12
12
|
|
|
13
13
|
## 1. Stop drawing hidden things
|
|
14
14
|
|
|
@@ -33,4 +33,4 @@ description: Use when a PlayCanvas rendering change is meant to leave the image
|
|
|
33
33
|
- Report the count, for example "0 of 65536 pixels differ", or the count and where the differences
|
|
34
34
|
are. "Looks the same" is not a result.
|
|
35
35
|
|
|
36
|
-
Keep harness code out of the shipped bundle
|
|
36
|
+
Keep harness code out of the shipped bundle; attach it through `inspect-runtime`.
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { Entity, RigidBodyComponentSystem } from 'playcanvas';
|
|
2
|
-
import { Script } from 'playcanvas';
|
|
2
|
+
import { Script, Vec3 } from 'playcanvas';
|
|
3
3
|
import { ThirdPersonController } from 'playcanvas/scripts/esm/third-person-controller.mjs';
|
|
4
4
|
|
|
5
|
+
/** Ground probes under the player's box collider (half width 0.4): centre, edges and corners */
|
|
6
|
+
const FEET = [-0.38, 0, 0.38].flatMap((x) => [-0.38, 0, 0.38].map((z) => [x, z]));
|
|
7
|
+
|
|
5
8
|
class RobotAnimation extends Script {
|
|
6
9
|
static scriptName = 'robotAnimation';
|
|
7
10
|
|
|
@@ -28,9 +31,10 @@ class RobotAnimation extends Script {
|
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
export const addThirdPersonController = (player: Entity, camera: Entity, model: Entity) => {
|
|
31
|
-
|
|
34
|
+
const sys = player.rigidbody!.system as RigidBodyComponentSystem;
|
|
35
|
+
sys.gravity.set(0, -18, 0);
|
|
32
36
|
if (!player.script) player.addComponent('script');
|
|
33
|
-
player.script!.create(ThirdPersonController, {
|
|
37
|
+
const controller = player.script!.create(ThirdPersonController, {
|
|
34
38
|
properties: {
|
|
35
39
|
camera,
|
|
36
40
|
characterModel: model,
|
|
@@ -41,6 +45,24 @@ export const addThirdPersonController = (player: Entity, camera: Entity, model:
|
|
|
41
45
|
jumpForce: 520,
|
|
42
46
|
speedGround: 60
|
|
43
47
|
}
|
|
48
|
+
})!;
|
|
49
|
+
|
|
50
|
+
// The stock ground check is one ray from the centre, so a player standing on a ledge reads as
|
|
51
|
+
// airborne and can't jump. Count it grounded when any probe under the collider finds a solid
|
|
52
|
+
// surface; triggers (colliders without a rigid body) only stop the camera
|
|
53
|
+
const [from, to] = [new Vec3(), new Vec3()];
|
|
54
|
+
const solid = { filterCallback: (e: Entity) => !!e.rigidbody };
|
|
55
|
+
let grounded = false;
|
|
56
|
+
Object.defineProperty(controller, '_grounded', {
|
|
57
|
+
get: () => grounded,
|
|
58
|
+
set: () => {
|
|
59
|
+
const p = player.getPosition();
|
|
60
|
+
grounded = FEET.some(([x, z]) => {
|
|
61
|
+
from.set(p.x + x, p.y, p.z + z);
|
|
62
|
+
to.set(from.x, p.y - 1.1, from.z);
|
|
63
|
+
return !!sys.raycastFirst(from, to, solid);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
44
66
|
});
|
|
45
67
|
|
|
46
68
|
if (!model.script) model.addComponent('script');
|
|
@@ -17,6 +17,10 @@ npm run dev
|
|
|
17
17
|
|
|
18
18
|
Open <http://localhost:5173>. Edit the files under `src/` and save to see the scene update.
|
|
19
19
|
|
|
20
|
+
## Inspector
|
|
21
|
+
|
|
22
|
+
`npm run dev` loads the [PlayCanvas Inspector](https://github.com/playcanvas/inspector), a debug panel for the hierarchy, assets, materials, textures, frame graph and physics. Press the backquote key (<kbd>`</kbd>) to show or hide it, <kbd>F9</kbd> to pause and <kbd>F10</kbd> to step a frame. It is wired up in `vite.config.ts` and `src/inspector.ts` and is left out of production builds.
|
|
23
|
+
|
|
20
24
|
## Scripts
|
|
21
25
|
|
|
22
26
|
| Command | Description |
|
|
@@ -15,14 +15,15 @@
|
|
|
15
15
|
"typecheck": "tsc --noEmit"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"playcanvas": "^2.
|
|
18
|
+
"playcanvas": "^2.23.1"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@playcanvas/eslint-config": "3.0.0-beta.8",
|
|
22
|
+
"@playcanvas/inspector": "^0.2.0",
|
|
22
23
|
"eslint": "^9.39.5",
|
|
23
24
|
"prettier": "^3.9.9",
|
|
24
25
|
"typescript": "~6.0.3",
|
|
25
|
-
"vite": "^8.3.
|
|
26
|
+
"vite": "^8.3.4"
|
|
26
27
|
},
|
|
27
28
|
"engines": {
|
|
28
29
|
"node": ">=22.23.2"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* Inspector key hint, added on the dev server only by src/inspector.ts. Styled like the starter panels */
|
|
2
|
+
.inspector-hint {
|
|
3
|
+
position: fixed;
|
|
4
|
+
bottom: 20px;
|
|
5
|
+
left: 20px;
|
|
6
|
+
z-index: 10;
|
|
7
|
+
margin: 0;
|
|
8
|
+
padding: 8px 12px;
|
|
9
|
+
border: 1px solid #ffffff24;
|
|
10
|
+
border-radius: 10px;
|
|
11
|
+
background: #0b1019d9;
|
|
12
|
+
box-shadow: 0 12px 40px #0008;
|
|
13
|
+
color: #aeb8c8;
|
|
14
|
+
font:
|
|
15
|
+
12px/1.6 Inter,
|
|
16
|
+
ui-sans-serif,
|
|
17
|
+
system-ui,
|
|
18
|
+
sans-serif;
|
|
19
|
+
backdrop-filter: blur(12px);
|
|
20
|
+
pointer-events: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.inspector-hint kbd {
|
|
24
|
+
padding: 1px 5px;
|
|
25
|
+
border-radius: 4px;
|
|
26
|
+
background: #263247;
|
|
27
|
+
color: #f8fafc;
|
|
28
|
+
font:
|
|
29
|
+
11px ui-monospace,
|
|
30
|
+
SFMono-Regular,
|
|
31
|
+
Menlo,
|
|
32
|
+
monospace;
|
|
33
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Loaded by the dev server only (see vite.config.ts), so production builds never include it
|
|
2
|
+
import { Inspector } from '@playcanvas/inspector';
|
|
3
|
+
import type { AppBase } from 'playcanvas';
|
|
4
|
+
|
|
5
|
+
import './inspector.css';
|
|
6
|
+
|
|
7
|
+
type Hook = {
|
|
8
|
+
register?(app: AppBase, info: object): void;
|
|
9
|
+
unregister?(app: AppBase): void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Every app announces itself to this hook as it starts, whichever format created it
|
|
13
|
+
const KEY = Symbol.for('playcanvas.inspector');
|
|
14
|
+
const scope = globalThis as unknown as Record<symbol, Hook | undefined>;
|
|
15
|
+
|
|
16
|
+
// Keep a hook installed earlier, such as a devtools extension's
|
|
17
|
+
const prev = scope[KEY];
|
|
18
|
+
|
|
19
|
+
scope[KEY] = {
|
|
20
|
+
register(app, info) {
|
|
21
|
+
prev?.register?.(app, info);
|
|
22
|
+
|
|
23
|
+
// Skip headless apps, such as the null apps @playcanvas/react creates for validation. Each
|
|
24
|
+
// panel destroys itself with its app
|
|
25
|
+
if (!app.graphicsDevice.isNull) new Inspector(app, { visible: false });
|
|
26
|
+
},
|
|
27
|
+
unregister(app) {
|
|
28
|
+
prev?.unregister?.(app);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
console.info('PlayCanvas Inspector: press ` to toggle');
|
|
33
|
+
|
|
34
|
+
// Key hint, a separate corner chip so the starter's own UI stays untouched
|
|
35
|
+
const hint = document.createElement('p');
|
|
36
|
+
hint.className = 'inspector-hint';
|
|
37
|
+
hint.innerHTML = '<kbd>`</kbd> inspector · <kbd>F9</kbd> pause · <kbd>F10</kbd> step';
|
|
38
|
+
document.body.append(hint);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
|
|
3
|
+
// https://vite.dev/config/
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [
|
|
6
|
+
{
|
|
7
|
+
// Dev only: install the PlayCanvas Inspector before the app starts (press ` to toggle)
|
|
8
|
+
name: 'playcanvas-inspector',
|
|
9
|
+
apply: 'serve',
|
|
10
|
+
config: () => ({ optimizeDeps: { include: ['@playcanvas/inspector'] } }),
|
|
11
|
+
transformIndexHtml: () => [
|
|
12
|
+
{ tag: 'script', attrs: { type: 'module', src: '/src/inspector.ts' }, injectTo: 'head-prepend' }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Layer
|
|
1
|
+
import type { Layer } from 'playcanvas';
|
|
2
2
|
import {
|
|
3
3
|
createGraphicsDevice,
|
|
4
4
|
AppBase,
|
|
@@ -89,7 +89,7 @@ const setupApp = async (canvas: HTMLCanvasElement, onClick: () => void) => {
|
|
|
89
89
|
app.start();
|
|
90
90
|
|
|
91
91
|
// Set up environment lighting (no skybox, just IBL)
|
|
92
|
-
app.scene.envAtlas = assets.envAtlas.resource
|
|
92
|
+
app.scene.envAtlas = assets.envAtlas.resource!;
|
|
93
93
|
const skyboxLayer = app.scene.layers.getLayerByName('Skybox');
|
|
94
94
|
if (skyboxLayer) {
|
|
95
95
|
skyboxLayer.enabled = false;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ContainerResource } from 'playcanvas';
|
|
2
1
|
import {
|
|
3
2
|
AppBase,
|
|
4
3
|
AppOptions,
|
|
@@ -57,7 +56,7 @@ await new Promise<void>((resolve) => {
|
|
|
57
56
|
app.start();
|
|
58
57
|
|
|
59
58
|
// A container asset holds the whole glTF scene, so instantiate it to get an entity hierarchy
|
|
60
|
-
app.root.addChild(
|
|
59
|
+
app.root.addChild(model.resource!.instantiateRenderEntity());
|
|
61
60
|
|
|
62
61
|
// The procedural sky is both the background and the image-based lighting, so no light or
|
|
63
62
|
// environment map asset is needed. Its default luminance is scene-scale bright, so turn it down to
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ContainerResource, RenderComponent } from 'playcanvas';
|
|
2
1
|
import {
|
|
3
2
|
AppBase,
|
|
4
3
|
AppOptions,
|
|
@@ -62,12 +61,12 @@ await new Promise<void>((resolve) => new AssetListLoader([product], app.assets).
|
|
|
62
61
|
|
|
63
62
|
app.start();
|
|
64
63
|
|
|
65
|
-
const car =
|
|
64
|
+
const car = product.resource!.instantiateRenderEntity();
|
|
66
65
|
car.setLocalEulerAngles(-90, -25, 0);
|
|
67
66
|
app.root.addChild(car);
|
|
68
67
|
app.root.syncHierarchy();
|
|
69
68
|
|
|
70
|
-
const meshes =
|
|
69
|
+
const meshes = car.findComponents('render').flatMap((render) => render.meshInstances);
|
|
71
70
|
const bounds = meshes[0].aabb.clone();
|
|
72
71
|
meshes.slice(1).forEach((mesh) => bounds.add(mesh.aabb));
|
|
73
72
|
const scale = 5 / Math.max(bounds.halfExtents.x * 2, bounds.halfExtents.z * 2);
|
|
@@ -77,10 +76,10 @@ bounds.copy(meshes[0].aabb);
|
|
|
77
76
|
meshes.slice(1).forEach((mesh) => bounds.add(mesh.aabb));
|
|
78
77
|
car.setPosition(-bounds.center.x, 0.03 - bounds.getMin().y, -bounds.center.z);
|
|
79
78
|
|
|
80
|
-
const body = meshes.filter((mesh) => mesh.material
|
|
81
|
-
const panels = meshes.filter((mesh) => mesh.material
|
|
82
|
-
const paint = body[0].material
|
|
83
|
-
const shade = panels[0].material
|
|
79
|
+
const body = meshes.filter((mesh) => mesh.material?.name === 'material');
|
|
80
|
+
const panels = meshes.filter((mesh) => mesh.material?.name === 'material_1');
|
|
81
|
+
const paint = body[0].material!.clone() as StandardMaterial;
|
|
82
|
+
const shade = panels[0].material!.clone() as StandardMaterial;
|
|
84
83
|
body.forEach((mesh) => (mesh.material = paint));
|
|
85
84
|
panels.forEach((mesh) => (mesh.material = shade));
|
|
86
85
|
paint.diffuse.fromString(PAINTS[0].color);
|
|
@@ -134,6 +134,9 @@ for (const [i, [x, z, size]] of TREES.entries()) {
|
|
|
134
134
|
crown.setPosition(x, size * y, z);
|
|
135
135
|
crown.setLocalScale(size * width, size * height, size * width);
|
|
136
136
|
crown.addComponent('render', { type: 'cone', material: j === 1 ? tips : leaves });
|
|
137
|
+
|
|
138
|
+
// A trigger: it stops the camera's line of sight but the player walks through it
|
|
139
|
+
crown.addComponent('collision', { type: 'cone', radius: (size * width) / 2, height: size * height });
|
|
137
140
|
app.root.addChild(crown);
|
|
138
141
|
}
|
|
139
142
|
}
|
|
@@ -142,11 +145,13 @@ prop('stump', [-1.8, 0.4, -2.2], [0.9, 0.8, 0.9], wood);
|
|
|
142
145
|
|
|
143
146
|
const player = new Entity('player');
|
|
144
147
|
player.setPosition(0, 1.1, 0);
|
|
148
|
+
|
|
149
|
+
// A box rather than a capsule, so the player stands on ledges instead of sliding off them. Its
|
|
150
|
+
// bottom sits 1 m below the body, inside the controller's 1.1 m ground probes
|
|
145
151
|
player.addComponent('collision', {
|
|
146
|
-
type: '
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
linearOffset: new Vec3(0, 0.1, 0)
|
|
152
|
+
type: 'box',
|
|
153
|
+
halfExtents: new Vec3(0.4, 1.2, 0.4),
|
|
154
|
+
linearOffset: new Vec3(0, 0.2, 0)
|
|
150
155
|
});
|
|
151
156
|
player.addComponent('rigidbody', { type: 'dynamic', mass: 70, angularFactor: Vec3.ZERO });
|
|
152
157
|
app.root.addChild(player);
|
|
@@ -162,7 +167,7 @@ pants.diffuse = new Color(0.18, 0.29, 0.52);
|
|
|
162
167
|
pants.update();
|
|
163
168
|
|
|
164
169
|
const model = new Entity('character');
|
|
165
|
-
model.setLocalPosition(0, 0.
|
|
170
|
+
model.setLocalPosition(0, 0.3, 0);
|
|
166
171
|
const part = (
|
|
167
172
|
parent: Entity,
|
|
168
173
|
name: string,
|
|
@@ -17,6 +17,10 @@ npm run dev
|
|
|
17
17
|
|
|
18
18
|
Open <http://localhost:5173>. Edit the files under `src/` and save to see the scene update.
|
|
19
19
|
|
|
20
|
+
## Inspector
|
|
21
|
+
|
|
22
|
+
`npm run dev` loads the [PlayCanvas Inspector](https://github.com/playcanvas/inspector), a debug panel for the hierarchy, assets, materials, textures, frame graph and physics. Press the backquote key (<kbd>`</kbd>) to show or hide it, <kbd>F9</kbd> to pause and <kbd>F10</kbd> to step a frame. It is wired up in `vite.config.ts` and `src/inspector.ts` and is left out of production builds.
|
|
23
|
+
|
|
20
24
|
## Scripts
|
|
21
25
|
|
|
22
26
|
| Command | Description |
|
|
@@ -16,19 +16,20 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@playcanvas/react": "^0.11.7",
|
|
19
|
-
"playcanvas": "^2.
|
|
19
|
+
"playcanvas": "^2.23.1",
|
|
20
20
|
"react": "^19.3.0",
|
|
21
21
|
"react-dom": "^19.3.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@playcanvas/eslint-config": "3.0.0-beta.8",
|
|
25
|
+
"@playcanvas/inspector": "^0.2.0",
|
|
25
26
|
"@types/react": "^19.3.0",
|
|
26
27
|
"@types/react-dom": "^19.3.0",
|
|
27
|
-
"@vitejs/plugin-react": "^6.1.
|
|
28
|
+
"@vitejs/plugin-react": "^6.1.2",
|
|
28
29
|
"eslint": "^9.39.5",
|
|
29
30
|
"prettier": "^3.9.9",
|
|
30
31
|
"typescript": "~6.0.3",
|
|
31
|
-
"vite": "^8.3.
|
|
32
|
+
"vite": "^8.3.4"
|
|
32
33
|
},
|
|
33
34
|
"engines": {
|
|
34
35
|
"node": ">=22.23.2"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* Inspector key hint, added on the dev server only by src/inspector.ts. Styled like the starter panels */
|
|
2
|
+
.inspector-hint {
|
|
3
|
+
position: fixed;
|
|
4
|
+
bottom: 20px;
|
|
5
|
+
left: 20px;
|
|
6
|
+
z-index: 10;
|
|
7
|
+
margin: 0;
|
|
8
|
+
padding: 8px 12px;
|
|
9
|
+
border: 1px solid #ffffff24;
|
|
10
|
+
border-radius: 10px;
|
|
11
|
+
background: #0b1019d9;
|
|
12
|
+
box-shadow: 0 12px 40px #0008;
|
|
13
|
+
color: #aeb8c8;
|
|
14
|
+
font:
|
|
15
|
+
12px/1.6 Inter,
|
|
16
|
+
ui-sans-serif,
|
|
17
|
+
system-ui,
|
|
18
|
+
sans-serif;
|
|
19
|
+
backdrop-filter: blur(12px);
|
|
20
|
+
pointer-events: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.inspector-hint kbd {
|
|
24
|
+
padding: 1px 5px;
|
|
25
|
+
border-radius: 4px;
|
|
26
|
+
background: #263247;
|
|
27
|
+
color: #f8fafc;
|
|
28
|
+
font:
|
|
29
|
+
11px ui-monospace,
|
|
30
|
+
SFMono-Regular,
|
|
31
|
+
Menlo,
|
|
32
|
+
monospace;
|
|
33
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Loaded by the dev server only (see vite.config.ts), so production builds never include it
|
|
2
|
+
import { Inspector } from '@playcanvas/inspector';
|
|
3
|
+
import type { AppBase } from 'playcanvas';
|
|
4
|
+
|
|
5
|
+
import './inspector.css';
|
|
6
|
+
|
|
7
|
+
type Hook = {
|
|
8
|
+
register?(app: AppBase, info: object): void;
|
|
9
|
+
unregister?(app: AppBase): void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Every app announces itself to this hook as it starts, whichever format created it
|
|
13
|
+
const KEY = Symbol.for('playcanvas.inspector');
|
|
14
|
+
const scope = globalThis as unknown as Record<symbol, Hook | undefined>;
|
|
15
|
+
|
|
16
|
+
// Keep a hook installed earlier, such as a devtools extension's
|
|
17
|
+
const prev = scope[KEY];
|
|
18
|
+
|
|
19
|
+
scope[KEY] = {
|
|
20
|
+
register(app, info) {
|
|
21
|
+
prev?.register?.(app, info);
|
|
22
|
+
|
|
23
|
+
// Skip headless apps, such as the null apps @playcanvas/react creates for validation. Each
|
|
24
|
+
// panel destroys itself with its app
|
|
25
|
+
if (!app.graphicsDevice.isNull) new Inspector(app, { visible: false });
|
|
26
|
+
},
|
|
27
|
+
unregister(app) {
|
|
28
|
+
prev?.unregister?.(app);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
console.info('PlayCanvas Inspector: press ` to toggle');
|
|
33
|
+
|
|
34
|
+
// Key hint, a separate corner chip so the starter's own UI stays untouched
|
|
35
|
+
const hint = document.createElement('p');
|
|
36
|
+
hint.className = 'inspector-hint';
|
|
37
|
+
hint.innerHTML = '<kbd>`</kbd> inspector · <kbd>F9</kbd> pause · <kbd>F10</kbd> step';
|
|
38
|
+
document.body.append(hint);
|
|
@@ -3,5 +3,16 @@ import { defineConfig } from 'vite';
|
|
|
3
3
|
|
|
4
4
|
// https://vite.dev/config/
|
|
5
5
|
export default defineConfig({
|
|
6
|
-
plugins: [
|
|
6
|
+
plugins: [
|
|
7
|
+
react(),
|
|
8
|
+
{
|
|
9
|
+
// Dev only: install the PlayCanvas Inspector before the app starts (press ` to toggle)
|
|
10
|
+
name: 'playcanvas-inspector',
|
|
11
|
+
apply: 'serve',
|
|
12
|
+
config: () => ({ optimizeDeps: { include: ['@playcanvas/inspector'] } }),
|
|
13
|
+
transformIndexHtml: () => [
|
|
14
|
+
{ tag: 'script', attrs: { type: 'module', src: '/src/inspector.ts' }, injectTo: 'head-prepend' }
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
7
18
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Application, Entity } from '@playcanvas/react';
|
|
2
2
|
import { Camera, Light, Script } from '@playcanvas/react/components';
|
|
3
3
|
import { useApp, useModel } from '@playcanvas/react/hooks';
|
|
4
|
-
import type { ContainerResource, Entity as PcEntity
|
|
4
|
+
import type { ContainerResource, Entity as PcEntity } from 'playcanvas';
|
|
5
5
|
import { Color, CylinderGeometry, Mesh, MeshInstance, StandardMaterial, TONEMAP_ACES2, Vec2, Vec3 } from 'playcanvas';
|
|
6
6
|
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
|
|
7
7
|
import { ProceduralSky } from 'playcanvas/scripts/esm/sky/procedural-sky.mjs';
|
|
@@ -32,7 +32,7 @@ function Product({ color, shade }: { color: string; shade: string }) {
|
|
|
32
32
|
entity.setLocalEulerAngles(-90, -25, 0);
|
|
33
33
|
root.current.addChild(entity);
|
|
34
34
|
|
|
35
|
-
const meshes =
|
|
35
|
+
const meshes = entity.findComponents('render').flatMap((render) => render.meshInstances);
|
|
36
36
|
const bounds = meshes[0].aabb.clone();
|
|
37
37
|
meshes.slice(1).forEach((mesh) => bounds.add(mesh.aabb));
|
|
38
38
|
const scale = 5 / Math.max(bounds.halfExtents.x * 2, bounds.halfExtents.z * 2);
|
|
@@ -42,10 +42,10 @@ function Product({ color, shade }: { color: string; shade: string }) {
|
|
|
42
42
|
meshes.slice(1).forEach((mesh) => bounds.add(mesh.aabb));
|
|
43
43
|
entity.setPosition(-bounds.center.x, 0.03 - bounds.getMin().y, -bounds.center.z);
|
|
44
44
|
|
|
45
|
-
const body = meshes.filter((mesh) => mesh.material
|
|
46
|
-
const panels = meshes.filter((mesh) => mesh.material
|
|
47
|
-
paint.current = body[0].material
|
|
48
|
-
tone.current = panels[0].material
|
|
45
|
+
const body = meshes.filter((mesh) => mesh.material?.name === 'material');
|
|
46
|
+
const panels = meshes.filter((mesh) => mesh.material?.name === 'material_1');
|
|
47
|
+
paint.current = body[0].material!.clone() as StandardMaterial;
|
|
48
|
+
tone.current = panels[0].material!.clone() as StandardMaterial;
|
|
49
49
|
body.forEach((mesh) => (mesh.material = paint.current!));
|
|
50
50
|
panels.forEach((mesh) => (mesh.material = tone.current!));
|
|
51
51
|
return () => {
|
|
@@ -79,6 +79,8 @@ function Pine({
|
|
|
79
79
|
scale={[size * width, size * height, size * width]}
|
|
80
80
|
>
|
|
81
81
|
<Render type="cone" material={i === 1 ? tips : leaves} />
|
|
82
|
+
{/* A trigger: it stops the camera's line of sight but the player walks through it */}
|
|
83
|
+
<Collision type="cone" radius={(size * width) / 2} height={size * height} />
|
|
82
84
|
</Entity>
|
|
83
85
|
))}
|
|
84
86
|
</>
|
|
@@ -139,9 +141,10 @@ function Scene() {
|
|
|
139
141
|
<Block name="fallen-log" position={[0, 0.35, 6]} scale={[3.8, 0.7, 0.7]} material={wood} />
|
|
140
142
|
<Block name="stump" position={[-1.8, 0.4, -2.2]} scale={[0.9, 0.8, 0.9]} material={wood} />
|
|
141
143
|
<Entity name="player" ref={player} position={[0, 1.1, 0]}>
|
|
142
|
-
|
|
144
|
+
{/* A box rather than a capsule, so the player stands on ledges instead of sliding off them */}
|
|
145
|
+
<Collision type="box" halfExtents={[0.4, 1.2, 0.4]} linearOffset={[0, 0.2, 0]} />
|
|
143
146
|
<RigidBody type="dynamic" mass={70} angularFactor={[0, 0, 0]} />
|
|
144
|
-
<Entity name="character" ref={model} position={[0, 0.
|
|
147
|
+
<Entity name="character" ref={model} position={[0, 0.3, 0]}>
|
|
145
148
|
<Entity name="body" scale={[0.56, 0.88, 0.38]}>
|
|
146
149
|
<Render type="box" material={shirt} />
|
|
147
150
|
</Entity>
|
|
@@ -18,6 +18,10 @@ npm run dev
|
|
|
18
18
|
|
|
19
19
|
Open <http://localhost:5173>. Edit `index.html` to change the scene.
|
|
20
20
|
|
|
21
|
+
## Inspector
|
|
22
|
+
|
|
23
|
+
`npm run dev` loads the [PlayCanvas Inspector](https://github.com/playcanvas/inspector), a debug panel for the hierarchy, assets, materials, textures, frame graph and physics. Press the backquote key (<kbd>`</kbd>) to show or hide it, <kbd>F9</kbd> to pause and <kbd>F10</kbd> to step a frame. It is wired up in `vite.config.ts` and `src/inspector.ts` and is left out of production builds.
|
|
24
|
+
|
|
21
25
|
## Scripts
|
|
22
26
|
|
|
23
27
|
| Command | Description |
|
|
@@ -15,15 +15,16 @@
|
|
|
15
15
|
"typecheck": "tsc --noEmit"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@playcanvas/web-components": "^0.
|
|
19
|
-
"playcanvas": "^2.
|
|
18
|
+
"@playcanvas/web-components": "^0.28.0",
|
|
19
|
+
"playcanvas": "^2.23.1"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@playcanvas/eslint-config": "3.0.0-beta.8",
|
|
23
|
+
"@playcanvas/inspector": "^0.2.0",
|
|
23
24
|
"eslint": "^9.39.5",
|
|
24
25
|
"prettier": "^3.9.9",
|
|
25
26
|
"typescript": "~6.0.3",
|
|
26
|
-
"vite": "^8.3.
|
|
27
|
+
"vite": "^8.3.4"
|
|
27
28
|
},
|
|
28
29
|
"engines": {
|
|
29
30
|
"node": ">=22.23.2"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* Inspector key hint, added on the dev server only by src/inspector.ts. Styled like the starter panels */
|
|
2
|
+
.inspector-hint {
|
|
3
|
+
position: fixed;
|
|
4
|
+
bottom: 20px;
|
|
5
|
+
left: 20px;
|
|
6
|
+
z-index: 10;
|
|
7
|
+
margin: 0;
|
|
8
|
+
padding: 8px 12px;
|
|
9
|
+
border: 1px solid #ffffff24;
|
|
10
|
+
border-radius: 10px;
|
|
11
|
+
background: #0b1019d9;
|
|
12
|
+
box-shadow: 0 12px 40px #0008;
|
|
13
|
+
color: #aeb8c8;
|
|
14
|
+
font:
|
|
15
|
+
12px/1.6 Inter,
|
|
16
|
+
ui-sans-serif,
|
|
17
|
+
system-ui,
|
|
18
|
+
sans-serif;
|
|
19
|
+
backdrop-filter: blur(12px);
|
|
20
|
+
pointer-events: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.inspector-hint kbd {
|
|
24
|
+
padding: 1px 5px;
|
|
25
|
+
border-radius: 4px;
|
|
26
|
+
background: #263247;
|
|
27
|
+
color: #f8fafc;
|
|
28
|
+
font:
|
|
29
|
+
11px ui-monospace,
|
|
30
|
+
SFMono-Regular,
|
|
31
|
+
Menlo,
|
|
32
|
+
monospace;
|
|
33
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Loaded by the dev server only (see vite.config.ts), so production builds never include it
|
|
2
|
+
import { Inspector } from '@playcanvas/inspector';
|
|
3
|
+
import type { AppBase } from 'playcanvas';
|
|
4
|
+
|
|
5
|
+
import './inspector.css';
|
|
6
|
+
|
|
7
|
+
type Hook = {
|
|
8
|
+
register?(app: AppBase, info: object): void;
|
|
9
|
+
unregister?(app: AppBase): void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Every app announces itself to this hook as it starts, whichever format created it
|
|
13
|
+
const KEY = Symbol.for('playcanvas.inspector');
|
|
14
|
+
const scope = globalThis as unknown as Record<symbol, Hook | undefined>;
|
|
15
|
+
|
|
16
|
+
// Keep a hook installed earlier, such as a devtools extension's
|
|
17
|
+
const prev = scope[KEY];
|
|
18
|
+
|
|
19
|
+
scope[KEY] = {
|
|
20
|
+
register(app, info) {
|
|
21
|
+
prev?.register?.(app, info);
|
|
22
|
+
|
|
23
|
+
// Skip headless apps, such as the null apps @playcanvas/react creates for validation. Each
|
|
24
|
+
// panel destroys itself with its app
|
|
25
|
+
if (!app.graphicsDevice.isNull) new Inspector(app, { visible: false });
|
|
26
|
+
},
|
|
27
|
+
unregister(app) {
|
|
28
|
+
prev?.unregister?.(app);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
console.info('PlayCanvas Inspector: press ` to toggle');
|
|
33
|
+
|
|
34
|
+
// Key hint, a separate corner chip so the starter's own UI stays untouched
|
|
35
|
+
const hint = document.createElement('p');
|
|
36
|
+
hint.className = 'inspector-hint';
|
|
37
|
+
hint.innerHTML = '<kbd>`</kbd> inspector · <kbd>F9</kbd> pause · <kbd>F10</kbd> step';
|
|
38
|
+
document.body.append(hint);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
|
|
3
|
+
// https://vite.dev/config/
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [
|
|
6
|
+
{
|
|
7
|
+
// Dev only: install the PlayCanvas Inspector before the app starts (press ` to toggle)
|
|
8
|
+
name: 'playcanvas-inspector',
|
|
9
|
+
apply: 'serve',
|
|
10
|
+
config: () => ({ optimizeDeps: { include: ['@playcanvas/inspector'] } }),
|
|
11
|
+
transformIndexHtml: () => [
|
|
12
|
+
{ tag: 'script', attrs: { type: 'module', src: '/src/inspector.ts' }, injectTo: 'head-prepend' }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { EntityElement } from '@playcanvas/web-components';
|
|
2
2
|
import { whenReady } from '@playcanvas/web-components';
|
|
3
|
-
import type { Texture } from 'playcanvas';
|
|
4
3
|
import { Asset, TEXTURETYPE_RGBP } from 'playcanvas';
|
|
5
4
|
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
|
|
6
5
|
import { Grid } from 'playcanvas/scripts/esm/grid.mjs';
|
|
@@ -40,7 +39,7 @@ await new Promise<void>((resolve) => {
|
|
|
40
39
|
});
|
|
41
40
|
|
|
42
41
|
// Light the scene from the environment, with the skybox itself left hidden
|
|
43
|
-
app.scene.envAtlas = envAtlas.resource
|
|
42
|
+
app.scene.envAtlas = envAtlas.resource!;
|
|
44
43
|
const skyboxLayer = app.scene.layers.getLayerByName('Skybox');
|
|
45
44
|
if (skyboxLayer) {
|
|
46
45
|
skyboxLayer.enabled = false;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { AssetElement, EntityElement, ModelElement } from '@playcanvas/web-components';
|
|
2
2
|
import { whenReady } from '@playcanvas/web-components';
|
|
3
|
-
import type { RenderComponent } from 'playcanvas';
|
|
4
3
|
import { Color, CylinderGeometry, Mesh, MeshInstance, StandardMaterial, Vec2, Vec3 } from 'playcanvas';
|
|
5
4
|
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
|
|
6
5
|
import { ProceduralSky } from 'playcanvas/scripts/esm/sky/procedural-sky.mjs';
|
|
@@ -63,7 +62,7 @@ ground.addComponent('render', {
|
|
|
63
62
|
const entity = model.entity!;
|
|
64
63
|
entity.setLocalEulerAngles(0, -25, 0);
|
|
65
64
|
entity.root.syncHierarchy();
|
|
66
|
-
const meshes =
|
|
65
|
+
const meshes = entity.findComponents('render').flatMap((render) => render.meshInstances);
|
|
67
66
|
const bounds = meshes[0].aabb.clone();
|
|
68
67
|
meshes.slice(1).forEach((mesh) => bounds.add(mesh.aabb));
|
|
69
68
|
const scale = 5 / Math.max(bounds.halfExtents.x * 2, bounds.halfExtents.z * 2);
|
|
@@ -73,10 +72,10 @@ bounds.copy(meshes[0].aabb);
|
|
|
73
72
|
meshes.slice(1).forEach((mesh) => bounds.add(mesh.aabb));
|
|
74
73
|
entity.setPosition(-bounds.center.x, 0.03 - bounds.getMin().y, -bounds.center.z);
|
|
75
74
|
|
|
76
|
-
const body = meshes.filter((mesh) => mesh.material
|
|
77
|
-
const panels = meshes.filter((mesh) => mesh.material
|
|
78
|
-
const paint = body[0].material
|
|
79
|
-
const shade = panels[0].material
|
|
75
|
+
const body = meshes.filter((mesh) => mesh.material?.name === 'material');
|
|
76
|
+
const panels = meshes.filter((mesh) => mesh.material?.name === 'material_1');
|
|
77
|
+
const paint = body[0].material!.clone() as StandardMaterial;
|
|
78
|
+
const shade = panels[0].material!.clone() as StandardMaterial;
|
|
80
79
|
body.forEach((mesh) => (mesh.material = paint));
|
|
81
80
|
panels.forEach((mesh) => (mesh.material = shade));
|
|
82
81
|
paint.diffuse.fromString('#d6293e');
|
|
@@ -65,12 +65,15 @@
|
|
|
65
65
|
></pc-entity>
|
|
66
66
|
<pc-entity name="tree-0-crown-0" position="-7 2.09 -6" scale="2.31 1.98 2.31"
|
|
67
67
|
><pc-render type="cone" material="leaves-material"></pc-render
|
|
68
|
+
><pc-collision type="cone" radius="1.155" height="1.98"></pc-collision
|
|
68
69
|
></pc-entity>
|
|
69
70
|
<pc-entity name="tree-0-crown-1" position="-7 2.915 -6" scale="1.76 1.705 1.76"
|
|
70
71
|
><pc-render type="cone" material="tips-material"></pc-render
|
|
72
|
+
><pc-collision type="cone" radius="0.88" height="1.705"></pc-collision
|
|
71
73
|
></pc-entity>
|
|
72
74
|
<pc-entity name="tree-0-crown-2" position="-7 3.63 -6" scale="1.21 1.54 1.21"
|
|
73
75
|
><pc-render type="cone" material="leaves-material"></pc-render
|
|
76
|
+
><pc-collision type="cone" radius="0.605" height="1.54"></pc-collision
|
|
74
77
|
></pc-entity>
|
|
75
78
|
<pc-entity name="tree-1-trunk" position="6.5 1.125 -6.5" scale="0.475 2.25 0.475"
|
|
76
79
|
><pc-render type="box" material="wood-material"></pc-render
|
|
@@ -79,12 +82,15 @@
|
|
|
79
82
|
></pc-entity>
|
|
80
83
|
<pc-entity name="tree-1-crown-0" position="6.5 2.375 -6.5" scale="2.625 2.25 2.625"
|
|
81
84
|
><pc-render type="cone" material="leaves-material"></pc-render
|
|
85
|
+
><pc-collision type="cone" radius="1.3125" height="2.25"></pc-collision
|
|
82
86
|
></pc-entity>
|
|
83
87
|
<pc-entity name="tree-1-crown-1" position="6.5 3.3125 -6.5" scale="2 1.9375 2"
|
|
84
88
|
><pc-render type="cone" material="tips-material"></pc-render
|
|
89
|
+
><pc-collision type="cone" radius="1" height="1.9375"></pc-collision
|
|
85
90
|
></pc-entity>
|
|
86
91
|
<pc-entity name="tree-1-crown-2" position="6.5 4.125 -6.5" scale="1.375 1.75 1.375"
|
|
87
92
|
><pc-render type="cone" material="leaves-material"></pc-render
|
|
93
|
+
><pc-collision type="cone" radius="0.6875" height="1.75"></pc-collision
|
|
88
94
|
></pc-entity>
|
|
89
95
|
<pc-entity name="tree-2-trunk" position="-7.5 0.81 5" scale="0.342 1.62 0.342"
|
|
90
96
|
><pc-render type="box" material="wood-material"></pc-render
|
|
@@ -93,12 +99,15 @@
|
|
|
93
99
|
></pc-entity>
|
|
94
100
|
<pc-entity name="tree-2-crown-0" position="-7.5 1.71 5" scale="1.89 1.62 1.89"
|
|
95
101
|
><pc-render type="cone" material="leaves-material"></pc-render
|
|
102
|
+
><pc-collision type="cone" radius="0.945" height="1.62"></pc-collision
|
|
96
103
|
></pc-entity>
|
|
97
104
|
<pc-entity name="tree-2-crown-1" position="-7.5 2.385 5" scale="1.44 1.395 1.44"
|
|
98
105
|
><pc-render type="cone" material="tips-material"></pc-render
|
|
106
|
+
><pc-collision type="cone" radius="0.72" height="1.395"></pc-collision
|
|
99
107
|
></pc-entity>
|
|
100
108
|
<pc-entity name="tree-2-crown-2" position="-7.5 2.97 5" scale="0.99 1.26 0.99"
|
|
101
109
|
><pc-render type="cone" material="leaves-material"></pc-render
|
|
110
|
+
><pc-collision type="cone" radius="0.495" height="1.26"></pc-collision
|
|
102
111
|
></pc-entity>
|
|
103
112
|
<pc-entity name="tree-3-trunk" position="7 1.035 5.5" scale="0.437 2.07 0.437"
|
|
104
113
|
><pc-render type="box" material="wood-material"></pc-render
|
|
@@ -107,12 +116,15 @@
|
|
|
107
116
|
></pc-entity>
|
|
108
117
|
<pc-entity name="tree-3-crown-0" position="7 2.185 5.5" scale="2.415 2.07 2.415"
|
|
109
118
|
><pc-render type="cone" material="leaves-material"></pc-render
|
|
119
|
+
><pc-collision type="cone" radius="1.2075" height="2.07"></pc-collision
|
|
110
120
|
></pc-entity>
|
|
111
121
|
<pc-entity name="tree-3-crown-1" position="7 3.0475 5.5" scale="1.84 1.7825 1.84"
|
|
112
122
|
><pc-render type="cone" material="tips-material"></pc-render
|
|
123
|
+
><pc-collision type="cone" radius="0.92" height="1.7825"></pc-collision
|
|
113
124
|
></pc-entity>
|
|
114
125
|
<pc-entity name="tree-3-crown-2" position="7 3.795 5.5" scale="1.265 1.61 1.265"
|
|
115
126
|
><pc-render type="cone" material="leaves-material"></pc-render
|
|
127
|
+
><pc-collision type="cone" radius="0.6325" height="1.61"></pc-collision
|
|
116
128
|
></pc-entity>
|
|
117
129
|
<pc-entity name="tree-4-trunk" position="-2.2 0.765 -8" scale="0.323 1.53 0.323"
|
|
118
130
|
><pc-render type="box" material="wood-material"></pc-render
|
|
@@ -121,12 +133,15 @@
|
|
|
121
133
|
></pc-entity>
|
|
122
134
|
<pc-entity name="tree-4-crown-0" position="-2.2 1.615 -8" scale="1.785 1.53 1.785"
|
|
123
135
|
><pc-render type="cone" material="leaves-material"></pc-render
|
|
136
|
+
><pc-collision type="cone" radius="0.8925" height="1.53"></pc-collision
|
|
124
137
|
></pc-entity>
|
|
125
138
|
<pc-entity name="tree-4-crown-1" position="-2.2 2.2525 -8" scale="1.36 1.3175 1.36"
|
|
126
139
|
><pc-render type="cone" material="tips-material"></pc-render
|
|
140
|
+
><pc-collision type="cone" radius="0.68" height="1.3175"></pc-collision
|
|
127
141
|
></pc-entity>
|
|
128
142
|
<pc-entity name="tree-4-crown-2" position="-2.2 2.805 -8" scale="0.935 1.19 0.935"
|
|
129
143
|
><pc-render type="cone" material="leaves-material"></pc-render
|
|
144
|
+
><pc-collision type="cone" radius="0.4675" height="1.19"></pc-collision
|
|
130
145
|
></pc-entity>
|
|
131
146
|
<pc-entity name="fallen-log" position="0 0.35 6" scale="3.8 0.7 0.7"
|
|
132
147
|
><pc-render type="box" material="wood-material"></pc-render
|
|
@@ -139,9 +154,9 @@
|
|
|
139
154
|
><pc-rigid-body type="static"></pc-rigid-body
|
|
140
155
|
></pc-entity>
|
|
141
156
|
<pc-entity name="player" position="0 1.1 0">
|
|
142
|
-
<pc-collision type="
|
|
157
|
+
<pc-collision type="box" half-extents="0.4 1.2 0.4" linear-offset="0 0.2 0"></pc-collision>
|
|
143
158
|
<pc-rigid-body type="dynamic" mass="70" angular-factor="0 0 0"></pc-rigid-body>
|
|
144
|
-
<pc-entity name="character" position="0 0.
|
|
159
|
+
<pc-entity name="character" position="0 0.3 0">
|
|
145
160
|
<pc-entity name="body" scale="0.56 0.88 0.38"
|
|
146
161
|
><pc-render type="box" material="shirt-material"></pc-render
|
|
147
162
|
></pc-entity>
|