🎮ArcadeLab

2D Racer - Version 1

by AstroPilot48
519 lines10.1 KB
▶ Play
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
      content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>2D Racer - Version 1</title>

<style>
* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    user-select: none;
    -webkit-user-select: none;
}

body {
    background: #111;
    overflow: hidden;
    font-family: Arial, sans-serif;
    touch-action: none;
}

canvas {
    display: block;
    margin: auto;
    background: #333;
}

#menu {
    position: fixed;
    inset: 0;
    background: linear-gradient(#111, #333);
    color: white;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    z-index: 10;
}

#menu h1 {
    font-size: 42px;
    margin-bottom: 8px;
}

#menu p {
    color: #ccc;
    margin-bottom: 35px;
}

button {
    border: none;
    border-radius: 12px;
    padding: 15px 45px;
    font-size: 22px;
    font-weight: bold;
    background: #ffcc00;
    color: #111;
    cursor: pointer;
}

button:active {
    transform: scale(.95);
}

#gameOver {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,.75);
    color: white;
    display: none;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    z-index: 20;
}

#gameOver h2 {
    font-size: 42px;
    margin-bottom: 15px;
}

#scoreText {
    font-size: 22px;
    margin-bottom: 25px;
}

#controls {
    position: fixed;
    bottom: 20px;
    left: 0;
    right: 0;
    display: flex;
    justify-content: space-between;
    padding: 0 25px;
    pointer-events: none;
    z-index: 5;
}

.control {
    width: 75px;
    height: 75px;
    border-radius: 50%;
    border: 2px solid white;
    background: rgba(255,255,255,.2);
    color: white;
    font-size: 35px;
    display: flex;
    justify-content: center;
    align-items: center;
    pointer-events: auto;
}
</style>
</head>

<body>

<div id="menu">
    <h1>🏁 2D RACER</h1>
    <p>Version 1</p>
    <button id="playButton">PLAY</button>
</div>

<canvas id="game"></canvas>

<div id="controls">
    <div class="control" id="leftBtn">◀</div>
    <div class="control" id="rightBtn">▶</div>
</div>

<div id="gameOver">
    <h2>GAME OVER</h2>
    <div id="scoreText">Score: 0</div>
    <button id="restartButton">RESTART</button>
</div>

<script>
const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");

let W, H;

function resize() {
    W = Math.min(window.innerWidth, 500);
    H = window.innerHeight;

    canvas.width = W;
    canvas.height = H;
}

window.addEventListener("resize", resize);
resize();

/* ---------------- GAME VARIABLES ---------------- */

let running = false;
let gameOver = false;

let score = 0;
let speed = 5;
let roadOffset = 0;

let keys = {
    left: false,
    right: false
};

const player = {
    width: 48,
    height: 82,
    x: 0,
    y: 0
};

let enemies = [];

/* ---------------- MENU ---------------- */

document.getElementById("playButton").addEventListener("click", startGame);
document.getElementById("restartButton").addEventListener("click", startGame);

function startGame() {
    document.getElementById("menu").style.display = "none";
    document.getElementById("gameOver").style.display = "none";

    running = true;
    gameOver = false;

    score = 0;
    speed = 5;
    enemies = [];

    player.x = W / 2 - player.width / 2;
    player.y = H - 150;

    for (let i = 0; i < 3; i++) {
        createEnemy(-i * 250 - 150);
    }

    requestAnimationFrame(gameLoop);
}

/* ---------------- ROAD ---------------- */

function roadLeft() {
    return W * 0.15;
}

function roadRight() {
    return W * 0.85;
}

function roadWidth() {
    return roadRight() - roadLeft();
}

function drawRoad() {
    // Grass
    ctx.fillStyle = "#267326";
    ctx.fillRect(0, 0, W, H);

    // Road
    ctx.fillStyle = "#444";
    ctx.fillRect(roadLeft(), 0, roadWidth(), H);

    // Road edges
    ctx.fillStyle = "#fff";
    ctx.fillRect(roadLeft() - 5, 0, 5, H);
    ctx.fillRect(roadRight(), 0, 5, H);

    // Lane markings
    ctx.strokeStyle = "#eee";
    ctx.lineWidth = 5;
    ctx.setLineDash([35, 35]);

    roadOffset += speed;

    if (roadOffset > 70) {
        roadOffset = 0;
    }

    ctx.lineDashOffset = roadOffset;

    const lane1 = roadLeft() + roadWidth() / 3;
    const lane2 = roadLeft() + roadWidth() * 2 / 3;

    ctx.beginPath();
    ctx.moveTo(lane1, -100);
    ctx.lineTo(lane1, H + 100);
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(lane2, -100);
    ctx.lineTo(lane2, H + 100);
    ctx.stroke();

    ctx.setLineDash([]);
}

/* ---------------- PLAYER CAR ---------------- */

function drawPlayer() {
    const x = player.x;
    const y = player.y;

    // Shadow
    ctx.fillStyle = "rgba(0,0,0,.35)";
    ctx.fillRect(x + 4, y + 6, player.width, player.height);

    // Car body
    ctx.fillStyle = "#e60000";
    ctx.fillRect(x, y, player.width, player.height);

    // Roof
    ctx.fillStyle = "#b30000";
    ctx.fillRect(x + 7, y + 18, player.width - 14, 40);

    // Windows
    ctx.fillStyle = "#87ceeb";
    ctx.fillRect(x + 11, y + 23, player.width - 22, 14);
    ctx.fillRect(x + 11, y + 42, player.width - 22, 11);

    // Wheels
    ctx.fillStyle = "#111";
    ctx.fillRect(x - 5, y + 12, 7, 20);
    ctx.fillRect(x + player.width - 2, y + 12, 7, 20);
    ctx.fillRect(x - 5, y + 52, 7, 20);
    ctx.fillRect(x + player.width - 2, y + 52, 7, 20);

    // Lights
    ctx.fillStyle = "#ffff99";
    ctx.fillRect(x + 6, y + 3, 10, 7);
    ctx.fillRect(x + player.width - 16, y + 3, 10, 7);
}

/* ---------------- ENEMY CARS ---------------- */

function createEnemy(y) {
    const lanes = [
        roadLeft() + roadWidth() / 6,
        roadLeft() + roadWidth() / 2,
        roadLeft() + roadWidth() * 5 / 6
    ];

    const lane = lanes[Math.floor(Math.random() * lanes.length)];

    enemies.push({
        width: 46,
        height: 78,
        x: lane - 23,
        y: y,
        color: randomCarColor()
    });
}

function randomCarColor() {
    const colors = [
        "#0066ff",
        "#ff9900",
        "#9b59b6",
        "#00aa66",
        "#ffffff",
        "#222222"
    ];

    return colors[Math.floor(Math.random() * colors.length)];
}

function drawEnemy(car) {
    const x = car.x;
    const y = car.y;

    ctx.fillStyle = "rgba(0,0,0,.35)";
    ctx.fillRect(x + 4, y + 5, car.width, car.height);

    ctx.fillStyle = car.color;
    ctx.fillRect(x, y, car.width, car.height);

    ctx.fillStyle = "#333";
    ctx.fillRect(x + 7, y + 17, car.width - 14, 40);

    ctx.fillStyle = "#87ceeb";
    ctx.fillRect(x + 11, y + 22, car.width - 22, 13);
    ctx.fillRect(x + 11, y + 41, car.width - 22, 11);

    ctx.fillStyle = "#111";
    ctx.fillRect(x - 5, y + 12, 7, 20);
    ctx.fillRect(x + car.width - 2, y + 12, 7, 20);
    ctx.fillRect(x - 5, y + 50, 7, 20);
    ctx.fillRect(x + car.width - 2, y + 50, 7, 20);
}

/* ---------------- COLLISION ---------------- */

function collision(a, b) {
    return (
        a.x < b.x + b.width &&
        a.x + a.width > b.x &&
        a.y < b.y + b.height &&
        a.y + a.height > b.y
    );
}

/* ---------------- GAME UPDATE ---------------- */

function update() {
    if (!running) return;

    // Player movement
    if (keys.left) {
        player.x -= 7;
    }

    if (keys.right) {
        player.x += 7;
    }

    // Keep player on road
    if (player.x < roadLeft() + 8) {
        player.x = roadLeft() + 8;
    }

    if (player.x + player.width > roadRight() - 8) {
        player.x = roadRight() - 8 - player.width;
    }

    // Move enemies
    enemies.forEach(car => {
        car.y += speed;
    });

    // Remove cars that leave screen
    enemies = enemies.filter(car => {
        if (car.y > H + 100) {
            score++;
            return false;
        }
        return true;
    });

    // Add new enemy
    if (enemies.length < 3) {
        createEnemy(-120);
    }

    // Difficulty
    speed = 5 + Math.min(score * 0.15, 6);

    // Collision check
    for (const car of enemies) {
        if (collision(player, car)) {
            endGame();
        }
    }
}

/* ---------------- HUD ---------------- */

function drawHUD() {
    ctx.fillStyle = "rgba(0,0,0,.55)";
    ctx.fillRect(10, 10, 145, 45);

    ctx.fillStyle = "white";
    ctx.font = "bold 22px Arial";
    ctx.fillText("Score: " + score, 20, 40);
}

/* ---------------- GAME LOOP ---------------- */

function gameLoop() {
    if (!running) return;

    ctx.clearRect(0, 0, W, H);

    drawRoad();
    update();

    enemies.forEach(drawEnemy);
    drawPlayer();
    drawHUD();

    if (running) {
        requestAnimationFrame(gameLoop);
    }
}

/* ---------------- GAME OVER ---------------- */

function endGame() {
    running = false;
    gameOver = true;

    document.getElementById("scoreText").textContent =
        "Score: " + score;

    document.getElementById("gameOver").style.display = "flex";
}

/* ---------------- KEYBOARD ---------------- */

document.addEventListener("keydown", e => {
    if (e.key === "ArrowLeft" || e.key.toLowerCase() === "a") {
        keys.left = true;
    }

    if (e.key === "ArrowRight" || e.key.toLowerCase() === "d") {
        keys.right = true;
    }
});

document.addEventListener("keyup", e => {
    if (e.key === "ArrowLeft" || e.key.toLowerCase() === "a") {
        keys.left = false;
    }

    if (e.key === "ArrowRight" || e.key.toLowerCase() === "d") {
        keys.right = false;
    }
});

/* ---------------- MOBILE TOUCH ---------------- */

function holdButton(element, direction) {
    element.addEventListener("touchstart", e => {
        e.preventDefault();
        keys[direction] = true;
    });

    element.addEventListener("touchend", e => {
        e.preventDefault();
        keys[direction] = false;
    });

    element.addEventListener("touchcancel", () => {
        keys[direction] = false;
    });

    element.addEventListener("mousedown", () => {
        keys[direction] = true;
    });

    element.addEventListener("mouseup", () => {
        keys[direction] = false;
    });

    element.addEventListener("mouseleave", () => {
        keys[direction] = false;
    });
}

holdButton(document.getElementById("leftBtn"), "left");
holdButton(document.getElementById("rightBtn"), "right");

</script>

</body>
</html>

Game Source: 2D Racer - Version 1

Creator: AstroPilot48

Libraries: none

Complexity: complex (519 lines, 10.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: 2d-racer-version-1-astropilot48" to link back to the original. Then publish at arcadelab.ai/publish.