@auraindustry/aurajs 0.1.3 → 0.1.5
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 +7 -0
- package/benchmarks/perf-thresholds.json +27 -0
- package/package.json +6 -1
- package/src/ai-guidance.mjs +302 -0
- package/src/authored-project.mjs +498 -2
- package/src/build-contract/capabilities.mjs +87 -1
- package/src/build-contract/constants.mjs +1 -0
- package/src/build-contract.mjs +2 -0
- package/src/bundler.mjs +143 -13
- package/src/cli.mjs +681 -13
- package/src/commands/packs.mjs +741 -0
- package/src/commands/project-authoring.mjs +128 -1
- package/src/conformance/cases/app-and-ui-runtime-cases.mjs +1 -2
- package/src/conformance/cases/core-runtime-cases.mjs +6 -2
- package/src/conformance/cases/scene3d-and-media-cases.mjs +238 -0
- package/src/conformance/cases/systems-and-gameplay-cases.mjs +265 -4
- package/src/conformance-mobile.mjs +166 -0
- package/src/conformance.mjs +89 -30
- package/src/evidence-bundle.mjs +242 -0
- package/src/headless-test/runtime-coordinator.mjs +186 -33
- package/src/headless-test.mjs +2 -0
- package/src/helpers/2d/index.mjs +183 -0
- package/src/helpers/index.mjs +26 -0
- package/src/helpers/starter-utils/adventure-objectives.js +102 -0
- package/src/helpers/starter-utils/adventure-world-2d.js +221 -0
- package/src/helpers/starter-utils/animation-2d.js +337 -0
- package/src/helpers/starter-utils/animation-packaging-2d.js +203 -0
- package/src/helpers/starter-utils/atlas-assets-2d.js +111 -0
- package/src/helpers/starter-utils/autoplay-debug-2d.js +215 -0
- package/src/helpers/starter-utils/avatar-3d.js +404 -0
- package/src/helpers/starter-utils/combat-feedback-2d.js +320 -0
- package/src/helpers/starter-utils/combat-runtime-2d.js +290 -0
- package/src/helpers/starter-utils/core.js +150 -0
- package/src/helpers/starter-utils/dialogue-2d.js +351 -0
- package/src/helpers/starter-utils/enemy-archetypes-2d.js +68 -0
- package/src/helpers/starter-utils/index.js +26 -0
- package/src/helpers/starter-utils/inventory-2d.js +268 -0
- package/src/helpers/starter-utils/journal-2d.js +267 -0
- package/src/helpers/starter-utils/platformer-3d.js +132 -0
- package/src/helpers/starter-utils/scene-audio-2d.js +236 -0
- package/src/helpers/starter-utils/streamed-world-2d.js +378 -0
- package/src/helpers/starter-utils/tilemap-nav-2d.js +499 -0
- package/src/helpers/starter-utils/tilemap-world-2d.js +205 -0
- package/src/helpers/starter-utils/triggers.js +662 -0
- package/src/helpers/starter-utils/tween-2d.js +615 -0
- package/src/helpers/starter-utils/wave-director.js +101 -0
- package/src/helpers/starter-utils/world-compositor-2d.js +253 -0
- package/src/helpers/starter-utils/world-persistence-2d.js +180 -0
- package/src/mobile/android/build.mjs +606 -0
- package/src/mobile/android/host-artifact.mjs +280 -0
- package/src/mobile/ios/build.mjs +1323 -0
- package/src/mobile/ios/host-artifact.mjs +819 -0
- package/src/mobile/shared/capabilities.mjs +174 -0
- package/src/packs/catalog.mjs +259 -0
- package/src/perf-benchmark-runner.mjs +17 -12
- package/src/perf-benchmark.mjs +408 -4
- package/src/publish-command.mjs +303 -6
- package/src/replay-runtime.mjs +257 -0
- package/src/scaffold/config.mjs +2 -0
- package/src/scaffold/fs.mjs +8 -1
- package/src/scaffold/project-docs.mjs +43 -1
- package/src/scaffold.mjs +4 -0
- package/src/session-runtime.mjs +4 -3
- package/src/web-conformance.mjs +0 -36
- package/templates/create/2d-adventure/config/gameplay/adventure.config.js +9 -6
- package/templates/create/2d-adventure/content/gameplay/dialogue.js +85 -0
- package/templates/create/2d-adventure/content/gameplay/world.js +32 -36
- package/templates/create/2d-adventure/content/gameplay/world.tilemap.json +273 -0
- package/templates/create/2d-adventure/docs/design/loop.md +4 -3
- package/templates/create/2d-adventure/prefabs/relic.prefab.js +10 -10
- package/templates/create/2d-adventure/prefabs/world.prefab.js +127 -74
- package/templates/create/2d-adventure/scenes/gameplay.scene.js +603 -112
- package/templates/create/2d-adventure/src/runtime/capabilities.js +16 -0
- package/templates/create/2d-adventure/ui/hud.screen.js +187 -4
- package/templates/create/2d-adventure/ui/journal.screen.js +183 -0
- package/templates/create/3d/scenes/gameplay.scene.js +30 -3
- package/templates/create/3d/src/runtime/capabilities.js +5 -0
- package/templates/create/3d/src/runtime/materials.js +10 -0
- package/templates/create/3d-adventure/scenes/gameplay.scene.js +30 -3
- package/templates/create/3d-adventure/src/runtime/capabilities.js +5 -0
- package/templates/create/3d-adventure/src/runtime/materials.js +11 -0
- package/templates/create/3d-collectathon/scenes/gameplay.scene.js +30 -3
- package/templates/create/3d-collectathon/src/runtime/capabilities.js +5 -0
- package/templates/create/3d-collectathon/src/runtime/materials.js +10 -0
- package/templates/create/shared/src/runtime/ui-forms.js +552 -0
- package/templates/create/shared/src/starter-utils/adventure-world-2d.js +221 -0
- package/templates/create/shared/src/starter-utils/animation-packaging-2d.js +203 -0
- package/templates/create/shared/src/starter-utils/atlas-assets-2d.js +111 -0
- package/templates/create/shared/src/starter-utils/autoplay-debug-2d.js +215 -0
- package/templates/create/shared/src/starter-utils/combat-runtime-2d.js +290 -0
- package/templates/create/shared/src/starter-utils/dialogue-2d.js +351 -0
- package/templates/create/shared/src/starter-utils/index.js +15 -1
- package/templates/create/shared/src/starter-utils/inventory-2d.js +268 -0
- package/templates/create/shared/src/starter-utils/journal-2d.js +267 -0
- package/templates/create/shared/src/starter-utils/scene-audio-2d.js +236 -0
- package/templates/create/shared/src/starter-utils/streamed-world-2d.js +378 -0
- package/templates/create/shared/src/starter-utils/tilemap-nav-2d.js +499 -0
- package/templates/create/shared/src/starter-utils/tilemap-world-2d.js +205 -0
- package/templates/create/shared/src/starter-utils/world-compositor-2d.js +253 -0
- package/templates/create/shared/src/starter-utils/world-persistence-2d.js +180 -0
- package/templates/create-bin/play.js +36 -7
- package/templates/skills/auramaxx/SKILL.md +46 -0
- package/templates/skills/auramaxx/project-requirements.md +68 -0
- package/templates/skills/auramaxx/starter-recipes.md +104 -0
- package/templates/skills/auramaxx/validation-checklist.md +49 -0
- package/templates/skills/aurajs/SKILL.md +0 -96
- package/templates/skills/aurajs/api-contract-3d.md +0 -7
- package/templates/skills/aurajs/api-contract.md +0 -7
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createInteractionPromptState,
|
|
3
|
+
createRectTrigger2D,
|
|
4
|
+
createTriggerTracker,
|
|
5
|
+
stepRectTriggers2D,
|
|
6
|
+
updateInteractionPromptState,
|
|
7
|
+
} from './triggers.js';
|
|
8
|
+
import { addInventoryItem2D, hasInventoryItem2D, removeInventoryItem2D } from './inventory-2d.js';
|
|
9
|
+
|
|
10
|
+
function finite(value, fallback = 0) {
|
|
11
|
+
const numeric = Number(value);
|
|
12
|
+
return Number.isFinite(numeric) ? numeric : fallback;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeText(value, fallback = null) {
|
|
16
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
17
|
+
return value.trim();
|
|
18
|
+
}
|
|
19
|
+
return fallback;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeKind(value) {
|
|
23
|
+
switch (value) {
|
|
24
|
+
case 'pickup':
|
|
25
|
+
case 'lock':
|
|
26
|
+
case 'checkpoint':
|
|
27
|
+
case 'prop':
|
|
28
|
+
return value;
|
|
29
|
+
default:
|
|
30
|
+
return 'prop';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function objectProperty(object, name) {
|
|
35
|
+
if (!object || typeof object !== 'object') return null;
|
|
36
|
+
const properties = object.properties && typeof object.properties === 'object' ? object.properties : null;
|
|
37
|
+
return properties && Object.prototype.hasOwnProperty.call(properties, name) ? properties[name] : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeInteractable(definition, index) {
|
|
41
|
+
const fallbackId = `interactable-${index + 1}`;
|
|
42
|
+
const id = normalizeText(definition?.id, fallbackId);
|
|
43
|
+
const width = Math.max(4, finite(definition?.w ?? definition?.width, definition?.point === true ? 16 : 0));
|
|
44
|
+
const height = Math.max(4, finite(definition?.h ?? definition?.height, definition?.point === true ? 16 : 0));
|
|
45
|
+
const x = definition?.point === true
|
|
46
|
+
? finite(definition?.x) - (width * 0.5)
|
|
47
|
+
: finite(definition?.x);
|
|
48
|
+
const y = definition?.point === true
|
|
49
|
+
? finite(definition?.y) - (height * 0.5)
|
|
50
|
+
: finite(definition?.y);
|
|
51
|
+
return {
|
|
52
|
+
id,
|
|
53
|
+
label: normalizeText(definition?.label ?? definition?.name, id),
|
|
54
|
+
kind: normalizeKind(normalizeText(definition?.kind ?? definition?.type)),
|
|
55
|
+
x,
|
|
56
|
+
y,
|
|
57
|
+
w: width,
|
|
58
|
+
h: height,
|
|
59
|
+
priority: Number.isFinite(Number(definition?.priority)) ? Number(definition.priority) : 0,
|
|
60
|
+
promptText: normalizeText(definition?.promptText ?? definition?.prompt),
|
|
61
|
+
lockedPromptText: normalizeText(definition?.lockedPromptText),
|
|
62
|
+
openedPromptText: normalizeText(definition?.openedPromptText),
|
|
63
|
+
interactionKey: normalizeText(definition?.interactionKey, 'E'),
|
|
64
|
+
requiredItemId: normalizeText(definition?.requiredItemId),
|
|
65
|
+
requiredQuantity: Math.max(1, finite(definition?.requiredQuantity, 1)),
|
|
66
|
+
consumeRequiredItem: definition?.consumeRequiredItem === true,
|
|
67
|
+
grantItem: definition?.grantItem && typeof definition.grantItem === 'object' ? { ...definition.grantItem } : null,
|
|
68
|
+
checkpointId: normalizeText(definition?.checkpointId, id),
|
|
69
|
+
once: definition?.once !== false,
|
|
70
|
+
collected: definition?.collected === true,
|
|
71
|
+
opened: definition?.opened === true,
|
|
72
|
+
activated: definition?.activated === true,
|
|
73
|
+
disabled: definition?.disabled === true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function promptForInteractable(interactable, inventory) {
|
|
78
|
+
if (!interactable || interactable.disabled === true) return null;
|
|
79
|
+
if (interactable.kind === 'pickup' && interactable.collected === true) return null;
|
|
80
|
+
if (interactable.kind === 'lock') {
|
|
81
|
+
if (interactable.opened === true) return interactable.openedPromptText || null;
|
|
82
|
+
if (interactable.requiredItemId && !hasInventoryItem2D(inventory, interactable.requiredItemId, interactable.requiredQuantity)) {
|
|
83
|
+
return interactable.lockedPromptText || `${interactable.label} is locked`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (interactable.kind === 'checkpoint' && interactable.activated === true && interactable.once === true) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
if (interactable.kind === 'prop' && interactable.activated === true && interactable.once === true) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return interactable.promptText || `Interact with ${interactable.label}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function buildTrigger(interactable, inventory) {
|
|
96
|
+
const promptText = promptForInteractable(interactable, inventory);
|
|
97
|
+
if (!promptText) return null;
|
|
98
|
+
return createRectTrigger2D(interactable.id, interactable.x, interactable.y, interactable.w, interactable.h, {
|
|
99
|
+
kind: interactable.kind,
|
|
100
|
+
role: interactable.kind,
|
|
101
|
+
priority: interactable.priority,
|
|
102
|
+
promptText,
|
|
103
|
+
interactionKey: interactable.interactionKey,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function successfulInteraction(interactable) {
|
|
108
|
+
if (!interactable) return false;
|
|
109
|
+
return interactable.kind === 'pickup'
|
|
110
|
+
? interactable.collected === true
|
|
111
|
+
: interactable.kind === 'lock'
|
|
112
|
+
? interactable.opened === true
|
|
113
|
+
: interactable.activated === true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function createAdventureInteractable2D(definition = {}, index = 0) {
|
|
117
|
+
return normalizeInteractable(definition, index);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function createAdventureInteractablesFromTilemapObjects2D(objects = [], defaults = {}) {
|
|
121
|
+
return (Array.isArray(objects) ? objects : []).map((object, index) => createAdventureInteractable2D({
|
|
122
|
+
id: normalizeText(object?.id != null ? String(object.id) : null, `object-${index + 1}`),
|
|
123
|
+
label: object?.name,
|
|
124
|
+
kind: normalizeText(object?.type, normalizeText(objectProperty(object, 'kind'), defaults.kind)),
|
|
125
|
+
x: finite(object?.worldX ?? object?.x),
|
|
126
|
+
y: finite(object?.worldY ?? object?.y),
|
|
127
|
+
width: finite(object?.width, defaults.width),
|
|
128
|
+
height: finite(object?.height, defaults.height),
|
|
129
|
+
point: object?.point === true,
|
|
130
|
+
promptText: normalizeText(objectProperty(object, 'promptText'), defaults.promptText),
|
|
131
|
+
lockedPromptText: normalizeText(objectProperty(object, 'lockedPromptText'), defaults.lockedPromptText),
|
|
132
|
+
requiredItemId: normalizeText(objectProperty(object, 'requiredItemId'), defaults.requiredItemId),
|
|
133
|
+
requiredQuantity: finite(objectProperty(object, 'requiredQuantity'), defaults.requiredQuantity),
|
|
134
|
+
checkpointId: normalizeText(objectProperty(object, 'checkpointId'), defaults.checkpointId),
|
|
135
|
+
}, index));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function createAdventureInteractionState2D(options = {}) {
|
|
139
|
+
return {
|
|
140
|
+
tracker: createTriggerTracker({ subjectId: normalizeText(options.subjectId, 'player'), dimension: '2d' }),
|
|
141
|
+
prompt: createInteractionPromptState({ prompt: normalizeText(options.prompt, 'Interact') }),
|
|
142
|
+
activeCheckpointId: normalizeText(options.activeCheckpointId),
|
|
143
|
+
lastResult: null,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function stepAdventureInteractables2D(
|
|
148
|
+
state,
|
|
149
|
+
subjectBounds,
|
|
150
|
+
interactables = [],
|
|
151
|
+
options = {},
|
|
152
|
+
) {
|
|
153
|
+
const inventory = options.inventory || null;
|
|
154
|
+
const normalized = (Array.isArray(interactables) ? interactables : []).map((entry, index) => createAdventureInteractable2D(entry, index));
|
|
155
|
+
const triggers = normalized
|
|
156
|
+
.map((entry) => buildTrigger(entry, inventory))
|
|
157
|
+
.filter(Boolean);
|
|
158
|
+
const triggerStep = stepRectTriggers2D(state?.tracker, subjectBounds, triggers);
|
|
159
|
+
updateInteractionPromptState(state.prompt, triggerStep, {
|
|
160
|
+
interactPressed: options.interactPressed === true,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const result = {
|
|
164
|
+
reasonCode: state.prompt.reasonCode,
|
|
165
|
+
acceptedId: state.prompt.acceptedTriggerId || null,
|
|
166
|
+
collectedIds: [],
|
|
167
|
+
openedIds: [],
|
|
168
|
+
activatedIds: [],
|
|
169
|
+
checkpointId: null,
|
|
170
|
+
promptText: state.prompt.prompt,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
if (!result.acceptedId) {
|
|
174
|
+
state.lastResult = result;
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const interactable = normalized.find((entry) => entry.id === result.acceptedId) || null;
|
|
179
|
+
if (!interactable) {
|
|
180
|
+
state.lastResult = result;
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (interactable.kind === 'pickup') {
|
|
185
|
+
if (!interactable.collected && interactable.grantItem && inventory) {
|
|
186
|
+
addInventoryItem2D(inventory, interactable.grantItem);
|
|
187
|
+
}
|
|
188
|
+
interactable.collected = true;
|
|
189
|
+
result.collectedIds.push(interactable.id);
|
|
190
|
+
} else if (interactable.kind === 'lock') {
|
|
191
|
+
const unlocked = !interactable.requiredItemId
|
|
192
|
+
|| hasInventoryItem2D(inventory, interactable.requiredItemId, interactable.requiredQuantity);
|
|
193
|
+
if (!unlocked) {
|
|
194
|
+
result.reasonCode = 'interaction_locked';
|
|
195
|
+
state.lastResult = result;
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
if (interactable.consumeRequiredItem === true && interactable.requiredItemId) {
|
|
199
|
+
removeInventoryItem2D(inventory, interactable.requiredItemId, interactable.requiredQuantity);
|
|
200
|
+
}
|
|
201
|
+
interactable.opened = true;
|
|
202
|
+
result.openedIds.push(interactable.id);
|
|
203
|
+
} else if (interactable.kind === 'checkpoint') {
|
|
204
|
+
interactable.activated = true;
|
|
205
|
+
state.activeCheckpointId = interactable.checkpointId;
|
|
206
|
+
result.checkpointId = interactable.checkpointId;
|
|
207
|
+
result.activatedIds.push(interactable.id);
|
|
208
|
+
} else {
|
|
209
|
+
interactable.activated = true;
|
|
210
|
+
result.activatedIds.push(interactable.id);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (interactable.once === true && successfulInteraction(interactable) && interactable.kind !== 'checkpoint') {
|
|
214
|
+
interactable.disabled = true;
|
|
215
|
+
}
|
|
216
|
+
if (result.reasonCode === 'interaction_accepted') {
|
|
217
|
+
result.reasonCode = `interaction_${interactable.kind}_accepted`;
|
|
218
|
+
}
|
|
219
|
+
state.lastResult = result;
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSpriteAnimationLibrary2D,
|
|
3
|
+
createSpriteAnimator2D,
|
|
4
|
+
hasSpriteAnimationState2D,
|
|
5
|
+
registerSpriteAnimationLibrary2D,
|
|
6
|
+
} from './animation-2d.js'
|
|
7
|
+
|
|
8
|
+
let nextAnimationPackageId = 1
|
|
9
|
+
|
|
10
|
+
function normalizePackageName(value) {
|
|
11
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
12
|
+
return value.trim()
|
|
13
|
+
}
|
|
14
|
+
return `animation-package-${nextAnimationPackageId}`
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizePackageClipName(value) {
|
|
18
|
+
const name = typeof value === 'string' ? value.trim() : ''
|
|
19
|
+
if (!name) {
|
|
20
|
+
throw new Error('animation package clip names must be non-empty strings.')
|
|
21
|
+
}
|
|
22
|
+
return name
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizePackageStateName(value) {
|
|
26
|
+
const name = typeof value === 'string' ? value.trim() : ''
|
|
27
|
+
if (!name) {
|
|
28
|
+
throw new Error('animation package state names must be non-empty strings.')
|
|
29
|
+
}
|
|
30
|
+
return name
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function normalizePackageStateSpec(stateName, spec, knownClips) {
|
|
34
|
+
if (typeof spec === 'string') {
|
|
35
|
+
const clip = spec.trim()
|
|
36
|
+
if (!knownClips.has(clip)) {
|
|
37
|
+
throw new Error(`animation package state "${stateName}" references unknown clip "${clip}".`)
|
|
38
|
+
}
|
|
39
|
+
return { clip }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!spec || typeof spec !== 'object') {
|
|
43
|
+
throw new Error(`animation package state "${stateName}" must be a clip name or object.`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const clip = typeof spec.clip === 'string' ? spec.clip.trim() : ''
|
|
47
|
+
if (!clip) {
|
|
48
|
+
throw new Error(`animation package state "${stateName}" must declare a clip.`)
|
|
49
|
+
}
|
|
50
|
+
if (!knownClips.has(clip)) {
|
|
51
|
+
throw new Error(`animation package state "${stateName}" references unknown clip "${clip}".`)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const state = { clip }
|
|
55
|
+
if (spec.frameDuration != null) {
|
|
56
|
+
state.frameDuration = spec.frameDuration
|
|
57
|
+
}
|
|
58
|
+
if (spec.loop != null) {
|
|
59
|
+
state.loop = spec.loop
|
|
60
|
+
}
|
|
61
|
+
return state
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function cloneClipSpec(clipName, spec) {
|
|
65
|
+
if (Array.isArray(spec)) {
|
|
66
|
+
if (spec.length === 0) {
|
|
67
|
+
throw new Error(`animation package clip "${clipName}" must include at least one frame.`)
|
|
68
|
+
}
|
|
69
|
+
return { frames: [...spec] }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (Number.isInteger(spec) && spec >= 0) {
|
|
73
|
+
return { frame: spec }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!spec || typeof spec !== 'object') {
|
|
77
|
+
throw new Error(`animation package clip "${clipName}" must be an integer, frame array, or object.`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const cloned = { ...spec }
|
|
81
|
+
if (Array.isArray(spec.frames)) {
|
|
82
|
+
if (spec.frames.length === 0) {
|
|
83
|
+
throw new Error(`animation package clip "${clipName}" must include at least one frame.`)
|
|
84
|
+
}
|
|
85
|
+
cloned.frames = [...spec.frames]
|
|
86
|
+
}
|
|
87
|
+
return cloned
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function buildLibraryStates(packageDefinition) {
|
|
91
|
+
const states = {}
|
|
92
|
+
for (const [stateName, stateSpec] of Object.entries(packageDefinition.states)) {
|
|
93
|
+
const clipSpec = cloneClipSpec(stateSpec.clip, packageDefinition.clips[stateSpec.clip])
|
|
94
|
+
if (stateSpec.frameDuration != null) {
|
|
95
|
+
clipSpec.frameDuration = stateSpec.frameDuration
|
|
96
|
+
}
|
|
97
|
+
if (stateSpec.loop != null) {
|
|
98
|
+
clipSpec.loop = stateSpec.loop
|
|
99
|
+
}
|
|
100
|
+
states[stateName] = clipSpec
|
|
101
|
+
}
|
|
102
|
+
return states
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function createAnimationPackage2D(options = {}) {
|
|
106
|
+
const rawClips = options.clips && typeof options.clips === 'object' ? options.clips : null
|
|
107
|
+
const rawStates = options.states && typeof options.states === 'object' ? options.states : null
|
|
108
|
+
|
|
109
|
+
if (!rawClips || Object.keys(rawClips).length === 0) {
|
|
110
|
+
throw new Error('animation packages must declare at least one clip.')
|
|
111
|
+
}
|
|
112
|
+
if (!rawStates || Object.keys(rawStates).length === 0) {
|
|
113
|
+
throw new Error('animation packages must declare at least one state.')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const packageId = `anim-pack-${nextAnimationPackageId}`
|
|
117
|
+
nextAnimationPackageId += 1
|
|
118
|
+
|
|
119
|
+
const name = normalizePackageName(options.name)
|
|
120
|
+
const clips = {}
|
|
121
|
+
for (const [rawClipName, spec] of Object.entries(rawClips)) {
|
|
122
|
+
const clipName = normalizePackageClipName(rawClipName)
|
|
123
|
+
clips[clipName] = cloneClipSpec(clipName, spec)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const knownClips = new Set(Object.keys(clips))
|
|
127
|
+
const states = {}
|
|
128
|
+
for (const [rawStateName, spec] of Object.entries(rawStates)) {
|
|
129
|
+
const stateName = normalizePackageStateName(rawStateName)
|
|
130
|
+
states[stateName] = normalizePackageStateSpec(stateName, spec, knownClips)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const defaultState = (
|
|
134
|
+
typeof options.defaultState === 'string'
|
|
135
|
+
&& Object.prototype.hasOwnProperty.call(states, options.defaultState.trim())
|
|
136
|
+
? options.defaultState.trim()
|
|
137
|
+
: Object.keys(states)[0]
|
|
138
|
+
)
|
|
139
|
+
const source = options.source && typeof options.source === 'object'
|
|
140
|
+
? { ...options.source }
|
|
141
|
+
: { type: 'inline-animation-package' }
|
|
142
|
+
|
|
143
|
+
const library = createSpriteAnimationLibrary2D({
|
|
144
|
+
image: options.image,
|
|
145
|
+
frameWidth: options.frameWidth,
|
|
146
|
+
frameHeight: options.frameHeight,
|
|
147
|
+
columns: options.columns,
|
|
148
|
+
anchorX: options.anchorX,
|
|
149
|
+
anchorY: options.anchorY,
|
|
150
|
+
defaultFrameDuration: options.defaultFrameDuration,
|
|
151
|
+
defaultState,
|
|
152
|
+
name,
|
|
153
|
+
states: buildLibraryStates({ clips, states }),
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
packageId,
|
|
158
|
+
name,
|
|
159
|
+
defaultState: library.defaultState,
|
|
160
|
+
clips,
|
|
161
|
+
states,
|
|
162
|
+
library,
|
|
163
|
+
source,
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function hasAnimationPackageState2D(animationPackage, stateName) {
|
|
168
|
+
return !!animationPackage && typeof stateName === 'string'
|
|
169
|
+
&& Object.prototype.hasOwnProperty.call(animationPackage.states || {}, stateName)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function registerAnimationPackage2D(runtime, animationPackage) {
|
|
173
|
+
if (!animationPackage?.library) {
|
|
174
|
+
throw new Error('animation package is required.')
|
|
175
|
+
}
|
|
176
|
+
return registerSpriteAnimationLibrary2D(runtime, animationPackage.library)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function createPackagedSpriteAnimator2D(runtime, animationPackage, options = {}) {
|
|
180
|
+
if (registerAnimationPackage2D(runtime, animationPackage) !== true) {
|
|
181
|
+
throw new Error(`failed to register animation package "${animationPackage?.packageId || 'unknown'}".`)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const initialState = hasAnimationPackageState2D(animationPackage, options.initialState)
|
|
185
|
+
? options.initialState
|
|
186
|
+
: animationPackage.defaultState
|
|
187
|
+
const animator = createSpriteAnimator2D(runtime, animationPackage.library, { initialState })
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
...animator,
|
|
191
|
+
packageId: animationPackage.packageId,
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function getAnimationPackageLibrary2D(animationPackage) {
|
|
196
|
+
return animationPackage?.library || null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function getAnimationPackageState2D(animationPackage, stateName) {
|
|
200
|
+
return hasAnimationPackageState2D(animationPackage, stateName)
|
|
201
|
+
? animationPackage.states[stateName]
|
|
202
|
+
: null
|
|
203
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { createAnimationPackage2D } from './animation-packaging-2d.js'
|
|
2
|
+
|
|
3
|
+
let nextAtlasAssetManifestId = 1
|
|
4
|
+
|
|
5
|
+
function cloneSpecMap(value, label) {
|
|
6
|
+
if (!value || typeof value !== 'object') {
|
|
7
|
+
throw new Error(`${label} must be an object.`)
|
|
8
|
+
}
|
|
9
|
+
const entries = Object.entries(value)
|
|
10
|
+
if (entries.length === 0) {
|
|
11
|
+
throw new Error(`${label} must not be empty.`)
|
|
12
|
+
}
|
|
13
|
+
return Object.fromEntries(entries.map(([key, spec]) => {
|
|
14
|
+
if (Array.isArray(spec)) {
|
|
15
|
+
return [key, [...spec]]
|
|
16
|
+
}
|
|
17
|
+
if (spec && typeof spec === 'object') {
|
|
18
|
+
return [key, { ...spec, frames: Array.isArray(spec.frames) ? [...spec.frames] : spec.frames }]
|
|
19
|
+
}
|
|
20
|
+
return [key, spec]
|
|
21
|
+
}))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readPositiveNumber(value, fallback, label) {
|
|
25
|
+
const numeric = Number(value)
|
|
26
|
+
const resolved = Number.isFinite(numeric) ? numeric : fallback
|
|
27
|
+
if (!(resolved > 0)) {
|
|
28
|
+
throw new Error(`${label} must be a positive number.`)
|
|
29
|
+
}
|
|
30
|
+
return resolved
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readFiniteNumber(value, fallback) {
|
|
34
|
+
const numeric = Number(value)
|
|
35
|
+
return Number.isFinite(numeric) ? numeric : fallback
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeAtlasManifestName(value, fallback) {
|
|
39
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
40
|
+
return value.trim()
|
|
41
|
+
}
|
|
42
|
+
return fallback
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createAtlasAssetManifest2D(options = {}) {
|
|
46
|
+
const image = typeof options.image === 'string' && options.image.trim().length > 0
|
|
47
|
+
? options.image.trim()
|
|
48
|
+
: null
|
|
49
|
+
if (!image) {
|
|
50
|
+
throw new Error('atlas asset manifest image is required.')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const manifest = options.manifest && typeof options.manifest === 'object'
|
|
54
|
+
? options.manifest
|
|
55
|
+
: options
|
|
56
|
+
const name = normalizeAtlasManifestName(
|
|
57
|
+
options.name ?? manifest.name,
|
|
58
|
+
`atlas-asset-manifest-${nextAtlasAssetManifestId}`,
|
|
59
|
+
)
|
|
60
|
+
const atlasManifestId = `atlas-asset-manifest-${nextAtlasAssetManifestId}`
|
|
61
|
+
nextAtlasAssetManifestId += 1
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
atlasManifestId,
|
|
65
|
+
assetType: 'atlasAssetManifest2d',
|
|
66
|
+
name,
|
|
67
|
+
image,
|
|
68
|
+
frameWidth: readPositiveNumber(manifest.frameWidth, 0, 'atlas asset manifest frameWidth'),
|
|
69
|
+
frameHeight: readPositiveNumber(manifest.frameHeight, 0, 'atlas asset manifest frameHeight'),
|
|
70
|
+
columns: Math.max(1, Math.floor(readPositiveNumber(manifest.columns, 1, 'atlas asset manifest columns'))),
|
|
71
|
+
defaultFrameDuration: readPositiveNumber(
|
|
72
|
+
manifest.defaultFrameDuration,
|
|
73
|
+
1 / 12,
|
|
74
|
+
'atlas asset manifest defaultFrameDuration',
|
|
75
|
+
),
|
|
76
|
+
anchorX: readFiniteNumber(manifest.anchorX, 0.5),
|
|
77
|
+
anchorY: readFiniteNumber(manifest.anchorY, 0.5),
|
|
78
|
+
defaultState: typeof manifest.defaultState === 'string' ? manifest.defaultState.trim() : '',
|
|
79
|
+
clips: cloneSpecMap(manifest.clips, 'atlas asset manifest clips'),
|
|
80
|
+
states: cloneSpecMap(manifest.states, 'atlas asset manifest states'),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function createAnimationPackageFromAtlasAsset2D(atlasAsset, overrides = {}) {
|
|
85
|
+
if (!atlasAsset?.atlasManifestId) {
|
|
86
|
+
throw new Error('atlas asset manifest is required.')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return createAnimationPackage2D({
|
|
90
|
+
name: normalizeAtlasManifestName(overrides.name, atlasAsset.name),
|
|
91
|
+
image: typeof overrides.image === 'string' && overrides.image.trim().length > 0
|
|
92
|
+
? overrides.image.trim()
|
|
93
|
+
: atlasAsset.image,
|
|
94
|
+
frameWidth: overrides.frameWidth ?? atlasAsset.frameWidth,
|
|
95
|
+
frameHeight: overrides.frameHeight ?? atlasAsset.frameHeight,
|
|
96
|
+
columns: overrides.columns ?? atlasAsset.columns,
|
|
97
|
+
anchorX: overrides.anchorX ?? atlasAsset.anchorX,
|
|
98
|
+
anchorY: overrides.anchorY ?? atlasAsset.anchorY,
|
|
99
|
+
defaultFrameDuration: overrides.defaultFrameDuration ?? atlasAsset.defaultFrameDuration,
|
|
100
|
+
clips: cloneSpecMap(atlasAsset.clips, 'atlas asset manifest clips'),
|
|
101
|
+
states: cloneSpecMap(atlasAsset.states, 'atlas asset manifest states'),
|
|
102
|
+
defaultState: typeof overrides.defaultState === 'string' && overrides.defaultState.trim().length > 0
|
|
103
|
+
? overrides.defaultState.trim()
|
|
104
|
+
: atlasAsset.defaultState,
|
|
105
|
+
source: {
|
|
106
|
+
type: 'atlas-asset-manifest',
|
|
107
|
+
atlasManifestId: atlasAsset.atlasManifestId,
|
|
108
|
+
image: atlasAsset.image,
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
}
|