diff --git a/www/html/Game/public/img/TowerBlock/README.txt b/www/html/Game/public/img/TowerBlock/README.txt index d7eb937..b6d8d4a 100644 --- a/www/html/Game/public/img/TowerBlock/README.txt +++ b/www/html/Game/public/img/TowerBlock/README.txt @@ -25,7 +25,7 @@ English: Upload Stack / tower assets under **`TowerBlock`** (no spaces). **`Towe | `sling.png` | เชือกเหวี่ยงบล็อกในเกม (ยืดจากจุดแขวนถึงบล็อก) — แนวตั้งในไฟล์ = แนวสาย; ไม่มีไฟล์จะใช้เส้นประแทน | | `heart-1.png` | โชว์กลางเหนือกองเมื่อ **พลาด** (Tower live) — ไม่มีจะ fallback ข้อความ `♥ −1` | | `life-full.png` | HUD มุมขวา: หัวใจ **เต็ม** ในแถบชีวิต (คู่ `life-hit.png`) — ไม่มีจะ fallback เป็น `heart-1.png` | -| `life-bar.png` | HUD: สไตล์ข้อความ **SYSTEM INTEGRITY** + วงเล็บ — วาง **ซ้ายของอวตาร** ในแผง cyan (mock รูป2) | +| `life-bar.png` | HUD: ใช้เฉพาะ **ครึ่งบน** (~38%) เป็นหัวข้อ SYSTEM INTEGRITY — แถวหัวใจวาดแยกใต้ + วงเล็บ `[` `]` จากโค้ด (ไม่ทับขอบใน PNG) · ปรับ `STACK_TOWER_LIFE_BAR_TITLE_FRAC` ใน `play.js` ถ้า art เปลี่ยน | | `life-hit.png` | หัวใจ **หาย / โดนตี** ในแถบชีวิต (คู่ `life-full.png`) | | `btn-drop.png` | ปุ่ม **DROP** มุมขวาล่างใน `play.html` (เทียบ Space / Enter) | | `score+10.png` | Tower live: โชว์เหนือบล็อกเมื่อได้ **+10** | diff --git a/www/html/Game/public/js/play.js b/www/html/Game/public/js/play.js index de9ccbe..2931d7d 100644 --- a/www/html/Game/public/js/play.js +++ b/www/html/Game/public/js/play.js @@ -4211,13 +4211,12 @@ } /** - * พื้นที่ระหว่างวงเล็บบน life-bar.png (normalized 0–1) — ปรับถ้า asset เปลี่ยนเลย์เอาต์ - * UV rect on life-bar.png for heart row between brackets (tune if art changes). + * สัดส่วนสูงของ life-bar.png ที่เป็นข้อความ "SYSTEM INTEGRITY" (ที่เหลือ = แถวหัวใจแยกวาดเอง ไม่ทับขอบ `[` `]` ใน PNG) */ - const STACK_TOWER_LIFE_BAR_HEART_SLOT_UV = { u0: 0.11, u1: 0.89, v0: 0.40, v1: 0.86 }; + const STACK_TOWER_LIFE_BAR_TITLE_FRAC = 0.38; /** - * วาด life-bar + หัวใจ life-full (เต็ม) / life-hit (หาย) ในกรอบ dest — คืนความสูงจริงที่วาด + * วาดหัวข้อจาก life-bar.png (ครอปบน) + แถวหัวใจ life-full / life-hit กึ่งกลาง — คืนความสูงที่ใช้จริง */ function drawStackTowerLifeIntegrityBarPlay(ctx, destX, destY, maxW, maxH, lifeSlots, livesRem) { ensureStackTowerLifeHudImagesPlay(); @@ -4246,35 +4245,69 @@ const bw0 = bar.naturalWidth; const bh0 = bar.naturalHeight; - const scale = Math.min(maxW / bw0, maxH / bh0, 1.35); + const titleSrcH = Math.max(2, Math.floor(bh0 * STACK_TOWER_LIFE_BAR_TITLE_FRAC)); + const scale = Math.min(maxW / bw0, 1.45); const bw = bw0 * scale; - const bh = bh0 * scale; + const titleDh = titleSrcH * scale; + const gap = 3; const bx = destX; - const by = destY + Math.max(0, (maxH - bh) * 0.5); + const by0 = destY + Math.max(0, (maxH - titleDh - gap - Math.min(40, maxH * 0.45)) * 0.35); + const heartsTop = by0 + titleDh + gap; + const roomHearts = Math.max(10, destY + maxH - heartsTop - 2); + const heartsH = Math.max(18, Math.min(roomHearts, 40, Math.floor(maxH * 0.5))); ctx.save(); try { - ctx.drawImage(bar, 0, 0, bw0, bh0, bx, by, bw, bh); + ctx.drawImage(bar, 0, 0, bw0, titleSrcH, bx, by0, bw, titleDh); } catch (e) { /* ignore */ } - const uv = STACK_TOWER_LIFE_BAR_HEART_SLOT_UV; - const ix = bx + uv.u0 * bw; - const iy = by + uv.v0 * bh; - const iwSlot = Math.max(4, (uv.u1 - uv.u0) * bw); - const ihSlot = Math.max(4, (uv.v1 - uv.v0) * bh); - const slotW = iwSlot / slots; - const iconMax = Math.min(slotW * 0.82, ihSlot * 0.88, 36); + const cy = heartsTop + heartsH * 0.5; + const bracketW = Math.min(14, Math.max(9, bw * 0.07)); + ctx.fillStyle = 'rgba(94, 239, 255, 0.88)'; + ctx.font = '600 ' + Math.round(Math.min(17, heartsH * 0.55)) + 'px ui-monospace, "Cascadia Mono", Consolas, monospace'; + ctx.textBaseline = 'middle'; + ctx.textAlign = 'left'; + ctx.shadowColor = 'rgba(0, 234, 255, 0.45)'; + ctx.shadowBlur = 4; + ctx.fillText('[', bx + 1, cy); + ctx.textAlign = 'right'; + ctx.fillText(']', bx + bw - 1, cy); + ctx.shadowBlur = 0; + + const ix = bx + bracketW + 4; + const iwInner = Math.max(24, bw - (bracketW + 4) * 2); + const slotW = iwInner / slots; + const iconMax = Math.min(heartsH * 0.78, slotW * 0.72, 34); for (let i = 0; i < slots; i++) { const cx = ix + slotW * (i + 0.5); - const cy = iy + ihSlot * 0.5; const useFull = i < lives; const im = useFull ? fullIm : hitIm; if (im && im.complete && im.naturalWidth > 0 && im.naturalHeight > 0) { - const ih = iconMax; - const iw = (ih * im.naturalWidth) / im.naturalHeight; + const nw = im.naturalWidth; + const nh = im.naturalHeight; + let ih = iconMax; + let iw = (ih * nw) / nh; + const capW = slotW * 0.82; + const capH = heartsH * 0.88; + if (iw > capW) { + iw = capW; + ih = (iw * nh) / nw; + } + if (ih > capH) { + ih = capH; + iw = (ih * nw) / nh; + } + if (iw < 6 && nh > 0) { + iw = 6; + ih = (iw * nh) / nw; + if (ih > capH) { + ih = capH; + iw = (ih * nw) / nh; + } + } try { - ctx.drawImage(im, 0, 0, im.naturalWidth, im.naturalHeight, cx - iw / 2, cy - ih / 2, iw, ih); + ctx.drawImage(im, 0, 0, nw, nh, cx - iw / 2, cy - ih / 2, iw, ih); } catch (e2) { /* ignore */ } } else { ctx.fillStyle = useFull ? '#9ece6a' : '#565f89'; @@ -4285,7 +4318,8 @@ } } ctx.restore(); - return bh; + const usedH = heartsTop + heartsH - by0 + 2; + return Math.min(maxH, Math.max(titleDh, usedH)); } function triggerStackTowerHeartMinusFxPlay() { diff --git a/www/html/Game/public/play.html b/www/html/Game/public/play.html index 2dc260d..0d50624 100644 --- a/www/html/Game/public/play.html +++ b/www/html/Game/public/play.html @@ -2753,7 +2753,7 @@ - +