diff --git a/www/html/Game/public/css/style.css b/www/html/Game/public/css/style.css index 583693f..f2bbc14 100644 --- a/www/html/Game/public/css/style.css +++ b/www/html/Game/public/css/style.css @@ -505,7 +505,13 @@ body.editor-embed-admin .grid-cell-image-gallery-item img { } .canvas-wrap { margin: 1rem 0; } .container { padding-left: 1rem; padding-right: 1rem; } -#editor-canvas { background: #1a1b26; display: block; touch-action: none; } +#editor-canvas { + background: #1a1b26; + display: block; + touch-action: none; + image-rendering: pixelated; + image-rendering: crisp-edges; +} .game-wrap { display: flex; flex-direction: column; height: 100vh; height: 100dvh; min-height: 0; } .game-header { flex-shrink: 0; background: #24283b; padding: 0.5rem 1rem; display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; } .game-header span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } @@ -517,6 +523,8 @@ body.editor-embed-admin .grid-cell-image-gallery-item img { min-height: 0; min-width: 0; touch-action: none; + image-rendering: pixelated; + image-rendering: crisp-edges; } .game-wrap .chat-panel { flex-shrink: 0; } .chat-panel { height: 160px; border-top: 1px solid #414868; display: flex; flex-direction: column; } diff --git a/www/html/Game/public/editor.html b/www/html/Game/public/editor.html index 5ae236d..2cb9968 100644 --- a/www/html/Game/public/editor.html +++ b/www/html/Game/public/editor.html @@ -6,7 +6,7 @@ Editor ฉาก — Game - +
@@ -263,6 +263,24 @@
+
@@ -289,7 +307,7 @@ - +
v —
diff --git a/www/html/Game/public/img/editor-bg-mnpz6rkp/intro.png b/www/html/Game/public/img/editor-bg-mnpz6rkp/intro.png new file mode 100644 index 0000000..0eb54cc Binary files /dev/null and b/www/html/Game/public/img/editor-bg-mnpz6rkp/intro.png differ diff --git a/www/html/Game/public/img/editor-bg-mnpz6rkp/loop.png b/www/html/Game/public/img/editor-bg-mnpz6rkp/loop.png new file mode 100644 index 0000000..fdeb764 Binary files /dev/null and b/www/html/Game/public/img/editor-bg-mnpz6rkp/loop.png differ diff --git a/www/html/Game/public/js/editor.js b/www/html/Game/public/js/editor.js index dc0e399..d8d3eda 100644 --- a/www/html/Game/public/js/editor.js +++ b/www/html/Game/public/js/editor.js @@ -121,6 +121,216 @@ let showMapInGame = true; let backgroundImage = null; let backgroundImageImg = null; + /** พื้นหลังเลื่อนแนวตั้ง — เฉพาะแมป mnpz6rkp (Editor) */ + const EDITOR_SCROLL_BG_MAP_ID = 'mnpz6rkp'; + const EDITOR_SCROLL_BG_DEFAULT_INTRO = BASE + '/img/editor-bg-mnpz6rkp/intro.png'; + const EDITOR_SCROLL_BG_DEFAULT_LOOP = BASE + '/img/editor-bg-mnpz6rkp/loop.png'; + let editorBgScrollConfig = { + enabled: false, + speedPxPerSec: 56, + /** 'up' = ล่าง→บน · 'down' = บน→ล่าง */ + scrollDirection: 'up', + introImage: null, + loopImage: null, + }; + let editorBgScrollIntroImg = null; + let editorBgScrollLoopImg = null; + /** viewport top ใน strip (>=0) — เพิ่ม = เลื่อนขึ้น (ล่าง→บน) */ + let editorBgScrollPx = 0; + let editorBgScrollRaf = null; + let editorBgScrollLastTs = 0; + + function stopEditorBgScrollRaf() { + if (editorBgScrollRaf != null) { + cancelAnimationFrame(editorBgScrollRaf); + editorBgScrollRaf = null; + } + editorBgScrollLastTs = 0; + } + + function isEditorScrollBgMap() { + return mapId === EDITOR_SCROLL_BG_MAP_ID; + } + + function isEditorScrollBgDrawing() { + if (!isEditorScrollBgMap() || !editorBgScrollConfig.enabled) return false; + const a = editorBgScrollIntroImg; + const b = editorBgScrollLoopImg; + return !!(a && a.complete && a.naturalWidth && b && b.complete && b.naturalWidth); + } + + function loadEditorBgScrollImagePair(introSrc, loopSrc, onDone) { + stopEditorBgScrollRaf(); + editorBgScrollIntroImg = null; + editorBgScrollLoopImg = null; + let pending = 2; + const doneOne = () => { + pending--; + if (pending <= 0 && typeof onDone === 'function') onDone(); + }; + const im1 = new Image(); + im1.onload = () => { + editorBgScrollIntroImg = im1; + doneOne(); + }; + im1.onerror = () => { doneOne(); }; + im1.src = introSrc; + const im2 = new Image(); + im2.onload = () => { + editorBgScrollLoopImg = im2; + doneOne(); + }; + im2.onerror = () => { doneOne(); }; + im2.src = loopSrc; + } + + function applyEditorBgScrollFromMap(m) { + if (!isEditorScrollBgMap()) { + editorBgScrollConfig = { enabled: false, speedPxPerSec: 56, scrollDirection: 'up', introImage: null, loopImage: null }; + stopEditorBgScrollRaf(); + editorBgScrollIntroImg = null; + editorBgScrollLoopImg = null; + return; + } + const raw = m && m.editorBgScroll && typeof m.editorBgScroll === 'object' ? m.editorBgScroll : {}; + const sp = Math.max(8, Math.min(400, Math.floor(Number(raw.speedPxPerSec)) || 56)); + const sd = String(raw.scrollDirection || raw.direction || 'up').toLowerCase(); + editorBgScrollConfig = { + enabled: !!raw.enabled, + speedPxPerSec: sp, + scrollDirection: (sd === 'down' || sd === 'top' || sd === 'toptobottom') ? 'down' : 'up', + introImage: typeof raw.introImage === 'string' && raw.introImage.length ? raw.introImage : null, + loopImage: typeof raw.loopImage === 'string' && raw.loopImage.length ? raw.loopImage : null, + }; + const introSrc = editorBgScrollConfig.introImage || EDITOR_SCROLL_BG_DEFAULT_INTRO; + const loopSrc = editorBgScrollConfig.loopImage || EDITOR_SCROLL_BG_DEFAULT_LOOP; + loadEditorBgScrollImagePair(introSrc, loopSrc, () => { + syncEditorBgScrollUiFromConfig(); + syncEditorBgScrollInitialToBottom(); + draw(); + if (editorBgScrollConfig.enabled) startEditorBgScrollRaf(); + }); + } + + function syncEditorBgScrollInitialToBottom() { + const intro = editorBgScrollIntroImg; + if (!intro || !intro.complete || !intro.naturalWidth) return; + const cwR = Math.max(1, Math.round(canvas.width)); + const chR = Math.max(1, Math.round(canvas.height)); + const drawHIntro = Math.round(intro.naturalHeight * (cwR / intro.naturalWidth)); + if (editorBgScrollConfig.scrollDirection === 'down') { + editorBgScrollPx = 0; + } else { + editorBgScrollPx = Math.max(0, drawHIntro - chR); + } + } + + function syncEditorBgScrollUiFromConfig() { + const block = document.getElementById('editor-bg-scroll-block'); + if (block) block.hidden = !isEditorScrollBgMap(); + const en = document.getElementById('editor-bg-scroll-enabled'); + if (en) en.checked = !!editorBgScrollConfig.enabled; + const sp = document.getElementById('editor-bg-scroll-speed'); + if (sp) sp.value = String(editorBgScrollConfig.speedPxPerSec || 56); + const dir = document.getElementById('editor-bg-scroll-direction'); + if (dir) dir.value = editorBgScrollConfig.scrollDirection === 'down' ? 'down' : 'up'; + } + + function readEditorBgScrollConfigForSave() { + if (!isEditorScrollBgMap()) return null; + const en = document.getElementById('editor-bg-scroll-enabled'); + const spEl = document.getElementById('editor-bg-scroll-speed'); + const sp = Math.max(8, Math.min(400, Math.floor(Number(spEl && spEl.value)) || 56)); + const dirEl = document.getElementById('editor-bg-scroll-direction'); + const out = { + enabled: !!(en && en.checked), + speedPxPerSec: sp, + scrollDirection: dirEl && dirEl.value === 'down' ? 'down' : 'up', + }; + if (editorBgScrollConfig.introImage) out.introImage = editorBgScrollConfig.introImage; + if (editorBgScrollConfig.loopImage) out.loopImage = editorBgScrollConfig.loopImage; + return out; + } + + function drawEditorScrollingBackground(cw, ch) { + const intro = editorBgScrollIntroImg; + const loop = editorBgScrollLoopImg; + if (!intro || !intro.complete || !loop || !loop.complete) return; + const cwR = Math.max(1, Math.round(cw)); + const chR = Math.max(1, Math.round(ch)); + const scaleI = cwR / intro.naturalWidth; + const drawHIntro = Math.round(intro.naturalHeight * scaleI); + const scaleL = cwR / loop.naturalWidth; + const drawHLoop = Math.max(1, Math.round(loop.naturalHeight * scaleL)); + const S = editorBgScrollPx; + const vp0 = S; + const vp1 = S + chR; + const yLimit = vp1 + drawHLoop * 2; + const flowDown = editorBgScrollConfig.scrollDirection === 'down'; + + function stripTopToDestY(stripTop, drawH) { + if (!flowDown) return stripTop - S; + return S + chR - (stripTop + drawH); + } + + ctx.save(); + ctx.imageSmoothingEnabled = false; + ctx.beginPath(); + ctx.rect(0, 0, cwR, chR); + ctx.clip(); + + if (vp0 < 0) { + let k = 1; + if (vp0 < -drawHLoop * 4) { + k = Math.max(1, Math.floor(-vp0 / drawHLoop) - 2); + } + for (; k < 50000; k++) { + const y0 = -k * drawHLoop; + if (y0 >= vp1 + drawHLoop) break; + if (y0 + drawHLoop <= vp0) continue; + const dy = Math.round(stripTopToDestY(y0, drawHLoop)); + ctx.drawImage(loop, 0, 0, loop.naturalWidth, loop.naturalHeight, 0, dy, cwR, drawHLoop); + } + } + + if (drawHIntro > vp0 && vp1 > 0) { + const dy = Math.round(stripTopToDestY(0, drawHIntro)); + ctx.drawImage(intro, 0, 0, intro.naturalWidth, intro.naturalHeight, 0, dy, cwR, drawHIntro); + } + + let y = drawHIntro; + if (y < yLimit && vp1 > drawHIntro) { + if (vp0 > drawHIntro) { + const n = Math.floor((vp0 - drawHIntro) / drawHLoop); + y = drawHIntro + Math.max(0, n - 1) * drawHLoop; + } + while (y < yLimit) { + const dy = Math.round(stripTopToDestY(y, drawHLoop)); + ctx.drawImage(loop, 0, 0, loop.naturalWidth, loop.naturalHeight, 0, dy, cwR, drawHLoop); + y += drawHLoop; + } + } + ctx.restore(); + } + + function editorBgScrollTick(ts) { + editorBgScrollRaf = null; + if (!isEditorScrollBgDrawing()) return; + if (!editorBgScrollLastTs) editorBgScrollLastTs = ts; + const dt = Math.min(64, Math.max(0, ts - editorBgScrollLastTs)); + editorBgScrollLastTs = ts; + editorBgScrollPx += (editorBgScrollConfig.speedPxPerSec || 56) * (dt / 1000); + draw(); + editorBgScrollRaf = requestAnimationFrame(editorBgScrollTick); + } + + function startEditorBgScrollRaf() { + if (!isEditorScrollBgDrawing()) return; + stopEditorBgScrollRaf(); + editorBgScrollLastTs = 0; + editorBgScrollRaf = requestAnimationFrame(editorBgScrollTick); + } + /** คลังรูป + สไปรต์ { i, x, y, w, h } — บันทึก gridImageSprites + gridImageCells (สำหรับรุ่นเก่า) */ let gridImageLibrary = []; let gridImageSprites = []; @@ -1212,7 +1422,9 @@ if (gtDraw === 'jump_survive') ensureJumpSurvivePlatformArea(); if (gtDraw === 'space_shooter') ensureShooterSpawnSlots(); if (gtDraw === 'balloon_boss') ensureBalloonBossPlayerSlots(); - if (backgroundImageImg && backgroundImageImg.complete && backgroundImageImg.naturalWidth) { + if (isEditorScrollBgDrawing()) { + drawEditorScrollingBackground(canvas.width, canvas.height); + } else if (backgroundImageImg && backgroundImageImg.complete && backgroundImageImg.naturalWidth) { ctx.drawImage(backgroundImageImg, 0, 0, canvas.width, canvas.height); } else { ctx.fillStyle = '#1a1b26'; @@ -2057,6 +2269,85 @@ draw(); }); + (function wireEditorBgScrollUi() { + if (!isEditorScrollBgMap()) return; + syncEditorBgScrollUiFromConfig(); + const en = document.getElementById('editor-bg-scroll-enabled'); + const sp = document.getElementById('editor-bg-scroll-speed'); + if (en) { + en.addEventListener('change', () => { + editorBgScrollConfig.enabled = !!en.checked; + if (editorBgScrollConfig.enabled) startEditorBgScrollRaf(); + else stopEditorBgScrollRaf(); + draw(); + }); + } + if (sp) { + sp.addEventListener('change', () => { + editorBgScrollConfig.speedPxPerSec = Math.max(8, Math.min(400, Math.floor(Number(sp.value)) || 56)); + }); + } + const dirSel = document.getElementById('editor-bg-scroll-direction'); + if (dirSel) { + dirSel.addEventListener('change', () => { + editorBgScrollConfig.scrollDirection = dirSel.value === 'down' ? 'down' : 'up'; + syncEditorBgScrollInitialToBottom(); + draw(); + if (editorBgScrollConfig.enabled) startEditorBgScrollRaf(); + }); + } + const introF = document.getElementById('editor-bg-scroll-intro-file'); + if (introF) { + introF.addEventListener('change', (e) => { + const file = e.target.files && e.target.files[0]; + if (!file) return; + const r = new FileReader(); + r.onload = () => { + editorBgScrollConfig.introImage = r.result; + loadEditorBgScrollImagePair(editorBgScrollConfig.introImage, editorBgScrollConfig.loopImage || EDITOR_SCROLL_BG_DEFAULT_LOOP, () => { + syncEditorBgScrollInitialToBottom(); + draw(); + if (editorBgScrollConfig.enabled) startEditorBgScrollRaf(); + }); + }; + r.readAsDataURL(file); + e.target.value = ''; + }); + } + const loopF = document.getElementById('editor-bg-scroll-loop-file'); + if (loopF) { + loopF.addEventListener('change', (e) => { + const file = e.target.files && e.target.files[0]; + if (!file) return; + const r = new FileReader(); + r.onload = () => { + editorBgScrollConfig.loopImage = r.result; + loadEditorBgScrollImagePair(editorBgScrollConfig.introImage || EDITOR_SCROLL_BG_DEFAULT_INTRO, editorBgScrollConfig.loopImage, () => { + syncEditorBgScrollInitialToBottom(); + draw(); + if (editorBgScrollConfig.enabled) startEditorBgScrollRaf(); + }); + }; + r.readAsDataURL(file); + e.target.value = ''; + }); + } + const btnR = document.getElementById('editor-bg-scroll-reset-assets'); + if (btnR) { + btnR.addEventListener('click', () => { + editorBgScrollConfig.introImage = null; + editorBgScrollConfig.loopImage = null; + loadEditorBgScrollImagePair(EDITOR_SCROLL_BG_DEFAULT_INTRO, EDITOR_SCROLL_BG_DEFAULT_LOOP, () => { + syncEditorBgScrollUiFromConfig(); + syncEditorBgScrollInitialToBottom(); + draw(); + if (editorBgScrollConfig.enabled) startEditorBgScrollRaf(); + }); + }); + } + })(); + if (isEditorScrollBgMap()) applyEditorBgScrollFromMap({}); + document.getElementById('btn-new').addEventListener('click', initGrid); document.getElementById('btn-spawn').addEventListener('click', () => { isSpawnMode = true; @@ -2275,6 +2566,8 @@ gridImageCells: deriveGridImageCellsFromSprites(), }; if (backgroundImage) body.backgroundImage = backgroundImage; + const ebsSave = readEditorBgScrollConfigForSave(); + if (ebsSave) body.editorBgScroll = ebsSave; body.lanes = gameType === 'frogger' ? lanes : []; sanitizeGauntletPlayerSpawns(); body.gauntletPlayerSpawns = gameType === 'gauntlet' @@ -2556,6 +2849,7 @@ backgroundImageImg.onload = () => { draw(); }; backgroundImageImg.src = backgroundImage; } else backgroundImageImg = null; + applyEditorBgScrollFromMap(m); spawn = m.spawn || { x: 1, y: 1 }; gauntletPlayerSpawns = Array.isArray(m.gauntletPlayerSpawns) ? m.gauntletPlayerSpawns.map((s) => ({ x: Math.floor(Number(s.x)), y: Math.floor(Number(s.y)) })) diff --git a/www/html/Game/public/js/play.js b/www/html/Game/public/js/play.js index 595fb09..bca9117 100644 --- a/www/html/Game/public/js/play.js +++ b/www/html/Game/public/js/play.js @@ -156,6 +156,141 @@ let stackFall = null; let lastStackTickMs = performance.now(); let mapBackgroundImg = null; + /** space_shooter + แมป mnpz6rkp — พื้นหลังเลื่อนแนวตั้ง (ข้อมูลจาก editorBgScroll ในแมป) */ + const PLAY_SCROLL_BG_MAP_ID = 'mnpz6rkp'; + const PLAY_SCROLL_BG_DEFAULT_INTRO = BASE + '/img/editor-bg-mnpz6rkp/intro.png'; + const PLAY_SCROLL_BG_DEFAULT_LOOP = BASE + '/img/editor-bg-mnpz6rkp/loop.png'; + let playScrollBgIntroImg = null; + let playScrollBgLoopImg = null; + /** ขอบบนของ viewport ใน strip — เพิ่มค่า = เลื่อนลง strip (เข้า loop ใต้ intro) */ + let playScrollBgPx = 0; + let playScrollBgSpeedPxPerSec = 56; + let playScrollBgOff = false; + /** true = บน→ล่าง: พลิกมุมมองใน viewport (ยังเลื่อน S+ เข้า loop ใต้ intro) */ + let playScrollBgFlowDown = false; + + function playScrollBgMapEligible() { + return !!(mapData && mapData.gameType === 'space_shooter' && (currentPlayMapId() || '').trim() === PLAY_SCROLL_BG_MAP_ID); + } + + function playScrollBgDrawActive() { + if (!playScrollBgMapEligible() || playScrollBgOff) return false; + const a = playScrollBgIntroImg; + const b = playScrollBgLoopImg; + return !!(a && a.complete && a.naturalWidth && b && b.complete && b.naturalWidth); + } + + function reloadPlayScrollBgFromMap() { + playScrollBgIntroImg = null; + playScrollBgLoopImg = null; + playScrollBgPx = 0; + if (!playScrollBgMapEligible()) return; + const raw = mapData.editorBgScroll && typeof mapData.editorBgScroll === 'object' ? mapData.editorBgScroll : {}; + playScrollBgOff = raw.enabled === false; + playScrollBgSpeedPxPerSec = Math.max(8, Math.min(400, Math.floor(Number(raw.speedPxPerSec)) || 56)); + const dirRaw = String(raw.scrollDirection || raw.direction || 'up').toLowerCase(); + playScrollBgFlowDown = (dirRaw === 'down' || dirRaw === 'top' || dirRaw === 'toptobottom'); + if (playScrollBgOff) return; + const introSrc = (typeof raw.introImage === 'string' && raw.introImage.length) ? raw.introImage : PLAY_SCROLL_BG_DEFAULT_INTRO; + const loopSrc = (typeof raw.loopImage === 'string' && raw.loopImage.length) ? raw.loopImage : PLAY_SCROLL_BG_DEFAULT_LOOP; + let pending = 2; + const bump = () => { + pending--; + if (pending <= 0) { + playScrollBgSyncInitialScrollToBottom(); + try { draw(); } catch (e) { /* ignore */ } + } + }; + const im1 = new Image(); + im1.onload = () => { playScrollBgIntroImg = im1; bump(); }; + im1.onerror = () => { bump(); }; + im1.src = introSrc; + const im2 = new Image(); + im2.onload = () => { playScrollBgLoopImg = im2; bump(); }; + im2.onerror = () => { bump(); }; + im2.src = loopSrc; + } + + function playScrollBgSyncInitialScrollToBottom() { + const intro = playScrollBgIntroImg; + if (!canvas || !intro || !intro.complete || !intro.naturalWidth) return; + const cwR = Math.max(1, Math.round(canvas.width)); + const chR = Math.max(1, Math.round(canvas.height)); + const drawHIntro = Math.round(intro.naturalHeight * (cwR / intro.naturalWidth)); + /* ล่าง→บน: S = HI−ch ให้ขอบล่าง intro ชิดล่างจอ · บน→ล่าง (พลิก dest): S=0 ถึงได้ขอบล่าง intro ชิดล่างจอ */ + if (playScrollBgFlowDown) { + playScrollBgPx = 0; + } else { + playScrollBgPx = Math.max(0, drawHIntro - chR); + } + } + + function drawPlayScrollBgFullCanvas(cw, ch) { + const intro = playScrollBgIntroImg; + const loop = playScrollBgLoopImg; + if (!intro || !intro.complete || !loop || !loop.complete) return; + const cwR = Math.max(1, Math.round(cw)); + const chR = Math.max(1, Math.round(ch)); + const scaleI = cwR / intro.naturalWidth; + const drawHIntro = Math.round(intro.naturalHeight * scaleI); + const scaleL = cwR / loop.naturalWidth; + const drawHLoop = Math.max(1, Math.round(loop.naturalHeight * scaleL)); + const S = playScrollBgPx; + const vp0 = S; + const vp1 = S + chR; + const yLimit = vp1 + drawHLoop * 2; + const flowDown = playScrollBgFlowDown; + + function stripTopToDestY(stripTop, drawH) { + if (!flowDown) return stripTop - S; + return S + chR - (stripTop + drawH); + } + + ctx.save(); + ctx.imageSmoothingEnabled = false; + ctx.beginPath(); + ctx.rect(0, 0, cwR, chR); + ctx.clip(); + + if (vp0 < 0) { + let k = 1; + if (vp0 < -drawHLoop * 4) { + k = Math.max(1, Math.floor(-vp0 / drawHLoop) - 2); + } + for (; k < 50000; k++) { + const y0 = -k * drawHLoop; + if (y0 >= vp1 + drawHLoop) break; + if (y0 + drawHLoop <= vp0) continue; + const dy = Math.round(stripTopToDestY(y0, drawHLoop)); + ctx.drawImage(loop, 0, 0, loop.naturalWidth, loop.naturalHeight, 0, dy, cwR, drawHLoop); + } + } + + if (drawHIntro > vp0 && vp1 > 0) { + const dy = Math.round(stripTopToDestY(0, drawHIntro)); + ctx.drawImage(intro, 0, 0, intro.naturalWidth, intro.naturalHeight, 0, dy, cwR, drawHIntro); + } + + let y = drawHIntro; + if (y < yLimit && vp1 > drawHIntro) { + if (vp0 > drawHIntro) { + const n = Math.floor((vp0 - drawHIntro) / drawHLoop); + y = drawHIntro + Math.max(0, n - 1) * drawHLoop; + } + while (y < yLimit) { + const dy = Math.round(stripTopToDestY(y, drawHLoop)); + ctx.drawImage(loop, 0, 0, loop.naturalWidth, loop.naturalHeight, 0, dy, cwR, drawHLoop); + y += drawHLoop; + } + } + ctx.restore(); + } + + function tickPlayScrollBg(dtSec) { + if (!playScrollBgDrawActive()) return; + playScrollBgPx += (playScrollBgSpeedPxPerSec || 56) * dtSec; + } + /** โหลดจาก mapData.gridImageLibrary — ดัชนีตรงกับ gridImageCells[y][x] */ let mapGridImageImgs = []; let mapGridImageHeldImgs = []; @@ -7217,6 +7352,7 @@ const now = performance.now(); const dt = Math.min(0.055, spaceShooterLastTickMs ? (now - spaceShooterLastTickMs) / 1000 : 0.016); spaceShooterLastTickMs = now; + tickPlayScrollBg(dt); spaceShooterTickAsteroidExplosions(dt); if (spaceShooterGameEnded) { @@ -7308,20 +7444,20 @@ const a = spaceShooterAsteroids[hit]; a.hp = (a.hp || 1) - 1; spaceShooterBullets.splice(bi, 1); + const add = 5; + if (b.ownerId === myId) { + me.spaceShooterScore = Math.max(0, (me.spaceShooterScore || 0) + add); + spaceShooterPopups.push({ x: me.spaceShooterCx, y: me.spaceShooterCy - 30, text: '+5', until: Date.now() + 700 }); + } else { + const o = others.get(b.ownerId); + if (o) { + o.spaceShooterScore = Math.max(0, (o.spaceShooterScore || 0) + add); + spaceShooterPopups.push({ x: o.spaceShooterCx, y: o.spaceShooterCy - 30, text: '+5', until: Date.now() + 700 }); + } + } if (a.hp <= 0) { spaceShooterSpawnAsteroidExplosion(a.x, a.y, a.r); spaceShooterAsteroids.splice(hit, 1); - const add = 5; - if (b.ownerId === myId) { - me.spaceShooterScore = Math.max(0, (me.spaceShooterScore || 0) + add); - spaceShooterPopups.push({ x: me.spaceShooterCx, y: me.spaceShooterCy - 30, text: '+5', until: Date.now() + 700 }); - } else { - const o = others.get(b.ownerId); - if (o) { - o.spaceShooterScore = Math.max(0, (o.spaceShooterScore || 0) + add); - spaceShooterPopups.push({ x: o.spaceShooterCx, y: o.spaceShooterCy - 30, text: '+5', until: Date.now() + 700 }); - } - } } } } @@ -8963,6 +9099,7 @@ mapBackgroundImg = new Image(); mapBackgroundImg.src = mapData.backgroundImage; } + reloadPlayScrollBgFromMap(); normalizeGridImageCellsOnMap(mapData); loadMapGridImages(); var modeLabel = isFrogger() ? ' | โหมดกบข้ามถนน' @@ -9628,6 +9765,7 @@ mapBackgroundImg = new Image(); mapBackgroundImg.src = mapData.backgroundImage; } + reloadPlayScrollBgFromMap(); normalizeGridImageCellsOnMap(mapData); loadMapGridImages(); if (mapData.gameType === 'gauntlet' && ev.peersSnap && Array.isArray(ev.peersSnap)) { @@ -11160,7 +11298,11 @@ const showGrid = mapData.showMapInGame !== false && mapData.showMapInGame !== 'false'; const timeMs = Date.now(); - if (mapBackgroundImg && mapBackgroundImg.complete && mapBackgroundImg.naturalWidth) { + if (playScrollBgDrawActive()) { + ctx.fillStyle = '#0a0e22'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + drawPlayScrollBgFullCanvas(canvas.width, canvas.height); + } else if (mapBackgroundImg && mapBackgroundImg.complete && mapBackgroundImg.naturalWidth) { /* กล้องเห็นพื้นที่นอก [0,map] ได้ — ห้ามสุ่มต้นทาง drawImage เกินขอบภาพ (จะเกิดซ้ำ/เส้นตัด/ซ้อนกันที่ขอบจอ) */ ctx.fillStyle = (isSpaceShooter() || isBalloonBoss()) ? '#0a0e22' : '#1a1b26'; ctx.fillRect(0, 0, canvas.width, canvas.height); diff --git a/www/html/Game/public/play.html b/www/html/Game/public/play.html index 081cc8f..7a56588 100644 --- a/www/html/Game/public/play.html +++ b/www/html/Game/public/play.html @@ -8,7 +8,7 @@ - +