create-playcanvas 0.8.1 → 0.10.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/package.json +2 -2
- package/skills/add-effects/SKILL.md +21 -83
- package/skills/apply-conventions/SKILL.md +33 -59
- package/skills/assemble-scene/SKILL.md +18 -43
- package/skills/assemble-scene/references/direct-engine.md +2 -2
- package/skills/assemble-scene/references/react.md +3 -3
- package/skills/assemble-scene/references/web-components.md +2 -2
- package/skills/bake-lighting/SKILL.md +25 -56
- package/skills/build-app/SKILL.md +38 -37
- package/skills/build-app/references/web-components.md +3 -3
- package/skills/calibrate-model/SKILL.md +18 -48
- package/skills/calibrate-model/references/web-components.md +3 -3
- package/skills/configure-animation/SKILL.md +21 -37
- package/skills/configure-animation/references/direct-engine.md +2 -2
- package/skills/configure-animation/references/react.md +3 -3
- package/skills/find-examples/SKILL.md +24 -60
- package/skills/inspect-glb/SKILL.md +21 -62
- package/skills/inspect-runtime/SKILL.md +41 -0
- package/skills/inspect-runtime/scripts/devtools-hook.js +23 -0
- package/skills/light-scene/SKILL.md +29 -97
- package/skills/load-assets/SKILL.md +52 -0
- package/skills/override-shader-chunks/SKILL.md +21 -61
- package/skills/reduce-draw-calls/SKILL.md +24 -54
- package/skills/reuse-scripts/SKILL.md +23 -46
- package/skills/reuse-scripts/references/direct-engine.md +3 -3
- package/skills/verify-pixels/SKILL.md +24 -58
- 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/react/base/README.md +4 -0
- package/templates/react/base/package.json +3 -2
- 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/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/skills/build-hud/SKILL.md +0 -38
- package/skills/manage-game-state/SKILL.md +0 -48
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-playcanvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
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",
|
|
@@ -1,92 +1,30 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: add-effects
|
|
3
|
-
description: Use when adding
|
|
3
|
+
description: Use when adding visual effects to a PlayCanvas app, to choose between particle systems, shader chunks, runtime meshes, and post-processing.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Effects
|
|
6
|
+
# Effects
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Pick the Engine building block first, then adapt its closest official example with
|
|
9
|
+
`find-examples`:
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
effect
|
|
11
|
+
- Particle systems: the `particlesystem` component (`graphics/particles-*` examples).
|
|
12
|
+
- Custom shading on existing surfaces: `StandardMaterial` chunk overrides
|
|
13
|
+
(`override-shader-chunks`), or a `ShaderMaterial` when the effect needs its own shader pair.
|
|
14
|
+
- Procedural or per-frame geometry: a runtime-updated `Mesh` on a `render` component
|
|
15
|
+
(`graphics/mesh-*` examples), or `WideLineRenderer` for lines with width.
|
|
16
|
+
- Full-screen effects: `CameraFrame` (`light-scene`).
|
|
17
|
+
- Gaussian splat effects: the shipped gsplat scripts (`reuse-scripts`).
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
per channel, such as `new CurveSet([0, x0, 1, x1], [0, y0, 1, y1], [0, z0, 1,
|
|
16
|
-
z1])`; do not flatten three channels into one array. A malformed curve can type-check and then fail
|
|
17
|
-
inside the particle texture upload. Create one emitter, render a frame, and require empty runtime
|
|
18
|
-
and console diagnostics before reusing the helper for smoke, spray, or splashes.
|
|
19
|
+
## Engine gotchas
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
- `CurveSet` takes one key array per channel. A flattened array type-checks, then fails at texture
|
|
22
|
+
upload.
|
|
23
|
+
- Create emitters and meshes at load and reuse them. Fire an emitter with `reset()` then `play()`;
|
|
24
|
+
one-shots use `loop: false`.
|
|
25
|
+
- The first use of a shader stalls a frame. Render each effect once behind a loading screen.
|
|
26
|
+
- Animate values with `setParameter`, not a per-frame `material.update()`.
|
|
27
|
+
- A lit material on a custom mesh needs normals; otherwise use an unlit material.
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- Place an effect at the emitter's world mount point; parent it there or copy the world transform once.
|
|
28
|
-
- Keep additive or transparent effects above the surface they sit on and biased toward the camera so
|
|
29
|
-
they do not z-fight. Inspect soft transparency at grazing angles for hard edges, black quads, or
|
|
30
|
-
dropout.
|
|
31
|
-
- Size and orient effects from the emitter's calibrated bounds, not a guessed constant.
|
|
32
|
-
|
|
33
|
-
## Pool and prewarm
|
|
34
|
-
|
|
35
|
-
- Create emitters, ribbons, flashes, materials, and meshes at load. Events check out an effect, set
|
|
36
|
-
its transform, and call `particlesystem.reset()` then `play()`; after its particles finish, call
|
|
37
|
-
`stop()` and return it to the pool.
|
|
38
|
-
- First-use shader compilation can stall a frame. Before gameplay, render every material and particle
|
|
39
|
-
variant through the relevant passes, on an offscreen rig or behind the ready overlay.
|
|
40
|
-
- Pulse or tint uniforms with `meshInstance.setParameter` or `material.setParameter`.
|
|
41
|
-
`material.update()` marks the whole material dirty and clears variants when defines or chunks
|
|
42
|
-
change; reserve it for material changes that require it.
|
|
43
|
-
|
|
44
|
-
## Moving trails
|
|
45
|
-
|
|
46
|
-
- Treat built-in render primitives as blocking geometry for soft effects. Follow the closest
|
|
47
|
-
official example to choose particles, a custom mesh, textures, materials, and blending for the
|
|
48
|
-
intended visual behavior.
|
|
49
|
-
- Match every custom mesh's vertex streams to its material. A lit `StandardMaterial` needs normals;
|
|
50
|
-
calculate and set them before `mesh.update()`, or deliberately use an unlit material. Treat a
|
|
51
|
-
missing vertex-attribute diagnostic as a rendering failure even when some frames still appear.
|
|
52
|
-
For a dynamic horizontal ribbon created with `setVertexStream`, allocate a normal array alongside
|
|
53
|
-
positions, fill every vertex with `(0, 1, 0)`, and upload it as `SEMANTIC_NORMAL` before the first
|
|
54
|
-
`update()`. Recheck this whenever changing the ribbon's material type.
|
|
55
|
-
- Never attach an empty dynamic mesh to an enabled render component. Upload a valid initial vertex
|
|
56
|
-
and index set before creating its `MeshInstance`, or keep the render entity disabled until the
|
|
57
|
-
first complete update. On reset, disable it or restore valid seed geometry; do not call
|
|
58
|
-
`mesh.clear()` while an enabled renderer still references it. Reflection and refraction cameras
|
|
59
|
-
render the same mesh and can expose this lifecycle bug before the gameplay camera does.
|
|
60
|
-
- Judge a trail from its intended cameras. If it reads as a flat decal, use the reference example to
|
|
61
|
-
add the minimum depth cue needed rather than stacking arbitrary geometry.
|
|
62
|
-
- Sample the trail continuously. Append segments on a small time- or distance-step and interpolate
|
|
63
|
-
between them; coarse per-distance chunks make the trail stutter and break into visible dashes as
|
|
64
|
-
the object speeds up.
|
|
65
|
-
- Anchor it to the mover's contact or exhaust point, scale emission rate and length with speed, and
|
|
66
|
-
fade the tail so it dissolves instead of cutting off with a hard edge.
|
|
67
|
-
|
|
68
|
-
## Camera shake and hit feedback
|
|
69
|
-
|
|
70
|
-
- Drive camera shake as a decaying offset on a camera parent or an additive layer; never write it
|
|
71
|
-
into the smoothed chase position, or it fights the follow. Keep shake and follow separate, as the
|
|
72
|
-
orbit-camera rule in `apply-conventions` requires.
|
|
73
|
-
- Stagger the parts of a compound effect over a few frames — flash, then smoke, then splash — so an
|
|
74
|
-
event reads as a sequence rather than a single pop.
|
|
75
|
-
- Keep the readable peak long enough for the gameplay camera to see the layers together. A flash can
|
|
76
|
-
be brief, but the longer-lived layers should overlap for several frames rather than leaving the
|
|
77
|
-
primary action frame visually identical to the idle state.
|
|
78
|
-
- For side-mounted emitters, derive every spawn point and direction from calibrated world-space
|
|
79
|
-
mounts on the mover. Confirm the effect starts outside the source geometry from both sides; an
|
|
80
|
-
active cooldown with only a tiny flash elsewhere in the frame is failed feedback.
|
|
81
|
-
|
|
82
|
-
## Prove it fires
|
|
83
|
-
|
|
84
|
-
Trigger each effect through real gameplay input and confirm with a screenshot captured at its peak,
|
|
85
|
-
not a saved filepath. Confirm the effect is visible from the gameplay camera and from a grazing
|
|
86
|
-
angle, that repeated events leave entity counts stable, and that nothing leaves a black quad or a
|
|
87
|
-
hard edge over the surface. Compare `graphicsDevice.shaders.length` after prewarming and after the
|
|
88
|
-
first real event; growth indicates a missed variant. Use two fixed poses and return screenshots at
|
|
89
|
-
accept-or-reject points only, not after every edit.
|
|
90
|
-
|
|
91
|
-
Choose the authoring surface with the `build-app` skill; create effects through its entity and
|
|
92
|
-
component primitives and own their lifecycle in the surface that spawned them.
|
|
29
|
+
Trigger each effect through the app's real inputs, and confirm repeated triggers keep entity counts
|
|
30
|
+
stable.
|
|
@@ -1,75 +1,49 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: apply-conventions
|
|
3
|
-
description: Use when writing
|
|
3
|
+
description: Use when writing PlayCanvas Engine code touching transforms, cameras, lights, materials, meshes, bounds, or imports, to apply Engine conventions and avoid legacy APIs.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Engine conventions
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
details. Apply these stable conventions throughout the project.
|
|
8
|
+
Check version-sensitive APIs against installed `playcanvas` declarations.
|
|
10
9
|
|
|
11
|
-
## Coordinates
|
|
10
|
+
## Coordinates
|
|
12
11
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
`
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
lerp across the 0/360 seam.
|
|
12
|
+
- Right-handed, +Y up, metres. `entity.forward`, cameras, and `lookAt` use -Z.
|
|
13
|
+
- Directional and spot lights shine along local -Y, straight down by default. Aim them with
|
|
14
|
+
`setEulerAngles`, not `lookAt`.
|
|
15
|
+
- glTF models usually face +Z; record per-model yaw with `calibrate-model`.
|
|
16
|
+
- Angle APIs take degrees in XYZ order. Keep pitch, yaw, and roll as app state; never feed
|
|
17
|
+
`getEulerAngles()` back into `setEulerAngles()`. Interpolate with `math.lerpAngle` or
|
|
18
|
+
`Quat.slerp`.
|
|
19
|
+
- Use world setters on semantic roots and `setLocal*` below a parent. Never scale rigid-body roots
|
|
20
|
+
or skinned bones.
|
|
23
21
|
|
|
24
|
-
##
|
|
22
|
+
## Current APIs
|
|
25
23
|
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
- Do not scale rigid-body roots or skinned bones.
|
|
24
|
+
- `render` and `anim` components, not `model` and `animation`.
|
|
25
|
+
- ESM `class X extends Script` with `static scriptName`, not `createScript`.
|
|
26
|
+
- `Mesh.fromGeometry(device, new BoxGeometry())`, not `createBox` and the other `create*` helpers.
|
|
27
|
+
- Named imports from `playcanvas`; shipped scripts only from `playcanvas/scripts/esm/**`.
|
|
31
28
|
|
|
32
|
-
## Cameras
|
|
29
|
+
## Cameras
|
|
33
30
|
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
`Vec3.lerp()` between orbit positions on opposite sides of the focus; the chord crosses the subject.
|
|
37
|
-
- Before writing custom camera input, inspect the installed controller script and its matching
|
|
38
|
-
Engine example. Preserve their gesture mapping and damping unless the product explicitly asks
|
|
39
|
-
for different behavior; do not derive signs from memory.
|
|
40
|
-
- Define the input contract in screen space, then verify positive and negative movement on both axes
|
|
41
|
-
with real input after a rendered frame. Judge the camera basis or a projected fixed landmark, not
|
|
42
|
-
world-space camera coordinates or internal angle signs; both can approve inverted behavior.
|
|
43
|
-
- Materials cull back faces by default; counter-clockwise winding defines the front face.
|
|
44
|
-
- Clone and reassign a shared material before editing it for only some meshes, including default
|
|
45
|
-
and asset-imported materials. Per-mesh uniforms use `meshInstance.setParameter()` without cloning.
|
|
46
|
-
- `setParameter` uploads raw values; author colours in sRGB and convert with `Color#linear()`
|
|
47
|
-
before upload — do not hand-roll gamma.
|
|
48
|
-
- Entities do not have an `aabb`. Union descendant `meshInstance.aabb` values after transforms have
|
|
49
|
-
synchronized.
|
|
50
|
-
- Let `Mesh.update()` calculate local bounds. If passing `false` for `updateBoundingBox`, assign an
|
|
51
|
-
explicit `mesh.aabb` before the mesh can be culled.
|
|
52
|
-
- Expand custom-mesh bounds by the maximum vertex-shader displacement; CPU bounds do not observe
|
|
53
|
-
shader deformation.
|
|
54
|
-
- Treat offline skinned-mesh bounds as bind-pose estimates and confirm animated poses at runtime.
|
|
31
|
+
- FOV is vertical, 45° by default. Check shipped camera scripts before writing controls
|
|
32
|
+
(`reuse-scripts`).
|
|
55
33
|
|
|
56
|
-
##
|
|
34
|
+
## Materials and bounds
|
|
57
35
|
|
|
58
|
-
|
|
59
|
-
|
|
36
|
+
- `StandardMaterial.useMetalness` defaults to `false`; enable it before using `metalness`.
|
|
37
|
+
- Call `material.update()` after property edits. Per-mesh values use `meshInstance.setParameter()`
|
|
38
|
+
with no update or clone.
|
|
39
|
+
- Clone shared materials, including imported ones, before editing them for some meshes.
|
|
40
|
+
- `setParameter` uploads raw values; convert sRGB colours with `Color#linear()`.
|
|
41
|
+
- Back faces cull by default; counter-clockwise winding is the front face.
|
|
42
|
+
- Entities have no `aabb`; union descendant `meshInstance.aabb`. If `mesh.update()` skips bounds,
|
|
43
|
+
set `mesh.aabb` yourself, and expand it by any vertex-shader displacement.
|
|
60
44
|
|
|
61
|
-
##
|
|
45
|
+
## Verify in the running app
|
|
62
46
|
|
|
63
|
-
Offline
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
- Start the project's own dev server or build; do not construct a separate harness.
|
|
67
|
-
- Log the value from inside the Engine, using `getPosition()`, `getEulerAngles()`, a
|
|
68
|
-
`meshInstance.aabb`, or an anim component's active state, from a `Script` or an update callback.
|
|
69
|
-
- For large or vertex-displaced meshes, inspect their runtime AABB and `visibleThisFrame` after a
|
|
70
|
-
rendered frame from at least two representative camera angles.
|
|
71
|
-
- Confirm a directional model's visible front aligns with its semantic root's `forward`; a stored
|
|
72
|
-
yaw or runtime transform alone cannot prove mesh facing.
|
|
73
|
-
- Read that logged value from the browser console. Screenshots judge composition and orientation;
|
|
74
|
-
numeric claims require logged values.
|
|
75
|
-
- Record the confirmed value in application state or an asset tuning record, then remove the logging.
|
|
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.
|
|
@@ -1,53 +1,28 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: assemble-scene
|
|
3
|
-
description: Use when composing PlayCanvas
|
|
3
|
+
description: Use when composing PlayCanvas entity hierarchies from models, colliders, rigid bodies, and scripts, so each object has one semantic root and clear lifecycle.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Scene assembly
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Each scene object has one named root. Put position, heading, `rigidbody`, `collision`, and
|
|
9
|
+
scripts on the root, and the calibrated visual from `calibrate-model` below it. Apply calibration
|
|
10
|
+
once; if the tuning record is missing, calibrate first.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
- Name roots by role, with predictable names for repeated instances.
|
|
13
|
+
- Size colliders from measured bounds and keep them aligned with render bounds.
|
|
14
|
+
- Space roots by the scaled footprint in the tuning record, not by pivot position. A non-90° yaw
|
|
15
|
+
rotates the footprint, so reserve its larger horizontal extent.
|
|
16
|
+
- Keep UI, effects, cameras, and lights outside model hierarchies.
|
|
17
|
+
- Remove the root through its surface's lifecycle and unregister events it owns.
|
|
18
|
+
- Rigid bodies need Ammo loaded first (`build-app`).
|
|
13
19
|
|
|
14
|
-
##
|
|
20
|
+
## Check placement
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
by pivot position. A non-90° authored yaw rotates a footprint, so reserve its larger horizontal
|
|
21
|
-
extent as a conservative radius.
|
|
22
|
-
- Put rigid-body and collision components on the same root. Size colliders from measured bounds.
|
|
23
|
-
- Use local transforms below a parent and world transforms only for semantic roots.
|
|
24
|
-
- Keep UI, effects, cameras, and lights outside gameplay model hierarchies.
|
|
25
|
-
- Remove the root through its authoring surface's lifecycle. Unregister external events it owns.
|
|
26
|
-
- Do not scale rigid-body roots or skinned bones.
|
|
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 read through `inspect-runtime`. A positive gap floats; a negative gap penetrates.
|
|
24
|
+
Overlapping world AABBs between unrelated roots are a failure unless two different views prove the
|
|
25
|
+
overlap is empty space.
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Declare intentional support, attachment, or containment relationships between roots. After a
|
|
31
|
-
rendered frame, measure each relationship at the supported object's actual position. Use a mount
|
|
32
|
-
point, collision ray, or geometry sample; use a support root's AABB maximum only when its top is flat
|
|
33
|
-
across the full footprint. Record the signed contact gap: positive means floating and negative means
|
|
34
|
-
penetration. A relationship name does not exempt the pair from this check.
|
|
35
|
-
|
|
36
|
-
Union descendant world `meshInstance.aabb` values for each semantic root and compare unrelated peer
|
|
37
|
-
roots. Any intersection with positive depth on X, Y, and Z beyond a small tolerance is an unresolved
|
|
38
|
-
placement failure: move the root from its calibrated footprint, or prove the apparent AABB overlap
|
|
39
|
-
is empty geometry from at least two materially different views. Never accept one model inside
|
|
40
|
-
another from a single screenshot.
|
|
41
|
-
|
|
42
|
-
Also check expected root counts, component descendants, aligned render and collider bounds, and
|
|
43
|
-
role-based names. Compare final dimensions with each calibration record's intended size and reference
|
|
44
|
-
object. Inspect composition and facing from images returned into your context by an image-capable
|
|
45
|
-
browser tool. A saved screenshot path, screen coordinate, AABB, or visibility flag is not visual
|
|
46
|
-
evidence. If you cannot inspect the image, report those claims unverified. Hierarchy and placement
|
|
47
|
-
claims require runtime values logged from the application itself, as the `apply-conventions` skill
|
|
48
|
-
describes.
|
|
49
|
-
|
|
50
|
-
Read exactly one reference matching the code being edited:
|
|
51
|
-
[direct Engine](references/direct-engine.md), [React](references/react.md), or
|
|
52
|
-
[Web Components](references/web-components.md). Choose from imports and markup, not installed
|
|
53
|
-
dependencies alone.
|
|
27
|
+
Read only the reference matching the code: [direct Engine](references/direct-engine.md),
|
|
28
|
+
[React](references/react.md), or [Web Components](references/web-components.md).
|
|
@@ -5,8 +5,8 @@ Create one root and place a previously calibrated model beneath it:
|
|
|
5
5
|
```ts
|
|
6
6
|
import { Entity } from 'playcanvas';
|
|
7
7
|
|
|
8
|
-
const root = new Entity('
|
|
9
|
-
root.addChild(instance('
|
|
8
|
+
const root = new Entity('Model_1');
|
|
9
|
+
root.addChild(instance('model'));
|
|
10
10
|
root.setPosition(x, 0, z);
|
|
11
11
|
root.setEulerAngles(0, heading, 0);
|
|
12
12
|
app.root.addChild(root);
|
|
@@ -7,11 +7,11 @@ import { Entity } from '@playcanvas/react';
|
|
|
7
7
|
import { Collision, RigidBody } from '@playcanvas/react/components';
|
|
8
8
|
|
|
9
9
|
// Model is the project's calibrated component from calibrate-model, not a library export
|
|
10
|
-
const
|
|
11
|
-
<Entity name={`
|
|
10
|
+
const SceneObject = ({ id, position, heading }) => (
|
|
11
|
+
<Entity name={`Model_${id}`} position={position} rotation={[0, heading, 0]}>
|
|
12
12
|
<RigidBody type="dynamic" mass={1} />
|
|
13
13
|
<Collision type="capsule" />
|
|
14
|
-
<Model id="
|
|
14
|
+
<Model id="model" src="/models/model.glb" />
|
|
15
15
|
</Entity>
|
|
16
16
|
);
|
|
17
17
|
```
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
Represent the whole gameplay object below one semantic root:
|
|
4
4
|
|
|
5
5
|
```html
|
|
6
|
-
<pc-entity name="
|
|
6
|
+
<pc-entity name="Model_1" position="4 0 -3" rotation="0 90 0">
|
|
7
7
|
<pc-rigid-body type="dynamic" mass="1"></pc-rigid-body>
|
|
8
8
|
<pc-collision type="capsule"></pc-collision>
|
|
9
9
|
<pc-entity position="0 0.8 0" rotation="0 180 0" scale="0.01 0.01 0.01">
|
|
10
|
-
<pc-model asset="
|
|
10
|
+
<pc-model asset="model"></pc-model>
|
|
11
11
|
</pc-entity>
|
|
12
12
|
</pc-entity>
|
|
13
13
|
```
|
|
@@ -1,67 +1,36 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bake-lighting
|
|
3
|
-
description: Use when
|
|
3
|
+
description: Use when static PlayCanvas lighting, shadows, or ambient occlusion cost too much per frame and should be baked into lightmaps.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Bake static lighting
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
shadow-map rendering and per-pixel dynamic lighting for that light entirely.
|
|
8
|
+
Lightmaps remove per-frame shadow and lighting cost for static lights on static geometry.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
- `Application` registers `Lightmapper`; manual `AppOptions` bootstraps set
|
|
11
|
+
`lightmapper = Lightmapper`.
|
|
12
|
+
- On the light, set `bake = true`; once every mesh it lights is lightmapped, set
|
|
13
|
+
`affectDynamic = false`.
|
|
14
|
+
- On each mesh, set `render.lightmapped = true`, or it goes unlit once `affectDynamic` is off. Size
|
|
15
|
+
it with `lightmapSizeMultiplier`; `castShadowsLightmap` lets meshes shadow each other.
|
|
16
|
+
- Set the mode on `scene.lightmapMode`: `BAKE_COLOR` for flat diffuse, or the default
|
|
17
|
+
`BAKE_COLORDIR`, which adds direction for normal- or specular-mapped materials.
|
|
18
|
+
- Bake AO without lights with `scene.ambientBake`, `ambientBakeNumSamples`, and
|
|
19
|
+
`ambientBakeOcclusionBrightness` and `ambientBakeOcclusionContrast`.
|
|
20
|
+
- The Engine bakes once on the first rendered frame. Call `app.lightmapper.bake(null, mode)` only
|
|
21
|
+
for nodes added later or to re-bake.
|
|
12
22
|
|
|
13
|
-
|
|
14
|
-
wires this by default; only a manual `AppOptions` assembly needs the explicit line.
|
|
15
|
-
- Per light: `light.bake = true`, then `light.affectDynamic = false` once every mesh it lights is
|
|
16
|
-
lightmapped, so it stops contributing to the real-time pass.
|
|
17
|
-
- Per mesh: `render.lightmapped = true` — required for the mesh to receive any light once
|
|
18
|
-
`affectDynamic` is false, a correctness requirement, not just an optimization —
|
|
19
|
-
`render.lightmapSizeMultiplier` to size its lightmap, `render.castShadowsLightmap` so lightmapped
|
|
20
|
-
meshes still shadow each other in the bake.
|
|
21
|
-
- Bake mode: `BAKE_COLOR` for flat diffuse materials; `BAKE_COLORDIR` adds a dominant-light-direction
|
|
22
|
-
pass for normal- or specular-mapped materials that need one.
|
|
23
|
-
- Baked ambient occlusion, independent of any light: `scene.ambientBake` plus
|
|
24
|
-
`ambientBakeNumSamples`, `ambientBakeOcclusionBrightness`/`OcclusionContrast`.
|
|
25
|
-
- Run `app.lightmapper.bake(null, mode)` once the scene exists; `null` bakes every lightmapped node.
|
|
23
|
+
## A second UV set is required
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
A node bakes only when every mesh instance has `SEMANTIC_TEXCOORD1`. Otherwise it is skipped, with
|
|
26
|
+
only a debug-build log, and stays dynamically lit. Primitives have the set; imported GLBs often do
|
|
27
|
+
not. Check, then unwrap or re-export.
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
(`mesh.vertexBuffer.format.hasUv1`). A node missing one is skipped silently — no warning, no
|
|
31
|
-
lightmap — and its mesh simply stays dynamically lit, which a glance at a screenshot will not catch.
|
|
32
|
-
Procedural primitives generate that set; imported GLB models often do not. Check `hasUv1` on every
|
|
33
|
-
mesh you expect to bake, and unwrap or re-export the ones without it before the bake. Do not carry a
|
|
34
|
-
primitive-only test result into production assets unchecked.
|
|
29
|
+
## On-device or offline
|
|
35
30
|
|
|
36
|
-
|
|
31
|
+
Time `bake()` on the target device. Move to an offline bake shipped as textures only when it misses
|
|
32
|
+
the startup budget or needs detail the runtime lightmapper cannot produce, and state the added
|
|
33
|
+
download size.
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
choosing a rung. Stay on-device unless:
|
|
41
|
-
|
|
42
|
-
- the measured bake time does not fit the startup or first-paint budget on the target device, or
|
|
43
|
-
- the result needs stylized material detail beyond lighting that the runtime `Lightmapper` cannot
|
|
44
|
-
produce.
|
|
45
|
-
|
|
46
|
-
Only then move to an offline bake shipped as assets, and size the pipeline to the scene count. For
|
|
47
|
-
one deterministic scene the whole pipeline is a build script that computes or renders the maps and
|
|
48
|
-
writes them next to the other static assets, plus a loader; that is sufficient. Registry-keyed
|
|
49
|
-
per-asset configs, worker-thread parallelism and a coverage audit that fails the bake on a missing
|
|
50
|
-
output belong to multi-asset pipelines — add them when the second scene arrives, not before.
|
|
51
|
-
|
|
52
|
-
Offline maps are payload. State the compressed size delta in the change description and compare it
|
|
53
|
-
with the download or package budget; a modest lightmap set can double the archive of a small web
|
|
54
|
-
app.
|
|
55
|
-
|
|
56
|
-
## Keep dynamic objects consistent
|
|
57
|
-
|
|
58
|
-
Whichever rung you use, dynamic objects moving through baked lighting must not read as ignoring it:
|
|
59
|
-
sample or approximate the baked occlusion/shadow field for them and their attachments so they dim to
|
|
60
|
-
the same levels as the static geometry around them, and ground them with a contact shadow. A
|
|
61
|
-
dynamic object whose custom shading must meet baked maps is `override-shader-chunks` territory.
|
|
62
|
-
|
|
63
|
-
## Prove it
|
|
64
|
-
|
|
65
|
-
Capture `verify-pixels`-style frames both in and out of the baked shadow, before and after the
|
|
66
|
-
change, and compare them. A screenshot glanced at once is not proof the bake reproduced the lighting
|
|
67
|
-
it removed.
|
|
35
|
+
Dynamic objects moving through baked light need approximated occlusion and a contact shadow, or
|
|
36
|
+
they look pasted on. Compare `verify-pixels` captures in and out of baked shadow.
|
|
@@ -1,43 +1,44 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: build-app
|
|
3
|
-
description: Use when creating or restructuring a PlayCanvas
|
|
3
|
+
description: Use when creating or restructuring a PlayCanvas app with the Engine API, @playcanvas/react, or @playcanvas/web-components, covering bootstrap, input, resize, physics, update loop, UI, and lifecycle.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Build an Engine application
|
|
7
7
|
|
|
8
|
-
Read `package.json` and the
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
`
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
|
|
8
|
+
Read `package.json` and the entry point, keep the surface that owns the lifecycle, and read only its
|
|
9
|
+
reference: [direct Engine](references/direct-engine.md), [React](references/react.md), or
|
|
10
|
+
[Web Components](references/web-components.md). Choose from imports and markup, not dependencies.
|
|
11
|
+
|
|
12
|
+
## Bootstrap
|
|
13
|
+
|
|
14
|
+
- Input is opt-in. `app.keyboard`, `mouse`, `touch`, `gamepads`, and `elementInput` stay `null`
|
|
15
|
+
unless passed to `new Application(canvas, { ... })`; UI buttons need `new ElementInput(canvas)`.
|
|
16
|
+
- The Engine adds no window resize listener; call `app.resizeCanvas()` from your own handler.
|
|
17
|
+
- `graphicsDevice.maxPixelRatio` defaults to 1. Raise it, at most to `window.devicePixelRatio`, only
|
|
18
|
+
where the target holds its frame budget, call `app.resizeCanvas()`, and confirm with
|
|
19
|
+
`canvas.width / canvas.clientWidth`.
|
|
20
|
+
- Rigid bodies stay inert until Ammo loads through `WasmModule`. Copy the loader from the
|
|
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.
|
|
24
|
+
|
|
25
|
+
## Update loop
|
|
26
|
+
|
|
27
|
+
- `dt` is already clamped by `app.maxDeltaTime` (0.1 s); tune it instead of clamping by hand.
|
|
28
|
+
`app.timeScale` scales it, and `0` pauses scaled time.
|
|
29
|
+
- Keep app state explicit and owned in one place; advance time-dependent logic from `dt`, and reset
|
|
30
|
+
through the same setup used at start.
|
|
31
|
+
|
|
32
|
+
## UI
|
|
33
|
+
|
|
34
|
+
- Render UI from app state; never keep the source of truth in the DOM or UI elements.
|
|
35
|
+
- Use a DOM or React overlay, or Engine `screen` and `element` components adapted from a
|
|
36
|
+
`user-interface` example.
|
|
37
|
+
- Hide unused elements with `enabled = false`; an element at opacity 0 still draws.
|
|
38
|
+
|
|
39
|
+
## Ownership
|
|
40
|
+
|
|
41
|
+
- Keep one owner for app creation, assets, entities, update callbacks, and teardown.
|
|
42
|
+
- Prefer the surface's declarative primitives. Put per-entity and per-frame behaviour in an Engine
|
|
43
|
+
`Script`.
|
|
44
|
+
- Remove external events and resources in the lifecycle that created them.
|
|
@@ -20,10 +20,10 @@ Build supported application structure declaratively:
|
|
|
20
20
|
|
|
21
21
|
```html
|
|
22
22
|
<pc-app>
|
|
23
|
-
<pc-asset id="
|
|
23
|
+
<pc-asset id="model" src="/models/model.glb"></pc-asset>
|
|
24
24
|
<pc-scene>
|
|
25
|
-
<pc-entity name="
|
|
26
|
-
<pc-model asset="
|
|
25
|
+
<pc-entity name="Model" position="0 0 -4">
|
|
26
|
+
<pc-model asset="model"></pc-model>
|
|
27
27
|
</pc-entity>
|
|
28
28
|
</pc-scene>
|
|
29
29
|
</pc-app>
|