Meta Description" name="description" />

Share this result

Previews are deleted daily. Get a permanent share link sent to your inbox:
Script
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover"> <title>Mobile 2D Craft</title> <style> * { box-sizing: border-box; user-select: none; -webkit-user-select: none; margin: 0; padding: 0; } body { background-color: #111; color: #fff; font-family: monospace; overflow: hidden; position: fixed; width: 100vw; height: 100vh; } #game-container { position: relative; width: 100%; height: 100%; } canvas { display: block; width: 100%; height: 100%; background: #87CEEB; /* Sky Blue */ } /* Mobile UI Layer */ .ui-element { position: absolute; z-index: 10; } /* Hotbar at the top */ #hotbar { top: 10px; left: 50%; transform: translateX(-50%); display: flex; background: rgba(0,0,0,0.6); padding: 5px; border: 2px solid #555; border-radius: 5px; } .slot { width: 40px; height: 40px; margin: 0 3px; border: 2px solid #888; display: flex; justify-content: center; align-items: center; font-size: 10px; font-weight: bold; position: relative; } .slot.active { border-color: #fff; background: rgba(255,255,255,0.2); } /* Touch Controls */ .dpad { bottom: 20px; left: 20px; display: grid; grid-template-columns: 50px 50px 50px; grid-template-rows: 50px 50px; gap: 5px; } .action-btns { bottom: 20px; right: 20px; display: flex; flex-direction: column; gap: 10px; } .btn { background: rgba(255, 255, 255, 0.3); border: 2px solid #fff; color: white; border-radius: 10px; display: flex; justify-content: center; align-items: center; font-size: 1.2rem; font-weight: bold; active-background: rgba(255, 255, 255, 0.6); } .btn:active { background: rgba(255,255,255,0.6); } #btn-left { grid-column: 1; grid-row: 2; width: 50px; height: 50px;} #btn-right { grid-column: 3; grid-row: 2; width: 50px; height: 50px;} #btn-jump { width: 60px; height: 60px; border-radius: 50%; } #btn-action { width: 60px; height: 40px; font-size: 0.8rem; } </style> </head> <body> <div id="game-container"> <canvas id="gameCanvas"></canvas> <!-- Hotbar --> <div id="hotbar" class="ui-element"> <div class="slot active" id="slot-1" style="background-color: #8B5A2B;">Dirt</div> <div class="slot" id="slot-2" style="background-color: #A0522D;">Wood</div> <div class="slot" id="slot-3" style="background-color: #228B22;">Leaf</div> <div class="slot" id="slot-4" style="background-color: #808080;">Stone</div> </div> <!-- D-Pad Directional Controls --> <div class="dpad ui-element"> <div id="btn-left" class="btn">◀</div> <div id="btn-right" class="btn">▶</div> </div> <!-- Action Controls --> <div class="action-btns ui-element"> <div id="btn-action" class="btn">MODE: DIG</div> <div id="btn-jump" class="btn">▲</div> </div> </div> <script> const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); // Resize Canvas dynamically function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } window.addEventListener('resize', resize); resize(); // Game Configuration const BLOCK_SIZE = 32; const WORLD_WIDTH = 100; // Total blocks wide const WORLD_HEIGHT = 30; // Total blocks high // Block Types definitions const BLOCKS = { 0: { name: 'Air', color: null }, 1: { name: 'Dirt', color: '#8B5A2B' }, 2: { name: 'Grass', color: '#4CAF50' }, 3: { name: 'Stone', color: '#808080' }, 4: { name: 'Wood', color: '#A0522D' }, 5: { name: 'Leaf', color: '#228B22' } }; // Inventory / Selection let currentBuildID = 1; // Default: Dirt let interactionMode = 'DIG'; // 'DIG' or 'PLACE' const inventoryOrder = [1, 4, 5, 3]; // Dirt, Wood, Leaf, Stone // Map Grid Array let world = []; // World Generation (Basic 2D Terrain) function generateWorld() { for (let x = 0; x < WORLD_WIDTH; x++) { world[x] = []; // Simple sine wave for terrain hills let surfaceY = Math.floor(12 + Math.sin(x * 0.15) * 3); for (let y = 0; y < WORLD_HEIGHT; y++) { if (y < surfaceY) { world[x][y] = 0; // Air } else if (y === surfaceY) { world[x][y] = 2; // Grass } else if (y > surfaceY && y < surfaceY + 4) { world[x][y] = 1; // Dirt } else { world[x][y] = 3; // Stone } } // Spawn a few random trees if (x > 5 && x < WORLD_WIDTH - 5 && Math.random() < 0.15 && x % 4 === 0) { let treeY = surfaceY; world[x][treeY-1] = 4; world[x][treeY-2] = 4; world[x][treeY-3] = 4; // Trunk // Leaves world[x][treeY-4] = 5; world[x-1][treeY-4] = 5; world[x+1][treeY-4] = 5; world[x][treeY-5] = 5; } } } generateWorld(); // Player Object const player = { x: 10 * BLOCK_SIZE, y: 5 * BLOCK_SIZE, width: BLOCK_SIZE * 0.8, height: BLOCK_SIZE * 1.6, vx: 0, vy: 0, speed: 3, jumpForce: -8, grounded: false }; // Camera Object const camera = { x: 0, y: 0 }; // Controller Inputs State const input = { left: false, right: false }; // Setup Mobile Button Listeners function bindTouch(id, action) { const el = document.getElementById(id); el.addEventListener('touchstart', (e) => { e.preventDefault(); action(true); }, {passive: false}); el.addEventListener('touchend', (e) => { e.preventDefault(); action(false); }, {passive: false}); // Fallbacks for mouse testing el.addEventListener('mousedown', () => action(true)); el.addEventListener('mouseup', () => action(false)); } bindTouch('btn-left', (val) => input.left = val); bindTouch('btn-right', (val) => input.right = val); // Jump action triggers instantly on press document.getElementById('btn-jump').addEventListener('touchstart', (e) => { e.preventDefault(); if (player.grounded) { player.vy = player.jumpForce; player.grounded = false; } }); document.getElementById('btn-jump').addEventListener('mousedown', () => { if (player.grounded) { player.vy = player.jumpForce; player.grounded = false; } }); // Toggle Action Mode (Dig vs Place) const actionBtn = document.getElementById('btn-action'); actionBtn.addEventListener('click', () => { interactionMode = interactionMode === 'DIG' ? 'PLACE' : 'DIG'; actionBtn.textContent = `MODE: ${interactionMode}`; }); // Cycle Inventory Hotbar slots on tap const slots = [ document.getElementById('slot-1'), document.getElementById('slot-2'), document.getElementById('slot-3'), document.getElementById('slot-4') ]; slots.forEach((slot, index) => { slot.addEventListener('click', () => { slots.forEach(s => s.classList.remove('active')); slot.classList.add('active'); currentBuildID = inventoryOrder[index]; }); }); // Canvas World Tap (Mining / Building) canvas.addEventListener('touchstart', handleCanvasTap, {passive: false}); canvas.addEventListener('mousedown', handleCanvasTap); function handleCanvasTap(e) { e.preventDefault(); let touchX = e.clientX || (e.touches && e.touches[0].clientX); let touchY = e.clientY || (e.touches && e.touches[0].clientY); if (!touchX || touchY > canvas.height - 100 && touchX < 150 || touchX > canvas.width - 100) { return; // Ignore taps inside UI button clusters } // Convert screen coordinate to World coordinate const worldX = touchX + camera.x; const worldY = touchY + camera.y; // Convert World coordinate to Grid space const gridX = Math.floor(worldX / BLOCK_SIZE); const gridY = Math.floor(worldY / BLOCK_SIZE); // Out of bounds safety check if (gridX >= 0 && gridX < WORLD_WIDTH && gridY >= 0 && gridY < WORLD_HEIGHT) { if (interactionMode === 'DIG') { world[gridX][gridY] = 0; // Turn to Air } else if (interactionMode === 'PLACE' && world[gridX][gridY] === 0) { // Check distance to player so they don't build infinitely away const dist = Math.hypot(gridX - Math.floor(player.x/BLOCK_SIZE), gridY - Math.floor(player.y/BLOCK_SIZE)); if (dist < 6) { world[gridX][gridY] = currentBuildID; } } } } // Collision Check Helper function getBlockAt(pixelX, pixelY) { const gx = Math.floor(pixelX / BLOCK_SIZE); const gy = Math.floor(pixelY / BLOCK_SIZE); if (gx < 0 || gx >= WORLD_WIDTH || gy < 0 || gy >= WORLD_HEIGHT) return 3; // treat border out of bounds as solid stone return world[gx][gy]; } // Game Update Loop function update() { // Apply Horizontal Movement if (input.left) player.vx = -player.speed; else if (input.right) player.vx = player.speed; else player.vx = 0; // Apply Gravity player.vy += 0.4; // gravity constant // Predict X movement & resolve collisions player.x += player.vx; if (getBlockAt(player.x, player.y) !== 0 || getBlockAt(player.x + player.width, player.y) !== 0 || getBlockAt(player.x, player.y + player.height - 2) !== 0 || getBlockAt(player.x + player.width, player.y + player.height - 2) !== 0) { // If hitting a block laterally, undo movement step player.x -= player.vx; } // Predict Y movement & resolve collisions player.grounded = false; player.y += player.vy; if (player.vy >= 0) { // Moving Downward if (getBlockAt(player.x + 4, player.y + player.height) !== 0 || getBlockAt(player.x + player.width - 4, player.y + player.height) !== 0) { player.y = Math.floor(player.y / BLOCK_SIZE) * BLOCK_SIZE; player.vy = 0; player.grounded = true; } } else { // Moving Upward if (getBlockAt(player.x + 4, player.y) !== 0 || getBlockAt(player.x + player.width - 4, player.y) !== 0) { player.vy = 0; player.y = Math.ceil(player.y / BLOCK_SIZE) * BLOCK_SIZE; } } // Camera Tracking Player smoothly camera.x = player.x - canvas.width / 2; camera.y = player.y - canvas.height / 2; // Camera limits camera.x = Math.max(0, Math.min(camera.x, WORLD_WIDTH * BLOCK_SIZE - canvas.width)); camera.y = Math.max(0, Math.min(camera.y, WORLD_HEIGHT * BLOCK_SIZE - canvas.height)); } // Render Graphics Loop function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw World Blocks visible on screen const startX = Math.max(0, Math.floor(camera.x / BLOCK_SIZE)); const endX = Math.min(WORLD_WIDTH, Math.ceil((camera.x + canvas.width) / BLOCK_SIZE)); const startY = Math.max(0, Math.floor(camera.y / BLOCK_SIZE)); const endY = Math.min(WORLD_HEIGHT, Math.ceil((camera.y + canvas.height) / BLOCK_SIZE)); for (let x = startX; x < endX; x++) { for (let y = startY; y < endY; y++) { const blockID = world[x][y]; if (blockID !== 0) { ctx.fillStyle = BLOCKS[blockID].color; ctx.fillRect(x * BLOCK_SIZE - camera.x, y * BLOCK_SIZE - camera.y, BLOCK_SIZE, BLOCK_SIZE); // Give blocks outline borders ctx.strokeStyle = 'rgba(0,0,0,0.15)'; ctx.strokeRect(x * BLOCK_SIZE - camera.x, y * BLOCK_SIZE - camera.y, BLOCK_SIZE, BLOCK_SIZE); } } } // Draw Player Character ctx.fillStyle = '#FFCC99'; // Skin color ctx.fillRect(player.x - camera.x, player.y - camera.y, player.width, player.height / 2); ctx.fillStyle = '#0000FF'; // Blue shirt ctx.fillRect(player.x - camera.x, player.y + player.height / 2 - camera.y, player.width, player.height / 2); } // Standard Main Engine Animation Loop function loop() { update(); draw(); requestAnimationFrame(loop); } loop(); // Extra mobile touch event blocking to stabilize rendering frames document.addEventListener('touchmove', (e) => { if(e.scale !== 1) { e.preventDefault(); } }, { passive: false }); </script> </body> </html>
Landing Page
This ad does not have a landing page available
Network Timeline
Performance Summary

1

Requests

1

Domains

15KB

Transfer Size

15KB

Content Size

155.0ms

Dom Content Loaded

204.0ms

First Paint

156.0ms

Load Time
Domain Breakdown
Transfer Size (bytes)
Loading...
Content Size (bytes)
Loading...
Header Size (bytes)
Loading...
Requests
Loading...
Timings (ms)
Loading...
Total Time
Loading...
Content Breakdown
Transfer Size (bytes)
Loading...
Content Size (bytes)
Loading...
Header Size (bytes)
Loading...
Requests
Loading...