Fix NPC rendering on top of path — move NPC to viewport layer, convert world-px to screen-px each tick
This commit is contained in:
@@ -205,7 +205,7 @@ class Game {
|
|||||||
const p = document.getElementById('map-player');
|
const p = document.getElementById('map-player');
|
||||||
if (p) {
|
if (p) {
|
||||||
const centreX = viewW / 2 - 20;
|
const centreX = viewW / 2 - 20;
|
||||||
const centreY = viewH * 0.60 + this.state.worldY;
|
const centreY = viewH * 0.75 + this.state.worldY;
|
||||||
p.style.left = centreX + 'px';
|
p.style.left = centreX + 'px';
|
||||||
p.style.bottom = (viewH - centreY - 40) + 'px';
|
p.style.bottom = (viewH - centreY - 40) + 'px';
|
||||||
p.className = `map-player walk-${dir}`;
|
p.className = `map-player walk-${dir}`;
|
||||||
@@ -214,10 +214,22 @@ class Game {
|
|||||||
this._checkNPCProximity();
|
this._checkNPCProximity();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Slide the world so the camera follows the player */
|
/* Slide the world and reposition NPC in screen-space every tick */
|
||||||
_applyCamera() {
|
_applyCamera() {
|
||||||
const world = document.getElementById('map-inner');
|
const world = document.getElementById('map-inner');
|
||||||
if (world) world.style.transform = `translateX(${-this.state.worldX}px)`;
|
if (world) world.style.transform = `translateX(${-this.state.worldX}px)`;
|
||||||
|
this._positionNPCOnScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
_positionNPCOnScreen() {
|
||||||
|
const world = document.getElementById('map-inner');
|
||||||
|
const viewH = world ? world.parentElement.offsetHeight : 300;
|
||||||
|
const npcWrap = document.getElementById('map-npc-wrap');
|
||||||
|
if (npcWrap) {
|
||||||
|
const screenX = this.state.npcWorldX - this.state.worldX;
|
||||||
|
npcWrap.style.left = (screenX - 24) + 'px';
|
||||||
|
npcWrap.style.bottom = (viewH * 0.18) + 'px';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkNPCProximity() {
|
_checkNPCProximity() {
|
||||||
@@ -233,7 +245,7 @@ class Game {
|
|||||||
if (hint) {
|
if (hint) {
|
||||||
hint.style.display = near ? 'block' : 'none';
|
hint.style.display = near ? 'block' : 'none';
|
||||||
hint.style.left = (viewW / 2 - 28) + 'px';
|
hint.style.left = (viewW / 2 - 28) + 'px';
|
||||||
hint.style.bottom = (viewH * 0.46) + 'px';
|
hint.style.bottom = (viewH * 0.32) + 'px';
|
||||||
}
|
}
|
||||||
if (bub) bub.style.opacity = near ? '0' : '1';
|
if (bub) bub.style.opacity = near ? '0' : '1';
|
||||||
this._updateDirArrow(near);
|
this._updateDirArrow(near);
|
||||||
@@ -250,7 +262,7 @@ class Game {
|
|||||||
arrow.textContent = npcScreenX >= viewW / 2 ? '▶' : '◀';
|
arrow.textContent = npcScreenX >= viewW / 2 ? '▶' : '◀';
|
||||||
arrow.style.display = 'block';
|
arrow.style.display = 'block';
|
||||||
arrow.style.left = (viewW / 2 + 30) + 'px';
|
arrow.style.left = (viewW / 2 + 30) + 'px';
|
||||||
arrow.style.bottom = (viewH * 0.40) + 'px';
|
arrow.style.bottom = (viewH * 0.28) + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
_nearNPC() {
|
_nearNPC() {
|
||||||
@@ -266,7 +278,7 @@ class Game {
|
|||||||
this.startQuestion();
|
this.startQuestion();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Place player at viewport centre, NPC at world-px position */
|
/* Place player at viewport centre, NPC via screen-space conversion */
|
||||||
_placeMapSprites() {
|
_placeMapSprites() {
|
||||||
const world = document.getElementById('map-inner');
|
const world = document.getElementById('map-inner');
|
||||||
const viewW = world ? world.parentElement.offsetWidth : 420;
|
const viewW = world ? world.parentElement.offsetWidth : 420;
|
||||||
@@ -275,15 +287,10 @@ class Game {
|
|||||||
const p = document.getElementById('map-player');
|
const p = document.getElementById('map-player');
|
||||||
if (p) {
|
if (p) {
|
||||||
p.style.left = (viewW / 2 - 20) + 'px';
|
p.style.left = (viewW / 2 - 20) + 'px';
|
||||||
p.style.bottom = (viewH * 0.32) + 'px';
|
p.style.bottom = (viewH * 0.18) + 'px';
|
||||||
p.style.transform = 'scaleX(-1)';
|
p.style.transform = 'scaleX(-1)';
|
||||||
p.className = 'map-player idle';
|
p.className = 'map-player idle';
|
||||||
}
|
}
|
||||||
const npcWrap = document.getElementById('map-npc-wrap');
|
|
||||||
if (npcWrap) {
|
|
||||||
npcWrap.style.left = (this.state.npcWorldX - 24) + 'px';
|
|
||||||
npcWrap.style.bottom = (viewH * 0.32) + 'px';
|
|
||||||
}
|
|
||||||
this._applyCamera();
|
this._applyCamera();
|
||||||
const hint = document.getElementById('map-talk-hint');
|
const hint = document.getElementById('map-talk-hint');
|
||||||
if (hint) hint.style.display = 'none';
|
if (hint) hint.style.display = 'none';
|
||||||
|
|||||||
+5
-5
@@ -107,14 +107,14 @@
|
|||||||
<span class="tree">🌲</span><span class="tree">🌳</span><span class="tree">🌲</span>
|
<span class="tree">🌲</span><span class="tree">🌳</span><span class="tree">🌲</span>
|
||||||
<span class="tree">🌳</span><span class="tree">🌲</span><span class="tree">🌳</span>
|
<span class="tree">🌳</span><span class="tree">🌲</span><span class="tree">🌳</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- NPC with ! bubble — position driven by JS in world-px -->
|
|
||||||
<div class="map-npc-wrap" id="map-npc-wrap">
|
|
||||||
<div class="npc-bubble" id="npc-bubble">!</div>
|
|
||||||
<div class="map-npc" id="map-npc"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Path stays fixed to the viewport — does not scroll -->
|
<!-- Path stays fixed to the viewport — does not scroll -->
|
||||||
<div class="map-ground"></div>
|
<div class="map-ground"></div>
|
||||||
|
<!-- NPC in viewport layer — JS positions it using screen-space X -->
|
||||||
|
<div class="map-npc-wrap" id="map-npc-wrap">
|
||||||
|
<div class="npc-bubble" id="npc-bubble">!</div>
|
||||||
|
<div class="map-npc" id="map-npc"></div>
|
||||||
|
</div>
|
||||||
<!-- Player stays fixed to viewport, only Y moves -->
|
<!-- Player stays fixed to viewport, only Y moves -->
|
||||||
<div class="map-player" id="map-player">🧢</div>
|
<div class="map-player" id="map-player">🧢</div>
|
||||||
<!-- Hints overlay the viewport, not the world -->
|
<!-- Hints overlay the viewport, not the world -->
|
||||||
|
|||||||
@@ -395,6 +395,7 @@ html, body {
|
|||||||
top: 0; bottom: 0; left: 0;
|
top: 0; bottom: 0; left: 0;
|
||||||
width: 400%;
|
width: 400%;
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
|
z-index: 5;
|
||||||
}
|
}
|
||||||
.map-trees {
|
.map-trees {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -436,7 +437,7 @@ html, body {
|
|||||||
filter: drop-shadow(0 0 4px rgba(248,192,48,.6));
|
filter: drop-shadow(0 0 4px rgba(248,192,48,.6));
|
||||||
}
|
}
|
||||||
/* NPC lives inside .map-inner, positioned in world-px via JS */
|
/* NPC lives inside .map-inner, positioned in world-px via JS */
|
||||||
.map-npc-wrap { position:absolute; text-align:center; }
|
.map-npc-wrap { position:absolute; text-align:center; z-index: 30; }
|
||||||
.npc-bubble { font-size:14px; font-family:var(--font); color:var(--black); background:var(--yellow); border:2px solid var(--black); width:20px; height:20px; display:flex; align-items:center; justify-content:center; margin:0 auto 2px; animation:bounce .7s ease-in-out infinite; }
|
.npc-bubble { font-size:14px; font-family:var(--font); color:var(--black); background:var(--yellow); border:2px solid var(--black); width:20px; height:20px; display:flex; align-items:center; justify-content:center; margin:0 auto 2px; animation:bounce .7s ease-in-out infinite; }
|
||||||
.map-npc { font-size:clamp(36px,10vw,52px); filter:drop-shadow(2px 3px 0 rgba(0,0,0,.35)); cursor:pointer; }
|
.map-npc { font-size:clamp(36px,10vw,52px); filter:drop-shadow(2px 3px 0 rgba(0,0,0,.35)); cursor:pointer; }
|
||||||
/* Player is fixed to the viewport centre horizontally; only Y moves */
|
/* Player is fixed to the viewport centre horizontally; only Y moves */
|
||||||
|
|||||||
Reference in New Issue
Block a user