mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-21 16:18:33 +02:00
feat(render): re-enable render-kit SP dungeon with player-centered camera + goblin monster
Commit to the render kit as THE single-player renderer (legacy = hard fallback only). Fixes the two things that broke it: - CAMERA: each renderer now exposes focusPoint (the player's pixel pos); the SP bridge mounts a self-sizing 16:10 viewport host and CSS-transforms the canvas to centre the player at a readable zoom (wheel to zoom). Renderer-agnostic (tiled/iso/3d). No more whole-level-in-a-corner. - MONSTER: the tiled renderer drew a hardcoded red dot; now it draws the Goblin character sprite (char-goblin, the same path the tavern draws characters), red orb only as last-resort fallback. Re-enabled: _drawGameScreen prefers RK.SPGame; spRenderPicker ENABLED. Suite 62/435 green. NEXT (still owed): make the splash + both waiting animations render through the kit too, so they use the chosen pack/mode instead of the ROT text screens.
This commit is contained in:
+20
-10
@@ -288,17 +288,27 @@ var Game = {
|
||||
},
|
||||
|
||||
_drawGameScreen: function() {
|
||||
// The render-kit dungeon path (RK.SPGame) is DISABLED until it has a player-centered camera
|
||||
// + real monster/entity sprites — without those it rendered the whole level tiny in a corner
|
||||
// with a red-dot monster. Until then the single-player game uses the proven legacy renderer.
|
||||
if (window.RK && RK.SPGame && RK.SPGame.hide) RK.SPGame.hide();
|
||||
if (!this._ensureDisplay()) return;
|
||||
const gameState = GameState.getGameStateForRender();
|
||||
RenderEngine.drawGameScreen(gameState);
|
||||
// JUICE: keep the FX overlay aligned to the base canvas and feed it the player's screen-space
|
||||
// position so its torch lighting tracks the camera.
|
||||
if (window.FX) {
|
||||
try { this._fxSyncLighting(); } catch (e) { /* best-effort */ }
|
||||
// Render the dungeon through the render kit (Tiled / ASCII / Iso / 3D + unlocked packs) with
|
||||
// a player-centered camera + goblin monster sprite. The legacy RenderEngine remains a HARD
|
||||
// fallback (used only if the kit is unavailable or a render throws), so the game never blanks.
|
||||
var rk = false;
|
||||
if (window.RK && RK.SPGame && RK.SPGame.available()) {
|
||||
RK.SPGame.show();
|
||||
rk = RK.SPGame.render(gameState, {
|
||||
cryptoType: (window.ScreenManager && ScreenManager._cryptoType) || undefined,
|
||||
playerAppearance: (window.SinglePlayerAvatar && SinglePlayerAvatar.currentAppearance)
|
||||
? SinglePlayerAvatar.currentAppearance() : null,
|
||||
isSpectating: this._isSpectating
|
||||
});
|
||||
}
|
||||
if (!rk) {
|
||||
if (window.RK && RK.SPGame) RK.SPGame.hide();
|
||||
if (!this._ensureDisplay()) return;
|
||||
RenderEngine.drawGameScreen(gameState);
|
||||
if (window.FX) {
|
||||
try { this._fxSyncLighting(); } catch (e) { /* best-effort */ }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -268,10 +268,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
this.focusPoint = null;
|
||||
for (var i = 0; i < scene.entities.length; i++) {
|
||||
var e = scene.entities[i];
|
||||
p = this._project(e.x, e.y, originX, originY);
|
||||
items.push({ type: 'entity', e: e, sx: p.x, sy: p.y, depth: e.x + e.y + 0.55 });
|
||||
if (e.you || e.kind === 'player') this.focusPoint = { x: p.x, y: p.y + this.tileH }; // SP camera target
|
||||
}
|
||||
items.sort(function (a, b) { return a.depth === b.depth ? (a.y || 0) - (b.y || 0) : a.depth - b.depth; });
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
function doc() { return root.document; }
|
||||
function gameDisplay() { return doc() && doc().getElementById('game-display'); }
|
||||
|
||||
// A dedicated child host so the RK canvas doesn't collide with the ROT canvas / overlays.
|
||||
// A dedicated, SELF-SIZING child host so the RK canvas doesn't collide with the ROT canvas and
|
||||
// doesn't depend on the ROT canvas for its dimensions (it's an aspect-ratio viewport the camera
|
||||
// transform is clipped to).
|
||||
function rkHost() {
|
||||
var gd = gameDisplay();
|
||||
if (!gd) return null;
|
||||
@@ -24,7 +26,9 @@
|
||||
if (!host) {
|
||||
host = doc().createElement('div');
|
||||
host.id = 'rk-game-host';
|
||||
host.style.display = 'none';
|
||||
host.style.cssText = 'display:none; position:relative; width:100%; max-width:900px;' +
|
||||
' aspect-ratio:16/10; margin:0 auto; overflow:hidden; background:#0a0c0f;' +
|
||||
' border:1px solid #2a313a; border-radius:6px; touch-action:none; cursor:grab;';
|
||||
gd.appendChild(host);
|
||||
}
|
||||
return host;
|
||||
@@ -67,10 +71,39 @@
|
||||
var host = rkHost();
|
||||
if (!host || !RK.createRenderer) return null;
|
||||
SP._renderer = RK.createRenderer(SP.mode(), host, { cell: 24 });
|
||||
if (RK.attachZoom) { try { RK.attachZoom(host); } catch (_) {} }
|
||||
// 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) {
|
||||
var c = SP._renderer.canvas;
|
||||
c.style.position = 'absolute'; c.style.top = '0'; c.style.left = '0';
|
||||
c.style.maxWidth = 'none'; c.style.imageRendering = 'pixelated';
|
||||
}
|
||||
if (!host._rkZoomBound) {
|
||||
host._rkZoomBound = true;
|
||||
host.addEventListener('wheel', function (ev) {
|
||||
ev.preventDefault();
|
||||
SP._zoom = Math.max(0.6, Math.min(4, (SP._zoom || 1.7) * Math.exp(-ev.deltaY * 0.0015)));
|
||||
SP._applyCamera();
|
||||
}, { passive: false });
|
||||
}
|
||||
return SP._renderer;
|
||||
};
|
||||
|
||||
// Centre the (whole-scene) canvas on the player via a CSS transform, clipped to the host —
|
||||
// renderer-agnostic (works for tiled / iso / 3d, each of which reports its own focusPoint).
|
||||
SP._applyCamera = function () {
|
||||
var r = SP._renderer;
|
||||
var host = doc() && doc().getElementById('rk-game-host');
|
||||
if (!r || !r.canvas || !host) return;
|
||||
var scale = SP._zoom || 1.7;
|
||||
var fp = r.focusPoint;
|
||||
r.canvas.style.transformOrigin = '0 0';
|
||||
if (!fp) { r.canvas.style.transform = 'scale(' + scale + ')'; return; }
|
||||
var w = host.clientWidth || 640, h = host.clientHeight || 400;
|
||||
var tx = w / 2 - fp.x * scale, ty = h / 2 - fp.y * scale;
|
||||
r.canvas.style.transform = 'translate(' + tx + 'px,' + ty + 'px) scale(' + scale + ')';
|
||||
};
|
||||
|
||||
function toggleLegacy(hide) {
|
||||
var gd = gameDisplay();
|
||||
if (!gd) return;
|
||||
@@ -117,6 +150,7 @@
|
||||
isSpectating: opts.isSpectating
|
||||
});
|
||||
r.render(scene);
|
||||
SP._applyCamera();
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (root.console) console.warn('SPGame render failed; falling back to legacy:', e && e.message);
|
||||
|
||||
@@ -203,10 +203,17 @@
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Dungeon monster.
|
||||
// Dungeon monster — draw the Goblin character sprite (same path the tavern draws
|
||||
// characters through), falling back to a red orb only if the sprite is unavailable.
|
||||
if (e.kind === 'monster') {
|
||||
drawEntityShadow(ctx, e.x * cell + cell / 2, e.y * cell + cell * 0.9, cell * 0.32, cell * 0.13);
|
||||
var mAvatar = e.avatar || 'char-goblin';
|
||||
if (window.RK && RK.avatarVisuals && RK.avatarVisuals.drawTopdownWorld) {
|
||||
var mv = RK.avatarVisuals.resolve({ avatar: mAvatar }, { projection: 'topdown', context: 'dungeon', entity: e });
|
||||
if (RK.avatarVisuals.drawTopdownWorld(ctx, mv, e, { screenX: e.x, screenY: e.y, cell: cell }, { now: now })) continue;
|
||||
}
|
||||
if (window.RK && RK.drawCharCanvas && RK.drawCharCanvas(ctx, { x: e.x, y: e.y, avatar: mAvatar }, cell, now)) continue;
|
||||
var mx = e.x * cell + cell / 2, my = e.y * cell + cell / 2, mr = cell * 0.34;
|
||||
drawEntityShadow(ctx, mx, e.y * cell + cell * 0.9, cell * 0.32, cell * 0.13);
|
||||
ctx.beginPath();
|
||||
ctx.arc(mx, my, mr, 0, Math.PI * 2);
|
||||
ctx.fillStyle = e.color || '#f85149';
|
||||
@@ -267,6 +274,10 @@
|
||||
|
||||
// Snapshot the base scene, then composite the torch light + vignette on top, and keep
|
||||
// an animation loop running so the light flickers and embers drift between renders.
|
||||
// Where the player is on this canvas — the SP camera centers the view on it.
|
||||
var _fp = findPlayer(scene);
|
||||
this.focusPoint = _fp ? { x: (_fp.x + 0.5) * cell, y: (_fp.y + 0.5) * cell } : null;
|
||||
|
||||
this._lastScene = scene;
|
||||
this._collectEmitters(scene);
|
||||
this._snapshot();
|
||||
|
||||
@@ -30,9 +30,8 @@
|
||||
doc().head.appendChild(s);
|
||||
}
|
||||
|
||||
// Disabled until the single-player render-kit path (RK.SPGame) has a camera + entity sprites.
|
||||
// Keeping the module wired so re-enabling is a one-line flip.
|
||||
var ENABLED = false;
|
||||
// Re-enabled now that the SP render kit has a player-centered camera + goblin sprite.
|
||||
var ENABLED = true;
|
||||
|
||||
function build() {
|
||||
var h = host();
|
||||
|
||||
Reference in New Issue
Block a user