Blockwalk
by LunarGalaxy87280 lines9.0 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Blockwalk</title>
<style>
html, body { margin: 0; padding: 0; overflow: hidden; background: #4a90d9; font-family: 'Segoe UI', Arial, sans-serif; height: 100%; }
canvas { display: block; image-rendering: pixelated; }
#hud {
position: fixed; top: 16px; left: 16px; color: white;
background: rgba(0,0,0,0.4); padding: 10px 16px; border-radius: 10px;
font-size: 14px; line-height: 1.5; pointer-events: none;
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
}
#startScreen {
position: fixed; inset: 0; background: linear-gradient(#4a90d9, #7fc97f);
display: flex; flex-direction: column; align-items: center; justify-content: center;
color: #1c2e1c; text-align: center; z-index: 10;
}
#startScreen h1 { font-size: 44px; margin: 0 0 8px; letter-spacing: 1px; }
#startScreen p { font-size: 15px; margin: 4px 0; opacity: 0.85; }
#startButton {
margin-top: 22px; padding: 14px 34px; font-size: 18px; border: none;
border-radius: 8px; background: #3a7d44; color: white; cursor: pointer;
box-shadow: 0 4px 0 #2a5a32;
}
#startButton:active { box-shadow: 0 1px 0 #2a5a32; transform: translateY(3px); }
.dpad {
position: fixed; bottom: 24px; left: 24px; display: none;
grid-template-columns: 56px 56px 56px;
grid-template-rows: 56px 56px 56px;
gap: 4px; z-index: 5;
}
.dpad button {
font-size: 20px; border: none; border-radius: 8px;
background: rgba(255,255,255,0.55); color: #223; touch-action: none;
}
.dpad button:active { background: rgba(255,255,255,0.9); }
</style>
</head>
<body>
<div id="startScreen">
<h1>🧱 Blockwalk</h1>
<p>A tiny top-down block world you can wander around.</p>
<p>WASD / Arrow keys or the on-screen pad to move</p>
<button id="startButton">Enter World</button>
</div>
<div id="hud" style="display:none;">WASD / Arrows: move around</div>
<div class="dpad" id="dpad">
<div></div><button data-dir="up">▲</button><div></div>
<button data-dir="left">◀</button><div></div><button data-dir="right">▶</button>
<div></div><button data-dir="down">▼</button><div></div>
</div>
<canvas id="game"></canvas>
<script>
(function() {
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const TILE = 40;
const WORLD_SIZE = 40;
let world = [];
let player = { x: WORLD_SIZE/2, y: WORLD_SIZE/2, dir: 'down', animT: 0 };
const keys = { up:false, down:false, left:false, right:false };
const colors = {
water: '#3a7bd5',
sand: '#e0d18f',
grass: '#5fae4a',
grassDark: '#4f9a3d',
tree: '#2d6b2f',
trunk: '#7a4a25',
stone: '#9a9a9a'
};
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
function noise2(x, y) {
return Math.sin(x*0.35 + y*0.11) + Math.sin(x*0.09 - y*0.28) + Math.sin((x+y)*0.05);
}
function generateWorld() {
for (let y = 0; y < WORLD_SIZE; y++) {
const row = [];
for (let x = 0; x < WORLD_SIZE; x++) {
const n = noise2(x, y);
let type = 'grass';
if (n < -1.4) type = 'water';
else if (n < -1.0) type = 'sand';
else if (n > 1.6) type = 'stone';
row.push({ type, tree: false, dark: (x + y) % 2 === 0 });
}
world.push(row);
}
for (let y = 0; y < WORLD_SIZE; y++) {
for (let x = 0; x < WORLD_SIZE; x++) {
if (world[y][x].type === 'grass' && Math.random() < 0.06) {
world[y][x].tree = true;
}
}
}
world[Math.floor(player.y)][Math.floor(player.x)] = { type: 'grass', tree: false, dark: false };
}
function isWalkable(tx, ty) {
if (tx < 0 || ty < 0 || tx >= WORLD_SIZE || ty >= WORLD_SIZE) return false;
const tile = world[ty][tx];
if (tile.type === 'water') return false;
if (tile.tree) return false;
return true;
}
const speed = 3.2;
let prevTime = performance.now();
function update(dt) {
let dx = 0, dy = 0;
if (keys.up) dy -= 1;
if (keys.down) dy += 1;
if (keys.left) dx -= 1;
if (keys.right) dx += 1;
if (dx !== 0 && dy !== 0) { dx *= 0.7071; dy *= 0.7071; }
if (dx !== 0 || dy !== 0) {
player.animT += dt;
if (Math.abs(dx) > Math.abs(dy)) player.dir = dx > 0 ? 'right' : 'left';
else if (dy !== 0) player.dir = dy > 0 ? 'down' : 'up';
const nx = player.x + dx * speed * dt;
const ny = player.y + dy * speed * dt;
if (isWalkable(Math.floor(nx), Math.floor(player.y))) player.x = nx;
if (isWalkable(Math.floor(player.x), Math.floor(ny))) player.y = ny;
player.x = Math.max(0.3, Math.min(WORLD_SIZE - 0.3, player.x));
player.y = Math.max(0.3, Math.min(WORLD_SIZE - 0.3, player.y));
} else {
player.animT = 0;
}
}
function drawTile(px, py, tile) {
let c = colors[tile.type];
if (tile.type === 'grass' && tile.dark) c = colors.grassDark;
ctx.fillStyle = c;
ctx.fillRect(px, py, TILE, TILE);
if (tile.type === 'water') {
ctx.fillStyle = 'rgba(255,255,255,0.15)';
ctx.fillRect(px, py + TILE*0.4, TILE, 3);
}
if (tile.tree) {
ctx.fillStyle = colors.trunk;
ctx.fillRect(px + TILE*0.42, py + TILE*0.55, TILE*0.16, TILE*0.4);
ctx.fillStyle = colors.tree;
ctx.beginPath();
ctx.arc(px + TILE*0.5, py + TILE*0.42, TILE*0.38, 0, Math.PI*2);
ctx.fill();
}
}
function drawPlayer(px, py, bob) {
const w = TILE * 0.5;
const h = TILE * 0.5;
const cx = px + TILE/2;
const cy = py + TILE/2 - bob;
ctx.fillStyle = 'rgba(0,0,0,0.25)';
ctx.beginPath();
ctx.ellipse(cx, py + TILE*0.82, TILE*0.28, TILE*0.1, 0, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#e2725b';
ctx.fillRect(cx - w/2, cy - h/2, w, h*0.75);
ctx.fillStyle = '#f2c49b';
ctx.beginPath();
ctx.arc(cx, cy - h*0.5, w*0.32, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#2b2b2b';
let ex = 0, ey = 0;
if (player.dir === 'left') ex = -w*0.12;
if (player.dir === 'right') ex = w*0.12;
if (player.dir === 'up') ey = -h*0.12;
if (player.dir === 'down') ey = h*0.06;
ctx.fillRect(cx - w*0.14 + ex, cy - h*0.55 + ey, 3, 3);
ctx.fillRect(cx + w*0.10 + ex, cy - h*0.55 + ey, 3, 3);
}
function render() {
ctx.imageSmoothingEnabled = false;
ctx.fillStyle = '#264d73';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const camX = player.x * TILE - canvas.width / 2;
const camY = player.y * TILE - canvas.height / 2;
const startCol = Math.max(0, Math.floor(camX / TILE) - 1);
const endCol = Math.min(WORLD_SIZE - 1, Math.ceil((camX + canvas.width) / TILE) + 1);
const startRow = Math.max(0, Math.floor(camY / TILE) - 1);
const endRow = Math.min(WORLD_SIZE - 1, Math.ceil((camY + canvas.height) / TILE) + 1);
for (let y = startRow; y <= endRow; y++) {
for (let x = startCol; x <= endCol; x++) {
const tile = world[y][x];
if (tile.tree) continue;
drawTile(x*TILE - camX, y*TILE - camY, tile);
}
}
for (let y = startRow; y <= endRow; y++) {
for (let x = startCol; x <= endCol; x++) {
const tile = world[y][x];
if (!tile.tree) continue;
ctx.fillStyle = tile.dark ? colors.grassDark : colors.grass;
ctx.fillRect(x*TILE - camX, y*TILE - camY, TILE, TILE);
drawTile(x*TILE - camX, y*TILE - camY, tile);
}
}
const bob = (player.animT > 0) ? Math.abs(Math.sin(player.animT * 8)) * 3 : 0;
drawPlayer(player.x*TILE - camX - TILE/2, player.y*TILE - camY - TILE/2, bob);
}
function loop(time) {
const dt = Math.min((time - prevTime) / 1000, 0.1);
prevTime = time;
update(dt);
render();
requestAnimationFrame(loop);
}
function setKey(code, val) {
switch(code) {
case 'KeyW': case 'ArrowUp': keys.up = val; break;
case 'KeyS': case 'ArrowDown': keys.down = val; break;
case 'KeyA': case 'ArrowLeft': keys.left = val; break;
case 'KeyD': case 'ArrowRight': keys.right = val; break;
}
}
document.addEventListener('keydown', e => setKey(e.code, true));
document.addEventListener('keyup', e => setKey(e.code, false));
const dpad = document.getElementById('dpad');
dpad.querySelectorAll('button').forEach(btn => {
const dir = btn.getAttribute('data-dir');
const map = { up:'up', down:'down', left:'left', right:'right' };
const press = (v) => (e) => { e.preventDefault(); keys[map[dir]] = v; };
btn.addEventListener('touchstart', press(true));
btn.addEventListener('touchend', press(false));
btn.addEventListener('mousedown', press(true));
btn.addEventListener('mouseup', press(false));
btn.addEventListener('mouseleave', press(false));
});
document.getElementById('startButton').addEventListener('click', () => {
document.getElementById('startScreen').style.display = 'none';
document.getElementById('hud').style.display = 'block';
if ('ontouchstart' in window) dpad.style.display = 'grid';
generateWorld();
prevTime = performance.now();
requestAnimationFrame(loop);
});
})();
</script>
</body>
</html>
Game Source: Blockwalk
Creator: LunarGalaxy87
Libraries: none
Complexity: complex (280 lines, 9.0 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: blockwalk-lunargalaxy87" to link back to the original. Then publish at arcadelab.ai/publish.