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,1406 @@
|
|
|
1
|
+
# 🎮 NOVA64 API REFERENCE
|
|
2
|
+
|
|
3
|
+
**Version:** 0.2.5
|
|
4
|
+
**Date:** March 20, 2026
|
|
5
|
+
**Resolution:** 640×360 pixels
|
|
6
|
+
**Target:** Nintendo 64 / PlayStation aesthetic
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 📚 Table of Contents
|
|
11
|
+
|
|
12
|
+
1. [Getting Started](#getting-started)
|
|
13
|
+
2. [Core 2D API](#core-2d-api)
|
|
14
|
+
3. [Color System](#color-system)
|
|
15
|
+
4. [Input System](#input-system)
|
|
16
|
+
5. [3D Graphics API](#3d-graphics-api)
|
|
17
|
+
6. [Visual Effects API](#visual-effects-api)
|
|
18
|
+
7. [Voxel Engine API](#voxel-engine-api)
|
|
19
|
+
8. [UI System](#ui-system)
|
|
20
|
+
9. [Audio System](#audio-system)
|
|
21
|
+
10. [Utility Functions](#utility-functions)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🚀 Getting Started
|
|
26
|
+
|
|
27
|
+
### Basic Game Structure
|
|
28
|
+
|
|
29
|
+
Every Nova64 game follows this pattern:
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
// Initialization - runs once
|
|
33
|
+
export function init() {
|
|
34
|
+
// Setup game state, load assets, initialize objects
|
|
35
|
+
console.log('🎮 Game initialized!');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Update - runs every frame (~60 FPS)
|
|
39
|
+
export function update(dt) {
|
|
40
|
+
// dt = delta time in seconds (typically 0.016)
|
|
41
|
+
// Handle input, update physics, game logic
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Draw - runs every frame after update
|
|
45
|
+
export function draw() {
|
|
46
|
+
// Render 2D graphics, UI, text
|
|
47
|
+
// 3D graphics are auto-rendered by GPU
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Screen Resolution
|
|
52
|
+
|
|
53
|
+
- **Internal:** 640×360 pixels (16:9 aspect ratio)
|
|
54
|
+
- **Scaling:** Automatically scales to fit browser window
|
|
55
|
+
- **Retro Look:** Pixel-perfect rendering with optional CRT effects
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 🎨 Core 2D API
|
|
60
|
+
|
|
61
|
+
### Drawing Functions
|
|
62
|
+
|
|
63
|
+
#### `cls(color?)`
|
|
64
|
+
|
|
65
|
+
Clear the screen with a color.
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
cls(); // Clear to black
|
|
69
|
+
cls(rgba8(0, 0, 0, 255)); // Clear to black
|
|
70
|
+
cls(rgba8(20, 40, 80, 255)); // Clear to dark blue
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### `pset(x, y, color)`
|
|
74
|
+
|
|
75
|
+
Set a single pixel.
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
pset(100, 100, rgba8(255, 0, 0, 255)); // Red pixel at (100, 100)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### `line(x0, y0, x1, y1, color)`
|
|
82
|
+
|
|
83
|
+
Draw a line between two points.
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
line(0, 0, 640, 360, rgba8(255, 255, 255, 255)); // Diagonal white line
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### `rect(x, y, width, height, color, fill?)`
|
|
90
|
+
|
|
91
|
+
Draw a rectangle.
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
rect(50, 50, 100, 80, rgba8(0, 255, 0, 255)); // Green outline
|
|
95
|
+
rect(50, 50, 100, 80, rgba8(0, 255, 0, 255), true); // Green filled
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### `rectfill(x, y, width, height, color)`
|
|
99
|
+
|
|
100
|
+
Draw a filled rectangle (alias for `rect(..., true)`).
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
rectfill(10, 10, 200, 100, rgba8(255, 128, 0, 255)); // Orange rectangle
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### `circle(x, y, radius, color, fill?)`
|
|
107
|
+
|
|
108
|
+
Draw a circle.
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
circle(320, 180, 50, rgba8(255, 0, 255, 255)); // Magenta outline
|
|
112
|
+
circle(320, 180, 50, rgba8(255, 0, 255, 255), true); // Magenta filled
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### `print(text, x, y, color?, scale?)`
|
|
116
|
+
|
|
117
|
+
Draw text using built-in bitmap font.
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
print('HELLO WORLD', 10, 10); // White text
|
|
121
|
+
print('SCORE: 1000', 10, 20, rgba8(255, 255, 0, 255)); // Yellow text
|
|
122
|
+
print('BIG', 100, 100, rgba8(255, 255, 255, 255), 2); // 2x scale (future)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Built-in Font Characters:**
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
129
|
+
abcdefghijklmnopqrstuvwxyz
|
|
130
|
+
0123456789
|
|
131
|
+
!@#$%^&*()_+-=[]{}|;:'",.<>?/\~`
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Camera Functions
|
|
135
|
+
|
|
136
|
+
#### `setCamera(x, y)`
|
|
137
|
+
|
|
138
|
+
Set camera offset for scrolling.
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
setCamera(playerX - 320, playerY - 180); // Center camera on player
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### `getCamera()`
|
|
145
|
+
|
|
146
|
+
Get current camera position.
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
const cam = getCamera();
|
|
150
|
+
console.log(`Camera at ${cam.x}, ${cam.y}`);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 🌈 Color System
|
|
156
|
+
|
|
157
|
+
### `rgba8(r, g, b, a?)`
|
|
158
|
+
|
|
159
|
+
Create a color from 8-bit RGBA values (0-255).
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
const red = rgba8(255, 0, 0, 255); // Opaque red
|
|
163
|
+
const green = rgba8(0, 255, 0, 255); // Opaque green
|
|
164
|
+
const blue = rgba8(0, 0, 255, 255); // Opaque blue
|
|
165
|
+
const cyan = rgba8(0, 255, 255, 255); // Cyan
|
|
166
|
+
const yellow = rgba8(255, 255, 0, 255); // Yellow
|
|
167
|
+
const white = rgba8(255, 255, 255, 255); // White
|
|
168
|
+
const trans = rgba8(255, 0, 0, 128); // 50% transparent red
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Common Colors
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
const COLORS = {
|
|
175
|
+
black: rgba8(0, 0, 0, 255),
|
|
176
|
+
white: rgba8(255, 255, 255, 255),
|
|
177
|
+
red: rgba8(255, 0, 0, 255),
|
|
178
|
+
green: rgba8(0, 255, 0, 255),
|
|
179
|
+
blue: rgba8(0, 0, 255, 255),
|
|
180
|
+
cyan: rgba8(0, 255, 255, 255),
|
|
181
|
+
magenta: rgba8(255, 0, 255, 255),
|
|
182
|
+
yellow: rgba8(255, 255, 0, 255),
|
|
183
|
+
orange: rgba8(255, 128, 0, 255),
|
|
184
|
+
purple: rgba8(128, 0, 255, 255),
|
|
185
|
+
gray: rgba8(128, 128, 128, 255),
|
|
186
|
+
};
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 🎮 Input System
|
|
192
|
+
|
|
193
|
+
### Keyboard Input
|
|
194
|
+
|
|
195
|
+
#### `btn(i)` - Check if button is held down
|
|
196
|
+
|
|
197
|
+
Maps button numbers to keys:
|
|
198
|
+
|
|
199
|
+
| Button | Keyboard | Gamepad |
|
|
200
|
+
| ------ | ------------- | ----------- |
|
|
201
|
+
| 0 | ← Arrow Left | D-pad Left |
|
|
202
|
+
| 1 | → Arrow Right | D-pad Right |
|
|
203
|
+
| 2 | ↑ Arrow Up | D-pad Up |
|
|
204
|
+
| 3 | ↓ Arrow Down | D-pad Down |
|
|
205
|
+
| 4 | Z | A/Cross |
|
|
206
|
+
| 5 | X | B/Circle |
|
|
207
|
+
| 6 | C | X/Square |
|
|
208
|
+
| 7 | V | Y/Triangle |
|
|
209
|
+
| 8 | A | L Trigger |
|
|
210
|
+
| 9 | S | R Trigger |
|
|
211
|
+
| 12 | Enter | Start |
|
|
212
|
+
| 13 | Space | Select |
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
if (btn(0)) {
|
|
216
|
+
// Left arrow held
|
|
217
|
+
playerX -= 5;
|
|
218
|
+
}
|
|
219
|
+
if (btn(4)) {
|
|
220
|
+
// Z key held
|
|
221
|
+
shoot();
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### `btnp(i)` - Check if button was just pressed (single frame)
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
if (btnp(13)) {
|
|
229
|
+
// Space just pressed
|
|
230
|
+
jump();
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### `isKeyDown(keyCode)` - Direct key checking (held)
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
if (isKeyDown('ArrowLeft')) playerX -= 2;
|
|
238
|
+
if (isKeyDown('ArrowRight')) playerX += 2;
|
|
239
|
+
if (isKeyDown('a') || isKeyDown('A')) shootLeft();
|
|
240
|
+
if (isKeyDown('Space')) boost();
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
#### `isKeyPressed(keyCode)` - Direct key checking (just pressed)
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
if (isKeyPressed('Enter')) startGame();
|
|
247
|
+
if (isKeyPressed('Space')) jump();
|
|
248
|
+
if (isKeyPressed('r') || isKeyPressed('R')) restart();
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Key Code Examples:**
|
|
252
|
+
|
|
253
|
+
- Arrow keys: `'ArrowLeft'`, `'ArrowRight'`, `'ArrowUp'`, `'ArrowDown'`
|
|
254
|
+
- Letters: `'a'`, `'A'`, `'KeyA'` (all work)
|
|
255
|
+
- Special: `'Space'`, `'Enter'`, `'Shift'`, `'Escape'`
|
|
256
|
+
|
|
257
|
+
### Mouse Input
|
|
258
|
+
|
|
259
|
+
#### `mouseX()` / `mouseY()`
|
|
260
|
+
|
|
261
|
+
Get mouse position (scaled to 640×360).
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
const mx = mouseX();
|
|
265
|
+
const my = mouseY();
|
|
266
|
+
print(`Mouse: ${mx}, ${my}`, 10, 10);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### `mouseDown()` / `mousePressed()`
|
|
270
|
+
|
|
271
|
+
Check mouse button state.
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
if (mouseDown()) {
|
|
275
|
+
// Mouse button held
|
|
276
|
+
dragObject(mouseX(), mouseY());
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (mousePressed()) {
|
|
280
|
+
// Mouse just clicked
|
|
281
|
+
clickButton(mouseX(), mouseY());
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Gamepad Input
|
|
286
|
+
|
|
287
|
+
#### `gamepadConnected()`
|
|
288
|
+
|
|
289
|
+
Check if a gamepad is connected.
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
if (gamepadConnected()) {
|
|
293
|
+
print('🎮 GAMEPAD READY', 10, 10);
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### Analog Stick Functions
|
|
298
|
+
|
|
299
|
+
```javascript
|
|
300
|
+
// Left stick
|
|
301
|
+
const lx = leftStickX(); // -1.0 to 1.0
|
|
302
|
+
const ly = leftStickY(); // -1.0 to 1.0
|
|
303
|
+
|
|
304
|
+
// Right stick
|
|
305
|
+
const rx = rightStickX(); // -1.0 to 1.0
|
|
306
|
+
const ry = rightStickY(); // -1.0 to 1.0
|
|
307
|
+
|
|
308
|
+
// Deadzone: 0.15 (automatically applied)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Example: Smooth analog movement**
|
|
312
|
+
|
|
313
|
+
```javascript
|
|
314
|
+
function update(dt) {
|
|
315
|
+
const moveSpeed = 100; // pixels per second
|
|
316
|
+
|
|
317
|
+
// Analog stick movement
|
|
318
|
+
if (gamepadConnected()) {
|
|
319
|
+
playerX += leftStickX() * moveSpeed * dt;
|
|
320
|
+
playerY += leftStickY() * moveSpeed * dt;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Keyboard fallback
|
|
324
|
+
if (btn(0)) playerX -= moveSpeed * dt; // Left
|
|
325
|
+
if (btn(1)) playerX += moveSpeed * dt; // Right
|
|
326
|
+
if (btn(2)) playerY -= moveSpeed * dt; // Up
|
|
327
|
+
if (btn(3)) playerY += moveSpeed * dt; // Down
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## 🎲 3D Graphics API
|
|
334
|
+
|
|
335
|
+
Nova64 uses Three.js for 3D rendering with a simplified API.
|
|
336
|
+
|
|
337
|
+
### Camera Functions
|
|
338
|
+
|
|
339
|
+
#### `setCameraPosition(x, y, z)`
|
|
340
|
+
|
|
341
|
+
Set camera position in 3D space.
|
|
342
|
+
|
|
343
|
+
```javascript
|
|
344
|
+
setCameraPosition(0, 10, 20); // Behind and above origin
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
#### `setCameraTarget(x, y, z)`
|
|
348
|
+
|
|
349
|
+
Set what the camera looks at.
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
setCameraTarget(0, 0, 0); // Look at origin
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
#### `setCameraLookAt(x, y, z)`
|
|
356
|
+
|
|
357
|
+
Set camera direction vector (for FPS controls).
|
|
358
|
+
|
|
359
|
+
```javascript
|
|
360
|
+
setCameraLookAt(player.x, player.y, player.z);
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
#### `setCameraFOV(degrees)`
|
|
364
|
+
|
|
365
|
+
Set field of view (30-120, default 75).
|
|
366
|
+
|
|
367
|
+
```javascript
|
|
368
|
+
setCameraFOV(90); // Wider view for FPS games
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Lighting
|
|
372
|
+
|
|
373
|
+
#### `setAmbientLight(hexColor)`
|
|
374
|
+
|
|
375
|
+
Set global ambient lighting.
|
|
376
|
+
|
|
377
|
+
```javascript
|
|
378
|
+
setAmbientLight(0x404040); // Gray ambient
|
|
379
|
+
setAmbientLight(0x1a1a2a); // Dark blue ambient
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
#### `setLightDirection(x, y, z)`
|
|
383
|
+
|
|
384
|
+
Set the main directional light direction vector.
|
|
385
|
+
|
|
386
|
+
```javascript
|
|
387
|
+
setLightDirection(-0.5, -1, -0.3); // From top-left
|
|
388
|
+
setLightDirection(1, 1, 0.5); // From upper-right
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
#### `setLightColor(hexColor, intensity?)`
|
|
392
|
+
|
|
393
|
+
Configure directional light colour and intensity.
|
|
394
|
+
|
|
395
|
+
```javascript
|
|
396
|
+
setLightColor(0xffffff, 1.0); // White light, full intensity
|
|
397
|
+
setLightColor(0xffd4a0, 0.8); // Warm sunset light
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
#### `createPointLight(hexColor, intensity, [x, y, z], distance?)`
|
|
401
|
+
|
|
402
|
+
Add a point light at a world-space position.
|
|
403
|
+
|
|
404
|
+
```javascript
|
|
405
|
+
const lamp = createPointLight(0xff8800, 2.0, [5, 3, 0], 20);
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Fog
|
|
409
|
+
|
|
410
|
+
#### `setFog(hexColor, near, far)`
|
|
411
|
+
|
|
412
|
+
Add distance fog for atmosphere.
|
|
413
|
+
|
|
414
|
+
```javascript
|
|
415
|
+
setFog(0x000020, 30, 150); // Dark blue fog
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### 3D Objects
|
|
419
|
+
|
|
420
|
+
#### `createCube(size, color, position, options?)`
|
|
421
|
+
|
|
422
|
+
Create a cube mesh.
|
|
423
|
+
|
|
424
|
+
```javascript
|
|
425
|
+
const cube = createCube(2, 0xff0000, [0, 0, 0]); // Red 2x2x2 cube
|
|
426
|
+
const box = createCube(1, 0x00ff00, [5, 1, 0], {
|
|
427
|
+
flatShading: true,
|
|
428
|
+
});
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
#### `createAdvancedCube(size, materialOptions, position)`
|
|
432
|
+
|
|
433
|
+
Create cube with emissive materials (for bloom).
|
|
434
|
+
|
|
435
|
+
```javascript
|
|
436
|
+
const neonCube = createAdvancedCube(
|
|
437
|
+
1,
|
|
438
|
+
{
|
|
439
|
+
color: 0x00ffff, // Cyan base
|
|
440
|
+
emissive: 0x00ffff, // Cyan glow
|
|
441
|
+
emissiveIntensity: 0.8, // Glow strength (0-2+)
|
|
442
|
+
flatShading: true, // Retro look
|
|
443
|
+
},
|
|
444
|
+
[0, 1, 0]
|
|
445
|
+
);
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
#### `createSphere(radius, color, position, segments?, options?)`
|
|
449
|
+
|
|
450
|
+
Create a sphere.
|
|
451
|
+
|
|
452
|
+
```javascript
|
|
453
|
+
const sphere = createSphere(1, 0xff00ff, [0, 2, 0], 8); // Low-poly
|
|
454
|
+
const glowSphere = createSphere(1, 0xffff00, [3, 1, 0], 12, {
|
|
455
|
+
emissive: 0xffff00,
|
|
456
|
+
emissiveIntensity: 1.0,
|
|
457
|
+
});
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
#### `createPlane(width, depth, color, position)`
|
|
461
|
+
|
|
462
|
+
Create a flat plane.
|
|
463
|
+
|
|
464
|
+
```javascript
|
|
465
|
+
const floor = createPlane(100, 100, 0x008800, [0, 0, 0]);
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Mesh Manipulation
|
|
469
|
+
|
|
470
|
+
#### `setPosition(meshId, x, y, z)` or `setPosition(meshId, [x, y, z])`
|
|
471
|
+
|
|
472
|
+
Move a mesh.
|
|
473
|
+
|
|
474
|
+
```javascript
|
|
475
|
+
setPosition(cube, playerX, playerY, playerZ);
|
|
476
|
+
setPosition(sphere, [10, 5, 3]);
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
#### `setRotation(meshId, x, y, z)` or `setRotation(meshId, [x, y, z])`
|
|
480
|
+
|
|
481
|
+
Rotate a mesh (radians).
|
|
482
|
+
|
|
483
|
+
```javascript
|
|
484
|
+
setRotation(cube, 0, Math.PI / 4, 0); // Rotate 45° around Y
|
|
485
|
+
setRotation(box, [angle, 0, 0]);
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
#### `setScale(meshId, x, y, z)` or `setScale(meshId, [x, y, z])`
|
|
489
|
+
|
|
490
|
+
Scale a mesh.
|
|
491
|
+
|
|
492
|
+
```javascript
|
|
493
|
+
setScale(cube, 2, 1, 2); // Wide and deep
|
|
494
|
+
setScale(sphere, [0.5, 0.5, 0.5]); // Half size
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
#### `destroyMesh(meshId)`
|
|
498
|
+
|
|
499
|
+
Remove a mesh from the scene.
|
|
500
|
+
|
|
501
|
+
```javascript
|
|
502
|
+
destroyMesh(cube);
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Skybox
|
|
506
|
+
|
|
507
|
+
#### `createSpaceSkybox(options?)`
|
|
508
|
+
|
|
509
|
+
Procedural starfield and nebulae — the default Nova64 space look.
|
|
510
|
+
|
|
511
|
+
```javascript
|
|
512
|
+
createSpaceSkybox(); // Default stars + nebulae
|
|
513
|
+
createSpaceSkybox({ starCount: 2000, nebulaCount: 4 });
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
#### `createGradientSkybox(topColor, bottomColor)`
|
|
517
|
+
|
|
518
|
+
Two-colour gradient sky — great for outdoor scenes and sunsets.
|
|
519
|
+
|
|
520
|
+
```javascript
|
|
521
|
+
createGradientSkybox(0x0077ff, 0x004488); // Blue sky
|
|
522
|
+
createGradientSkybox(0xff6a00, 0x1a0033); // Sunset
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
#### `createSolidSkybox(color)`
|
|
526
|
+
|
|
527
|
+
Flat solid colour sky — good for caves or indoor scenes.
|
|
528
|
+
|
|
529
|
+
```javascript
|
|
530
|
+
createSolidSkybox(0x000000); // Pure black
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
#### `createImageSkybox([px, nx, py, ny, pz, nz])`
|
|
534
|
+
|
|
535
|
+
Cube-map skybox from 6 image URLs. Also enables image-based lighting (IBL).
|
|
536
|
+
|
|
537
|
+
```javascript
|
|
538
|
+
createImageSkybox([
|
|
539
|
+
'/assets/sky_px.jpg',
|
|
540
|
+
'/assets/sky_nx.jpg',
|
|
541
|
+
'/assets/sky_py.jpg',
|
|
542
|
+
'/assets/sky_ny.jpg',
|
|
543
|
+
'/assets/sky_pz.jpg',
|
|
544
|
+
'/assets/sky_nz.jpg',
|
|
545
|
+
]);
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
#### `clearSkybox()`
|
|
549
|
+
|
|
550
|
+
Remove the current skybox.
|
|
551
|
+
|
|
552
|
+
```javascript
|
|
553
|
+
clearSkybox();
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
#### `enableSkyboxAutoAnimate(speed?)` / `disableSkyboxAutoAnimate()`
|
|
557
|
+
|
|
558
|
+
Auto-rotate the skybox every frame.
|
|
559
|
+
|
|
560
|
+
```javascript
|
|
561
|
+
enableSkyboxAutoAnimate(0.5); // Slow drift
|
|
562
|
+
disableSkyboxAutoAnimate();
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
#### `animateSkybox(dt)`
|
|
566
|
+
|
|
567
|
+
Manually advance skybox animation by delta-time (call in `update`).
|
|
568
|
+
|
|
569
|
+
```javascript
|
|
570
|
+
export function update(dt) {
|
|
571
|
+
animateSkybox(dt);
|
|
572
|
+
}
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
#### `setSkyboxSpeed(multiplier)`
|
|
576
|
+
|
|
577
|
+
Scale the auto-animation speed.
|
|
578
|
+
|
|
579
|
+
```javascript
|
|
580
|
+
setSkyboxSpeed(2.0); // Double speed
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
## ✨ Visual Effects API
|
|
586
|
+
|
|
587
|
+
### Post-Processing
|
|
588
|
+
|
|
589
|
+
#### `enableBloom(strength?, radius?, threshold?)`
|
|
590
|
+
|
|
591
|
+
Add bloom glow effect.
|
|
592
|
+
|
|
593
|
+
```javascript
|
|
594
|
+
enableBloom(); // Default settings
|
|
595
|
+
enableBloom(1.2, 0.6, 0.3); // Balanced neon glow
|
|
596
|
+
enableBloom(2.0, 0.8, 0.2); // Strong dramatic glow
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
**Parameters:**
|
|
600
|
+
|
|
601
|
+
- `strength` (0.5-3.0): Glow intensity. **Optimal: 1.0-1.5**
|
|
602
|
+
- `radius` (0.0-1.0): Glow spread. **Optimal: 0.6-0.8**
|
|
603
|
+
- `threshold` (0.0-1.0): Brightness cutoff. **Optimal: 0.2-0.4**
|
|
604
|
+
|
|
605
|
+
#### `disableBloom()`
|
|
606
|
+
|
|
607
|
+
Turn off bloom effect.
|
|
608
|
+
|
|
609
|
+
```javascript
|
|
610
|
+
disableBloom();
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
#### `setBloomStrength(value)`
|
|
614
|
+
|
|
615
|
+
Adjust bloom strength at runtime.
|
|
616
|
+
|
|
617
|
+
```javascript
|
|
618
|
+
setBloomStrength(1.5); // Increase intensity
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
#### `enableFXAA()`
|
|
622
|
+
|
|
623
|
+
Enable anti-aliasing (smooths edges).
|
|
624
|
+
|
|
625
|
+
```javascript
|
|
626
|
+
enableFXAA(); // Usually paired with bloom
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
#### `disableFXAA()`
|
|
630
|
+
|
|
631
|
+
Disable anti-aliasing.
|
|
632
|
+
|
|
633
|
+
```javascript
|
|
634
|
+
disableFXAA();
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### Optimal Bloom Settings Guide
|
|
638
|
+
|
|
639
|
+
| Use Case | Strength | Radius | Threshold | Emissive |
|
|
640
|
+
| ----------- | -------- | ------- | --------- | -------- |
|
|
641
|
+
| Subtle glow | 0.5-0.8 | 0.4-0.5 | 0.4-0.5 | 0.3-0.5 |
|
|
642
|
+
| Neon/Tron | 1.0-1.5 | 0.6-0.8 | 0.2-0.3 | 0.6-0.8 |
|
|
643
|
+
| Dramatic | 1.5-2.0 | 0.8-1.0 | 0.1-0.2 | 0.8-1.2 |
|
|
644
|
+
| Extreme | 2.0+ | 1.0 | 0.1 | 1.0+ |
|
|
645
|
+
|
|
646
|
+
**Warning:** Too much bloom causes white-out! Keep strength ≤ 1.5 for most scenes.
|
|
647
|
+
|
|
648
|
+
### Example: Neon Scene
|
|
649
|
+
|
|
650
|
+
```javascript
|
|
651
|
+
export function init() {
|
|
652
|
+
// Enable balanced bloom for neon aesthetic
|
|
653
|
+
enableBloom(1.2, 0.6, 0.3);
|
|
654
|
+
enableFXAA();
|
|
655
|
+
|
|
656
|
+
// Dark environment
|
|
657
|
+
setAmbientLight(0x1a1a2a);
|
|
658
|
+
setFog(0x000020, 30, 150);
|
|
659
|
+
|
|
660
|
+
// Glowing objects
|
|
661
|
+
const neonCube = createAdvancedCube(
|
|
662
|
+
1,
|
|
663
|
+
{
|
|
664
|
+
color: 0x00ffff,
|
|
665
|
+
emissive: 0x00ffff,
|
|
666
|
+
emissiveIntensity: 0.8, // Sweet spot for visibility
|
|
667
|
+
},
|
|
668
|
+
[0, 1, 0]
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
---
|
|
674
|
+
|
|
675
|
+
## 🧱 Voxel Engine API
|
|
676
|
+
|
|
677
|
+
Build Minecraft-style block worlds.
|
|
678
|
+
|
|
679
|
+
### World Management
|
|
680
|
+
|
|
681
|
+
#### `updateVoxelWorld(playerX, playerZ)`
|
|
682
|
+
|
|
683
|
+
Load/unload chunks around player.
|
|
684
|
+
|
|
685
|
+
```javascript
|
|
686
|
+
updateVoxelWorld(player.x, player.z); // Call when player moves
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
### Block Types
|
|
690
|
+
|
|
691
|
+
```javascript
|
|
692
|
+
BLOCK_TYPES = {
|
|
693
|
+
AIR: 0,
|
|
694
|
+
GRASS: 1,
|
|
695
|
+
DIRT: 2,
|
|
696
|
+
STONE: 3,
|
|
697
|
+
SAND: 4,
|
|
698
|
+
WATER: 5, // Transparent
|
|
699
|
+
WOOD: 6,
|
|
700
|
+
LEAVES: 7,
|
|
701
|
+
COBBLESTONE: 8,
|
|
702
|
+
PLANKS: 9,
|
|
703
|
+
GLASS: 10, // Transparent
|
|
704
|
+
BRICK: 11,
|
|
705
|
+
SNOW: 12,
|
|
706
|
+
ICE: 13,
|
|
707
|
+
BEDROCK: 14,
|
|
708
|
+
};
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### Block Interaction
|
|
712
|
+
|
|
713
|
+
#### `getVoxelBlock(x, y, z)`
|
|
714
|
+
|
|
715
|
+
Get block type at position.
|
|
716
|
+
|
|
717
|
+
```javascript
|
|
718
|
+
const block = getVoxelBlock(10, 35, 10);
|
|
719
|
+
if (block === BLOCK_TYPES.STONE) {
|
|
720
|
+
console.log('Found stone!');
|
|
721
|
+
}
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
#### `setVoxelBlock(x, y, z, blockType)`
|
|
725
|
+
|
|
726
|
+
Place or remove a block.
|
|
727
|
+
|
|
728
|
+
```javascript
|
|
729
|
+
setVoxelBlock(10, 35, 10, BLOCK_TYPES.STONE); // Place stone
|
|
730
|
+
setVoxelBlock(10, 35, 10, BLOCK_TYPES.AIR); // Remove block
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
#### `raycastVoxelBlock(origin, direction, maxDistance)`
|
|
734
|
+
|
|
735
|
+
Find block player is looking at.
|
|
736
|
+
|
|
737
|
+
```javascript
|
|
738
|
+
const result = raycastVoxelBlock(
|
|
739
|
+
[player.x, player.y + 1.6, player.z], // Eye position
|
|
740
|
+
lookDirection, // [dx, dy, dz]
|
|
741
|
+
10 // Max reach
|
|
742
|
+
);
|
|
743
|
+
|
|
744
|
+
if (result.hit) {
|
|
745
|
+
const [x, y, z] = result.position;
|
|
746
|
+
console.log(`Looking at block at ${x}, ${y}, ${z}`);
|
|
747
|
+
}
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### Physics
|
|
751
|
+
|
|
752
|
+
#### `checkVoxelCollision(position, size)`
|
|
753
|
+
|
|
754
|
+
Check if position collides with solid blocks.
|
|
755
|
+
|
|
756
|
+
```javascript
|
|
757
|
+
const colliding = checkVoxelCollision(
|
|
758
|
+
[player.x, player.y, player.z],
|
|
759
|
+
0.3 // Collision radius
|
|
760
|
+
);
|
|
761
|
+
|
|
762
|
+
if (colliding) {
|
|
763
|
+
console.log('Hit a block!');
|
|
764
|
+
}
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
### Structures
|
|
768
|
+
|
|
769
|
+
#### `placeVoxelTree(x, y, z)`
|
|
770
|
+
|
|
771
|
+
Generate a tree.
|
|
772
|
+
|
|
773
|
+
```javascript
|
|
774
|
+
placeVoxelTree(20, 35, 20); // Trunk + leaves
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
### Example: Basic Voxel Game
|
|
778
|
+
|
|
779
|
+
```javascript
|
|
780
|
+
let player = { x: 0, y: 50, z: 0, vy: 0 };
|
|
781
|
+
|
|
782
|
+
export function init() {
|
|
783
|
+
updateVoxelWorld(0, 0); // Initial world gen
|
|
784
|
+
setCameraFOV(95); // Wide FOV for voxels
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
export function update(dt) {
|
|
788
|
+
// Movement
|
|
789
|
+
const speed = 10;
|
|
790
|
+
if (btn(0)) player.x -= speed * dt;
|
|
791
|
+
if (btn(1)) player.x += speed * dt;
|
|
792
|
+
if (btn(2)) player.z -= speed * dt;
|
|
793
|
+
if (btn(3)) player.z += speed * dt;
|
|
794
|
+
|
|
795
|
+
// Gravity
|
|
796
|
+
player.vy -= 20 * dt;
|
|
797
|
+
player.y += player.vy * dt;
|
|
798
|
+
|
|
799
|
+
// Ground collision
|
|
800
|
+
const onGround = checkVoxelCollision([player.x, player.y, player.z], 0.3);
|
|
801
|
+
if (onGround && player.vy < 0) {
|
|
802
|
+
player.vy = 0;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Jump
|
|
806
|
+
if (btnp(13) && onGround) {
|
|
807
|
+
player.vy = 10;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Update world
|
|
811
|
+
updateVoxelWorld(player.x, player.z);
|
|
812
|
+
}
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
---
|
|
816
|
+
|
|
817
|
+
## 🖼️ UI System
|
|
818
|
+
|
|
819
|
+
### Buttons
|
|
820
|
+
|
|
821
|
+
#### `createButton(x, y, width, height, text, callback, options?)`
|
|
822
|
+
|
|
823
|
+
Create an interactive button.
|
|
824
|
+
|
|
825
|
+
```javascript
|
|
826
|
+
const startBtn = createButton(
|
|
827
|
+
200,
|
|
828
|
+
150,
|
|
829
|
+
240,
|
|
830
|
+
60,
|
|
831
|
+
'START GAME',
|
|
832
|
+
() => {
|
|
833
|
+
gameState = 'playing';
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
normalColor: rgba8(0, 128, 255, 255),
|
|
837
|
+
hoverColor: rgba8(50, 150, 255, 255),
|
|
838
|
+
pressedColor: rgba8(0, 100, 200, 255),
|
|
839
|
+
}
|
|
840
|
+
);
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
#### `updateAllButtons()`
|
|
844
|
+
|
|
845
|
+
Check for button clicks (call in `update()`).
|
|
846
|
+
|
|
847
|
+
```javascript
|
|
848
|
+
export function update(dt) {
|
|
849
|
+
const clicked = updateAllButtons();
|
|
850
|
+
if (clicked) {
|
|
851
|
+
console.log('A button was clicked!');
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
#### `drawAllButtons()`
|
|
857
|
+
|
|
858
|
+
Render all buttons (call in `draw()`).
|
|
859
|
+
|
|
860
|
+
```javascript
|
|
861
|
+
export function draw() {
|
|
862
|
+
drawAllButtons();
|
|
863
|
+
}
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
#### `clearButtons()`
|
|
867
|
+
|
|
868
|
+
Remove all buttons.
|
|
869
|
+
|
|
870
|
+
```javascript
|
|
871
|
+
clearButtons(); // Clear menu when starting game
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
### Panels
|
|
875
|
+
|
|
876
|
+
#### `createPanel(x, y, width, height, options?)`
|
|
877
|
+
|
|
878
|
+
Create a styled panel background.
|
|
879
|
+
|
|
880
|
+
```javascript
|
|
881
|
+
const panel = createPanel(50, 50, 300, 200, {
|
|
882
|
+
bgColor: rgba8(20, 20, 40, 200), // Semi-transparent dark blue
|
|
883
|
+
borderColor: rgba8(0, 255, 255, 255), // Cyan border
|
|
884
|
+
borderWidth: 3,
|
|
885
|
+
shadow: true,
|
|
886
|
+
gradient: true,
|
|
887
|
+
gradientColor: rgba8(40, 40, 80, 200),
|
|
888
|
+
});
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
#### `drawPanel(panel)`
|
|
892
|
+
|
|
893
|
+
Draw a panel.
|
|
894
|
+
|
|
895
|
+
```javascript
|
|
896
|
+
drawPanel(panel);
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
### Pre-defined UI Colors
|
|
900
|
+
|
|
901
|
+
```javascript
|
|
902
|
+
const uiColors = {
|
|
903
|
+
primary: rgba8(0, 128, 255, 255), // Blue
|
|
904
|
+
secondary: rgba8(128, 128, 128, 255), // Gray
|
|
905
|
+
success: rgba8(0, 255, 0, 255), // Green
|
|
906
|
+
warning: rgba8(255, 255, 0, 255), // Yellow
|
|
907
|
+
danger: rgba8(255, 0, 0, 255), // Red
|
|
908
|
+
light: rgba8(220, 220, 220, 255), // Light gray
|
|
909
|
+
dark: rgba8(40, 40, 40, 255), // Dark gray
|
|
910
|
+
};
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
### Text Helpers
|
|
914
|
+
|
|
915
|
+
#### `setFont(size)`
|
|
916
|
+
|
|
917
|
+
Change font size (future feature).
|
|
918
|
+
|
|
919
|
+
```javascript
|
|
920
|
+
setFont('normal'); // 8px (default)
|
|
921
|
+
setFont('large'); // 16px
|
|
922
|
+
setFont('huge'); // 24px
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
#### `setTextAlign(align)`
|
|
926
|
+
|
|
927
|
+
Set text alignment.
|
|
928
|
+
|
|
929
|
+
```javascript
|
|
930
|
+
setTextAlign('left'); // Default
|
|
931
|
+
setTextAlign('center'); // Centered
|
|
932
|
+
setTextAlign('right'); // Right-aligned
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
#### `drawText(text, x, y, color, align?)`
|
|
936
|
+
|
|
937
|
+
Draw text with alignment.
|
|
938
|
+
|
|
939
|
+
```javascript
|
|
940
|
+
drawText('GAME OVER', 320, 180, rgba8(255, 0, 0, 255), 1); // Centered
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
#### `drawTextShadow(text, x, y, color, shadowColor, shadowOffset, align?)`
|
|
944
|
+
|
|
945
|
+
Draw text with shadow.
|
|
946
|
+
|
|
947
|
+
```javascript
|
|
948
|
+
drawTextShadow(
|
|
949
|
+
'TITLE',
|
|
950
|
+
320,
|
|
951
|
+
50,
|
|
952
|
+
rgba8(255, 255, 255, 255), // White text
|
|
953
|
+
rgba8(0, 0, 0, 255), // Black shadow
|
|
954
|
+
4,
|
|
955
|
+
1 // Offset, alignment
|
|
956
|
+
);
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
#### `drawTextOutline(text, x, y, color, outlineColor, thickness, align?)`
|
|
960
|
+
|
|
961
|
+
Draw text with outline.
|
|
962
|
+
|
|
963
|
+
```javascript
|
|
964
|
+
drawTextOutline(
|
|
965
|
+
'SCORE: 1000',
|
|
966
|
+
320,
|
|
967
|
+
20,
|
|
968
|
+
rgba8(255, 255, 0, 255), // Yellow text
|
|
969
|
+
rgba8(0, 0, 0, 255), // Black outline
|
|
970
|
+
2,
|
|
971
|
+
1 // Thickness, alignment
|
|
972
|
+
);
|
|
973
|
+
```
|
|
974
|
+
|
|
975
|
+
### Gradient Rectangle
|
|
976
|
+
|
|
977
|
+
#### `drawGradientRect(x, y, width, height, colorTop, colorBottom, vertical?)`
|
|
978
|
+
|
|
979
|
+
Draw a gradient-filled rectangle.
|
|
980
|
+
|
|
981
|
+
```javascript
|
|
982
|
+
// Vertical gradient
|
|
983
|
+
drawGradientRect(
|
|
984
|
+
0,
|
|
985
|
+
0,
|
|
986
|
+
640,
|
|
987
|
+
360,
|
|
988
|
+
rgba8(0, 50, 100, 255), // Top: dark blue
|
|
989
|
+
rgba8(0, 0, 20, 255), // Bottom: darker
|
|
990
|
+
true
|
|
991
|
+
);
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
---
|
|
995
|
+
|
|
996
|
+
## 🔊 Audio System
|
|
997
|
+
|
|
998
|
+
### Music
|
|
999
|
+
|
|
1000
|
+
#### `playMusic(trackName, options?)`
|
|
1001
|
+
|
|
1002
|
+
Play background music.
|
|
1003
|
+
|
|
1004
|
+
```javascript
|
|
1005
|
+
playMusic('theme', { loop: true, volume: 0.7 });
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
#### `stopMusic()`
|
|
1009
|
+
|
|
1010
|
+
Stop current music.
|
|
1011
|
+
|
|
1012
|
+
```javascript
|
|
1013
|
+
stopMusic();
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
### Sound Effects
|
|
1017
|
+
|
|
1018
|
+
#### `playSound(soundName, options?)`
|
|
1019
|
+
|
|
1020
|
+
Play a sound effect.
|
|
1021
|
+
|
|
1022
|
+
```javascript
|
|
1023
|
+
playSound('jump', { volume: 0.5 });
|
|
1024
|
+
playSound('explosion', { volume: 1.0, pitch: 1.2 });
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
#### `stopSound(soundName)`
|
|
1028
|
+
|
|
1029
|
+
Stop a specific sound.
|
|
1030
|
+
|
|
1031
|
+
```javascript
|
|
1032
|
+
stopSound('laser');
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
---
|
|
1036
|
+
|
|
1037
|
+
## 🛠️ Utility Functions
|
|
1038
|
+
|
|
1039
|
+
### Math Helpers
|
|
1040
|
+
|
|
1041
|
+
```javascript
|
|
1042
|
+
// Clamp value between min and max
|
|
1043
|
+
function clamp(value, min, max) {
|
|
1044
|
+
return Math.max(min, Math.min(max, value));
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// Linear interpolation
|
|
1048
|
+
function lerp(a, b, t) {
|
|
1049
|
+
return a + (b - a) * t;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// Distance between two points
|
|
1053
|
+
function dist(x1, y1, x2, y2) {
|
|
1054
|
+
const dx = x2 - x1;
|
|
1055
|
+
const dy = y2 - y1;
|
|
1056
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// Random integer between min and max (inclusive)
|
|
1060
|
+
function rnd(min, max) {
|
|
1061
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// Random float between min and max
|
|
1065
|
+
function rndFloat(min, max) {
|
|
1066
|
+
return Math.random() * (max - min) + min;
|
|
1067
|
+
}
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
### Timing
|
|
1071
|
+
|
|
1072
|
+
#### `getDeltaTime()`
|
|
1073
|
+
|
|
1074
|
+
Get frame delta time (seconds).
|
|
1075
|
+
|
|
1076
|
+
```javascript
|
|
1077
|
+
const dt = getDeltaTime(); // ~0.016 for 60 FPS
|
|
1078
|
+
```
|
|
1079
|
+
|
|
1080
|
+
#### `getFPS()`
|
|
1081
|
+
|
|
1082
|
+
Get current frames per second.
|
|
1083
|
+
|
|
1084
|
+
```javascript
|
|
1085
|
+
const fps = getFPS();
|
|
1086
|
+
print(`FPS: ${Math.round(fps)}`, 10, 10);
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
### Storage
|
|
1090
|
+
|
|
1091
|
+
#### `save(key, value)`
|
|
1092
|
+
|
|
1093
|
+
Save data to browser storage.
|
|
1094
|
+
|
|
1095
|
+
```javascript
|
|
1096
|
+
save('highScore', 10000);
|
|
1097
|
+
save('playerName', 'ACE');
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
#### `load(key, defaultValue?)`
|
|
1101
|
+
|
|
1102
|
+
Load data from storage.
|
|
1103
|
+
|
|
1104
|
+
```javascript
|
|
1105
|
+
const score = load('highScore', 0);
|
|
1106
|
+
const name = load('playerName', 'PLAYER');
|
|
1107
|
+
```
|
|
1108
|
+
|
|
1109
|
+
---
|
|
1110
|
+
|
|
1111
|
+
## 📝 Complete Example Game
|
|
1112
|
+
|
|
1113
|
+
```javascript
|
|
1114
|
+
// Simple platformer demonstrating multiple APIs
|
|
1115
|
+
|
|
1116
|
+
let player = {
|
|
1117
|
+
x: 100,
|
|
1118
|
+
y: 100,
|
|
1119
|
+
vx: 0,
|
|
1120
|
+
vy: 0,
|
|
1121
|
+
width: 16,
|
|
1122
|
+
height: 16,
|
|
1123
|
+
onGround: false,
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
let gameState = 'menu';
|
|
1127
|
+
let score = 0;
|
|
1128
|
+
|
|
1129
|
+
export function init() {
|
|
1130
|
+
console.log('🎮 Platformer initialized!');
|
|
1131
|
+
|
|
1132
|
+
// Create menu button
|
|
1133
|
+
createButton(
|
|
1134
|
+
200,
|
|
1135
|
+
200,
|
|
1136
|
+
240,
|
|
1137
|
+
60,
|
|
1138
|
+
'START GAME',
|
|
1139
|
+
() => {
|
|
1140
|
+
gameState = 'playing';
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
normalColor: rgba8(0, 128, 255, 255),
|
|
1144
|
+
hoverColor: rgba8(50, 150, 255, 255),
|
|
1145
|
+
}
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
export function update(dt) {
|
|
1150
|
+
if (gameState === 'menu') {
|
|
1151
|
+
updateAllButtons();
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// Horizontal movement
|
|
1156
|
+
const moveSpeed = 200;
|
|
1157
|
+
if (btn(0))
|
|
1158
|
+
player.vx = -moveSpeed; // Left
|
|
1159
|
+
else if (btn(1))
|
|
1160
|
+
player.vx = moveSpeed; // Right
|
|
1161
|
+
else player.vx *= 0.8; // Friction
|
|
1162
|
+
|
|
1163
|
+
// Jump
|
|
1164
|
+
if (btnp(13) && player.onGround) {
|
|
1165
|
+
player.vy = -400; // Jump velocity
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// Gravity
|
|
1169
|
+
player.vy += 1000 * dt;
|
|
1170
|
+
|
|
1171
|
+
// Apply velocity
|
|
1172
|
+
player.x += player.vx * dt;
|
|
1173
|
+
player.y += player.vy * dt;
|
|
1174
|
+
|
|
1175
|
+
// Ground collision (simple)
|
|
1176
|
+
if (player.y > 300) {
|
|
1177
|
+
player.y = 300;
|
|
1178
|
+
player.vy = 0;
|
|
1179
|
+
player.onGround = true;
|
|
1180
|
+
} else {
|
|
1181
|
+
player.onGround = false;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// Wrap screen
|
|
1185
|
+
if (player.x < 0) player.x = 640;
|
|
1186
|
+
if (player.x > 640) player.x = 0;
|
|
1187
|
+
|
|
1188
|
+
// Camera follows player
|
|
1189
|
+
setCamera(player.x - 320, 0);
|
|
1190
|
+
|
|
1191
|
+
// Score increases over time
|
|
1192
|
+
score += Math.floor(dt * 10);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
export function draw() {
|
|
1196
|
+
// Clear to sky blue
|
|
1197
|
+
cls(rgba8(100, 150, 255, 255));
|
|
1198
|
+
|
|
1199
|
+
if (gameState === 'menu') {
|
|
1200
|
+
// Menu screen
|
|
1201
|
+
drawTextShadow('PLATFORMER', 320, 100, rgba8(255, 255, 255, 255), rgba8(0, 0, 0, 255), 4, 1);
|
|
1202
|
+
drawAllButtons();
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// Draw ground
|
|
1207
|
+
rectfill(0, 316, 640, 44, rgba8(50, 150, 50, 255));
|
|
1208
|
+
|
|
1209
|
+
// Draw player
|
|
1210
|
+
rectfill(
|
|
1211
|
+
player.x - player.width / 2,
|
|
1212
|
+
player.y - player.height,
|
|
1213
|
+
player.width,
|
|
1214
|
+
player.height,
|
|
1215
|
+
rgba8(255, 0, 0, 255)
|
|
1216
|
+
);
|
|
1217
|
+
|
|
1218
|
+
// Draw HUD (no camera offset)
|
|
1219
|
+
setCamera(0, 0);
|
|
1220
|
+
print(`SCORE: ${score}`, 10, 10, rgba8(255, 255, 255, 255));
|
|
1221
|
+
print(`FPS: ${Math.round(getFPS())}`, 10, 25, rgba8(255, 255, 0, 255));
|
|
1222
|
+
}
|
|
1223
|
+
```
|
|
1224
|
+
|
|
1225
|
+
---
|
|
1226
|
+
|
|
1227
|
+
## 🎯 Best Practices
|
|
1228
|
+
|
|
1229
|
+
### Performance
|
|
1230
|
+
|
|
1231
|
+
1. **Use `rectfill()` instead of filled `rect()`** - More explicit
|
|
1232
|
+
2. **Minimize `pset()` calls** - Draw shapes instead of individual pixels
|
|
1233
|
+
3. **Clear buttons when not needed** - Call `clearButtons()` on state changes
|
|
1234
|
+
4. **Use camera for scrolling** - Don't offset every draw call manually
|
|
1235
|
+
5. **Keep bloom moderate** - Strength 1.0-1.5, emissive 0.6-0.8
|
|
1236
|
+
6. **Update voxel world sparingly** - Only when player moves significantly
|
|
1237
|
+
|
|
1238
|
+
### Code Organization
|
|
1239
|
+
|
|
1240
|
+
```javascript
|
|
1241
|
+
// Good: Separate concerns
|
|
1242
|
+
const GAME_STATES = {
|
|
1243
|
+
MENU: 'menu',
|
|
1244
|
+
PLAYING: 'playing',
|
|
1245
|
+
PAUSED: 'paused',
|
|
1246
|
+
GAMEOVER: 'gameover',
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1249
|
+
let gameState = GAME_STATES.MENU;
|
|
1250
|
+
|
|
1251
|
+
function updateMenu(dt) {
|
|
1252
|
+
/* ... */
|
|
1253
|
+
}
|
|
1254
|
+
function updatePlaying(dt) {
|
|
1255
|
+
/* ... */
|
|
1256
|
+
}
|
|
1257
|
+
function updateGameOver(dt) {
|
|
1258
|
+
/* ... */
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
export function update(dt) {
|
|
1262
|
+
switch (gameState) {
|
|
1263
|
+
case GAME_STATES.MENU:
|
|
1264
|
+
updateMenu(dt);
|
|
1265
|
+
break;
|
|
1266
|
+
case GAME_STATES.PLAYING:
|
|
1267
|
+
updatePlaying(dt);
|
|
1268
|
+
break;
|
|
1269
|
+
case GAME_STATES.GAMEOVER:
|
|
1270
|
+
updateGameOver(dt);
|
|
1271
|
+
break;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
```
|
|
1275
|
+
|
|
1276
|
+
### Input Handling
|
|
1277
|
+
|
|
1278
|
+
```javascript
|
|
1279
|
+
// Good: Support multiple input methods
|
|
1280
|
+
function update(dt) {
|
|
1281
|
+
let moving = false;
|
|
1282
|
+
|
|
1283
|
+
// Keyboard
|
|
1284
|
+
if (btn(0)) {
|
|
1285
|
+
player.x -= speed * dt;
|
|
1286
|
+
moving = true;
|
|
1287
|
+
}
|
|
1288
|
+
if (btn(1)) {
|
|
1289
|
+
player.x += speed * dt;
|
|
1290
|
+
moving = true;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// Gamepad analog
|
|
1294
|
+
if (gamepadConnected()) {
|
|
1295
|
+
const stickX = leftStickX();
|
|
1296
|
+
if (Math.abs(stickX) > 0.1) {
|
|
1297
|
+
player.x += stickX * speed * dt;
|
|
1298
|
+
moving = true;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// Apply friction when not moving
|
|
1303
|
+
if (!moving) {
|
|
1304
|
+
player.vx *= 0.9;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
```
|
|
1308
|
+
|
|
1309
|
+
---
|
|
1310
|
+
|
|
1311
|
+
## 🐛 Common Issues
|
|
1312
|
+
|
|
1313
|
+
### "Function is not defined"
|
|
1314
|
+
|
|
1315
|
+
**Problem:** Calling API function that doesn't exist.
|
|
1316
|
+
**Solution:** Check spelling, ensure function is exposed by runtime.
|
|
1317
|
+
|
|
1318
|
+
```javascript
|
|
1319
|
+
// Wrong
|
|
1320
|
+
addCube(1, 0xff0000, [0, 0, 0]); // ❌ No such function
|
|
1321
|
+
|
|
1322
|
+
// Right
|
|
1323
|
+
createCube(1, 0xff0000, [0, 0, 0]); // ✅ Correct name
|
|
1324
|
+
```
|
|
1325
|
+
|
|
1326
|
+
### White screen / Everything too bright
|
|
1327
|
+
|
|
1328
|
+
**Problem:** Bloom settings too high.
|
|
1329
|
+
**Solution:** Reduce bloom strength and emissive intensities.
|
|
1330
|
+
|
|
1331
|
+
```javascript
|
|
1332
|
+
// Wrong - causes white-out
|
|
1333
|
+
enableBloom(5.0, 1.0, 0.05); // ❌ Too extreme
|
|
1334
|
+
|
|
1335
|
+
// Right - balanced glow
|
|
1336
|
+
enableBloom(1.2, 0.6, 0.3); // ✅ Visible details
|
|
1337
|
+
```
|
|
1338
|
+
|
|
1339
|
+
### 3D scene not visible
|
|
1340
|
+
|
|
1341
|
+
**Problem:** Drawing `cls()` in `draw()` clears GPU-rendered scene.
|
|
1342
|
+
**Solution:** Remove `cls()` from `draw()` - 3D is auto-rendered.
|
|
1343
|
+
|
|
1344
|
+
```javascript
|
|
1345
|
+
// Wrong
|
|
1346
|
+
export function draw() {
|
|
1347
|
+
cls(); // ❌ Clears 3D scene!
|
|
1348
|
+
print('HUD', 10, 10);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// Right
|
|
1352
|
+
export function draw() {
|
|
1353
|
+
// ✅ 3D scene renders first automatically
|
|
1354
|
+
print('HUD', 10, 10); // 2D overlay on top
|
|
1355
|
+
}
|
|
1356
|
+
```
|
|
1357
|
+
|
|
1358
|
+
### Button not responding
|
|
1359
|
+
|
|
1360
|
+
**Problem:** Not calling `updateAllButtons()` or button callback not firing.
|
|
1361
|
+
**Solution:** Call update function, check keyboard support with `isKeyDown()`.
|
|
1362
|
+
|
|
1363
|
+
```javascript
|
|
1364
|
+
export function update(dt) {
|
|
1365
|
+
updateAllButtons(); // ✅ Required!
|
|
1366
|
+
|
|
1367
|
+
// Keyboard fallback
|
|
1368
|
+
if (isKeyDown('Space') || isKeyDown('Enter')) {
|
|
1369
|
+
startGame();
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
```
|
|
1373
|
+
|
|
1374
|
+
---
|
|
1375
|
+
|
|
1376
|
+
## 📚 Additional Resources
|
|
1377
|
+
|
|
1378
|
+
- **EFFECTS_API_GUIDE.md** - Detailed effects documentation
|
|
1379
|
+
- **VOXEL_ENGINE_GUIDE.md** - Complete voxel system guide
|
|
1380
|
+
- **START_SCREEN_GUIDE.md** - Creating start screens
|
|
1381
|
+
- **GAMEPAD_SUPPORT.md** - Controller mapping details
|
|
1382
|
+
- **Examples folder** - 20+ working game demos
|
|
1383
|
+
|
|
1384
|
+
---
|
|
1385
|
+
|
|
1386
|
+
## 🎨 Example Games Included
|
|
1387
|
+
|
|
1388
|
+
1. **hello-3d** - Basic 3D shapes
|
|
1389
|
+
2. **demoscene** - Visual effects showcase (5 scenes)
|
|
1390
|
+
3. **tron-racer-3d** - Light cycle racing with bloom
|
|
1391
|
+
4. **cyberpunk-city-3d** - Flying city explorer
|
|
1392
|
+
5. **minecraft-demo** - Voxel world builder
|
|
1393
|
+
6. **f-zero-nova-3d** - Futuristic racing
|
|
1394
|
+
7. **star-fox-nova-3d** - Space combat
|
|
1395
|
+
8. **shooter-demo-3d** - Space shooter
|
|
1396
|
+
9. **3d-advanced** - Epic space battle
|
|
1397
|
+
10. **knight-platformer** - Side-scrolling action
|
|
1398
|
+
|
|
1399
|
+
---
|
|
1400
|
+
|
|
1401
|
+
**Built with ❤️ by the Nova64 team**
|
|
1402
|
+
**Target Platform:** Nintendo 64 / PlayStation aesthetic
|
|
1403
|
+
**Resolution:** 640×360 @ 60 FPS
|
|
1404
|
+
**Powered by:** Three.js + Custom Runtime
|
|
1405
|
+
|
|
1406
|
+
🎮 **Happy Game Development!** 🎮
|