Melon Sandbox: Финал
by HyperRider36569 lines22.1 KB
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Melon Sandbox: Финал</title>
<style>
body { margin: 0; background: #0a0a12; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; font-family: Arial; overflow: hidden; touch-action: none; }
canvas { background: #1a1a2e; border: 2px solid #4CAF50; border-radius: 8px; touch-action: none; }
#hud { font-size: 13px; margin-bottom: 4px; color: #4CAF50; font-weight: bold; text-align: center; }
#toolbar { position: absolute; bottom: 15px; left: 0; width: 100%; display: flex; justify-content: center; gap: 8px; pointer-events: none; flex-wrap: wrap; padding: 0 5px; box-sizing: border-box; }
.tool { width: 50px; height: 50px; border-radius: 10px; background: rgba(76,175,80,0.25); color: #fff; font-size: 20px; border: 2px solid #4CAF50; display: flex; align-items: center; justify-content: center; user-select: none; pointer-events: auto; touch-action: none; }
.tool.active { background: rgba(76,175,80,0.6); box-shadow: 0 0 10px #4CAF50; }
#clearBtn { background: rgba(255,0,102,0.25); border-color: #ff0066; }
</style>
</head>
<body>
<div id="hud">🍉 Объектов: 0 | 💥 Взрывов: 0 | 🩸 Ранений: 0</div>
<canvas id="game" width="420" height="600"></canvas>
<div id="toolbar">
<div class="tool active" data-tool="melon">🍉</div>
<div class="tool" data-tool="box">📦</div>
<div class="tool" data-tool="spike">🔺</div>
<div class="tool" data-tool="gun">🔫</div>
<div class="tool" data-tool="sword">⚔️</div>
<div class="tool" data-tool="bomb">💣</div>
<div class="tool" id="clearBtn">🗑</div>
</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const hud = document.getElementById('hud');
let objects = [];
let bullets = [];
let explosions = [];
const groundY = 560;
let selectedTool = 'melon';
let dragged = null;
let dragOffsetX = 0, dragOffsetY = 0;
let mouseX = 0, mouseY = 0;
let explosionCount = 0;
let woundCount = 0;
// --- МУЗЫКА ---
let audioCtx = null, musicOn = true, musicNodes = [], beatTimer = null, melodyTimer = null;
let soundOn = true;
function initAudio() {
if (audioCtx) return;
try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); startMusic(); } catch(e) {}
}
function startMusic() {
if (!audioCtx || !musicOn) return;
stopMusic();
try {
const bass = audioCtx.createOscillator();
const bg = audioCtx.createGain();
bass.type = 'sine'; bass.frequency.value = 65; bg.gain.value = 0.03;
bass.connect(bg); bg.connect(audioCtx.destination); bass.start();
musicNodes.push(bass, bg);
function beat() {
if (!audioCtx || !musicOn) return;
try {
const o = audioCtx.createOscillator(); const g = audioCtx.createGain();
o.type = 'square'; o.frequency.value = 80;
g.gain.setValueAtTime(0.05, audioCtx.currentTime);
g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.1);
o.connect(g); g.connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime + 0.1);
beatTimer = setTimeout(beat, 500);
} catch(e) {}
}
beat();
const mel = [262,330,392,330,262,294,349,294]; let mi = 0;
function melo() {
if (!audioCtx || !musicOn) return;
try {
const o = audioCtx.createOscillator(); const g = audioCtx.createGain();
o.type = 'triangle'; o.frequency.value = mel[mi % mel.length];
g.gain.setValueAtTime(0.03, audioCtx.currentTime);
g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.3);
o.connect(g); g.connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime + 0.3);
mi++; melodyTimer = setTimeout(melo, 600);
} catch(e) {}
}
melo();
} catch(e) {}
}
function stopMusic() {
if (beatTimer) { clearTimeout(beatTimer); beatTimer = null; }
if (melodyTimer) { clearTimeout(melodyTimer); melodyTimer = null; }
musicNodes.forEach(n => { try { n.stop(); } catch(e) {} try { n.disconnect(); } catch(e) {} });
musicNodes = [];
}
function playSound(freq, dur, type, vol) {
if (!audioCtx || !soundOn) return;
try {
const o = audioCtx.createOscillator(); const g = audioCtx.createGain();
o.type = type || 'sine'; o.frequency.value = freq;
g.gain.setValueAtTime(vol || 0.08, audioCtx.currentTime);
g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + dur);
o.connect(g); g.connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime + dur);
} catch(e) {}
}
function sfxSpawn() { playSound(600, 0.08, 'sine', 0.06); }
function sfxThud() { playSound(80, 0.12, 'sawtooth', 0.1); }
function sfxShoot() { playSound(900, 0.06, 'square', 0.1); }
function sfxSlash() { playSound(400, 0.08, 'sawtooth', 0.08); }
function sfxBoom() { playSound(60, 0.4, 'sawtooth', 0.2); setTimeout(() => playSound(40, 0.3, 'sawtooth', 0.15), 100); }
function sfxPain() { playSound(300, 0.15, 'sawtooth', 0.1); setTimeout(() => playSound(200, 0.15, 'sawtooth', 0.08), 80); }
function sfxClick() { playSound(800, 0.05, 'square', 0.06); }
// --- СОЗДАНИЕ ---
function createMelon(x, y) {
const bw = 20, bh = 28, hr = 10;
return { type: 'melon', alive: true, parts: [
{ x: x, y: y, w: bw, h: bh, vx: 0, vy: 0, angle: 0, va: 0, type: 'body', hitTimer: 0 },
{ x: x + bw/2, y: y - hr, r: hr, vx: 0, vy: 0, angle: 0, va: 0, type: 'head', hitTimer: 0 },
{ x: x - 5, y: y + 5, w: 6, h: 16, vx: 0, vy: 0, angle: 0, va: 0, type: 'armL', hitTimer: 0 },
{ x: x + bw - 1, y: y + 5, w: 6, h: 16, vx: 0, vy: 0, angle: 0, va: 0, type: 'armR', hitTimer: 0 },
{ x: x + 2, y: y + bh - 2, w: 7, h: 14, vx: 0, vy: 0, angle: 0, va: 0, type: 'legL', hitTimer: 0 },
{ x: x + bw - 9, y: y + bh - 2, w: 7, h: 14, vx: 0, vy: 0, angle: 0, va: 0, type: 'legR', hitTimer: 0 }
]};
}
function createBox(x, y) { return { type: 'box', alive: true, x, y, w: 40, h: 40, vx: 0, vy: 0, angle: 0, va: 0, fallen: false }; }
function createSpike(x, y) { return { type: 'spike', alive: true, x, y, w: 30, h: 30, vx: 0, vy: 0, angle: 0, va: 0, fallen: false }; }
function createGun(x, y) { return { type: 'gun', alive: true, x, y, w: 30, h: 14, vx: 0, vy: 0, angle: 0, va: 0, cooldown: 0, fallen: false }; }
function createSword(x, y) { return { type: 'sword', alive: true, x, y, w: 40, h: 8, vx: 0, vy: 0, angle: 0, va: 0, slashTimer: 0, fallen: false }; }
function createBomb(x, y) { return { type: 'bomb', alive: true, x, y, w: 24, h: 24, vx: 0, vy: 0, angle: 0, va: 0, fuse: 120, fallen: false }; }
function spawnAt(x, y) {
initAudio();
if (y > groundY - 40) y = groundY - 40;
if (x < 50) x = 50;
if (x > canvas.width - 50) x = canvas.width - 50;
if (selectedTool === 'melon') objects.push(createMelon(x - 15, y - 30));
else if (selectedTool === 'box') objects.push(createBox(x - 20, y - 20));
else if (selectedTool === 'spike') objects.push(createSpike(x - 15, y - 15));
else if (selectedTool === 'gun') objects.push(createGun(x - 15, y - 7));
else if (selectedTool === 'sword') objects.push(createSword(x - 20, y - 4));
else if (selectedTool === 'bomb') objects.push(createBomb(x - 12, y - 12));
sfxSpawn();
updateHUD();
}
function updateHUD() { hud.textContent = `🍉 Объектов: ${objects.length} | 💥 Взрывов: ${explosionCount} | 🩸 Ранений: ${woundCount}`; }
// --- ФИЗИКА (ИСПРАВЛЕНА) ---
function updatePhysics() {
const G = 0.6, FR = 0.95, BOUNCE = 0.2;
const W = canvas.width;
objects.forEach(obj => {
if (!obj.alive) return;
if (obj.type === 'melon') {
// Сначала применяем гравитацию ко всем частям
obj.parts.forEach(p => {
if (p.hitTimer > 0) p.hitTimer--;
if (dragged && dragged.parts && dragged.parts.includes(p)) return;
p.vy += G;
p.vx *= FR;
p.x += p.vx;
p.y += p.vy;
p.angle += p.va;
p.va *= 0.95;
});
// Затем разрешаем коллизии с землёй и стенами
obj.parts.forEach(p => {
if (dragged && dragged.parts && dragged.parts.includes(p)) return;
const bottom = p.type === 'head' ? p.y + p.r : p.y + p.h;
if (bottom > groundY) {
if (p.type === 'head') p.y = groundY - p.r; else p.y = groundY - p.h;
p.vy = -p.vy * BOUNCE;
if (Math.abs(p.vy) > 1) sfxThud();
}
if (p.x < 0) { p.x = 0; p.vx = -p.vx * BOUNCE; }
const pw = p.w || p.r*2;
if (p.x + pw > W) { p.x = W - pw; p.vx = -p.vx * BOUNCE; }
});
// Связи между частями (чтобы арбуз стоял как человечек)
const body = obj.parts[0];
const head = obj.parts[1];
const armL = obj.parts[2];
const armR = obj.parts[3];
const legL = obj.parts[4];
const legR = obj.parts[5];
// Голова держится на теле
if (!dragged || !dragged.parts.includes(head)) {
const targetX = body.x + body.w/2 - head.r;
const targetY = body.y - head.r * 2;
head.x += (targetX - head.x) * 0.2;
head.y += (targetY - head.y) * 0.2;
}
// Руки держатся за тело
if (!dragged || !dragged.parts.includes(armL)) {
armL.x += (body.x - 5 - armL.x) * 0.15;
armL.y += (body.y + 5 - armL.y) * 0.15;
}
if (!dragged || !dragged.parts.includes(armR)) {
armR.x += (body.x + body.w - 1 - armR.x) * 0.15;
armR.y += (body.y + 5 - armR.y) * 0.15;
}
// Ноги держатся за тело
if (!dragged || !dragged.parts.includes(legL)) {
legL.x += (body.x + 2 - legL.x) * 0.15;
legL.y += (body.y + body.h - 2 - legL.y) * 0.15;
}
if (!dragged || !dragged.parts.includes(legR)) {
legR.x += (body.x + body.w - 9 - legR.x) * 0.15;
legR.y += (body.y + body.h - 2 - legR.y) * 0.15;
}
} else {
// Оружие и предметы — падают
if (dragged === obj) return;
obj.vy += G;
obj.vx *= FR;
obj.x += obj.vx;
obj.y += obj.vy;
obj.angle += obj.va;
obj.va *= 0.95;
if (obj.y + obj.h > groundY) {
obj.y = groundY - obj.h;
obj.vy = -obj.vy * BOUNCE;
obj.fallen = true;
}
if (obj.x < 0) { obj.x = 0; obj.vx = -obj.vx * BOUNCE; }
if (obj.x + obj.w > W) { obj.x = W - obj.w; obj.vx = -obj.vx * BOUNCE; }
// Оружие работает
if (obj.type === 'gun') {
if (obj.cooldown > 0) obj.cooldown--;
if (obj.cooldown === 0 && obj.fallen && Math.random() < 0.01) {
bullets.push({ x: obj.x + obj.w/2, y: obj.y + obj.h/2, vx: (Math.random() - 0.5) * 8, vy: -2, life: 120 });
obj.cooldown = 60;
sfxShoot();
}
}
if (obj.type === 'sword') {
if (obj.slashTimer > 0) obj.slashTimer--;
}
if (obj.type === 'bomb') {
obj.fuse--;
if (obj.fuse <= 0) {
explode(obj.x + obj.w/2, obj.y + obj.h/2);
obj.alive = false;
}
}
}
});
// Пули
bullets.forEach((b, i) => {
b.x += b.vx; b.y += b.vy; b.vy += 0.2; b.life--;
if (b.life <= 0 || b.y > groundY) { bullets.splice(i, 1); return; }
objects.forEach(obj => {
if (obj.type === 'melon' && obj.alive) {
obj.parts.forEach(p => {
const cx = p.x + (p.w || p.r*2)/2;
const cy = p.y + (p.h || p.r*2)/2;
const r = (p.w || p.r*2)/2;
if (Math.hypot(b.x - cx, b.y - cy) < r + 5) {
p.vx += b.vx * 0.5; p.vy -= 2;
p.hitTimer = 20;
woundCount++;
sfxPain();
bullets.splice(i, 1);
updateHUD();
}
});
}
});
});
bullets = bullets.filter(b => b.life > 0);
// Взрывы
explosions.forEach((ex, i) => {
if (ex.isPain) {
ex.x += ex.vx; ex.y += ex.vy; ex.alpha -= 0.05;
} else {
ex.r += 8; ex.alpha -= 0.05;
}
if (ex.alpha <= 0) explosions.splice(i, 1);
});
}
function explode(x, y) {
explosions.push({ x, y, r: 10, alpha: 1, isPain: false });
explosionCount++;
sfxBoom();
updateHUD();
objects.forEach(obj => {
if (obj.type === 'melon' && obj.alive) {
obj.parts.forEach(p => {
const dx = (p.x + (p.w||p.r*2)/2) - x;
const dy = (p.y + (p.h||p.r*2)/2) - y;
const dist = Math.hypot(dx, dy);
if (dist < 150) {
const force = (150 - dist) / 150 * 15;
p.vx += (dx / (dist || 1)) * force;
p.vy += (dy / (dist || 1)) * force - 3;
p.hitTimer = 30;
}
});
}
});
}
// --- РИСОВАНИЕ ---
function drawGround() {
ctx.fillStyle = '#2d1f0d';
ctx.fillRect(0, groundY, canvas.width, canvas.height - groundY);
ctx.fillStyle = '#4CAF50';
ctx.fillRect(0, groundY, canvas.width, 8);
ctx.fillStyle = '#388E3C';
for (let x = 0; x < canvas.width; x += 12) ctx.fillRect(x, groundY - 5, 3, 8);
}
function drawMelon(obj) {
obj.parts.forEach(p => {
ctx.save();
ctx.translate(p.x + (p.w || p.r*2)/2, p.y + (p.h || p.r*2)/2);
ctx.rotate(p.angle);
const hurt = p.hitTimer > 0;
if (p.type === 'body') {
ctx.fillStyle = hurt ? '#ff4444' : '#4CAF50';
ctx.beginPath(); ctx.ellipse(0, 0, p.w/2, p.h/2, 0, 0, Math.PI * 2); ctx.fill();
ctx.strokeStyle = '#2E7D32'; ctx.lineWidth = 2; ctx.stroke();
if (!hurt) {
ctx.strokeStyle = '#1B5E20'; ctx.lineWidth = 1;
for (let i = -2; i <= 2; i++) { ctx.beginPath(); ctx.moveTo(i * 5, -p.h/2 + 3); ctx.lineTo(i * 5, p.h/2 - 3); ctx.stroke(); }
}
} else if (p.type === 'head') {
ctx.fillStyle = hurt ? '#ff4444' : '#66BB6A';
ctx.beginPath(); ctx.arc(0, 0, p.r, 0, Math.PI * 2); ctx.fill();
ctx.strokeStyle = '#2E7D32'; ctx.lineWidth = 2; ctx.stroke();
if (hurt) {
ctx.strokeStyle = '#fff'; ctx.lineWidth = 2;
ctx.beginPath(); ctx.moveTo(-5, -4); ctx.lineTo(-1, 0); ctx.moveTo(-1, -4); ctx.lineTo(-5, 0); ctx.stroke();
ctx.beginPath(); ctx.moveTo(3, -4); ctx.lineTo(7, 0); ctx.moveTo(7, -4); ctx.lineTo(3, 0); ctx.stroke();
} else {
ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(-3, -2, 2.5, 0, Math.PI * 2); ctx.arc(3, -2, 2.5, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#000'; ctx.beginPath(); ctx.arc(-2.5, -2, 1.2, 0, Math.PI * 2); ctx.arc(3.5, -2, 1.2, 0, Math.PI * 2); ctx.fill();
ctx.strokeStyle = '#000'; ctx.lineWidth = 1; ctx.beginPath(); ctx.arc(0, 2, 3, 0.1, Math.PI - 0.1); ctx.stroke();
}
} else {
ctx.fillStyle = hurt ? '#ff4444' : '#4CAF50';
ctx.fillRect(-p.w/2, -p.h/2, p.w, p.h);
ctx.strokeStyle = '#2E7D32'; ctx.lineWidth = 1; ctx.strokeRect(-p.w/2, -p.h/2, p.w, p.h);
}
ctx.restore();
});
}
function drawBox(obj) {
ctx.save(); ctx.translate(obj.x + obj.w/2, obj.y + obj.h/2); ctx.rotate(obj.angle);
ctx.fillStyle = '#d4a017'; ctx.fillRect(-obj.w/2, -obj.h/2, obj.w, obj.h);
ctx.strokeStyle = '#8B4513'; ctx.lineWidth = 3; ctx.strokeRect(-obj.w/2, -obj.h/2, obj.w, obj.h);
ctx.strokeStyle = '#8B4513'; ctx.lineWidth = 2;
ctx.beginPath(); ctx.moveTo(-obj.w/2, -obj.h/2); ctx.lineTo(obj.w/2, obj.h/2); ctx.stroke();
ctx.beginPath(); ctx.moveTo(obj.w/2, -obj.h/2); ctx.lineTo(-obj.w/2, obj.h/2); ctx.stroke();
ctx.restore();
}
function drawSpike(obj) {
ctx.save(); ctx.translate(obj.x + obj.w/2, obj.y + obj.h/2); ctx.rotate(obj.angle);
ctx.fillStyle = '#e94560'; ctx.beginPath();
ctx.moveTo(0, -obj.h/2); ctx.lineTo(obj.w/2, obj.h/2); ctx.lineTo(-obj.w/2, obj.h/2); ctx.closePath(); ctx.fill();
ctx.strokeStyle = '#aa0044'; ctx.lineWidth = 2; ctx.stroke();
ctx.restore();
}
function drawGun(obj) {
ctx.save(); ctx.translate(obj.x + obj.w/2, obj.y + obj.h/2); ctx.rotate(obj.angle);
ctx.fillStyle = '#333'; ctx.fillRect(-obj.w/2, -obj.h/2, obj.w, obj.h);
ctx.fillStyle = '#666'; ctx.fillRect(-obj.w/2, -obj.h/2 - 3, obj.w * 0.6, 4);
ctx.strokeStyle = '#000'; ctx.lineWidth = 1; ctx.strokeRect(-obj.w/2, -obj.h/2, obj.w, obj.h);
ctx.restore();
}
function drawSword(obj) {
ctx.save(); ctx.translate(obj.x + obj.w/2, obj.y + obj.h/2); ctx.rotate(obj.angle);
ctx.fillStyle = '#ccc'; ctx.fillRect(-obj.w/2, -2, obj.w * 0.7, 4);
ctx.fillStyle = '#8B4513'; ctx.fillRect(obj.w/2 - obj.w * 0.3, -4, obj.w * 0.3, 8);
if (obj.slashTimer > 0) {
ctx.strokeStyle = 'rgba(255,255,255,0.8)'; ctx.lineWidth = 3;
ctx.beginPath(); ctx.arc(0, 0, 30, -0.5, 0.5); ctx.stroke();
}
ctx.restore();
}
function drawBomb(obj) {
ctx.save(); ctx.translate(obj.x + obj.w/2, obj.y + obj.h/2); ctx.rotate(obj.angle);
ctx.fillStyle = '#222'; ctx.beginPath(); ctx.arc(0, 0, obj.w/2, 0, Math.PI * 2); ctx.fill();
ctx.strokeStyle = '#000'; ctx.lineWidth = 2; ctx.stroke();
ctx.strokeStyle = '#ff6600'; ctx.lineWidth = 2;
ctx.beginPath(); ctx.moveTo(0, -obj.w/2); ctx.lineTo(5, -obj.w/2 - 8); ctx.stroke();
if (Math.floor(Date.now() / 100) % 2 === 0) {
ctx.fillStyle = '#ffdd00'; ctx.beginPath(); ctx.arc(5, -obj.w/2 - 8, 3, 0, Math.PI * 2); ctx.fill();
}
ctx.fillStyle = '#fff'; ctx.font = 'bold 10px Arial'; ctx.textAlign = 'center';
ctx.fillText(Math.ceil(obj.fuse / 60), 0, 3); ctx.textAlign = 'left';
ctx.restore();
}
function draw() {
const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
grad.addColorStop(0, '#1a1a2e'); grad.addColorStop(1, '#2a2a4e');
ctx.fillStyle = grad; ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGround();
objects.forEach(obj => {
if (!obj.alive) return;
if (obj.type === 'melon') drawMelon(obj);
else if (obj.type === 'box') drawBox(obj);
else if (obj.type === 'spike') drawSpike(obj);
else if (obj.type === 'gun') drawGun(obj);
else if (obj.type === 'sword') drawSword(obj);
else if (obj.type === 'bomb') drawBomb(obj);
});
bullets.forEach(b => {
ctx.fillStyle = '#ffdd00';
ctx.beginPath(); ctx.arc(b.x, b.y, 4, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = 'rgba(255,221,0,0.3)';
ctx.beginPath(); ctx.arc(b.x, b.y, 8, 0, Math.PI * 2); ctx.fill();
});
explosions.forEach(ex => {
ctx.globalAlpha = ex.alpha;
if (ex.isPain) {
ctx.fillStyle = '#ff4444';
ctx.beginPath(); ctx.arc(ex.x, ex.y, ex.r, 0, Math.PI * 2); ctx.fill();
} else {
ctx.fillStyle = '#ff6600';
ctx.beginPath(); ctx.arc(ex.x, ex.y, ex.r, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#ffdd00';
ctx.beginPath(); ctx.arc(ex.x, ex.y, ex.r * 0.6, 0, Math.PI * 2); ctx.fill();
}
ctx.globalAlpha = 1;
});
if (dragged) {
ctx.strokeStyle = 'rgba(76,175,80,0.6)'; ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
ctx.strokeRect(mouseX - 30, mouseY - 30, 60, 60);
ctx.setLineDash([]);
}
}
function loop() { updatePhysics(); draw(); requestAnimationFrame(loop); }
// --- УПРАВЛЕНИЕ ---
function getPos(e) {
const rect = canvas.getBoundingClientRect();
const t = e.touches ? e.touches[0] : e;
return { x: t.clientX - rect.left, y: t.clientY - rect.top };
}
function hitTest(x, y) {
for (let i = objects.length - 1; i >= 0; i--) {
const obj = objects[i];
if (!obj.alive) continue;
if (obj.type === 'melon') {
for (let p of obj.parts) {
const cx = p.x + (p.w || p.r*2)/2, cy = p.y + (p.h || p.r*2)/2;
const hw = (p.w || p.r*2)/2 + 8, hh = (p.h || p.r*2)/2 + 8;
if (Math.abs(x - cx) < hw && Math.abs(y - cy) < hh) return obj;
}
} else {
if (x > obj.x - 5 && x < obj.x + obj.w + 5 && y > obj.y - 5 && y < obj.y + obj.h + 5) return obj;
}
}
return null;
}
function onStart(e) {
e.preventDefault(); initAudio();
const p = getPos(e); mouseX = p.x; mouseY = p.y;
const hit = hitTest(p.x, p.y);
if (hit) {
dragged = hit;
if (hit.type === 'melon') {
dragOffsetX = p.x - (hit.parts[0].x + hit.parts[0].w/2);
dragOffsetY = p.y - (hit.parts[0].y + hit.parts[0].h/2);
} else {
dragOffsetX = p.x - (hit.x + hit.w/2);
dragOffsetY = p.y - (hit.y + hit.h/2);
}
// Меч — рубит
if (hit.type === 'sword') {
hit.slashTimer = 20;
sfxSlash();
objects.forEach(o => {
if (o.type === 'melon' && o.alive && o !== hit) {
o.parts.forEach(pp => {
const cx = pp.x + (pp.w||pp.r*2)/2, cy = pp.y + (pp.h||pp.r*2)/2;
if (Math.hypot(p.x - cx, p.y - cy) < 70) {
pp.vx += (cx - p.x) * 0.8;
pp.vy -= 6;
pp.hitTimer = 30;
woundCount++;
for (let k = 0; k < 5; k++) {
explosions.push({
x: cx, y: cy, r: 3, alpha: 1, isPain: true,
vx: (Math.random() - 0.5) * 4, vy: (Math.random() - 0.5) * 4
});
}
}
});
}
});
sfxPain();
updateHUD();
}
sfxClick();
} else {
spawnAt(p.x, p.y);
}
}
function onMove(e) {
e.preventDefault();
if (!dragged) return;
const p = getPos(e); mouseX = p.x; mouseY = p.y;
if (dragged.type === 'melon') {
const body = dragged.parts[0];
const dx = p.x - dragOffsetX - (body.x + body.w/2);
const dy = p.y - dragOffsetY - (body.y + body.h/2);
dragged.parts.forEach(part => { part.x += dx; part.y += dy; part.vx = 0; part.vy = 0; });
} else {
dragged.x = p.x - dragOffsetX - dragged.w/2;
dragged.y = p.y - dragOffsetY - dragged.h/2;
dragged.vx = 0; dragged.vy = 0;
}
}
function onEnd(e) {
e.preventDefault();
if (dragged) {
const p = getPos(e);
if (dragged.type === 'melon') {
const body = dragged.parts[0];
body.vx = (p.x - mouseX) * 0.5; body.vy = (p.y - mouseY) * 0.5;
} else {
dragged.vx = (p.x - mouseX) * 0.5; dragged.vy = (p.y - mouseY) * 0.5;
}
dragged = null;
}
}
canvas.addEventListener('touchstart', onStart, { passive: false });
canvas.addEventListener('touchmove', onMove, { passive: false });
canvas.addEventListener('touchend', onEnd, { passive: false });
canvas.addEventListener('mousedown', onStart);
canvas.addEventListener('mousemove', onMove);
canvas.addEventListener('mouseup', onEnd);
// Тулбар
document.querySelectorAll('.tool[data-tool]').forEach(el => {
el.addEventListener('click', () => {
selectedTool = el.dataset.tool;
document.querySelectorAll('.tool').forEach(t => t.classListGame Source: Melon Sandbox: Финал
Creator: HyperRider36
Libraries: none
Complexity: complex (569 lines, 22.1 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: melon-sandbox-hyperrider36" to link back to the original. Then publish at arcadelab.ai/publish.