@waica/archetype-third-person 0.30.1 → 0.31.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 +30 -0
- package/dist/prefabs.d.ts +4 -1
- package/dist/prefabs.js +47 -13
- package/dist/scene-default.js +7 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -19,6 +19,11 @@ npx @waica/cli # New project → 3D → Third person
|
|
|
19
19
|
- **Camera-relative controls.** `CharacterMotor.relativeTo: 'camera'`:
|
|
20
20
|
`W` walks away from the camera. `Shift` runs, `C` rolls (invulnerable while
|
|
21
21
|
it lasts), `Space` jumps, a click or `J` attacks.
|
|
22
|
+
- **Animation.** The Knight's idle, walk and run play one `Locomotion`
|
|
23
|
+
blend (ADR 0033): `Idle`, `Walking_A` and `Running_A` mixed by the planar
|
|
24
|
+
speed the `character` role writes to the Animator, so a half stick walks
|
|
25
|
+
slowly without its feet sliding and walk turns into run smoothly. The
|
|
26
|
+
attack and the dodge fade in over 0.05 s, the death over 0.3 s.
|
|
22
27
|
- **Combat.** `MeleeAttack` strikes a box in front of the Knight's yaw;
|
|
23
28
|
skeletons (the `enemy` role) patrol, chase, wind up, strike, flinch and
|
|
24
29
|
die. Hits push back, blink the model and shake the camera.
|
|
@@ -30,6 +35,31 @@ npx @waica/cli # New project → 3D → Third person
|
|
|
30
35
|
goal chest on a raised platform: reach it to win. The blank start is a
|
|
31
36
|
ground plane, a Sun and the playable Knight.
|
|
32
37
|
|
|
38
|
+
## Shadows
|
|
39
|
+
|
|
40
|
+
Demo and blank explicitly use `render.shadows: { quality: 'medium', maxDistance: 40 }`.
|
|
41
|
+
Every Model casts/receives; the Sun and all four demo torches cast. Static and
|
|
42
|
+
animated glTF assets retain their materials and gameplay.
|
|
43
|
+
|
|
44
|
+
Profiles (Sun / PointLight per face): Low 1024²/256², Medium 2048²/512²,
|
|
45
|
+
High 4096²/1024². Set `render.shadows.quality: 'off'` to restore the previous
|
|
46
|
+
shadow-free image/cost. Existing 3D scenes change; 2D projects do not.
|
|
47
|
+
|
|
48
|
+
Sun follows the active camera within finite reach. Torch shadows use the smaller
|
|
49
|
+
of lighting distance and shadow reach, without shortening illumination. Each
|
|
50
|
+
PointLight can add six views, hence 25 for four torches plus Sun. No FPS-based
|
|
51
|
+
light caps or universal frame-rate guarantee are introduced.
|
|
52
|
+
|
|
53
|
+
The editor exposes scene quality/reach and nullable per-light overrides. `null`
|
|
54
|
+
inherits the scene, not zero; resetting an instance to its prefab is separate.
|
|
55
|
+
See the [engine format](../engine/README.md#3d-shadows-and-migration) and
|
|
56
|
+
[benchmark notes](../bench/README.md). WebGPU remains unverified on this host;
|
|
57
|
+
its two external evidence gates were withdrawn from this delivery by the user,
|
|
58
|
+
not marked as passing. Implementation and strict tests remain available.
|
|
59
|
+
Automatic CI requires the new visual shadow checks on WebGL2; request their
|
|
60
|
+
strict WebGPU counterparts with `pnpm test:e2e --webgpu-shadows`. Legacy backend
|
|
61
|
+
checks remain required on both APIs.
|
|
62
|
+
|
|
33
63
|
## Controls
|
|
34
64
|
|
|
35
65
|
| Action | Keys | Gamepad |
|
package/dist/prefabs.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { PrefabJson } from '@waica/engine';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* The Knight's clip for each `character` state (assets/ATTRIBUTION.md): idle,
|
|
4
|
+
* walk and run reach theirs through the Locomotion blend (issue #205).
|
|
5
|
+
*/
|
|
3
6
|
export declare const KNIGHT_CLIPS: Readonly<Record<string, string>>;
|
|
4
7
|
/** The Skeleton Warrior's clip for each `enemy` state (assets/ATTRIBUTION.md). */
|
|
5
8
|
export declare const SKELETON_CLIPS: Readonly<Record<string, string>>;
|
package/dist/prefabs.js
CHANGED
|
@@ -3,7 +3,10 @@ import { CHARACTER_STATE_GRAPH, ENEMY_STATE_GRAPH } from '@waica/behaviors';
|
|
|
3
3
|
function withClips(states, clips) {
|
|
4
4
|
return Object.fromEntries(Object.entries(states).map(([name, state]) => [name, clips[name] ? { ...state, clip: clips[name] } : state]));
|
|
5
5
|
}
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* The Knight's clip for each `character` state (assets/ATTRIBUTION.md): idle,
|
|
8
|
+
* walk and run reach theirs through the Locomotion blend (issue #205).
|
|
9
|
+
*/
|
|
7
10
|
export const KNIGHT_CLIPS = {
|
|
8
11
|
idle: 'Idle',
|
|
9
12
|
walk: 'Walking_A',
|
|
@@ -31,19 +34,50 @@ function fixedBox(size, offset = [0, size[1] / 2, 0]) {
|
|
|
31
34
|
function capsule(height, layer, strikes) {
|
|
32
35
|
return { type: 'Collider', props: { shape: 'capsule', radius: 0.5, height, offset: [0, height / 2, 0], layer, collidesWith: [strikes] } };
|
|
33
36
|
}
|
|
37
|
+
/** Every character and level piece participates in the scene's shadows. */
|
|
38
|
+
function gltfModel(src) {
|
|
39
|
+
return { type: 'Model', props: { src, castShadows: true, receiveShadows: true } };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The Knight's walk cycle (issue #205 CA-15): Idle, Walking_A and Running_A
|
|
43
|
+
* mixed by the planar speed the `character` role writes, at the motor's
|
|
44
|
+
* speeds (still, `speed` 6 and `runSpeed` 10), so a half stick walks slowly
|
|
45
|
+
* without sliding its feet. idle, walk and run all play it: switching among
|
|
46
|
+
* them keeps it playing, and the smoothed speed does the transition.
|
|
47
|
+
*/
|
|
48
|
+
const LOCOMOTION = 'Locomotion';
|
|
49
|
+
const LOCOMOTION_BLEND = {
|
|
50
|
+
param: 'speed',
|
|
51
|
+
points: [{ clip: KNIGHT_CLIPS.idle, at: 0 }, { clip: KNIGHT_CLIPS.walk, at: 6 }, { clip: KNIGHT_CLIPS.run, at: 10 }],
|
|
52
|
+
smoothing: 0.15,
|
|
53
|
+
};
|
|
34
54
|
const PLAYER = {
|
|
35
55
|
waicaPrefab: 1,
|
|
36
56
|
type: 'character',
|
|
37
57
|
components: [
|
|
38
|
-
|
|
58
|
+
gltfModel('waica:tp-knight'),
|
|
39
59
|
{
|
|
40
60
|
type: 'Animator',
|
|
41
61
|
props: {
|
|
42
62
|
initialClip: 'Idle',
|
|
43
|
-
|
|
63
|
+
// The attack and the dodge answer the press at once; the death settles in slower.
|
|
64
|
+
clips: {
|
|
65
|
+
Jump_Start: { loop: false },
|
|
66
|
+
'1H_Melee_Attack_Chop': { loop: false, fade: 0.05 },
|
|
67
|
+
Dodge_Forward: { loop: false, fade: 0.05 },
|
|
68
|
+
Death_A: { loop: false, fade: 0.3 },
|
|
69
|
+
},
|
|
70
|
+
blends: { [LOCOMOTION]: LOCOMOTION_BLEND },
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: 'StateMachine',
|
|
75
|
+
props: {
|
|
76
|
+
role: 'player',
|
|
77
|
+
initial: CHARACTER_STATE_GRAPH.initial,
|
|
78
|
+
states: withClips(CHARACTER_STATE_GRAPH.states, { ...KNIGHT_CLIPS, idle: LOCOMOTION, walk: LOCOMOTION, run: LOCOMOTION }),
|
|
44
79
|
},
|
|
45
80
|
},
|
|
46
|
-
{ type: 'StateMachine', props: { role: 'player', initial: CHARACTER_STATE_GRAPH.initial, states: withClips(CHARACTER_STATE_GRAPH.states, KNIGHT_CLIPS) } },
|
|
47
81
|
capsule(2.3, 'player', 'enemy'),
|
|
48
82
|
{ type: 'RigidBody', props: { type: 'kinematic' } },
|
|
49
83
|
{ type: 'CharacterMotor', props: { relativeTo: 'camera' } },
|
|
@@ -60,7 +94,7 @@ const SKELETON = {
|
|
|
60
94
|
waicaPrefab: 1,
|
|
61
95
|
type: 'character',
|
|
62
96
|
components: [
|
|
63
|
-
|
|
97
|
+
gltfModel('waica:tp-skeleton-warrior'),
|
|
64
98
|
{
|
|
65
99
|
type: 'Animator',
|
|
66
100
|
props: { initialClip: 'Walking_A', clips: { '1H_Melee_Attack_Chop': { loop: false }, Hit_A: { loop: false }, Death_A: { loop: false } } },
|
|
@@ -100,7 +134,7 @@ export const THIRD_PERSON_PREFABS = {
|
|
|
100
134
|
waicaPrefab: 1,
|
|
101
135
|
type: 'object',
|
|
102
136
|
components: [
|
|
103
|
-
|
|
137
|
+
gltfModel('waica:tp-banner'),
|
|
104
138
|
// A wall banner: its origin on the wall, its sensor and spawn on the floor in front of the wall (local +z).
|
|
105
139
|
{ type: 'Collider', props: { shape: 'box', size: [2, 3, 2], offset: [0, 1.5, CHECKPOINT_REACH], sensor: true, layer: 'world' } },
|
|
106
140
|
{ type: 'Checkpoint', props: { spawnOffset: [0, 0, CHECKPOINT_REACH] } },
|
|
@@ -110,26 +144,26 @@ export const THIRD_PERSON_PREFABS = {
|
|
|
110
144
|
waicaPrefab: 1,
|
|
111
145
|
type: 'object',
|
|
112
146
|
components: [
|
|
113
|
-
|
|
147
|
+
gltfModel('waica:tp-chest'),
|
|
114
148
|
{ type: 'Collider', props: { shape: 'box', size: [2.4, 2, 2.4], offset: [0, 1, 0], sensor: true, layer: 'world' } },
|
|
115
149
|
{ type: 'GoalChest' },
|
|
116
150
|
],
|
|
117
151
|
},
|
|
118
|
-
'tiles/wall': { waicaPrefab: 1, type: 'tile', components: [
|
|
152
|
+
'tiles/wall': { waicaPrefab: 1, type: 'tile', components: [gltfModel('waica:tp-wall'), fixedBox([4, 4, 1])] },
|
|
119
153
|
'tiles/floor': {
|
|
120
154
|
waicaPrefab: 1,
|
|
121
155
|
type: 'tile',
|
|
122
|
-
components: [
|
|
156
|
+
components: [gltfModel('waica:tp-floor'), fixedBox([4, 0.2, 4], [0, -0.05, 0])],
|
|
123
157
|
},
|
|
124
|
-
'tiles/step': { waicaPrefab: 1, type: 'tile', components: [
|
|
125
|
-
'tiles/column': { waicaPrefab: 1, type: 'tile', components: [
|
|
158
|
+
'tiles/step': { waicaPrefab: 1, type: 'tile', components: [gltfModel('waica:tp-foundation'), fixedBox([2.2, 2, 2.2])] },
|
|
159
|
+
'tiles/column': { waicaPrefab: 1, type: 'tile', components: [gltfModel('waica:tp-pillar'), fixedBox([1.5, 4, 1.5])] },
|
|
126
160
|
'tiles/torch': {
|
|
127
161
|
waicaPrefab: 1,
|
|
128
162
|
type: 'tile',
|
|
129
163
|
components: [
|
|
130
|
-
|
|
164
|
+
gltfModel('waica:tp-torch'),
|
|
131
165
|
fixedBox([0.6, 1.1, 0.7], [0, 0.15, 0.3]),
|
|
132
|
-
{ type: 'PointLight', props: { color: '#ffb060', intensity: 14, distance: 9, offsetY: 0.6, offsetZ: 0.6 } },
|
|
166
|
+
{ type: 'PointLight', props: { color: '#ffb060', intensity: 14, distance: 9, offsetY: 0.6, offsetZ: 0.6, castShadows: true } },
|
|
133
167
|
],
|
|
134
168
|
},
|
|
135
169
|
};
|
package/dist/scene-default.js
CHANGED
|
@@ -19,14 +19,18 @@ function orbitCamera() {
|
|
|
19
19
|
function threeDScene(entities) {
|
|
20
20
|
return {
|
|
21
21
|
waicaScene: 3,
|
|
22
|
-
render: {
|
|
22
|
+
render: {
|
|
23
|
+
space: '3d',
|
|
24
|
+
lighting: { ambient: { color: '#b8c8ff', intensity: 0.55 } },
|
|
25
|
+
shadows: { quality: 'medium', maxDistance: 40 },
|
|
26
|
+
},
|
|
23
27
|
simulation: { gravity: [0, -9.81, 0] },
|
|
24
28
|
camera: orbitCamera(),
|
|
25
29
|
entities,
|
|
26
30
|
ui: ['player-health', 'controls-hint'],
|
|
27
31
|
};
|
|
28
32
|
}
|
|
29
|
-
const SUN = { name: 'Sun', components: [{ type: 'Sun', props: { direction: [-0.5, -1, -0.35], intensity: 2.2 } }] };
|
|
33
|
+
const SUN = { name: 'Sun', components: [{ type: 'Sun', props: { direction: [-0.5, -1, -0.35], intensity: 2.2, castShadows: true } }] };
|
|
30
34
|
/** Half the courtyard's side: 5 × 5 floor tiles of 4 units, walls on the edge. */
|
|
31
35
|
const HALF = 10;
|
|
32
36
|
const TILE_CENTERS = [-8, -4, 0, 4, 8];
|
|
@@ -91,7 +95,7 @@ export const THIRD_PERSON_BLANK_SCENE = threeDScene([
|
|
|
91
95
|
name: 'Ground',
|
|
92
96
|
scale: [24, 1, 24],
|
|
93
97
|
components: [
|
|
94
|
-
{ type: 'Model', props: { shape: 'plane', color: '#5a6b4e' } },
|
|
98
|
+
{ type: 'Model', props: { shape: 'plane', color: '#5a6b4e', castShadows: true, receiveShadows: true } },
|
|
95
99
|
{ type: 'Collider', props: { shape: 'box', size: [1, 1, 1], offset: [0, -0.5, 0], layer: 'world' } },
|
|
96
100
|
],
|
|
97
101
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waica/archetype-third-person",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.1",
|
|
4
4
|
"description": "Waica third-person archetype — an orbit camera, camera-relative controls and 3D melee combat in a dungeon courtyard",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@waica/behaviors": "^0.
|
|
28
|
-
"@waica/engine": "^0.
|
|
27
|
+
"@waica/behaviors": "^0.31.1",
|
|
28
|
+
"@waica/engine": "^0.31.1"
|
|
29
29
|
}
|
|
30
30
|
}
|