Spaces:
Running
Running
| <html lang="ru"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>Minecraft Mobile Final</title> | |
| <style> | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| background-color: #87CEEB; | |
| font-family: sans-serif; | |
| touch-action: none; | |
| user-select: none; | |
| } | |
| #game-container { | |
| position: relative; | |
| width: 100vw; | |
| height: 100vh; | |
| background-color: #87CEEB; | |
| overflow: hidden; | |
| } | |
| canvas { | |
| display: block; | |
| } | |
| #controls { | |
| position: absolute; | |
| bottom: 20px; /* Почти в самый низ */ | |
| left: 30px; | |
| width: 120px; | |
| height: 120px; | |
| background: rgba(255, 255, 255, 0.15); | |
| backdrop-filter: blur(5px); | |
| border-radius: 50%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| border: 1px solid rgba(255,255,255,0.3); | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.2); | |
| } | |
| #joystick { | |
| width: 50px; | |
| height: 50px; | |
| background: rgba(0, 0, 0, 0.6); | |
| border-radius: 50%; | |
| position: relative; | |
| border: 2px solid rgba(255,255,255,0.8); | |
| box-shadow: 0 4px 10px rgba(0,0,0,0.4); | |
| } | |
| #jump-btn { | |
| position: absolute; | |
| bottom: 20px; /* Почти в самый низ */ | |
| right: 30px; | |
| width: 80px; | |
| height: 80px; | |
| background: rgba(0, 0, 0, 0.6); | |
| backdrop-filter: blur(5px); | |
| color: white; | |
| border-radius: 50%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| font-weight: bold; | |
| border: 2px solid rgba(255,255,255,0.8); | |
| text-align: center; | |
| font-size: 13px; | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.2); | |
| } | |
| #mode-switch { | |
| position: absolute; | |
| top: 40px; /* Сместили чуть ниже от края */ | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background: rgba(0, 0, 0, 0.5); | |
| backdrop-filter: blur(8px); | |
| color: white; | |
| padding: 10px 20px; | |
| border-radius: 20px; | |
| font-size: 15px; | |
| border: 1px solid rgba(255,255,255,0.4); | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.2); | |
| letter-spacing: 0.5px; | |
| } | |
| #hotbar { | |
| position: absolute; | |
| bottom: 160px; /* Опустили ещё ниже, ближе к управлению */ | |
| left: 50%; | |
| transform: translateX(-50%); | |
| display: flex; | |
| gap: 12px; | |
| background: rgba(0, 0, 0, 0.4); | |
| backdrop-filter: blur(10px); | |
| padding: 12px; | |
| border-radius: 16px; | |
| border: 1px solid rgba(255,255,255,0.3); | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.3); | |
| } | |
| .slot { | |
| width: 45px; | |
| height: 45px; | |
| border: 3px solid #888; | |
| background: #555; | |
| cursor: pointer; | |
| transition: 0.2s; | |
| } | |
| .slot.active { border-color: #fff; transform: scale(1.1); box-shadow: 0 0 10px #fff; } | |
| .block-grass { background-color: #5da130; } | |
| .block-dirt { background-color: #8B4513; } | |
| .block-stone { background-color: #808080; } | |
| .block-wood { background-color: #A0522D; } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="game-container"> | |
| <div id="mode-switch">Режим: Ломать ⛏️</div> | |
| <canvas id="gameCanvas"></canvas> | |
| <div id="controls"> | |
| <div id="joystick"></div> | |
| </div> | |
| <div id="jump-btn">ПРЫЖОК</div> | |
| <div id="hotbar"> | |
| <div class="slot active block-grass" data-type="grass"></div> | |
| <div class="slot block-dirt" data-type="dirt"></div> | |
| <div class="slot block-stone" data-type="stone"></div> | |
| <div class="slot block-wood" data-type="wood"></div> | |
| </div> | |
| </div> | |
| <script> | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const modeBtn = document.getElementById('mode-switch'); | |
| function resize() { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| } | |
| window.onresize = resize; | |
| resize(); | |
| const gridSize = 40; | |
| const world = {}; | |
| const player = { | |
| x: 400, y: 100, w: 36, h: 64, | |
| vx: 0, vy: 0, speed: 5, | |
| jumpPower: -12, gravity: 0.6, grounded: false, | |
| animTimer: 0 | |
| }; | |
| const camera = { x: 0, y: 0 }; | |
| let selectedBlock = 'grass'; | |
| let isBuildMode = false; | |
| const blockColors = { | |
| grass: '#5da130', dirt: '#8B4513', stone: '#808080', wood: '#A0522D' | |
| }; | |
| // Генерируем ОГРОМНЫЙ мир (от -200 до 500 блоков) | |
| for(let x = -200; x < 500; x++) { | |
| for(let y = 8; y < 30; y++) { | |
| if(y === 8) world[`${x},${y}`] = 'grass'; | |
| else if(y > 15) world[`${x},${y}`] = 'stone'; | |
| else world[`${x},${y}`] = 'dirt'; | |
| } | |
| } | |
| // Touch handling | |
| let joystickActive = false; | |
| let joyX = 0; | |
| const joystick = document.getElementById('joystick'); | |
| document.getElementById('controls').addEventListener('touchstart', (e) => { | |
| joystickActive = true; | |
| e.preventDefault(); | |
| }, {passive: false}); | |
| window.addEventListener('touchend', () => { | |
| joystickActive = false; | |
| joyX = 0; | |
| joystick.style.transform = `translate(0, 0)`; | |
| }); | |
| window.addEventListener('touchmove', (e) => { | |
| if(!joystickActive) return; | |
| const touch = e.touches[0]; | |
| const rect = document.getElementById('controls').getBoundingClientRect(); | |
| const centerX = rect.left + rect.width/2; | |
| joyX = (touch.clientX - centerX) / (rect.width/2); | |
| if(joyX > 1) joyX = 1; | |
| if(joyX < -1) joyX = -1; | |
| joystick.style.transform = `translate(${joyX * 25}px, 0)`; | |
| }, {passive: false}); | |
| document.getElementById('jump-btn').addEventListener('touchstart', (e) => { | |
| if(player.grounded) player.vy = player.jumpPower; | |
| e.preventDefault(); | |
| }); | |
| modeBtn.addEventListener('touchstart', (e) => { | |
| isBuildMode = !isBuildMode; | |
| modeBtn.innerText = isBuildMode ? "Режим: Строить 📦" : "Режим: Ломать ⛏️"; | |
| e.preventDefault(); | |
| }); | |
| document.querySelectorAll('.slot').forEach(slot => { | |
| slot.addEventListener('touchstart', (e) => { | |
| document.querySelectorAll('.slot').forEach(s => s.classList.remove('active')); | |
| slot.classList.add('active'); | |
| selectedBlock = slot.dataset.type; | |
| e.stopPropagation(); | |
| }); | |
| }); | |
| canvas.addEventListener('touchstart', (e) => { | |
| const touch = e.touches[0]; | |
| // КРИТИЧЕСКИЙ МОМЕНТ: прибавляем координаты камеры | |
| const gx = Math.floor((touch.clientX + camera.x) / gridSize); | |
| const gy = Math.floor((touch.clientY + camera.y) / gridSize); | |
| if(isBuildMode) { | |
| if(!world[`${gx},${gy}`]) world[`${gx},${gy}`] = selectedBlock; | |
| } else { | |
| delete world[`${gx},${gy}`]; | |
| } | |
| }); | |
| function update() { | |
| player.vx = joyX * player.speed; | |
| player.vy += player.gravity; | |
| player.x += player.vx; | |
| player.y += player.vy; | |
| player.grounded = false; | |
| const px = Math.floor((player.x + player.w/2) / gridSize); | |
| const py = Math.floor((player.y + player.h) / gridSize); | |
| if(world[`${px},${py}`]) { | |
| player.y = py * gridSize - player.h; | |
| player.vy = 0; | |
| player.grounded = true; | |
| } | |
| // Плавное перемещение камеры за игроком | |
| camera.x = player.x - canvas.width / 2; | |
| camera.y = player.y - canvas.height / 2; | |
| if (camera.y > 0) camera.y = 0; // Чтобы не видеть «пустоту» выше неба | |
| } | |
| function draw() { | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| ctx.save(); | |
| ctx.translate(-camera.x, -camera.y); | |
| // Рисуем блоки | |
| for(let key in world) { | |
| const [x, y] = key.split(',').map(Number); | |
| // Оптимизация отрисовки (только то, что на экране) | |
| if (x * gridSize < camera.x - gridSize || x * gridSize > camera.x + canvas.width) continue; | |
| if (y * gridSize < camera.y - gridSize || y * gridSize > camera.y + canvas.height) continue; | |
| ctx.fillStyle = blockColors[world[key]]; | |
| ctx.fillRect(x * gridSize, y * gridSize, gridSize, gridSize); | |
| // Эффект объема | |
| ctx.fillStyle = 'rgba(255,255,255,0.2)'; | |
| ctx.fillRect(x * gridSize, y * gridSize, gridSize, 4); | |
| ctx.fillStyle = 'rgba(0,0,0,0.2)'; | |
| ctx.fillRect(x * gridSize, y * gridSize + gridSize - 4, gridSize, 4); | |
| ctx.strokeStyle = 'rgba(0,0,0,0.05)'; | |
| ctx.strokeRect(x * gridSize, y * gridSize, gridSize, gridSize); | |
| } | |
| // Анимация Стива | |
| player.animTimer += 0.1; | |
| const bobbing = Math.sin(player.animTimer) * 2; | |
| const renderY = player.y + (player.vx !== 0 ? bobbing : 0); | |
| // Тело | |
| ctx.fillStyle = '#006400'; | |
| ctx.fillRect(player.x, renderY + 20, player.w, 25); | |
| // Голова | |
| ctx.fillStyle = '#FFDBAC'; | |
| ctx.fillRect(player.x + 4, renderY, player.w - 8, 22); | |
| // Глаза | |
| ctx.fillStyle = '#fff'; | |
| ctx.fillRect(player.x + 8, renderY + 8, 6, 4); | |
| ctx.fillRect(player.x + player.w - 14, renderY + 8, 6, 4); | |
| ctx.fillStyle = '#000'; | |
| ctx.fillRect(player.x + 10, renderY + 9, 3, 3); | |
| ctx.fillRect(player.x + player.w - 11, renderY + 9, 3, 3); | |
| // Рот | |
| ctx.fillStyle = '#8B4513'; | |
| ctx.fillRect(player.x + 12, renderY + 16, 12, 3); | |
| // Штаны | |
| ctx.fillStyle = '#0000CD'; | |
| ctx.fillRect(player.x, renderY + 45, player.w, 19); | |
| // Руки | |
| ctx.fillStyle = '#FFDBAC'; | |
| ctx.fillRect(player.x - 6, renderY + 22, 6, 15); | |
| ctx.fillRect(player.x + player.w, renderY + 22, 6, 15); | |
| ctx.restore(); | |
| } | |
| function loop() { | |
| update(); | |
| draw(); | |
| requestAnimationFrame(loop); | |
| } | |
| loop(); | |
| </script> | |
| </body> | |
| </html> |