From 91669bb65b48541476696ed2bda42b25774c86de Mon Sep 17 00:00:00 2001 From: giteaadmin Date: Tue, 5 May 2026 07:17:20 +0000 Subject: [PATCH] minigame fixed zoom --- www/html/Game/public/js/play.js | 140 ++++++++++++++++++++++------- www/html/Game/public/js/version.js | 2 +- www/html/Game/public/play.html | 122 ++++++++++++++++++++----- 3 files changed, 209 insertions(+), 55 deletions(-) diff --git a/www/html/Game/public/js/play.js b/www/html/Game/public/js/play.js index 7227cea..d885237 100644 --- a/www/html/Game/public/js/play.js +++ b/www/html/Game/public/js/play.js @@ -163,6 +163,17 @@ } /** ทดสอบจากเอดิเตอร์: เติมบอทให้ครบจำนวน (รวมผู้เล่นจริง) — ?fillTotal=6 (ค่าเริ่ม 6) */ const previewFillBots = previewMode && editorEmbedReturn; + /** พรีวิวใน iframe เอดิเตอร์: ซูมมุมมองได้ (เดิมไม่มี — zDraw จาก zoom คงที่) */ + const PLAY_EMBED_USER_ZOOM_MIN = 0.5; + const PLAY_EMBED_USER_ZOOM_MAX = 3; + const PLAY_EMBED_ZOOM_STEP_KEY = 1.12; + let playEmbedUserZoomMul = 1; + let lastPlayZDrawForInput = 1.4; + /** คูณ world→พิกเซลบนแคนวาสให้ตรงกับ zDraw ใน draw() (รวม preview+embed zoom) — ใช้ซิงก์ DOM ทับฉาก */ + function playDomSyncZoom() { + const z = lastPlayZDrawForInput; + return Number.isFinite(z) && z > 0 ? z : zoom; + } const previewTargetHeadcount = Math.min(24, Math.max(1, parseInt(params.get('fillTotal'), 10) || 6)); const PREVIEW_BOT_PREFIX = '__pv_bot_'; let previewBotSeq = 0; @@ -2756,10 +2767,11 @@ } const camX = me.x * tileSize; const camY = me.y * tileSize; - const left = (bounds.minX * tileSize - camX) * zoom + canvas.width / 2; - const top = (bounds.minY * tileSize - camY) * zoom + canvas.height / 2; - const wPx = (bounds.maxX - bounds.minX + 1) * tileSize * zoom; - const hPx = (bounds.maxY - bounds.minY + 1) * tileSize * zoom; + const zDom = playDomSyncZoom(); + const left = (bounds.minX * tileSize - camX) * zDom + canvas.width / 2; + const top = (bounds.minY * tileSize - camY) * zDom + canvas.height / 2; + const wPx = (bounds.maxX - bounds.minX + 1) * tileSize * zDom; + const hPx = (bounds.maxY - bounds.minY + 1) * tileSize * zDom; textEl.textContent = text; panel.style.left = Math.round(left) + 'px'; panel.style.top = Math.round(top) + 'px'; @@ -2782,7 +2794,7 @@ const thFont = isQuizCarry() ? getEffectiveCarryMapPanelThemeForApply() : getEffectiveQuizMapPanelThemeForApply(); - const startFontPx = quizMapQuestionFontPxLikeCarryPlaques(thFont, zoom); + const startFontPx = quizMapQuestionFontPxLikeCarryPlaques(thFont, zDom); const mnFont = Math.max(8, Math.round(Number(thFont.questionFontMinPx) || 10)); /* ไม่โหลด hud-question-plaque.png อัตโนมัติ — บนเซิร์ฟมักไม่มีไฟล์ → 404 ใน Network; ใช้คลาส + CSS แทน (อัปโหลด PNG แล้วค่อยผูกทีหลังได้) */ if (isQuiz() && isQuizQuestionMissionUiMapPlay() && quizQuestionMissionPhase === 'live') { @@ -2884,10 +2896,11 @@ } const camX = me.x * tileSize; const camY = me.y * tileSize; - const left = (bounds.minX * tileSize - camX) * zoom + canvas.width / 2; - const top = (bounds.minY * tileSize - camY) * zoom + canvas.height / 2; - const wPx = (bounds.maxX - bounds.minX + 1) * tileSize * zoom; - const hPx = (bounds.maxY - bounds.minY + 1) * tileSize * zoom; + const zDom = playDomSyncZoom(); + const left = (bounds.minX * tileSize - camX) * zDom + canvas.width / 2; + const top = (bounds.minY * tileSize - camY) * zDom + canvas.height / 2; + const wPx = (bounds.maxX - bounds.minX + 1) * tileSize * zDom; + const hPx = (bounds.maxY - bounds.minY + 1) * tileSize * zDom; anchor.style.left = Math.round(left) + 'px'; anchor.style.top = Math.round(top) + 'px'; anchor.style.width = Math.round(Math.max(48, wPx)) + 'px'; @@ -2955,10 +2968,11 @@ } const camX = me.x * tileSize; const camY = me.y * tileSize; - const l = (bounds.minX * tileSize - camX) * zoom + canvasEl.width / 2; - const t = (bounds.minY * tileSize - camY) * zoom + canvasEl.height / 2; - const wPx = (bounds.maxX - bounds.minX + 1) * tileSize * zoom; - const hPx = (bounds.maxY - bounds.minY + 1) * tileSize * zoom; + const zDom = playDomSyncZoom(); + const l = (bounds.minX * tileSize - camX) * zDom + canvasEl.width / 2; + const t = (bounds.minY * tileSize - camY) * zDom + canvasEl.height / 2; + const wPx = (bounds.maxX - bounds.minX + 1) * tileSize * zDom; + const hPx = (bounds.maxY - bounds.minY + 1) * tileSize * zDom; const r = canvasEl.getBoundingClientRect(); const sl = r.left + l; const st = r.top + t; @@ -12302,6 +12316,7 @@ const prevWasSpaceShooter = mapData && mapData.gameType === 'space_shooter'; const prevWasBalloonBoss = mapData && mapData.gameType === 'balloon_boss'; mapData = gameMapData; + playEmbedUserZoomMul = 1; if (mapData.gameType !== 'quiz_carry') { quizCarryWalkSpeedMultActive = QUIZ_CARRY_WALK_SPEED_MULT; } @@ -13154,6 +13169,7 @@ if (!ev || !ev.mapId) return; const applySnap = (md) => { mapData = md; + playEmbedUserZoomMul = 1; if (ev.mapId != null && String(ev.mapId).trim() !== '') { playSessionMapId = String(ev.mapId).trim(); } @@ -13521,11 +13537,19 @@ const vh = window.innerHeight || document.documentElement.clientHeight || 600; const header = document.querySelector('.game-header'); const headerH = header && header.offsetHeight ? header.offsetHeight : 48; - const parent = canvas && canvas.parentElement; + const stage = document.getElementById('play-canvas-stage'); + const parent = stage || (canvas && canvas.parentElement); let cw = canvas.clientWidth || 0; let ch = canvas.clientHeight || 0; if (parent && parent.clientWidth > 80) cw = Math.max(cw, parent.clientWidth); if (parent && parent.clientHeight > 80) ch = Math.max(ch, parent.clientHeight); + /* จอใหญ่ / iframe embed: client บางทียัง 0 ช้ากว่า layout — ใช้ offset เป็นทางเลือก */ + if (parent) { + const ow = parent.offsetWidth || 0; + const oh = parent.offsetHeight || 0; + if (ow > 80) cw = Math.max(cw, ow); + if (oh > 80) ch = Math.max(ch, oh); + } if (ch < 120) ch = Math.max(240, vh - headerH - 6); if (cw < 120) cw = Math.max(320, vw); canvas.width = Math.max(320, cw); @@ -13537,6 +13561,29 @@ if (!mapData || !isStack() || isChatFocused()) return; tryHumanStackDrop(); }); + (function setupPlayEditorEmbedViewZoom() { + const stack = document.getElementById('play-canvas-stack'); + if (!stack) return; + const wheelZoomHandler = (e) => { + if (!previewMode || !editorEmbedReturn || !mapData) return; + const t = e.target; + if (t && typeof t.closest === 'function') { + if (t.closest('button, input, textarea, select, a[href]')) return; + } + /* ล้อธรรม / trackpad pinch มักไม่มี ctrl — ให้ซูมได้ใน embed; Ctrl/⌘+ล้อยังใช้ได้ */ + e.preventDefault(); + e.stopPropagation(); + const raw = e.deltaMode === 1 ? e.deltaY * 14 : e.deltaMode === 2 ? e.deltaY * 320 : e.deltaY; + const k = Math.exp(-raw * 0.002); + playEmbedUserZoomMul = Math.max( + PLAY_EMBED_USER_ZOOM_MIN, + Math.min(PLAY_EMBED_USER_ZOOM_MAX, playEmbedUserZoomMul * k), + ); + draw(); + }; + stack.addEventListener('wheel', wheelZoomHandler, { passive: false, capture: true }); + })(); + canvas.addEventListener('dblclick', (e) => { if (!mapData || isFrogger() || isGauntlet() || isStack() || isJumpSurvive() || isSpaceShooter() || isBalloonBoss() || isChatFocused() || isQuizCarryEmbedCountdownBlockingMovement()) return; const r = canvas.getBoundingClientRect(); @@ -13544,8 +13591,9 @@ const sy = e.clientY - r.top; const camX = me.x * tileSize; const camY = me.y * tileSize; - const gx = (sx - canvas.width / 2) / zoom + camX; - const gy = (sy - canvas.height / 2) / zoom + camY; + const zHit = lastPlayZDrawForInput > 0 ? lastPlayZDrawForInput : zoom; + const gx = (sx - canvas.width / 2) / zHit + camX; + const gy = (sy - canvas.height / 2) / zHit + camY; const tx = Math.floor(gx / tileSize); const ty = Math.floor(gy / tileSize); const mw = mapData.width || 20, mh = mapData.height || 15; @@ -14818,20 +14866,6 @@ statusEl.textContent = 'SPECTATOR · BALLOONS GONE'; } else if (isGauntletCrownHeistMapPlay()) { statusEl.textContent = ''; - const tag = document.createElement('div'); - tag.className = 'play-cyber-self-tagline'; - tag.textContent = 'QUIZ LINK ·'; - const sub = document.createElement('div'); - sub.className = 'play-cyber-self-subtag'; - sub.textContent = 'ประลองความรู้'; - const nm = document.createElement('div'); - nm.className = 'play-cyber-self-nick-line'; - const raw = String(me.nickname || nick || '').trim(); - nm.textContent = raw || 'LINK'; - nm.title = raw || ''; - statusEl.appendChild(tag); - statusEl.appendChild(sub); - statusEl.appendChild(nm); } else { statusEl.textContent = 'OPERATOR ONLINE'; } @@ -15119,11 +15153,15 @@ camY = mhPx * 0.5; zDraw = Math.min(canvas.width / mwPx, canvas.height / mhPx) * 0.96; } + if (previewMode && editorEmbedReturn && mapData) { + zDraw *= playEmbedUserZoomMul; + } const gauntletGroupCam = getGauntletCrownHeistGroupCameraCenterPxPlay(tileSize, canvas.width, canvas.height, zDraw); if (gauntletGroupCam) { camX = gauntletGroupCam.px; camY = gauntletGroupCam.py; } + lastPlayZDrawForInput = zDraw; const halfW = canvas.width / (2 * zDraw); const halfH = canvas.height / (2 * zDraw); @@ -15885,6 +15923,24 @@ document.addEventListener('keydown', (e) => { if (isMovementKey(e.code) && isChatFocused()) return; + if (previewMode && editorEmbedReturn && mapData && !isChatFocused()) { + if (e.code === 'BracketLeft' || e.code === 'Minus' || e.code === 'NumpadSubtract') { + e.preventDefault(); + playEmbedUserZoomMul = Math.max(PLAY_EMBED_USER_ZOOM_MIN, playEmbedUserZoomMul / PLAY_EMBED_ZOOM_STEP_KEY); + draw(); + return; + } + if ( + e.code === 'BracketRight' + || e.code === 'NumpadAdd' + || (e.code === 'Equal' && e.shiftKey) + ) { + e.preventDefault(); + playEmbedUserZoomMul = Math.min(PLAY_EMBED_USER_ZOOM_MAX, playEmbedUserZoomMul * PLAY_EMBED_ZOOM_STEP_KEY); + draw(); + return; + } + } if (isSpaceShooter() && mapData && !isChatFocused() && isSpaceShooterMissionPregameBlockingPlay()) { if (['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(e.code)) { e.preventDefault(); @@ -16097,7 +16153,8 @@ others.forEach((o) => { if (o) clampPlayEntityFootprintToMap(o, mapData); }); - const zGa = zoom; + let zGa = zoom; + if (previewMode && editorEmbedReturn && mapData) zGa *= playEmbedUserZoomMul; const gCam = getGauntletCrownHeistGroupCameraCenterPxPlay(tileSize, canvas.width, canvas.height, zGa); if (gCam) maybeEliminateGauntletPreviewBotsTailOffCameraPlay(gCam.px, gCam.py, zGa); draw(); @@ -16416,4 +16473,25 @@ resizeCanvas(); draw(); }); + /** แคนวาส flex ขยายทีหลัง (จอใหญ่ / editor iframe) — ResizeObserver จับได้ดีกว่า resize อย่างเดียว */ + (function setupPlayCanvasStackResizeObserver() { + const stack = document.getElementById('play-canvas-stack'); + if (!stack || typeof ResizeObserver === 'undefined') return; + let roRaf = null; + const run = () => { + roRaf = null; + if (!mapData || !canvas) return; + resizeCanvas(); + draw(); + }; + const ro = new ResizeObserver(() => { + if (roRaf) cancelAnimationFrame(roRaf); + roRaf = requestAnimationFrame(run); + }); + ro.observe(stack); + const wrap = stack.closest('.game-wrap'); + if (wrap) ro.observe(wrap); + const stageEl = document.getElementById('play-canvas-stage'); + if (stageEl) ro.observe(stageEl); + })(); })(); diff --git a/www/html/Game/public/js/version.js b/www/html/Game/public/js/version.js index 35c9bb1..866681f 100644 --- a/www/html/Game/public/js/version.js +++ b/www/html/Game/public/js/version.js @@ -1,6 +1,6 @@ // ทุกครั้งที่มีการเพิ่มหรือเปลี่ยน ให้เพิ่ม v +0.0001 // หลังแก้ค่าแล้วต้อง copy ไป path ที่ Nginx ชี้ (หรือรัน copy-frogger-files-only.sh) ถึงจะเห็นบนเว็บ -window.APP_VERSION = '0.0260'; +window.APP_VERSION = '0.0286'; document.addEventListener('DOMContentLoaded', function () { var t = document.querySelector('.version-tag'); if (t) t.textContent = 'v ' + window.APP_VERSION; diff --git a/www/html/Game/public/play.html b/www/html/Game/public/play.html index f83677d..987bef7 100644 --- a/www/html/Game/public/play.html +++ b/www/html/Game/public/play.html @@ -10,8 +10,36 @@