Add moon surface intro level with asteroid hazards and unarmed mechanics
Introduce moon01.lvl as the starting level — a pure jump-and-run intro with no gun and no enemies, just platforming over gaps and dodging falling asteroids. The player picks up their gun upon transitioning to level01. New features: - Moon tileset and PARALLAX_STYLE_MOON with crater terrain backgrounds - Asteroid entity (ENT_ASTEROID): falls from sky, damages on contact, explodes on ground with particles, respawns after delay - PLAYER_UNARMED directive disables gun for the level - Pit rescue mechanic: falling costs 1 HP and auto-dashes upward - Gun powerup entity type for future armed-pickup levels - Segment-based procedural level generator with themed rooms - Extended editor with entity palette and improved tile cycling - Web shell improvements for Emscripten builds
This commit is contained in:
148
web/shell.html
148
web/shell.html
@@ -23,6 +23,8 @@
|
||||
}
|
||||
canvas.emscripten {
|
||||
display: block;
|
||||
width: 1280px;
|
||||
height: 720px;
|
||||
image-rendering: pixelated;
|
||||
image-rendering: crisp-edges;
|
||||
}
|
||||
@@ -46,6 +48,51 @@
|
||||
transition: width 0.2s;
|
||||
}
|
||||
.hidden { display: none !important; }
|
||||
|
||||
#controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
max-width: 1300px;
|
||||
}
|
||||
#hint { color: #666; }
|
||||
.ctrl-btn {
|
||||
color: #4ecdc4;
|
||||
background: transparent;
|
||||
border: 1px solid #4ecdc4;
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ctrl-btn:hover {
|
||||
background: #4ecdc4;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
#level-select {
|
||||
background: #1a1a2e;
|
||||
color: #4ecdc4;
|
||||
border: 1px solid #4ecdc4;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#level-select option {
|
||||
background: #1a1a2e;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
.ctrl-sep {
|
||||
color: #333;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -53,6 +100,18 @@
|
||||
<canvas class="emscripten" id="canvas" tabindex="1"
|
||||
width="640" height="360"></canvas>
|
||||
</div>
|
||||
<div id="controls">
|
||||
<a class="ctrl-btn" id="editor-link" href="?edit">Editor</a>
|
||||
<span class="ctrl-sep">|</span>
|
||||
<button class="ctrl-btn" id="btn-save" title="Save level (download .lvl)">Save</button>
|
||||
<button class="ctrl-btn" id="btn-load" title="Load .lvl from disk">Load</button>
|
||||
<span class="ctrl-sep">|</span>
|
||||
<select id="level-select" title="Open a built-in level in the editor">
|
||||
<option value="">-- Open level --</option>
|
||||
</select>
|
||||
<span class="ctrl-sep">|</span>
|
||||
<span id="hint">E=editor P=test play 1-6=tools</span>
|
||||
</div>
|
||||
<div id="status">Loading...</div>
|
||||
<div id="progress-bar"><div id="progress-bar-inner"></div></div>
|
||||
|
||||
@@ -63,6 +122,9 @@
|
||||
|
||||
var Module = {
|
||||
canvas: document.getElementById('canvas'),
|
||||
/* Force 1:1 pixel mapping so SDL_RenderSetLogicalSize doesn't
|
||||
compute a viewport offset on HiDPI / fractional-scale displays */
|
||||
devicePixelRatio: 1,
|
||||
print: function(text) { console.log(text); },
|
||||
printErr: function(text) { console.error(text); },
|
||||
setStatus: function(text) {
|
||||
@@ -92,6 +154,92 @@
|
||||
window.onerror = function() {
|
||||
Module.setStatus('Error - check the browser console');
|
||||
};
|
||||
|
||||
/* ── Keyboard shortcuts: Ctrl+S / Ctrl+O ────────── */
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (typeof _editor_save_flag_ptr === 'function') {
|
||||
HEAP32[_editor_save_flag_ptr() >> 2] = 1;
|
||||
}
|
||||
}
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'o') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (typeof _editor_load_flag_ptr === 'function') {
|
||||
HEAP32[_editor_load_flag_ptr() >> 2] = 1;
|
||||
}
|
||||
}
|
||||
}, true); /* useCapture=true to intercept before SDL */
|
||||
|
||||
/* ── Button handlers ────────────────────────────── */
|
||||
document.getElementById('btn-save').addEventListener('click', function() {
|
||||
if (typeof _editor_save_flag_ptr === 'function') {
|
||||
HEAP32[_editor_save_flag_ptr() >> 2] = 1;
|
||||
}
|
||||
/* Return focus to canvas so keys keep working */
|
||||
document.getElementById('canvas').focus();
|
||||
});
|
||||
|
||||
document.getElementById('btn-load').addEventListener('click', function() {
|
||||
if (typeof _editor_load_flag_ptr === 'function') {
|
||||
HEAP32[_editor_load_flag_ptr() >> 2] = 1;
|
||||
}
|
||||
document.getElementById('canvas').focus();
|
||||
});
|
||||
|
||||
/* ── Level picker dropdown ──────────────────────── */
|
||||
var levelSelect = document.getElementById('level-select');
|
||||
|
||||
/* Populate the dropdown after the module is ready (FS available) */
|
||||
Module.postRun = Module.postRun || [];
|
||||
Module.postRun.push(function() {
|
||||
/* Scan for .lvl files in the virtual FS */
|
||||
var levels = [];
|
||||
try {
|
||||
var entries = FS.readdir('assets/levels');
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
if (entries[i].endsWith('.lvl') && entries[i][0] !== '_') {
|
||||
levels.push('assets/levels/' + entries[i]);
|
||||
}
|
||||
}
|
||||
levels.sort();
|
||||
} catch(e) {
|
||||
console.error('Could not read levels dir:', e);
|
||||
}
|
||||
|
||||
for (var j = 0; j < levels.length; j++) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = levels[j];
|
||||
/* Show just the filename without path */
|
||||
opt.textContent = levels[j].replace('assets/levels/', '');
|
||||
levelSelect.appendChild(opt);
|
||||
}
|
||||
});
|
||||
|
||||
levelSelect.addEventListener('change', function() {
|
||||
var path = this.value;
|
||||
if (!path) return;
|
||||
|
||||
if (typeof _editor_load_vfs_file === 'function') {
|
||||
/* Pass the path string to C */
|
||||
var len = lengthBytesUTF8(path) + 1;
|
||||
var buf = _malloc(len);
|
||||
stringToUTF8(path, buf, len);
|
||||
_editor_load_vfs_file(buf);
|
||||
_free(buf);
|
||||
}
|
||||
|
||||
/* Reset dropdown to placeholder */
|
||||
this.selectedIndex = 0;
|
||||
document.getElementById('canvas').focus();
|
||||
});
|
||||
|
||||
/* Update title if in editor mode */
|
||||
if (window.location.search.indexOf('edit') !== -1) {
|
||||
document.title = 'Jump \'n Run - Level Editor';
|
||||
}
|
||||
</script>
|
||||
{{{ SCRIPT }}}
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user