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.
- package/README.md +25 -8
- package/bin/nova64.js +165 -0
- package/dist/assets/console-CY_kygm3.js +14 -0
- package/dist/assets/console-CY_kygm3.js.map +1 -0
- package/dist/assets/main-l0sNRNKZ.js.map +1 -0
- package/dist/assets/sky/studio/nx.png +0 -0
- package/dist/assets/sky/studio/ny.png +0 -0
- package/dist/assets/sky/studio/nz.png +0 -0
- package/dist/assets/sky/studio/px.png +0 -0
- package/dist/assets/sky/studio/py.png +0 -0
- package/dist/assets/sky/studio/pz.png +0 -0
- package/dist/assets/vanilla-Dcuy32gi.js +2 -0
- package/dist/assets/vanilla-Dcuy32gi.js.map +1 -0
- package/dist/console.html +899 -0
- package/dist/docs/BENCHMARK.md +77 -0
- package/dist/docs/CHEATSHEET.md +255 -0
- package/dist/docs/EFFECTS_API_GUIDE.md +577 -0
- package/dist/docs/EFFECTS_QUICK_REFERENCE.md +331 -0
- package/dist/docs/FONT_CHARACTER_REFERENCE.md +219 -0
- package/dist/docs/FREE_GLB_ASSETS.md +330 -0
- package/dist/docs/FULLSCREEN_BUTTON_FEATURE.md +296 -0
- package/dist/docs/GAMEPAD_SUPPORT.md +348 -0
- package/dist/docs/GAME_IMPROVEMENTS.md +278 -0
- package/dist/docs/GAME_QUALITY_STATUS.md +300 -0
- package/dist/docs/MIGRATION_GUIDE.md +553 -0
- package/dist/docs/NOVA64_3D_API.md +356 -0
- package/dist/docs/NOVA64_API_REFERENCE.md +1406 -0
- package/dist/docs/NOVA64_UI_API.md +503 -0
- package/dist/docs/UI_SYSTEM_SUMMARY.md +445 -0
- package/dist/docs/VOXEL_ENGINE_GUIDE.md +662 -0
- package/dist/docs/VOXEL_QUICK_REFERENCE.md +386 -0
- package/dist/docs/api-3d.html +750 -0
- package/dist/docs/api-effects.html +385 -0
- package/dist/docs/api-improvements.md +121 -0
- package/dist/docs/api-skybox.html +407 -0
- package/dist/docs/api-sprites.html +321 -0
- package/dist/docs/api-voxel.html +337 -0
- package/dist/docs/api.html +543 -0
- package/dist/docs/assets.html +306 -0
- package/dist/docs/audio.html +340 -0
- package/dist/docs/blogs.html +286 -0
- package/dist/docs/collision.html +316 -0
- package/dist/docs/console.html +247 -0
- package/dist/docs/editor.html +297 -0
- package/dist/docs/font.html +247 -0
- package/dist/docs/framebuffer.html +247 -0
- package/dist/docs/fullscreen-button.html +297 -0
- package/dist/docs/gpu-systems.html +247 -0
- package/dist/docs/index.html +580 -0
- package/dist/docs/input.html +491 -0
- package/dist/docs/physics.html +311 -0
- package/dist/docs/screens.html +311 -0
- package/dist/docs/storage.html +311 -0
- package/dist/docs/textinput.html +332 -0
- package/dist/docs/ui.html +488 -0
- package/dist/examples/3d-advanced/code.js +695 -0
- package/dist/examples/adventure-comic-3d/code.js +342 -0
- package/dist/examples/audio-lab/code.js +150 -0
- package/dist/examples/boids-flocking/code.js +270 -0
- package/dist/examples/crystal-cathedral-3d/code.js +706 -0
- package/dist/examples/cyberpunk-city-3d/code.js +1383 -0
- package/dist/examples/demoscene/README.md +192 -0
- package/dist/examples/demoscene/code.js +1081 -0
- package/dist/examples/demoscene/meta.json +21 -0
- package/dist/examples/dungeon-crawler-3d/code.js +1117 -0
- package/dist/examples/f-zero-nova-3d/code.js +865 -0
- package/dist/examples/f-zero-nova-3d/code_old.js +1555 -0
- package/dist/examples/fps-demo-3d/code.js +744 -0
- package/dist/examples/game-of-life-3d/code.js +338 -0
- package/dist/examples/generative-art/code.js +632 -0
- package/dist/examples/hello-3d/code.js +325 -0
- package/dist/examples/hello-skybox/code.js +183 -0
- package/dist/examples/hello-world/code.js +19 -0
- package/dist/examples/input-showcase/code.js +109 -0
- package/dist/examples/instancing-demo/code.js +315 -0
- package/dist/examples/minecraft-demo/code.js +387 -0
- package/dist/examples/model-viewer-3d/code.js +114 -0
- package/dist/examples/mystical-realm-3d/code.js +1203 -0
- package/dist/examples/nature-explorer-3d/code.js +1318 -0
- package/dist/examples/particles-demo/code.js +522 -0
- package/dist/examples/pbr-showcase/code.js +140 -0
- package/dist/examples/physics-demo-3d/code.js +948 -0
- package/dist/examples/screen-demo/code.js +267 -0
- package/dist/examples/shooter-demo-3d/code.js +1286 -0
- package/dist/examples/space-combat-3d/IMPLEMENTATION_SUMMARY.md +109 -0
- package/dist/examples/space-combat-3d/README.md +135 -0
- package/dist/examples/space-combat-3d/code.js +1332 -0
- package/dist/examples/space-harrier-3d/code.js +923 -0
- package/dist/examples/star-fox-nova-3d/code.js +1116 -0
- package/dist/examples/star-fox-nova-3d/code_backup.js +410 -0
- package/dist/examples/star-fox-nova-3d/code_broken.js +1821 -0
- package/dist/examples/storage-quest/code.js +209 -0
- package/dist/examples/strider-demo-3d/IMPROVEMENT_OPTIONS.md +285 -0
- package/dist/examples/strider-demo-3d/cache-test.html +132 -0
- package/dist/examples/strider-demo-3d/code-fixed.js +582 -0
- package/dist/examples/strider-demo-3d/code-old.js +1537 -0
- package/dist/examples/strider-demo-3d/code.js +1462 -0
- package/dist/examples/strider-demo-3d/code.js.bak2 +1169 -0
- package/dist/examples/strider-demo-3d/fix-game.sh +53 -0
- package/dist/examples/super-plumber-64/README.md +128 -0
- package/dist/examples/super-plumber-64/code.js +1185 -0
- package/dist/examples/super-plumber-64/index.html +88 -0
- package/dist/examples/test-2d-overlay/code.js +32 -0
- package/dist/examples/test-font/code.js +51 -0
- package/dist/examples/test-minimal/code.js +21 -0
- package/dist/examples/ui-demo/code.js +306 -0
- package/dist/examples/wing-commander-space/README.md +180 -0
- package/dist/examples/wing-commander-space/code.js +1285 -0
- package/dist/examples/wizardry-3d/CHANGELOG.md +366 -0
- package/dist/examples/wizardry-3d/code.js +3928 -0
- package/dist/index.html +666 -0
- package/dist/os9-shell/assets/index-DIHfrTaW.css +1 -0
- package/dist/os9-shell/assets/index-KchE_ngx.js +483 -0
- package/dist/os9-shell/assets/index-KchE_ngx.js.map +1 -0
- package/dist/os9-shell/index.html +23 -0
- package/dist/os9-shell/nova-icon.svg +12 -0
- package/index.html +6 -1
- package/package.json +37 -32
- package/public/assets/sky/studio/nx.png +0 -0
- package/public/assets/sky/studio/ny.png +0 -0
- package/public/assets/sky/studio/nz.png +0 -0
- package/public/assets/sky/studio/px.png +0 -0
- package/public/assets/sky/studio/py.png +0 -0
- package/public/assets/sky/studio/pz.png +0 -0
- package/public/os9-shell/assets/index-KchE_ngx.js +483 -0
- package/public/os9-shell/assets/index-KchE_ngx.js.map +1 -0
- package/public/os9-shell/index.html +10 -1
- package/runtime/api-2d.js +301 -21
- package/runtime/api-3d/pbr.js +45 -1
- package/runtime/api-3d.js +1 -0
- package/runtime/api-effects.js +90 -3
- package/runtime/api-gameutils.js +476 -0
- package/runtime/api-generative.js +610 -0
- package/runtime/api-skybox.js +54 -0
- package/runtime/api-voxel.js +139 -28
- package/runtime/gpu-threejs.js +13 -9
- package/runtime/ui.js +2 -2
- package/src/main.js +24 -1
- package/public/os9-shell/assets/index-B1Uvacma.js +0 -32825
- 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
|
+
}
|