minigame 6 rocker 1.7

This commit is contained in:
2026-05-01 15:31:35 +00:00
parent e548b796fc
commit b85cc3a4e5
7 changed files with 482 additions and 18 deletions
+9 -1
View File
@@ -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; }
+20 -2
View File
@@ -6,7 +6,7 @@
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<title>Editor ฉาก — Game</title>
<link rel="stylesheet" href="css/style.css?v=20260430-footprint-groups">
<link rel="stylesheet" href="css/style.css?v=20260427-canvas-pixel">
</head>
<body>
<div class="container editor-page">
@@ -263,6 +263,24 @@
<label>รูปแผนที่ <input type="file" id="bg-image" accept="image/*"></label>
<button type="button" id="btn-clear-bg">ลบรูปพื้นหลัง</button>
</div>
<div id="editor-bg-scroll-block" class="editor-bg-scroll-block" hidden>
<p class="legend" style="margin:0.5rem 0 0.35rem;line-height:1.45"><strong>เลื่อนพื้นหลังแนวตั้ง</strong> (เฉพาะฉากรหัส <code>mnpz6rkp</code>) — รูป intro ต่อด้วย loop · บันทึกฉากแล้วหน้าเล่นใช้ค่าเดียวกัน · <em>English:</em> Intro + looping strip; saved in map JSON.</p>
<div class="editor-card__row editor-card__row--wrap" style="align-items:center;gap:0.75rem 1rem">
<label class="editor-bg-scroll-check"><input type="checkbox" id="editor-bg-scroll-enabled"> เปิดเลื่อน / Enable scroll</label>
<label>ความเร็ว px/s <input type="number" id="editor-bg-scroll-speed" min="8" max="400" step="4" value="56" style="width:5rem" title="ความเร็วการเลื่อนแนวตั้ง"></label>
<label>ทิศทาง
<select id="editor-bg-scroll-direction" title="เริ่มชิดล่าง intro แล้วเลื่อนเข้า loop ใต้ intro — ล่าง→บน = พิกเซลไหลขึ้น · บน→ล่าง = พลิกมุมมองในกรอบจอให้ไหลลง (strip เดิม)">
<option value="up" selected>ล่าง → บน (ไหลขึ้น)</option>
<option value="down">บน → ล่าง (ไหลลง)</option>
</select>
</label>
</div>
<div class="editor-card__row editor-card__row--wrap" style="margin-top:0.35rem">
<label>รูปต้นทาง (intro) <input type="file" id="editor-bg-scroll-intro-file" accept="image/*"></label>
<label>รูปลูป (loop) <input type="file" id="editor-bg-scroll-loop-file" accept="image/*"></label>
<button type="button" id="editor-bg-scroll-reset-assets">คืนรูปเริ่มจากเซิร์ฟ / Reset to bundled</button>
</div>
</div>
</section>
<section class="editor-card editor-card--help" aria-labelledby="editor-card-help-label">
@@ -289,7 +307,7 @@
</div>
</div>
<script src="js/version.js?v=0.0169"></script>
<script src="js/editor.js?v=20260427-violent-crime-howto"></script>
<script src="js/editor.js?v=20260427-bg-down-start"></script>
<div class="version-tag">v —</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

+295 -1
View File
@@ -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)) }))
+154 -12
View File
@@ -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);
+4 -2
View File
@@ -8,7 +8,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700;800&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/style.css?v=20260427-canvas-pixel">
<style>
html, body { height: 100%; margin: 0; }
body { min-height: 100dvh; }
@@ -37,6 +37,8 @@
width: 100%;
display: block;
touch-action: none;
image-rendering: pixelated;
image-rendering: crisp-edges;
}
/* หลังกดรับหลักฐานในหน้าสรุป — BG ดำ alpha ทั้งจอ + timeup ติดโซนโต๊ะเหมือน #quiz-map-question-panel */
#quiz-carry-timeup-desk-layer {
@@ -1897,7 +1899,7 @@
</div>
<script src="/Game/socket.io/socket.io.js"></script>
<script src="js/version.js?v=0.0184"></script>
<script src="js/play.js?v=0.224"></script>
<script src="js/play.js?v=0.231"></script>
<div class="version-tag">v —</div>
</body>
</html>