nova64 0.2.4 → 0.2.6

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.
Files changed (140) hide show
  1. package/README.md +25 -8
  2. package/bin/nova64.js +165 -0
  3. package/dist/assets/console-CY_kygm3.js +14 -0
  4. package/dist/assets/console-CY_kygm3.js.map +1 -0
  5. package/dist/assets/main-l0sNRNKZ.js.map +1 -0
  6. package/dist/assets/sky/studio/nx.png +0 -0
  7. package/dist/assets/sky/studio/ny.png +0 -0
  8. package/dist/assets/sky/studio/nz.png +0 -0
  9. package/dist/assets/sky/studio/px.png +0 -0
  10. package/dist/assets/sky/studio/py.png +0 -0
  11. package/dist/assets/sky/studio/pz.png +0 -0
  12. package/dist/assets/vanilla-Dcuy32gi.js +2 -0
  13. package/dist/assets/vanilla-Dcuy32gi.js.map +1 -0
  14. package/dist/console.html +899 -0
  15. package/dist/docs/BENCHMARK.md +77 -0
  16. package/dist/docs/CHEATSHEET.md +255 -0
  17. package/dist/docs/EFFECTS_API_GUIDE.md +577 -0
  18. package/dist/docs/EFFECTS_QUICK_REFERENCE.md +331 -0
  19. package/dist/docs/FONT_CHARACTER_REFERENCE.md +219 -0
  20. package/dist/docs/FREE_GLB_ASSETS.md +330 -0
  21. package/dist/docs/FULLSCREEN_BUTTON_FEATURE.md +296 -0
  22. package/dist/docs/GAMEPAD_SUPPORT.md +348 -0
  23. package/dist/docs/GAME_IMPROVEMENTS.md +278 -0
  24. package/dist/docs/GAME_QUALITY_STATUS.md +300 -0
  25. package/dist/docs/MIGRATION_GUIDE.md +553 -0
  26. package/dist/docs/NOVA64_3D_API.md +356 -0
  27. package/dist/docs/NOVA64_API_REFERENCE.md +1406 -0
  28. package/dist/docs/NOVA64_UI_API.md +503 -0
  29. package/dist/docs/UI_SYSTEM_SUMMARY.md +445 -0
  30. package/dist/docs/VOXEL_ENGINE_GUIDE.md +662 -0
  31. package/dist/docs/VOXEL_QUICK_REFERENCE.md +386 -0
  32. package/dist/docs/api-3d.html +750 -0
  33. package/dist/docs/api-effects.html +385 -0
  34. package/dist/docs/api-improvements.md +121 -0
  35. package/dist/docs/api-skybox.html +407 -0
  36. package/dist/docs/api-sprites.html +321 -0
  37. package/dist/docs/api-voxel.html +337 -0
  38. package/dist/docs/api.html +543 -0
  39. package/dist/docs/assets.html +306 -0
  40. package/dist/docs/audio.html +340 -0
  41. package/dist/docs/blogs.html +286 -0
  42. package/dist/docs/collision.html +316 -0
  43. package/dist/docs/console.html +247 -0
  44. package/dist/docs/editor.html +297 -0
  45. package/dist/docs/font.html +247 -0
  46. package/dist/docs/framebuffer.html +247 -0
  47. package/dist/docs/fullscreen-button.html +297 -0
  48. package/dist/docs/gpu-systems.html +247 -0
  49. package/dist/docs/index.html +580 -0
  50. package/dist/docs/input.html +491 -0
  51. package/dist/docs/physics.html +311 -0
  52. package/dist/docs/screens.html +311 -0
  53. package/dist/docs/storage.html +311 -0
  54. package/dist/docs/textinput.html +332 -0
  55. package/dist/docs/ui.html +488 -0
  56. package/dist/examples/3d-advanced/code.js +695 -0
  57. package/dist/examples/adventure-comic-3d/code.js +342 -0
  58. package/dist/examples/audio-lab/code.js +150 -0
  59. package/dist/examples/boids-flocking/code.js +270 -0
  60. package/dist/examples/crystal-cathedral-3d/code.js +706 -0
  61. package/dist/examples/cyberpunk-city-3d/code.js +1383 -0
  62. package/dist/examples/demoscene/README.md +192 -0
  63. package/dist/examples/demoscene/code.js +1081 -0
  64. package/dist/examples/demoscene/meta.json +21 -0
  65. package/dist/examples/dungeon-crawler-3d/code.js +1117 -0
  66. package/dist/examples/f-zero-nova-3d/code.js +865 -0
  67. package/dist/examples/f-zero-nova-3d/code_old.js +1555 -0
  68. package/dist/examples/fps-demo-3d/code.js +744 -0
  69. package/dist/examples/game-of-life-3d/code.js +338 -0
  70. package/dist/examples/generative-art/code.js +632 -0
  71. package/dist/examples/hello-3d/code.js +325 -0
  72. package/dist/examples/hello-skybox/code.js +183 -0
  73. package/dist/examples/hello-world/code.js +19 -0
  74. package/dist/examples/input-showcase/code.js +109 -0
  75. package/dist/examples/instancing-demo/code.js +315 -0
  76. package/dist/examples/minecraft-demo/code.js +387 -0
  77. package/dist/examples/model-viewer-3d/code.js +114 -0
  78. package/dist/examples/mystical-realm-3d/code.js +1203 -0
  79. package/dist/examples/nature-explorer-3d/code.js +1318 -0
  80. package/dist/examples/particles-demo/code.js +522 -0
  81. package/dist/examples/pbr-showcase/code.js +140 -0
  82. package/dist/examples/physics-demo-3d/code.js +948 -0
  83. package/dist/examples/screen-demo/code.js +267 -0
  84. package/dist/examples/shooter-demo-3d/code.js +1286 -0
  85. package/dist/examples/space-combat-3d/IMPLEMENTATION_SUMMARY.md +109 -0
  86. package/dist/examples/space-combat-3d/README.md +135 -0
  87. package/dist/examples/space-combat-3d/code.js +1332 -0
  88. package/dist/examples/space-harrier-3d/code.js +923 -0
  89. package/dist/examples/star-fox-nova-3d/code.js +1116 -0
  90. package/dist/examples/star-fox-nova-3d/code_backup.js +410 -0
  91. package/dist/examples/star-fox-nova-3d/code_broken.js +1821 -0
  92. package/dist/examples/storage-quest/code.js +209 -0
  93. package/dist/examples/strider-demo-3d/IMPROVEMENT_OPTIONS.md +285 -0
  94. package/dist/examples/strider-demo-3d/cache-test.html +132 -0
  95. package/dist/examples/strider-demo-3d/code-fixed.js +582 -0
  96. package/dist/examples/strider-demo-3d/code-old.js +1537 -0
  97. package/dist/examples/strider-demo-3d/code.js +1462 -0
  98. package/dist/examples/strider-demo-3d/code.js.bak2 +1169 -0
  99. package/dist/examples/strider-demo-3d/fix-game.sh +53 -0
  100. package/dist/examples/super-plumber-64/README.md +128 -0
  101. package/dist/examples/super-plumber-64/code.js +1185 -0
  102. package/dist/examples/super-plumber-64/index.html +88 -0
  103. package/dist/examples/test-2d-overlay/code.js +32 -0
  104. package/dist/examples/test-font/code.js +51 -0
  105. package/dist/examples/test-minimal/code.js +21 -0
  106. package/dist/examples/ui-demo/code.js +306 -0
  107. package/dist/examples/wing-commander-space/README.md +180 -0
  108. package/dist/examples/wing-commander-space/code.js +1285 -0
  109. package/dist/examples/wizardry-3d/CHANGELOG.md +366 -0
  110. package/dist/examples/wizardry-3d/code.js +3928 -0
  111. package/dist/index.html +666 -0
  112. package/dist/os9-shell/assets/index-DIHfrTaW.css +1 -0
  113. package/dist/os9-shell/assets/index-KchE_ngx.js +483 -0
  114. package/dist/os9-shell/assets/index-KchE_ngx.js.map +1 -0
  115. package/dist/os9-shell/index.html +23 -0
  116. package/dist/os9-shell/nova-icon.svg +12 -0
  117. package/index.html +6 -1
  118. package/package.json +37 -32
  119. package/public/assets/sky/studio/nx.png +0 -0
  120. package/public/assets/sky/studio/ny.png +0 -0
  121. package/public/assets/sky/studio/nz.png +0 -0
  122. package/public/assets/sky/studio/px.png +0 -0
  123. package/public/assets/sky/studio/py.png +0 -0
  124. package/public/assets/sky/studio/pz.png +0 -0
  125. package/public/os9-shell/assets/index-KchE_ngx.js +483 -0
  126. package/public/os9-shell/assets/index-KchE_ngx.js.map +1 -0
  127. package/public/os9-shell/index.html +10 -1
  128. package/runtime/api-2d.js +301 -21
  129. package/runtime/api-3d/pbr.js +45 -1
  130. package/runtime/api-3d.js +1 -0
  131. package/runtime/api-effects.js +90 -3
  132. package/runtime/api-gameutils.js +476 -0
  133. package/runtime/api-generative.js +610 -0
  134. package/runtime/api-skybox.js +54 -0
  135. package/runtime/api-voxel.js +139 -28
  136. package/runtime/gpu-threejs.js +13 -9
  137. package/runtime/ui.js +2 -2
  138. package/src/main.js +24 -1
  139. package/public/os9-shell/assets/index-B1Uvacma.js +0 -32825
  140. package/public/os9-shell/assets/index-B1Uvacma.js.map +0 -1
@@ -0,0 +1,582 @@
1
+ // SHADOW NINJA 3D - PROPERLY DESIGNED PLATFORMER
2
+ // Fixed version with real game design principles
3
+ // VERSION: v007-PROPER-GAME-DESIGN
4
+
5
+ console.log('🎮 Loading proper platformer...');
6
+
7
+ // ============================================
8
+ // GAME CONFIGURATION
9
+ // ============================================
10
+ const CONFIG = {
11
+ // Player physics - tuned for fun gameplay
12
+ PLAYER_SPEED: 8,
13
+ JUMP_POWER: 12,
14
+ GRAVITY: 30,
15
+ FRICTION: 0.85,
16
+ AIR_CONTROL: 0.6,
17
+
18
+ // Camera settings
19
+ CAMERA_DISTANCE: 15,
20
+ CAMERA_HEIGHT: 5,
21
+ CAMERA_SMOOTH: 0.08,
22
+
23
+ // Level design
24
+ PLATFORM_SIZE: 5,
25
+ PLATFORM_GAP: 3,
26
+
27
+ // Visual
28
+ USE_GLB_MODELS: false, // Set true to use GLB models (requires model files)
29
+ };
30
+
31
+ // ============================================
32
+ // GAME STATE
33
+ // ============================================
34
+ let gameState = 'start';
35
+ let gameTime = 0;
36
+ let score = 0;
37
+ let coins = 0;
38
+
39
+ // Player
40
+ let player = {
41
+ pos: { x: 0, y: 2, z: 0 },
42
+ vel: { x: 0, y: 0, z: 0 },
43
+ grounded: false,
44
+ facingRight: true,
45
+ canJump: true,
46
+ coyoteTime: 0, // Grace period after leaving platform
47
+ jumpBufferTime: 0, // Grace period before landing
48
+ };
49
+
50
+ // Camera
51
+ let camera = {
52
+ pos: { x: 0, y: 5, z: 15 },
53
+ target: { x: 0, y: 3, z: 0 },
54
+ };
55
+
56
+ // Game objects
57
+ let platforms = [];
58
+ let collectibles = [];
59
+ let enemies = [];
60
+ let playerMesh = null;
61
+ let uiButtons = [];
62
+
63
+ // ============================================
64
+ // INITIALIZATION
65
+ // ============================================
66
+ export async function init() {
67
+ console.log('🎮 Initializing proper platformer...');
68
+
69
+ // Reset everything
70
+ gameState = 'start';
71
+ gameTime = 0;
72
+ score = 0;
73
+ coins = 0;
74
+
75
+ player = {
76
+ pos: { x: 0, y: 2, z: 0 },
77
+ vel: { x: 0, y: 0, z: 0 },
78
+ grounded: false,
79
+ facingRight: true,
80
+ canJump: true,
81
+ coyoteTime: 0,
82
+ jumpBufferTime: 0,
83
+ };
84
+
85
+ camera = {
86
+ pos: { x: 0, y: 5, z: 15 },
87
+ target: { x: 0, y: 3, z: 0 },
88
+ };
89
+
90
+ platforms = [];
91
+ collectibles = [];
92
+ enemies = [];
93
+
94
+ // Create player
95
+ if (CONFIG.USE_GLB_MODELS) {
96
+ // Try to load GLB model
97
+ try {
98
+ playerMesh = await loadModel('/models/ninja.glb', [0, 2, 0], 1);
99
+ } catch (e) {
100
+ console.warn('GLB model not found, using fallback');
101
+ playerMesh = createSimplePlayer();
102
+ }
103
+ } else {
104
+ playerMesh = createSimplePlayer();
105
+ }
106
+
107
+ // Build level
108
+ createLevel();
109
+
110
+ // Setup lighting
111
+ setAmbientLight(0xffffff);
112
+ setLightColor(0xffffff);
113
+ setLightDirection(0.5, -0.7, 0.5);
114
+
115
+ // Setup camera
116
+ setCameraPosition(camera.pos.x, camera.pos.y, camera.pos.z);
117
+ setCameraTarget(camera.target.x, camera.target.y, camera.target.z);
118
+
119
+ // Create UI
120
+ createUI();
121
+
122
+ // Focus canvas for input
123
+ const canvas = document.querySelector('canvas');
124
+ if (canvas) {
125
+ canvas.focus();
126
+ canvas.tabIndex = 1;
127
+ }
128
+
129
+ console.log('✅ Platformer ready!');
130
+ }
131
+
132
+ // ============================================
133
+ // PLAYER CREATION
134
+ // ============================================
135
+ function createSimplePlayer() {
136
+ // Create a visible, well-designed character
137
+ const meshes = {};
138
+
139
+ // Body - bright blue
140
+ meshes.body = createCube(0.8, 0x4466ff, [0, 0, 0]);
141
+ setScale(meshes.body, 1, 1.5, 0.8);
142
+
143
+ // Head - white
144
+ meshes.head = createSphere(0.4, 0xffffff, [0, 1, 0]);
145
+
146
+ // Eyes - glowing yellow
147
+ meshes.leftEye = createSphere(0.12, 0xffff00, [-0.15, 1.1, 0.35]);
148
+ meshes.rightEye = createSphere(0.12, 0xffff00, [0.15, 1.1, 0.35]);
149
+
150
+ // Feet - orange
151
+ meshes.leftFoot = createCube(0.3, 0xff8844, [-0.25, -1, 0]);
152
+ setScale(meshes.leftFoot, 0.3, 0.3, 0.5);
153
+ meshes.rightFoot = createCube(0.3, 0xff8844, [0.25, -1, 0]);
154
+ setScale(meshes.rightFoot, 0.3, 0.3, 0.5);
155
+
156
+ return meshes;
157
+ }
158
+
159
+ // ============================================
160
+ // LEVEL CREATION - PROPER DESIGN
161
+ // ============================================
162
+ function createLevel() {
163
+ platforms = [];
164
+ collectibles = [];
165
+ enemies = [];
166
+
167
+ // Ground - solid foundation
168
+ const ground = {
169
+ x: 25,
170
+ y: -1,
171
+ z: 0,
172
+ width: 100,
173
+ height: 2,
174
+ depth: 10,
175
+ mesh: createCube(100, 0x66bb66, [25, -1, 0]),
176
+ };
177
+ setScale(ground.mesh, 1, 2, 1);
178
+ platforms.push(ground);
179
+
180
+ // Tutorial section - teach basics (X: 0-20)
181
+ addPlatform(5, 2, 4); // First jump
182
+ addCoin(5, 4);
183
+
184
+ addPlatform(10, 3, 4); // Second jump
185
+ addCoin(10, 5);
186
+
187
+ addPlatform(15, 2, 4); // Come back down
188
+ addCoin(15, 4);
189
+
190
+ // Easy section - build confidence (X: 20-40)
191
+ addPlatform(22, 4, 3.5);
192
+ addCoin(22, 6);
193
+
194
+ addPlatform(27, 5.5, 3.5);
195
+ addCoin(27, 7.5);
196
+
197
+ addPlatform(32, 7, 3.5);
198
+ addCoin(32, 9);
199
+
200
+ addPlatform(37, 5, 4);
201
+ addCoin(37, 7);
202
+
203
+ // Medium section - introduce challenges (X: 40-60)
204
+ addPlatform(42, 4, 3);
205
+ addEnemy(42, 5);
206
+
207
+ addPlatform(47, 6, 3);
208
+ addCoin(47, 8);
209
+
210
+ addPlatform(52, 8, 3);
211
+ addCoin(52, 10);
212
+
213
+ addPlatform(57, 6, 3);
214
+ addEnemy(57, 7);
215
+
216
+ // Hard section - test skills (X: 60-80)
217
+ addPlatform(62, 9, 2.5);
218
+ addCoin(62, 11);
219
+
220
+ addPlatform(66, 11, 2.5);
221
+ addCoin(66, 13);
222
+
223
+ addPlatform(70, 9, 2.5);
224
+ addCoin(70, 11);
225
+
226
+ addPlatform(74, 7, 3);
227
+ addEnemy(74, 8);
228
+
229
+ // Victory platform
230
+ addPlatform(80, 5, 5);
231
+ addCoin(80, 7);
232
+ addCoin(81, 8);
233
+ addCoin(82, 9);
234
+
235
+ // Background decoration
236
+ createBackground();
237
+ }
238
+
239
+ function addPlatform(x, y, width = 4) {
240
+ const platform = {
241
+ x,
242
+ y,
243
+ z: 0,
244
+ width,
245
+ height: 0.8,
246
+ depth: 4,
247
+ mesh: createCube(width, 0x8866ff, [x, y, 0]),
248
+ };
249
+ setScale(platform.mesh, 1, 0.8, 1);
250
+ platforms.push(platform);
251
+
252
+ // Add edge glow
253
+ const edge = createCube(width + 0.1, 0xffff00, [x, y + 0.45, 0]);
254
+ setScale(edge, 1, 0.1, 1);
255
+ }
256
+
257
+ function addCoin(x, y) {
258
+ const coin = {
259
+ x,
260
+ y,
261
+ z: 0,
262
+ collected: false,
263
+ rotation: 0,
264
+ mesh: createSphere(0.3, 0xffdd00, [x, y, 0]),
265
+ };
266
+ collectibles.push(coin);
267
+ }
268
+
269
+ function addEnemy(x, y) {
270
+ const enemy = {
271
+ x,
272
+ y,
273
+ z: 0,
274
+ vx: 2,
275
+ patrol: { min: x - 2, max: x + 2 },
276
+ mesh: createCube(0.6, 0xff4444, [x, y, 0]),
277
+ };
278
+ setScale(enemy.mesh, 0.8, 0.8, 0.8);
279
+ enemies.push(enemy);
280
+ }
281
+
282
+ function createBackground() {
283
+ // Sky gradient simulation
284
+ const skyTop = createPlane(200, 100, 0x88bbff, [40, 30, -30]);
285
+ setRotation(skyTop, 0, 0, 0);
286
+
287
+ // Ground
288
+ const groundPlane = createPlane(200, 100, 0x66aa66, [40, -5, -5]);
289
+ setRotation(groundPlane, -Math.PI / 2, 0, 0);
290
+
291
+ // Mountains in background
292
+ for (let i = 0; i < 8; i++) {
293
+ const x = i * 15 - 10;
294
+ const height = 15 + Math.random() * 10;
295
+ const mountain = createCube(12, 0x8866aa, [x, height / 2, -25]);
296
+ setScale(mountain, 1, height, 1);
297
+ }
298
+
299
+ // Clouds
300
+ for (let i = 0; i < 10; i++) {
301
+ const cloud = createSphere(2, 0xffffff, [
302
+ Math.random() * 80 - 10,
303
+ 15 + Math.random() * 10,
304
+ -15 - Math.random() * 10,
305
+ ]);
306
+ }
307
+ }
308
+
309
+ // ============================================
310
+ // UPDATE LOOP - PROPER PHYSICS
311
+ // ============================================
312
+ export function update() {
313
+ const dt = 1 / 60;
314
+ gameTime += dt;
315
+
316
+ if (gameState === 'start') {
317
+ updateStartScreen();
318
+ return;
319
+ }
320
+
321
+ if (gameState === 'playing') {
322
+ updateInput(dt);
323
+ updatePhysics(dt);
324
+ updatePlayer(dt);
325
+ updateEnemies(dt);
326
+ updateCollectibles(dt);
327
+ updateCamera(dt);
328
+ }
329
+ }
330
+
331
+ function updateStartScreen() {
332
+ // Check for start input
333
+ if (isKeyDown('Enter') || isKeyDown('Space')) {
334
+ gameState = 'playing';
335
+ console.log('🎮 Game started!');
336
+ }
337
+ }
338
+
339
+ // ============================================
340
+ // INPUT - RESPONSIVE CONTROLS
341
+ // ============================================
342
+ function updateInput(dt) {
343
+ const moveSpeed = CONFIG.PLAYER_SPEED;
344
+ const controlMultiplier = player.grounded ? 1 : CONFIG.AIR_CONTROL;
345
+
346
+ // Horizontal movement
347
+ if (isKeyDown('ArrowLeft')) {
348
+ player.vel.x = -moveSpeed * controlMultiplier;
349
+ player.facingRight = false;
350
+ } else if (isKeyDown('ArrowRight')) {
351
+ player.vel.x = moveSpeed * controlMultiplier;
352
+ player.facingRight = true;
353
+ } else if (player.grounded) {
354
+ // Apply friction only when grounded
355
+ player.vel.x *= CONFIG.FRICTION;
356
+ }
357
+
358
+ // Jump with buffer and coyote time
359
+ if (isKeyPressed('Space') || isKeyPressed('ArrowUp')) {
360
+ player.jumpBufferTime = 0.1; // 100ms buffer
361
+ }
362
+
363
+ // Coyote time - grace period after leaving platform
364
+ if (player.grounded) {
365
+ player.coyoteTime = 0.15; // 150ms grace
366
+ } else {
367
+ player.coyoteTime -= dt;
368
+ }
369
+
370
+ // Execute jump if conditions met
371
+ if (player.jumpBufferTime > 0) {
372
+ if (player.coyoteTime > 0 || player.grounded) {
373
+ player.vel.y = CONFIG.JUMP_POWER;
374
+ player.coyoteTime = 0;
375
+ player.jumpBufferTime = 0;
376
+ player.grounded = false;
377
+ }
378
+ }
379
+
380
+ player.jumpBufferTime -= dt;
381
+ }
382
+
383
+ // ============================================
384
+ // PHYSICS - PROPER COLLISION
385
+ // ============================================
386
+ function updatePhysics(dt) {
387
+ // Apply gravity
388
+ player.vel.y -= CONFIG.GRAVITY * dt;
389
+
390
+ // Apply velocity
391
+ player.pos.x += player.vel.x * dt;
392
+ player.pos.y += player.vel.y * dt;
393
+
394
+ // Check collisions
395
+ player.grounded = false;
396
+
397
+ for (const platform of platforms) {
398
+ // Simple AABB collision
399
+ const playerLeft = player.pos.x - 0.4;
400
+ const playerRight = player.pos.x + 0.4;
401
+ const playerTop = player.pos.y + 1.5;
402
+ const playerBottom = player.pos.y - 1;
403
+
404
+ const platformLeft = platform.x - platform.width / 2;
405
+ const platformRight = platform.x + platform.width / 2;
406
+ const platformTop = platform.y + platform.height / 2;
407
+ const platformBottom = platform.y - platform.height / 2;
408
+
409
+ // Check overlap
410
+ if (
411
+ playerRight > platformLeft &&
412
+ playerLeft < platformRight &&
413
+ player.pos.z > platform.z - platform.depth / 2 &&
414
+ player.pos.z < platform.z + platform.depth / 2
415
+ ) {
416
+ // Landing on top
417
+ if (playerBottom <= platformTop && playerBottom > platformTop - 1 && player.vel.y <= 0) {
418
+ player.pos.y = platformTop + 1;
419
+ player.vel.y = 0;
420
+ player.grounded = true;
421
+ }
422
+ }
423
+ }
424
+
425
+ // Death - fall off world
426
+ if (player.pos.y < -10) {
427
+ resetPlayer();
428
+ }
429
+
430
+ // Victory - reached end
431
+ if (player.pos.x > 82) {
432
+ gameState = 'victory';
433
+ }
434
+ }
435
+
436
+ function resetPlayer() {
437
+ player.pos.x = 0;
438
+ player.pos.y = 2;
439
+ player.vel.x = 0;
440
+ player.vel.y = 0;
441
+ score = Math.max(0, score - 50);
442
+ }
443
+
444
+ // ============================================
445
+ // GAME OBJECT UPDATES
446
+ // ============================================
447
+ function updatePlayer(dt) {
448
+ if (!playerMesh || !playerMesh.body) return;
449
+
450
+ // Update all body parts
451
+ const parts = Object.values(playerMesh);
452
+ const offsetX = player.facingRight ? 0 : 0;
453
+
454
+ setPosition(playerMesh.body, player.pos.x + offsetX, player.pos.y, player.pos.z);
455
+ setPosition(playerMesh.head, player.pos.x, player.pos.y + 1, player.pos.z);
456
+ setPosition(playerMesh.leftEye, player.pos.x - 0.15, player.pos.y + 1.1, player.pos.z + 0.35);
457
+ setPosition(playerMesh.rightEye, player.pos.x + 0.15, player.pos.y + 1.1, player.pos.z + 0.35);
458
+
459
+ // Animate feet based on movement
460
+ const footBob = player.grounded && Math.abs(player.vel.x) > 1 ? Math.sin(gameTime * 10) * 0.1 : 0;
461
+ setPosition(playerMesh.leftFoot, player.pos.x - 0.25, player.pos.y - 1 + footBob, player.pos.z);
462
+ setPosition(playerMesh.rightFoot, player.pos.x + 0.25, player.pos.y - 1 - footBob, player.pos.z);
463
+
464
+ // Rotate body based on direction
465
+ setRotation(playerMesh.body, 0, player.facingRight ? 0 : Math.PI, 0);
466
+ }
467
+
468
+ function updateEnemies(dt) {
469
+ enemies.forEach(enemy => {
470
+ // Simple patrol AI
471
+ enemy.x += enemy.vx * dt;
472
+
473
+ if (enemy.x < enemy.patrol.min || enemy.x > enemy.patrol.max) {
474
+ enemy.vx *= -1;
475
+ }
476
+
477
+ setPosition(enemy.mesh, enemy.x, enemy.y, enemy.z);
478
+
479
+ // Check collision with player
480
+ const dist = Math.sqrt(
481
+ Math.pow(enemy.x - player.pos.x, 2) + Math.pow(enemy.y - player.pos.y, 2)
482
+ );
483
+
484
+ if (dist < 1) {
485
+ resetPlayer();
486
+ }
487
+ });
488
+ }
489
+
490
+ function updateCollectibles(dt) {
491
+ collectibles.forEach(coin => {
492
+ if (coin.collected) return;
493
+
494
+ // Animate rotation
495
+ coin.rotation += dt * 5;
496
+ setRotation(coin.mesh, 0, coin.rotation, 0);
497
+
498
+ // Bob up and down
499
+ const bob = Math.sin(gameTime * 3 + coin.x) * 0.2;
500
+ setPosition(coin.mesh, coin.x, coin.y + bob, coin.z);
501
+
502
+ // Check collection
503
+ const dist = Math.sqrt(Math.pow(coin.x - player.pos.x, 2) + Math.pow(coin.y - player.pos.y, 2));
504
+
505
+ if (dist < 1) {
506
+ coin.collected = true;
507
+ destroyMesh(coin.mesh);
508
+ coins++;
509
+ score += 10;
510
+ }
511
+ });
512
+ }
513
+
514
+ function updateCamera(dt) {
515
+ // Smooth follow camera
516
+ const targetX = player.pos.x + 3; // Look slightly ahead
517
+ const targetY = player.pos.y;
518
+
519
+ camera.target.x += (targetX - camera.target.x) * CONFIG.CAMERA_SMOOTH;
520
+ camera.target.y += (targetY - camera.target.y) * CONFIG.CAMERA_SMOOTH;
521
+
522
+ camera.pos.x = camera.target.x - 5;
523
+ camera.pos.y = camera.target.y + CONFIG.CAMERA_HEIGHT;
524
+ camera.pos.z = CONFIG.CAMERA_DISTANCE;
525
+
526
+ setCameraPosition(camera.pos.x, camera.pos.y, camera.pos.z);
527
+ setCameraTarget(camera.target.x, camera.target.y, 0);
528
+ }
529
+
530
+ // ============================================
531
+ // UI
532
+ // ============================================
533
+ function createUI() {
534
+ uiButtons = [];
535
+ // Add start button or other UI as needed
536
+ }
537
+
538
+ export function draw() {
539
+ if (gameState === 'start') {
540
+ drawStartScreen();
541
+ } else if (gameState === 'playing') {
542
+ drawHUD();
543
+ } else if (gameState === 'victory') {
544
+ drawVictoryScreen();
545
+ }
546
+ }
547
+
548
+ function drawStartScreen() {
549
+ rect(0, 0, 640, 360, rgba8(10, 10, 30, 200), true);
550
+
551
+ print('SHADOW NINJA', 250, 100, rgba8(255, 255, 255, 255));
552
+ print('PROPER PLATFORMER', 230, 130, rgba8(180, 180, 255, 255));
553
+
554
+ const pulse = Math.sin(gameTime * 3) * 0.5 + 0.5;
555
+ print('PRESS SPACE TO START', 220, 180, rgba8(255, 255, 100, Math.floor(pulse * 255)));
556
+
557
+ print('Controls:', 280, 220, rgba8(200, 200, 200, 255));
558
+ print('ARROWS = Move', 260, 250, rgba8(180, 180, 180, 255));
559
+ print('SPACE = Jump', 265, 280, rgba8(180, 180, 180, 255));
560
+ }
561
+
562
+ function drawHUD() {
563
+ // Score
564
+ rect(10, 10, 150, 60, rgba8(0, 0, 0, 150), true);
565
+ rect(10, 10, 150, 60, rgba8(100, 100, 255, 180), false);
566
+ print(`Score: ${score}`, 20, 25, rgba8(255, 255, 255, 255));
567
+ print(`Coins: ${coins}`, 20, 45, rgba8(255, 215, 0, 255));
568
+ }
569
+
570
+ function drawVictoryScreen() {
571
+ rect(0, 0, 640, 360, rgba8(0, 50, 0, 200), true);
572
+
573
+ print('VICTORY!', 270, 120, rgba8(100, 255, 100, 255));
574
+ print(`Final Score: ${score}`, 250, 160, rgba8(255, 255, 255, 255));
575
+ print(`Coins: ${coins}`, 280, 190, rgba8(255, 215, 0, 255));
576
+
577
+ print('Press R to Restart', 240, 240, rgba8(200, 200, 200, 255));
578
+
579
+ if (isKeyDown('KeyR')) {
580
+ init();
581
+ }
582
+ }