Ueban2's picture
Deploy from HuggingChat
46c1f7d verified
Raw
History Blame Contribute Delete
11.7 kB
<!DOCTYPE html>
<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>