mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-19 07:08:33 +02:00
fix(render): 3D lazy-loads THREE; ASCII torch lights; iso never-black placeholder
Three mode-switch issues: - Picking 3D gave "original": SP._ensureRenderer used the sync createRenderer, which falls back to Tiled when THREE (lazy CDN load) is not ready. Now it kicks off RK.ensureThree and re-mounts as real 3D once loaded (renders Tiled meanwhile so it is not blank). - ASCII torches were inert glyphs: they now draw a warm flickering radial glow (actual light source) with the glyph at full brightness, driven by a throttled ~22fps RAF (cancelled on destroy). - Switching to iso could show black while the pack images load: tiles now draw a legend-coloured iso diamond placeholder until the real image is ready, so the dungeon shape + camera always read.
This commit is contained in:
@@ -16,7 +16,8 @@
|
||||
|
||||
AsciiRenderer.prototype.render = function (scene) {
|
||||
if (!scene) return;
|
||||
var cell = this.cell, ctx = this.ctx;
|
||||
this.lastScene = scene;
|
||||
var cell = this.cell, ctx = this.ctx, now = Date.now(), hasTorch = false;
|
||||
this.canvas.width = scene.cols * cell;
|
||||
this.canvas.height = scene.rows * cell;
|
||||
|
||||
@@ -38,14 +39,33 @@
|
||||
ctx.fillText(walk ? '·' : '#', x * cell + cell / 2, y * cell + cell / 2);
|
||||
} else {
|
||||
var def = scene.legend[scene.grid[y][x]] || { char: '?', color: '#555' };
|
||||
ctx.fillStyle = def.color;
|
||||
// Dungeon lighting: dim tiles in shadow.
|
||||
if (scene.lightGrid && scene.lightGrid[y] && scene.lightGrid[y][x] != null) {
|
||||
var b = scene.lightGrid[y][x];
|
||||
if (b < 1) ctx.globalAlpha = b;
|
||||
var gx = x * cell + cell / 2, gy = y * cell + cell / 2;
|
||||
if (def.fx === 'fire') {
|
||||
// Torches are ACTUAL light sources: a warm radial glow behind the glyph that
|
||||
// gently flickers; the glyph itself stays at full brightness (never dimmed).
|
||||
hasTorch = true;
|
||||
var seed = x * 7 + y * 13;
|
||||
var fl = 0.7 + Math.sin(now / 170 + seed) * 0.3;
|
||||
var rad = cell * (1.9 + Math.sin(now / 240 + seed) * 0.18);
|
||||
var g = ctx.createRadialGradient(gx, gy, 0, gx, gy, rad);
|
||||
g.addColorStop(0, 'rgba(255,196,96,' + (0.6 * fl) + ')');
|
||||
g.addColorStop(0.45, 'rgba(255,140,44,' + (0.24 * fl) + ')');
|
||||
g.addColorStop(1, 'rgba(255,120,30,0)');
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.fillStyle = g;
|
||||
ctx.fillRect(gx - rad, gy - rad, rad * 2, rad * 2);
|
||||
ctx.fillStyle = '#ffd9a0';
|
||||
ctx.fillText(def.char, gx, gy);
|
||||
} else {
|
||||
ctx.fillStyle = def.color;
|
||||
// Dungeon lighting: dim tiles in shadow.
|
||||
if (scene.lightGrid && scene.lightGrid[y] && scene.lightGrid[y][x] != null) {
|
||||
var b = scene.lightGrid[y][x];
|
||||
if (b < 1) ctx.globalAlpha = b;
|
||||
}
|
||||
ctx.fillText(def.char, gx, gy);
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
ctx.fillText(def.char, x * cell + cell / 2, y * cell + cell / 2);
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,9 +115,29 @@
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep a light RAF alive so the torch glows flicker between game updates (~22fps).
|
||||
this._hasTorches = hasTorch;
|
||||
if (hasTorch) this._scheduleFlicker();
|
||||
};
|
||||
|
||||
// Re-render on a throttled RAF so torch glows breathe even when the game isn't updating.
|
||||
AsciiRenderer.prototype._scheduleFlicker = function () {
|
||||
if (this._flickerRaf || !this._hasTorches || !this.ctx) return;
|
||||
var self = this;
|
||||
this._flickerRaf = requestAnimationFrame(function () {
|
||||
self._flickerRaf = null;
|
||||
if (!self.ctx || !self._hasTorches || !self.lastScene) return;
|
||||
var t = Date.now();
|
||||
if (self._lastFlick && t - self._lastFlick < 45) { self._scheduleFlicker(); return; }
|
||||
self._lastFlick = t;
|
||||
self.render(self.lastScene);
|
||||
});
|
||||
};
|
||||
|
||||
AsciiRenderer.prototype.destroy = function () {
|
||||
this._hasTorches = false;
|
||||
if (this._flickerRaf) { cancelAnimationFrame(this._flickerRaf); this._flickerRaf = null; }
|
||||
if (this.canvas && this.canvas.parentNode) this.canvas.parentNode.removeChild(this.canvas);
|
||||
this.canvas = null;
|
||||
this.ctx = null;
|
||||
|
||||
@@ -73,6 +73,17 @@
|
||||
this.ctx.drawImage(img, cx - w / 2, baseY - h, w, h);
|
||||
};
|
||||
|
||||
// A flat iso diamond in the legend colour — the placeholder a tile draws while its pack image
|
||||
// loads (or if an image is missing), so the view shows the dungeon shape instead of going black.
|
||||
IsoRenderer.prototype._diamond = function (cx, cy, color) {
|
||||
var ctx = this.ctx, hw = this.tileW / 2, hh = this.tileH / 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx, cy - hh); ctx.lineTo(cx + hw, cy); ctx.lineTo(cx, cy + hh); ctx.lineTo(cx - hw, cy);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = color; ctx.fill();
|
||||
ctx.strokeStyle = 'rgba(0,0,0,0.28)'; ctx.lineWidth = 1; ctx.stroke();
|
||||
};
|
||||
|
||||
// Soft contact shadow grounds props/characters so they don't float on the floor.
|
||||
IsoRenderer.prototype._contactShadow = function (cx, cy, rx, ry) {
|
||||
var ctx = this.ctx;
|
||||
@@ -286,6 +297,7 @@
|
||||
else tileKind = this._floorVariant(tileKind, it.x, it.y);
|
||||
var rec = this._load(this._tileUrl(tileKind));
|
||||
if (rec && rec.ready) this._drawImage(rec.img, it.sx, it.sy + this.tileH, this.imageW, this.imageH);
|
||||
else if (it.kind !== 'dark') this._diamond(it.sx, it.sy + this.tileH * 0.5, (legend[it.kind] && legend[it.kind].color) || '#3a4048');
|
||||
} else if (it.type === 'prop') {
|
||||
this._contactShadow(it.sx, it.sy + this.tileH * 1.4, 22, 9);
|
||||
rec = this._load(this._tileUrl(it.kind));
|
||||
|
||||
@@ -75,7 +75,23 @@
|
||||
if (SP._renderer) return SP._renderer;
|
||||
var host = rkHost();
|
||||
if (!host || !RK.createRenderer) return null;
|
||||
SP._renderer = RK.createRenderer(SP.mode(), host, { cell: 24 });
|
||||
var mode = SP.mode();
|
||||
// 3D needs THREE, lazy-loaded from a CDN. The sync createRenderer() silently returns a Tiled
|
||||
// renderer when THREE isn't loaded yet ("picked 3D, got original"). Kick off the load and
|
||||
// re-mount as real 3D once it's ready; meanwhile we render Tiled so the screen isn't blank.
|
||||
if (mode === '3d' && RK.ensureThree && RK.threeReady && !RK.threeReady()) {
|
||||
if (!SP._threePending) {
|
||||
SP._threePending = true;
|
||||
RK.ensureThree(function (ok) {
|
||||
SP._threePending = false;
|
||||
if (ok && SP._live && SP.mode() === '3d') {
|
||||
SP._destroyRenderer();
|
||||
if (SP._lastState) SP.render(SP._lastState, SP._lastOpts);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
SP._renderer = RK.createRenderer(mode, host, { cell: 24 });
|
||||
// The camera owns the canvas transform (centre on the player), so position it absolutely
|
||||
// and let it overflow — do NOT use RK.attachZoom (it fights this transform).
|
||||
if (SP._renderer && SP._renderer.canvas) {
|
||||
|
||||
Reference in New Issue
Block a user