Add '...' chat bubble above player when pressing talk before reaching NPC

This commit is contained in:
justin committed 2026-05-20 10:08:10 -07:00
1 parent f0585c30c3
commit 697a303a75
3 files changed
+61 -7

No files matched your search

+27 -6
View File
@@ -386,11 +386,15 @@ class Game {
/* Player sprite stays horizontally centred; only Y shifts */ /* Player sprite stays horizontally centred; only Y shifts */
const p = document.getElementById('map-player'); const p = document.getElementById('map-player');
if (p) { if (p) {
const centreX = viewW / 2 - 16; const centreX = viewW / 2 - 16;
const centreY = viewH * 0.65 + this.state.worldY; const centreY = viewH * 0.65 + this.state.worldY;
const bottomPx = viewH - centreY - 40;
p.style.left = centreX + 'px'; p.style.left = centreX + 'px';
p.style.bottom = (viewH - centreY - 40) + 'px'; p.style.bottom = bottomPx + 'px';
p.className = `map-player walk-${dir}`; p.className = `map-player walk-${dir}`;
/* Keep "..." bubble above player */
const bub = document.getElementById('player-bubble');
if (bub) { bub.style.left = (centreX - 4) + 'px'; bub.style.bottom = (bottomPx + 66) + 'px'; }
} }
this._checkNPCProximity(); this._checkNPCProximity();
@@ -453,8 +457,22 @@ class Game {
} }
_talkToNPC() { _talkToNPC() {
if (!this._nearNPC()) {
/* Too far away — show "..." bubble above player */
const bub = document.getElementById('player-bubble');
if (bub) {
bub.classList.remove('visible');
void bub.offsetWidth;
bub.classList.add('visible');
clearTimeout(this._bubbleTimer);
this._bubbleTimer = setTimeout(() => bub.classList.remove('visible'), 1500);
}
return;
}
const arrow = document.getElementById('map-dir-arrow'); const arrow = document.getElementById('map-dir-arrow');
if (arrow) arrow.style.display = 'none'; if (arrow) arrow.style.display = 'none';
const bub = document.getElementById('player-bubble');
if (bub) bub.classList.remove('visible');
SFX.encounter(); SFX.encounter();
this.startQuestion(); this.startQuestion();
} }
@@ -467,10 +485,13 @@ class Game {
const p = document.getElementById('map-player'); const p = document.getElementById('map-player');
if (p) { if (p) {
p.style.left = (viewW / 2 - 16) + 'px'; const left = viewW / 2 - 16;
p.style.bottom = (viewH * 0.28) + 'px'; const bottom = viewH * 0.28;
p.style.left = left + 'px';
p.style.bottom = bottom + 'px';
p.className = 'map-player idle'; p.className = 'map-player idle';
const bub = document.getElementById('player-bubble');
if (bub) { bub.style.left = (left - 4) + 'px'; bub.style.bottom = (bottom + 66) + 'px'; }
} }
this._applyCamera(); this._applyCamera();
const hint = document.getElementById('map-talk-hint'); const hint = document.getElementById('map-talk-hint');
+1
View File
@@ -109,6 +109,7 @@
<div class="map-npc" id="map-npc"></div> <div class="map-npc" id="map-npc"></div>
</div> </div>
<!-- Player stays fixed to viewport, only Y moves --> <!-- Player stays fixed to viewport, only Y moves -->
<div class="player-bubble" id="player-bubble">...</div>
<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 -->
<div class="map-talk-hint" id="map-talk-hint" style="display:none"><span class="hint-pc">Press E</span><span class="hint-touch">Press A</span></div> <div class="map-talk-hint" id="map-talk-hint" style="display:none"><span class="hint-pc">Press E</span><span class="hint-touch">Press A</span></div>
+33 -1
View File
@@ -483,6 +483,38 @@ html, body {
.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 */
/* "..." bubble above player when pressing talk away from NPC */
.player-bubble {
font-size: 11px;
font-family: var(--font);
color: var(--black);
background: #fff;
border: 2px solid var(--black);
padding: 0 5px;
height: 20px;
display: none;
align-items: center;
justify-content: center;
border-radius: 2px;
white-space: nowrap;
position: absolute;
z-index: 100;
box-shadow: 1px 1px 0 rgba(0,0,0,.3);
}
.player-bubble::after {
content: '';
position: absolute;
bottom: -6px;
left: 50%;
transform: translateX(-50%);
border: 3px solid transparent;
border-top-color: var(--black);
}
.player-bubble.visible {
display: flex;
animation: bounce .5s ease-in-out 3;
}
.map-player { .map-player {
position: absolute; position: absolute;
width: 32px; width: 32px;
@@ -493,7 +525,7 @@ html, body {
background-position: 0 0; background-position: 0 0;
image-rendering: pixelated; image-rendering: pixelated;
filter: drop-shadow(2px 3px 0 rgba(0,0,0,.4)); filter: drop-shadow(2px 3px 0 rgba(0,0,0,.4));
z-index: 99; z-index: 10;
font-size: 0; font-size: 0;
color: transparent; color: transparent;
} }