@auraindustry/aurajs 0.0.2 → 0.0.4

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.
@@ -0,0 +1,454 @@
1
+ function isPlainObject(value) {
2
+ return value != null && typeof value === 'object' && Object.prototype.toString.call(value) === '[object Object]';
3
+ }
4
+
5
+ function cloneLifecycle(value) {
6
+ const source = isPlainObject(value) ? value : {};
7
+ return {
8
+ running: source.running === true,
9
+ setupCalls: toNonNegativeInt(source.setupCalls),
10
+ updateCalls: toNonNegativeInt(source.updateCalls),
11
+ drawCalls: toNonNegativeInt(source.drawCalls),
12
+ frameCount: toNonNegativeInt(source.frameCount),
13
+ lastDeltaSeconds: toNonNegativeNumber(source.lastDeltaSeconds),
14
+ };
15
+ }
16
+
17
+ function toNonNegativeInt(value) {
18
+ const numeric = Number(value);
19
+ if (!Number.isFinite(numeric) || numeric < 0) return 0;
20
+ return Math.floor(numeric);
21
+ }
22
+
23
+ function toNonNegativeNumber(value) {
24
+ const numeric = Number(value);
25
+ if (!Number.isFinite(numeric) || numeric < 0) return 0;
26
+ return Number(numeric.toFixed(6));
27
+ }
28
+
29
+ function sanitizeState(value) {
30
+ const source = isPlainObject(value) ? value : {};
31
+ const lastError = isPlainObject(source.lastError)
32
+ ? {
33
+ reasonCode: typeof source.lastError.reasonCode === 'string' ? source.lastError.reasonCode : null,
34
+ layer: typeof source.lastError.layer === 'string' ? source.lastError.layer : null,
35
+ retryable: source.lastError.retryable === true,
36
+ details: isPlainObject(source.lastError.details) ? { ...source.lastError.details } : {},
37
+ }
38
+ : null;
39
+ return {
40
+ phase: typeof source.phase === 'string' ? source.phase : 'idle',
41
+ reasonCode: typeof source.reasonCode === 'string' ? source.reasonCode : null,
42
+ lifecycle: cloneLifecycle(source.lifecycle),
43
+ lastError,
44
+ };
45
+ }
46
+
47
+ function normalizeConfig(value) {
48
+ if (value == null) {
49
+ return {
50
+ rootUrl: '.',
51
+ mount: null,
52
+ };
53
+ }
54
+ if (!isPlainObject(value)) {
55
+ throw createApiError(
56
+ 'web_api_invalid_config',
57
+ 'Configuration must be a plain object.',
58
+ 'api',
59
+ false,
60
+ { expectedType: 'object' },
61
+ );
62
+ }
63
+
64
+ const normalized = {
65
+ rootUrl: '.',
66
+ mount: null,
67
+ };
68
+
69
+ if (Object.prototype.hasOwnProperty.call(value, 'rootUrl')) {
70
+ if (typeof value.rootUrl !== 'string' || value.rootUrl.trim().length === 0) {
71
+ throw createApiError(
72
+ 'web_api_invalid_config',
73
+ 'Config rootUrl must be a non-empty string.',
74
+ 'api',
75
+ false,
76
+ { field: 'rootUrl' },
77
+ );
78
+ }
79
+ normalized.rootUrl = value.rootUrl.trim();
80
+ }
81
+
82
+ if (Object.prototype.hasOwnProperty.call(value, 'mount')) {
83
+ const mount = value.mount;
84
+ const mountIsSelector = typeof mount === 'string' && mount.trim().length > 0;
85
+ const mountIsNodeLike = mount != null && typeof mount === 'object' && typeof mount.appendChild === 'function';
86
+ if (mount == null) {
87
+ normalized.mount = null;
88
+ } else if (mountIsSelector || mountIsNodeLike) {
89
+ normalized.mount = mount;
90
+ } else {
91
+ throw createApiError(
92
+ 'web_api_invalid_config',
93
+ 'Config mount must be a selector string or HTMLElement-like node.',
94
+ 'api',
95
+ false,
96
+ { field: 'mount' },
97
+ );
98
+ }
99
+ }
100
+
101
+ return normalized;
102
+ }
103
+
104
+ function mergeConfig(baseConfig, patch) {
105
+ const base = normalizeConfig(baseConfig);
106
+ if (patch == null) return base;
107
+ if (!isPlainObject(patch)) {
108
+ throw createApiError(
109
+ 'web_api_invalid_config',
110
+ 'Configuration must be a plain object.',
111
+ 'api',
112
+ false,
113
+ { expectedType: 'object' },
114
+ );
115
+ }
116
+
117
+ const next = {
118
+ rootUrl: base.rootUrl,
119
+ mount: base.mount,
120
+ };
121
+
122
+ if (Object.prototype.hasOwnProperty.call(patch, 'rootUrl')) {
123
+ if (typeof patch.rootUrl !== 'string' || patch.rootUrl.trim().length === 0) {
124
+ throw createApiError(
125
+ 'web_api_invalid_config',
126
+ 'Config rootUrl must be a non-empty string.',
127
+ 'api',
128
+ false,
129
+ { field: 'rootUrl' },
130
+ );
131
+ }
132
+ next.rootUrl = patch.rootUrl.trim();
133
+ }
134
+
135
+ if (Object.prototype.hasOwnProperty.call(patch, 'mount')) {
136
+ const mount = patch.mount;
137
+ const mountIsSelector = typeof mount === 'string' && mount.trim().length > 0;
138
+ const mountIsNodeLike = mount != null && typeof mount === 'object' && typeof mount.appendChild === 'function';
139
+ if (mount == null) {
140
+ next.mount = null;
141
+ } else if (mountIsSelector || mountIsNodeLike) {
142
+ next.mount = mount;
143
+ } else {
144
+ throw createApiError(
145
+ 'web_api_invalid_config',
146
+ 'Config mount must be a selector string or HTMLElement-like node.',
147
+ 'api',
148
+ false,
149
+ { field: 'mount' },
150
+ );
151
+ }
152
+ }
153
+
154
+ return next;
155
+ }
156
+
157
+ function normalizeCallbacks(options = {}) {
158
+ const source = isPlainObject(options) ? options : {};
159
+ const callbacks = {
160
+ onPhaseChange: typeof source.onPhaseChange === 'function' ? source.onPhaseChange : null,
161
+ onLifecycle: typeof source.onLifecycle === 'function' ? source.onLifecycle : null,
162
+ onError: typeof source.onError === 'function' ? source.onError : null,
163
+ };
164
+ return callbacks;
165
+ }
166
+
167
+ function createApiError(reasonCode, message, layer, retryable, details) {
168
+ const error = new Error(message);
169
+ error.ok = false;
170
+ error.reasonCode = reasonCode;
171
+ error.layer = layer;
172
+ error.retryable = retryable === true;
173
+ error.details = isPlainObject(details) ? { ...details } : {};
174
+ return error;
175
+ }
176
+
177
+ function normalizeApiError(error, fallback) {
178
+ if (error && typeof error === 'object' && typeof error.reasonCode === 'string') {
179
+ return error;
180
+ }
181
+
182
+ return createApiError(
183
+ fallback.reasonCode,
184
+ fallback.message,
185
+ fallback.layer,
186
+ fallback.retryable,
187
+ {
188
+ cause: String(error && error.message ? error.message : error),
189
+ },
190
+ );
191
+ }
192
+
193
+ function snapshotError(error) {
194
+ if (!error || typeof error !== 'object') {
195
+ return null;
196
+ }
197
+ return {
198
+ reasonCode: typeof error.reasonCode === 'string' ? error.reasonCode : null,
199
+ layer: typeof error.layer === 'string' ? error.layer : null,
200
+ retryable: error.retryable === true,
201
+ details: isPlainObject(error.details) ? { ...error.details } : {},
202
+ };
203
+ }
204
+
205
+ function resolveLoader(injectedLoader) {
206
+ if (injectedLoader && typeof injectedLoader === 'object') {
207
+ return injectedLoader;
208
+ }
209
+ if (typeof globalThis !== 'undefined' && globalThis && typeof globalThis.AuraWebLoader === 'object') {
210
+ return globalThis.AuraWebLoader;
211
+ }
212
+ return null;
213
+ }
214
+
215
+ function stableLifecycleKey(lifecycle) {
216
+ const source = cloneLifecycle(lifecycle);
217
+ return JSON.stringify(source);
218
+ }
219
+
220
+ function stableErrorKey(error) {
221
+ if (!error) return null;
222
+ return JSON.stringify({
223
+ reasonCode: error.reasonCode || null,
224
+ layer: error.layer || null,
225
+ retryable: error.retryable === true,
226
+ details: isPlainObject(error.details) ? error.details : {},
227
+ });
228
+ }
229
+
230
+ export function createAuraGame(options = {}) {
231
+ const opts = isPlainObject(options) ? options : {};
232
+ let loader = resolveLoader(opts.loader);
233
+ let config = normalizeConfig(opts.config || null);
234
+ const callbacks = normalizeCallbacks(opts);
235
+ let state = sanitizeState(loader && typeof loader.getState === 'function' ? loader.getState() : null);
236
+ let lastPhase = state.phase;
237
+ let lastLifecycle = stableLifecycleKey(state.lifecycle);
238
+ let lastError = stableErrorKey(state.lastError);
239
+
240
+ function readLoaderState() {
241
+ if (!loader || typeof loader.getState !== 'function') {
242
+ return null;
243
+ }
244
+ try {
245
+ return sanitizeState(loader.getState());
246
+ } catch (_) {
247
+ return null;
248
+ }
249
+ }
250
+
251
+ function currentState() {
252
+ if (state.phase === 'error') {
253
+ return sanitizeState(state);
254
+ }
255
+
256
+ const loaderState = readLoaderState();
257
+ const merged = loaderState
258
+ ? sanitizeState({
259
+ phase: loaderState.phase,
260
+ reasonCode: loaderState.reasonCode,
261
+ lifecycle: loaderState.lifecycle,
262
+ lastError: loaderState.lastError || state.lastError,
263
+ })
264
+ : sanitizeState(state);
265
+ state = merged;
266
+ return merged;
267
+ }
268
+
269
+ function emitFromState(nextState, force = false) {
270
+ const phase = nextState.phase;
271
+ const lifecycle = cloneLifecycle(nextState.lifecycle);
272
+ const error = snapshotError(nextState.lastError);
273
+
274
+ const phaseChanged = phase !== lastPhase;
275
+ const lifecycleKey = stableLifecycleKey(lifecycle);
276
+ const lifecycleChanged = lifecycleKey !== lastLifecycle;
277
+ const errorKey = stableErrorKey(error);
278
+ const errorChanged = errorKey !== lastError && error != null;
279
+
280
+ if ((phaseChanged || force) && callbacks.onPhaseChange) {
281
+ callbacks.onPhaseChange(phase, {
282
+ phase,
283
+ reasonCode: nextState.reasonCode,
284
+ lifecycle,
285
+ lastError: error,
286
+ });
287
+ }
288
+ if ((lifecycleChanged || force) && callbacks.onLifecycle) {
289
+ callbacks.onLifecycle(lifecycle, {
290
+ phase,
291
+ reasonCode: nextState.reasonCode,
292
+ lifecycle,
293
+ lastError: error,
294
+ });
295
+ }
296
+ if (errorChanged && callbacks.onError) {
297
+ callbacks.onError(error, {
298
+ phase,
299
+ reasonCode: nextState.reasonCode,
300
+ lifecycle,
301
+ lastError: error,
302
+ });
303
+ }
304
+
305
+ lastPhase = phase;
306
+ lastLifecycle = lifecycleKey;
307
+ lastError = errorKey;
308
+ state = sanitizeState({
309
+ phase,
310
+ reasonCode: nextState.reasonCode,
311
+ lifecycle,
312
+ lastError: error,
313
+ });
314
+ }
315
+
316
+ function ensureLoader() {
317
+ if (!loader) {
318
+ loader = resolveLoader(null);
319
+ }
320
+ if (!loader || typeof loader !== 'object') {
321
+ throw createApiError(
322
+ 'web_api_loader_missing',
323
+ 'AuraWebLoader is not available. Ensure build/web/js/aura-web-loader.js is loaded or inject a loader.',
324
+ 'api',
325
+ false,
326
+ {},
327
+ );
328
+ }
329
+ const required = ['load', 'mount', 'unmount', 'getState'];
330
+ for (const name of required) {
331
+ if (typeof loader[name] !== 'function') {
332
+ throw createApiError(
333
+ 'web_api_loader_invalid',
334
+ 'AuraWebLoader is missing required method: ' + name,
335
+ 'api',
336
+ false,
337
+ { method: name },
338
+ );
339
+ }
340
+ }
341
+ return loader;
342
+ }
343
+
344
+ async function mount(target, runtimeConfig) {
345
+ const resolvedLoader = ensureLoader();
346
+ const mergedConfig = mergeConfig(config, runtimeConfig || null);
347
+
348
+ try {
349
+ await resolvedLoader.load({
350
+ rootUrl: mergedConfig.rootUrl,
351
+ mount: mergedConfig.mount,
352
+ });
353
+ await resolvedLoader.mount(target != null ? target : mergedConfig.mount, {
354
+ rootUrl: mergedConfig.rootUrl,
355
+ mount: mergedConfig.mount,
356
+ });
357
+ const loaderState = currentState();
358
+ emitFromState({
359
+ ...loaderState,
360
+ phase: loaderState.phase === 'idle' ? 'mounted' : loaderState.phase,
361
+ reasonCode: loaderState.phase === 'idle' ? null : loaderState.reasonCode,
362
+ }, true);
363
+ return currentState();
364
+ } catch (error) {
365
+ const normalized = normalizeApiError(error, {
366
+ reasonCode: 'web_api_mount_failed',
367
+ message: 'Failed to mount Aura runtime.',
368
+ layer: 'api',
369
+ retryable: true,
370
+ });
371
+ emitFromState({
372
+ ...currentState(),
373
+ phase: 'error',
374
+ reasonCode: normalized.reasonCode,
375
+ lastError: snapshotError(normalized),
376
+ }, true);
377
+ throw normalized;
378
+ }
379
+ }
380
+
381
+ async function unmount() {
382
+ const resolvedLoader = ensureLoader();
383
+ try {
384
+ await resolvedLoader.unmount();
385
+ const loaderState = currentState();
386
+ emitFromState({
387
+ ...loaderState,
388
+ phase: loaderState.phase === 'idle' ? 'unmounted' : loaderState.phase,
389
+ reasonCode: loaderState.phase === 'idle' ? null : loaderState.reasonCode,
390
+ }, true);
391
+ return currentState();
392
+ } catch (error) {
393
+ const normalized = normalizeApiError(error, {
394
+ reasonCode: 'web_api_unmount_failed',
395
+ message: 'Failed to unmount Aura runtime.',
396
+ layer: 'api',
397
+ retryable: true,
398
+ });
399
+ emitFromState({
400
+ ...currentState(),
401
+ phase: 'error',
402
+ reasonCode: normalized.reasonCode,
403
+ lastError: snapshotError(normalized),
404
+ }, true);
405
+ throw normalized;
406
+ }
407
+ }
408
+
409
+ function setConfig(nextConfig) {
410
+ config = mergeConfig(config, nextConfig);
411
+ return getConfig();
412
+ }
413
+
414
+ function getConfig() {
415
+ return {
416
+ rootUrl: config.rootUrl,
417
+ mount: config.mount,
418
+ };
419
+ }
420
+
421
+ function getState() {
422
+ return currentState();
423
+ }
424
+
425
+ return {
426
+ mount,
427
+ unmount,
428
+ setConfig,
429
+ getConfig,
430
+ getState,
431
+ };
432
+ }
433
+
434
+ const defaultGame = createAuraGame();
435
+
436
+ export function mount(target, options) {
437
+ return defaultGame.mount(target, options);
438
+ }
439
+
440
+ export function unmount() {
441
+ return defaultGame.unmount();
442
+ }
443
+
444
+ export function setConfig(nextConfig) {
445
+ return defaultGame.setConfig(nextConfig);
446
+ }
447
+
448
+ export function getState() {
449
+ return defaultGame.getState();
450
+ }
451
+
452
+ export function getConfig() {
453
+ return defaultGame.getConfig();
454
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "identity": {
3
+ "name": "{{PROJECT_TITLE}}",
4
+ "version": "{{PROJECT_VERSION}}",
5
+ "executable": "{{PROJECT_BIN_NAME}}",
6
+ "icon": null
7
+ },
8
+ "window": {
9
+ "title": "{{PROJECT_TITLE}}",
10
+ "width": 1280,
11
+ "height": 720,
12
+ "resizable": true,
13
+ "fullscreen": false,
14
+ "vsync": true,
15
+ "hidpi": true
16
+ },
17
+ "build": {
18
+ "entry": "src/main.js",
19
+ "outDir": "build",
20
+ "assetDir": "assets",
21
+ "assetMode": "embed"
22
+ },
23
+ "modules": {
24
+ "physics": false,
25
+ "network": true,
26
+ "steam": false
27
+ }
28
+ }
@@ -0,0 +1,196 @@
1
+ // {{PROJECT_TITLE}} — AuraJS 2D shooter starter
2
+
3
+ import { axisFromKeys, centeredRect, clamp, consumeCooldown, createCooldown, createWaveDirector, pickEnemyArchetypeForWave2D, rectsOverlap, removeWhere, spawnEnemy2D, stepWaveDirector, tickCooldown } from './starter-utils/index.js';
4
+
5
+ const PLAYER_W = 34;
6
+ const PLAYER_H = 26;
7
+ const PLAYER_SPEED = 420;
8
+ const BULLET_W = 6;
9
+ const BULLET_H = 14;
10
+ const BULLET_SPEED = 640;
11
+ const SHOOT_COOLDOWN = 0.14;
12
+
13
+ const WAVE_CONFIG = [
14
+ { maxSpawns: 9, spawnEvery: 0.62, archetype: 'scout', speedScale: 1.0 },
15
+ { maxSpawns: 10, spawnEvery: 0.56, archetype: 'striker', speedScale: 1.06 },
16
+ { maxSpawns: 8, spawnEvery: 0.74, archetype: 'tank', speedScale: 1.02 },
17
+ { maxSpawns: 12, spawnEvery: 0.48, archetype: null, speedScale: 1.12 },
18
+ ];
19
+
20
+ let player = { x: 0, y: 0 };
21
+ let bullets = [];
22
+ let enemies = [];
23
+ let lives = 3;
24
+ let score = 0;
25
+ let shootCooldown = createCooldown(SHOOT_COOLDOWN);
26
+ let waveDirector = createWaveDirector({ waves: WAVE_CONFIG, loop: true, betweenWaveDelay: 1.0 });
27
+ let gameOver = false;
28
+
29
+ function hasMethod(obj, method) {
30
+ return Boolean(obj) && typeof obj[method] === 'function';
31
+ }
32
+
33
+ function failWithReason(reasonCode, message) {
34
+ throw new Error(`[2d-shooter-template] ${message} [reason:${reasonCode}]`);
35
+ }
36
+
37
+ function assertRuntimeCapabilities() {
38
+ const missing = [];
39
+ if (!hasMethod(aura.window, 'getSize')) missing.push('aura.window.getSize');
40
+ if (!hasMethod(aura.window, 'getFPS')) missing.push('aura.window.getFPS');
41
+ if (!hasMethod(aura.input, 'isKeyDown')) missing.push('aura.input.isKeyDown');
42
+ if (!hasMethod(aura.input, 'isKeyPressed')) missing.push('aura.input.isKeyPressed');
43
+ if (!hasMethod(aura.draw2d, 'clear')) missing.push('aura.draw2d.clear');
44
+ if (!hasMethod(aura.draw2d, 'rect')) missing.push('aura.draw2d.rect');
45
+ if (!hasMethod(aura.draw2d, 'text')) missing.push('aura.draw2d.text');
46
+ if (!hasMethod(aura.draw2d, 'measureText')) missing.push('aura.draw2d.measureText');
47
+ if (typeof aura.rgb !== 'function') missing.push('aura.rgb');
48
+ if (typeof aura.rgba !== 'function') missing.push('aura.rgba');
49
+ if (!aura.Color || !aura.Color.WHITE) missing.push('aura.Color.WHITE');
50
+
51
+ if (missing.length > 0) {
52
+ failWithReason('missing_runtime_api', `runtime missing required APIs: ${missing.join(', ')}`);
53
+ }
54
+
55
+ const small = aura.draw2d.measureText('Probe', { size: 8 });
56
+ const large = aura.draw2d.measureText('Probe', { size: 24 });
57
+ if (!Number.isFinite(Number(small?.width)) || !Number.isFinite(Number(large?.width)) || Number(large.width) <= Number(small.width)) {
58
+ failWithReason(
59
+ 'placeholder_runtime_behavior',
60
+ 'draw2d.measureText appears to be placeholder behavior (size does not affect width).',
61
+ );
62
+ }
63
+ }
64
+
65
+ function resetRun() {
66
+ const size = aura.window.getSize();
67
+ player.x = size.width * 0.5;
68
+ player.y = size.height - 70;
69
+ bullets = [];
70
+ enemies = [];
71
+ lives = 3;
72
+ score = 0;
73
+ shootCooldown = createCooldown(SHOOT_COOLDOWN);
74
+ waveDirector = createWaveDirector({ waves: WAVE_CONFIG, loop: true, betweenWaveDelay: 1.0 });
75
+ gameOver = false;
76
+ }
77
+
78
+ function spawnBullet() {
79
+ bullets.push({
80
+ x: player.x - (BULLET_W * 0.5),
81
+ y: player.y - PLAYER_H,
82
+ });
83
+ }
84
+
85
+ function spawnEnemy(width, waveState) {
86
+ const wave = waveState?.wave || null;
87
+ const archetype = wave?.archetype || pickEnemyArchetypeForWave2D(waveState?.waveIndex || 0).id;
88
+ const enemy = spawnEnemy2D({
89
+ x: 24 + (Math.random() * Math.max(1, width - 48)),
90
+ y: -22,
91
+ waveIndex: waveState?.waveIndex || 0,
92
+ archetypeId: archetype,
93
+ });
94
+
95
+ const speedScale = Number.isFinite(Number(wave?.speedScale))
96
+ ? Number(wave.speedScale)
97
+ : 1.0;
98
+ enemy.speed *= Math.max(0.4, speedScale);
99
+ enemies.push(enemy);
100
+ }
101
+
102
+ aura.setup = function () {
103
+ assertRuntimeCapabilities();
104
+ resetRun();
105
+ console.log('{{PROJECT_TITLE}} started (2D shooter template)');
106
+ };
107
+
108
+ aura.update = function (dt) {
109
+ const size = aura.window.getSize();
110
+
111
+ if (gameOver) {
112
+ if (aura.input.isKeyPressed('enter')) resetRun();
113
+ return;
114
+ }
115
+
116
+ const moveX = axisFromKeys(aura.input, ['arrowleft', 'a'], ['arrowright', 'd']);
117
+ const moveY = axisFromKeys(aura.input, ['arrowup', 'w'], ['arrowdown', 's']);
118
+
119
+ player.x += moveX * PLAYER_SPEED * dt;
120
+ player.y += moveY * PLAYER_SPEED * dt;
121
+ player.x = clamp(player.x, PLAYER_W * 0.5, size.width - (PLAYER_W * 0.5));
122
+ player.y = clamp(player.y, PLAYER_H * 0.5, size.height - (PLAYER_H * 0.5));
123
+
124
+ tickCooldown(shootCooldown, dt);
125
+ if ((aura.input.isKeyDown('space') || aura.input.isKeyDown('z')) && consumeCooldown(shootCooldown)) {
126
+ spawnBullet();
127
+ }
128
+
129
+ stepWaveDirector(waveDirector, dt, (waveState) => {
130
+ spawnEnemy(size.width, waveState);
131
+ });
132
+
133
+ for (const bullet of bullets) {
134
+ bullet.y -= BULLET_SPEED * dt;
135
+ }
136
+ removeWhere(bullets, (bullet) => bullet.y + BULLET_H <= 0);
137
+
138
+ for (const enemy of enemies) {
139
+ enemy.y += enemy.speed * dt;
140
+ }
141
+
142
+ const survivors = [];
143
+ for (const enemy of enemies) {
144
+ const enemyBox = centeredRect(enemy.x, enemy.y, enemy.size, enemy.size);
145
+ let hit = false;
146
+
147
+ for (let i = bullets.length - 1; i >= 0; i -= 1) {
148
+ const bullet = bullets[i];
149
+ const bulletBox = { x: bullet.x, y: bullet.y, w: BULLET_W, h: BULLET_H };
150
+ if (!rectsOverlap(enemyBox, bulletBox)) continue;
151
+ bullets.splice(i, 1);
152
+ hit = true;
153
+ score += enemy.scoreValue;
154
+ break;
155
+ }
156
+
157
+ if (hit) continue;
158
+ if (enemy.y - (enemy.size * 0.5) > size.height) {
159
+ lives -= 1;
160
+ if (lives <= 0) gameOver = true;
161
+ continue;
162
+ }
163
+ survivors.push(enemy);
164
+ }
165
+ enemies = survivors;
166
+ };
167
+
168
+ aura.draw = function () {
169
+ const fps = aura.window.getFPS().toFixed(0);
170
+ aura.draw2d.clear(aura.rgba(0.05, 0.06, 0.1, 1.0));
171
+
172
+ for (const bullet of bullets) {
173
+ aura.draw2d.rect(bullet.x, bullet.y, BULLET_W, BULLET_H, aura.rgb(1.0, 0.95, 0.72));
174
+ }
175
+
176
+ for (const enemy of enemies) {
177
+ aura.draw2d.rect(
178
+ enemy.x - (enemy.size * 0.5),
179
+ enemy.y - (enemy.size * 0.5),
180
+ enemy.size,
181
+ enemy.size,
182
+ aura.rgb(enemy.color.r, enemy.color.g, enemy.color.b),
183
+ );
184
+ }
185
+
186
+ aura.draw2d.rect(player.x - (PLAYER_W * 0.5), player.y - (PLAYER_H * 0.5), PLAYER_W, PLAYER_H, aura.rgb(0.28, 0.78, 1.0));
187
+
188
+ const waveLabel = waveDirector.completed ? 'Done' : String(waveDirector.waveIndex + 1);
189
+ aura.draw2d.text('Move: Arrows/WASD Fire: Space/Z', 12, 12, { color: aura.Color.WHITE, size: 15, align: 'left' });
190
+ aura.draw2d.text(`Wave ${waveLabel} Score ${score} Lives ${lives} FPS ${fps}`, 12, 34, { color: aura.Color.WHITE, size: 14, align: 'left' });
191
+
192
+ if (gameOver) {
193
+ aura.draw2d.text('GAME OVER', 12, 64, { color: aura.rgb(1.0, 0.42, 0.42), size: 24, align: 'left' });
194
+ aura.draw2d.text('Press Enter to restart', 12, 92, { color: aura.Color.WHITE, size: 16, align: 'left' });
195
+ }
196
+ };
@@ -0,0 +1,28 @@
1
+ {
2
+ "identity": {
3
+ "name": "{{PROJECT_TITLE}}",
4
+ "version": "{{PROJECT_VERSION}}",
5
+ "executable": "{{PROJECT_BIN_NAME}}",
6
+ "icon": null
7
+ },
8
+ "window": {
9
+ "title": "{{PROJECT_TITLE}}",
10
+ "width": 1280,
11
+ "height": 720,
12
+ "resizable": true,
13
+ "fullscreen": false,
14
+ "vsync": true,
15
+ "hidpi": true
16
+ },
17
+ "build": {
18
+ "entry": "src/main.js",
19
+ "outDir": "build",
20
+ "assetDir": "assets",
21
+ "assetMode": "embed"
22
+ },
23
+ "modules": {
24
+ "physics": false,
25
+ "network": true,
26
+ "steam": false
27
+ }
28
+ }