2D Car Racing Game

 <!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">

    <title>2D Car Racing Game</title>

    <style>

        body {

            margin: 0;

            overflow: hidden;

            display: flex;

            flex-direction: column;

            justify-content: center;

            align-items: center;

            height: 100vh;

            background-color: #333;

            font-family: Arial, sans-serif;

        }


        #gameContainer {

            width: 90vw;

            max-width: 400px;

            height: 85vh;

            max-height: 700px;

            background-color: #4CAF50; /* Grass/Shoulders */

            overflow: hidden;

            position: relative;

            border: 5px solid black;

            box-shadow: 0 0 15px rgba(0,0,0,0.5);

        }


        #road {

            width: 70%; /* Road width relative to gameContainer */

            height: 100%;

            background-color: #555; /* Road color */

            position: absolute;

            left: 15%; /* Centering the road (100% - 70%)/2 */

            top: 0;

            overflow: hidden; /* Clip obstacles and lines */

        }


        .roadLine {

            width: 10px;

            height: 50px;

            background-color: white;

            position: absolute;

            left: 50%;

            transform: translateX(-50%);

        }


        #playerCar {

            width: 50px;

            height: 80px;

            background-color: #f44336; /* Red */

            position: absolute;

            z-index: 10;

            border-radius: 10px 10px 0 0;

            border: 2px solid #333;

            box-sizing: border-box;

            box-shadow: 0 2px 5px rgba(0,0,0,0.3);

        }


        .obstacle {

            width: 50px;

            height: 80px;

            position: absolute;

            z-index: 5;

            border-radius: 10px 10px 0 0;

            border: 2px solid #333;

            box-sizing: border-box;

            box-shadow: 0 2px 5px rgba(0,0,0,0.2);

        }


        #score {

            position: absolute;

            top: 10px;

            left: 10px; /* Position relative to gameContainer */

            color: white;

            font-size: clamp(16px, 4vw, 24px); /* Responsive font size */

            z-index: 20;

            text-shadow: 1px 1px 2px black;

        }


        #gameOverScreen {

            position: absolute;

            top: 50%;

            left: 50%;

            transform: translate(-50%, -50%);

            background-color: rgba(0, 0, 0, 0.85);

            color: white;

            padding: 20px;

            text-align: center;

            border-radius: 10px;

            z-index: 100;

            width: 80%;

            box-sizing: border-box;

        }


        #gameOverScreen h2 {

            margin-top: 0;

            font-size: clamp(20px, 5vw, 30px);

        }

        #gameOverScreen p {

            font-size: clamp(16px, 4vw, 24px);

        }


        #restartButton {

            padding: 10px 20px;

            font-size: clamp(14px, 3.5vw, 20px);

            margin-top: 15px;

            cursor: pointer;

            background-color: #4CAF50;

            color: white;

            border: none;

            border-radius: 5px;

            transition: background-color 0.3s;

        }

        #restartButton:hover {

            background-color: #45a049;

        }


        #mobileControls {

            margin-top: 10px;

            display: none; /* Hidden by default, shown by JS if on mobile */

            user-select: none; /* Prevent text selection on button press */

        }

        .control-btn-row {

            display: flex;

            justify-content: center;

            align-items: center;

        }

        .control-btn {

            background-color: #666;

            border: 2px solid #444;

            color: white;

            padding: 10px;

            width: 60px; /* Fixed size for touch targets */

            height: 50px;

            text-align: center;

            font-size: 20px;

            cursor: pointer;

            border-radius: 8px;

            margin: 5px;

            box-shadow: 0 2px 3px rgba(0,0,0,0.3);

        }

        .control-btn:active {

            background-color: #555;

            transform: translateY(1px);

        }

    </style>

</head>

<body>

    <div id="gameContainer">

        <div id="road">

            <!-- Road lines will be added here by JS -->

            <div id="playerCar"></div>

            <!-- Obstacles will be added here by JS -->

        </div>

        <div id="score">Score: 0</div>

        <div id="gameOverScreen" style="display: none;">

            <h2>Game Over</h2>

            <p id="finalScore">Your Score: 0</p>

            <button id="restartButton">Restart</button>

        </div>

    </div>


    <div id="mobileControls">

        <div class="control-btn-row">

            <button class="control-btn" id="btnUp">▲</button>

        </div>

        <div class="control-btn-row">

            <button class="control-btn" id="btnLeft">◄</button>

            <button class="control-btn" id="btnDown">▼</button>

            <button class="control-btn" id="btnRight">►</button>

        </div>

    </div>


    <script>

        const gameContainer = document.getElementById('gameContainer');

        const road = document.getElementById('road');

        const playerCarElement = document.getElementById('playerCar');

        const scoreDisplay = document.getElementById('score');

        const gameOverScreen = document.getElementById('gameOverScreen');

        const finalScoreDisplay = document.getElementById('finalScore');

        const restartButton = document.getElementById('restartButton');

        const mobileControls = document.getElementById('mobileControls');


        const PLAYER_CAR_WIDTH = 50;

        const PLAYER_CAR_HEIGHT = 80;

        const OBSTACLE_WIDTH = 50;

        const OBSTACLE_HEIGHT = 80;

        const ROAD_LINE_HEIGHT = 50;

        const ROAD_LINE_GAP = 30;


        const OBSTACLE_COLORS = ['#007bff', '#6f42c1', '#28a745', '#fd7e14', '#17a2b8'];



        let player = {

            x: 0, // Relative to road, from left

            y: 0, // Relative to road, from top (used for style.top)

            speed: 7, // Pixels per frame for horizontal/vertical movement

            width: PLAYER_CAR_WIDTH,

            height: PLAYER_CAR_HEIGHT

        };


        let game = {

            score: 0,

            isOver: true,

            animationFrameId: null,

            roadSpeed: 5, // Initial speed of road/obstacles

            baseRoadSpeed: 5,

            speedIncrement: 0.002, // How much roadSpeed increases per frame

            obstacleSpawnCounter: 0,

            obstacleSpawnInterval: 80, // Frames between potential spawns, decreases over time

            minObstacleSpawnInterval: 30,

            roadLines: [],

            obstacles: []

        };


        let keys = {

            ArrowUp: false,

            ArrowDown: false,

            ArrowLeft: false,

            ArrowRight: false

        };


        function isMobileDevice() {

            return ('ontouchstart' in window || navigator.maxTouchPoints > 0);

        }


        function startGame() {

            game.isOver = false;

            game.score = 0;

            game.roadSpeed = game.baseRoadSpeed;

            game.obstacleSpawnCounter = 0;

            game.obstacleSpawnInterval = 80;

            

            scoreDisplay.textContent = 'Score: 0';

            gameOverScreen.style.display = 'none';


            // Clear previous obstacles and road lines

            game.obstacles.forEach(obs => obs.element.remove());

            game.obstacles = [];

            game.roadLines.forEach(line => line.element.remove());

            game.roadLines = [];


            // Initial player position (bottom center of the road)

            player.x = road.offsetWidth / 2 - player.width / 2;

            player.y = road.offsetHeight - player.height - 20; // 20px from bottom

            playerCarElement.style.left = player.x + 'px';

            playerCarElement.style.top = player.y + 'px';

            playerCarElement.style.display = 'block';


            // Create initial road lines

            const numLines = Math.ceil(road.offsetHeight / (ROAD_LINE_HEIGHT + ROAD_LINE_GAP)) + 1;

            for (let i = 0; i < numLines; i++) {

                const lineElement = document.createElement('div');

                lineElement.className = 'roadLine';

                lineElement.style.height = ROAD_LINE_HEIGHT + 'px';

                const lineTop = i * (ROAD_LINE_HEIGHT + ROAD_LINE_GAP) - ROAD_LINE_GAP; // Start lines from top

                lineElement.style.top = lineTop + 'px';

                road.appendChild(lineElement);

                game.roadLines.push({ element: lineElement, top: lineTop });

            }

            

            if (game.animationFrameId) {

                cancelAnimationFrame(game.animationFrameId);

            }

            gameLoop();

        }


        function updatePlayerPosition() {

            let dx = 0;

            let dy = 0;


            if (keys.ArrowLeft) dx -= player.speed;

            if (keys.ArrowRight) dx += player.speed;

            if (keys.ArrowUp) dy -= player.speed;

            if (keys.ArrowDown) dy += player.speed;


            player.x += dx;

            player.y += dy;


            // Boundary checks for player car within the road

            player.x = Math.max(0, Math.min(player.x, road.offsetWidth - player.width));

            player.y = Math.max(0, Math.min(player.y, road.offsetHeight - player.height));


            playerCarElement.style.left = player.x + 'px';

            playerCarElement.style.top = player.y + 'px';

        }


        function moveRoadLines() {

            game.roadLines.forEach(line => {

                line.top += game.roadSpeed;

                if (line.top > road.offsetHeight) {

                    line.top = -ROAD_LINE_HEIGHT - ROAD_LINE_GAP; // Move to top above screen

                }

                line.element.style.top = line.top + 'px';

            });

        }


        function spawnObstacle() {

            const obstacleElement = document.createElement('div');

            obstacleElement.className = 'obstacle';

            obstacleElement.style.width = OBSTACLE_WIDTH + 'px';

            obstacleElement.style.height = OBSTACLE_HEIGHT + 'px';

            

            const obstacleX = Math.random() * (road.offsetWidth - OBSTACLE_WIDTH);

            obstacleElement.style.left = obstacleX + 'px';

            obstacleElement.style.top = -OBSTACLE_HEIGHT + 'px'; // Start above screen

            obstacleElement.style.backgroundColor = OBSTACLE_COLORS[Math.floor(Math.random() * OBSTACLE_COLORS.length)];


            road.appendChild(obstacleElement);

            game.obstacles.push({

                element: obstacleElement,

                x: obstacleX,

                y: -OBSTACLE_HEIGHT,

                width: OBSTACLE_WIDTH,

                height: OBSTACLE_HEIGHT

            });

        }


        function moveObstacles() {

            for (let i = game.obstacles.length - 1; i >= 0; i--) {

                const obs = game.obstacles[i];

                obs.y += game.roadSpeed;

                obs.element.style.top = obs.y + 'px';


                if (obs.y > road.offsetHeight) {

                    obs.element.remove();

                    game.obstacles.splice(i, 1);

                }

            }

        }


        function checkCollision(rect1, rect2) {

            // rect1 and rect2 are objects {x, y, width, height}

            return rect1.x < rect2.x + rect2.width &&

                   rect1.x + rect1.width > rect2.x &&

                   rect1.y < rect2.y + rect2.height &&

                   rect1.y + rect1.height > rect2.y;

        }


        function handleCollisions() {

            const playerRect = {

                x: player.x,

                y: player.y,

                width: player.width,

                height: player.height

            };


            for (const obs of game.obstacles) {

                if (checkCollision(playerRect, obs)) {

                    gameOver();

                    break;

                }

            }

        }

        

        function updateScore() {

            game.score++;

            scoreDisplay.textContent = `Score: ${game.score}`;

        }


        function increaseDifficulty() {

            game.roadSpeed += game.speedIncrement;

            // Decrease spawn interval but not below min

            if (game.obstacleSpawnInterval > game.minObstacleSpawnInterval) {

                 game.obstacleSpawnInterval -= 0.05; // Gradually decrease

            }

        }


        function gameOver() {

            game.isOver = true;

            cancelAnimationFrame(game.animationFrameId);

            playerCarElement.style.display = 'none'; // Hide car

            gameOverScreen.style.display = 'block';

            finalScoreDisplay.textContent = `Your Score: ${game.score}`;

        }


        function gameLoop() {

            if (game.isOver) return;


            updatePlayerPosition();

            moveRoadLines();

            

            game.obstacleSpawnCounter++;

            if (game.obstacleSpawnCounter >= Math.max(game.minObstacleSpawnInterval, game.obstacleSpawnInterval)) {

                spawnObstacle();

                game.obstacleSpawnCounter = 0;

            }


            moveObstacles();

            handleCollisions();

            if (game.isOver) return; // Collision might have happened


            updateScore();

            increaseDifficulty();


            game.animationFrameId = requestAnimationFrame(gameLoop);

        }


        // Event Listeners

        window.addEventListener('keydown', (e) => {

            if (keys.hasOwnProperty(e.key)) {

                keys[e.key] = true;

                e.preventDefault(); // Prevent page scroll for arrow keys

            }

        });


        window.addEventListener('keyup', (e) => {

            if (keys.hasOwnProperty(e.key)) {

                keys[e.key] = false;

                e.preventDefault();

            }

        });


        restartButton.addEventListener('click', startGame);


        // Mobile Controls Setup

        if (isMobileDevice()) {

            mobileControls.style.display = 'block';

            const controlButtons = {

                btnUp: 'ArrowUp',

                btnDown: 'ArrowDown',

                btnLeft: 'ArrowLeft',

                btnRight: 'ArrowRight'

            };


            for (const [btnId, keyName] of Object.entries(controlButtons)) {

                const btn = document.getElementById(btnId);

                if (btn) {

                    btn.addEventListener('touchstart', (e) => {

                        keys[keyName] = true;

                        e.preventDefault();

                    });

                    btn.addEventListener('touchend', (e) => {

                        keys[keyName] = false;

                        e.preventDefault();

                    });

                }

            }

        }

        

        // Set initial player car size for JS logic (could also get from element.offsetWidth but this is safer before display)

        playerCarElement.style.width = player.width + 'px';

        playerCarElement.style.height = player.height + 'px';


        // Start game automatically or show a start screen

        // For now, let's require a click on restart (which acts as start initially)

        // To make it start automatically:

        // document.addEventListener('DOMContentLoaded', startGame);

        // But since it's all in one file, simply calling startGame() might be delayed

        // Let's make the game over screen the initial screen with "Start Game"

        gameOverScreen.style.display = 'block';

        finalScoreDisplay.innerHTML = "Press Start to Play!";

        document.querySelector('#gameOverScreen h2').textContent = "2D Car Racer";

        restartButton.textContent = "Start Game";


        restartButton.addEventListener('click', () => {

            if (restartButton.textContent === "Start Game") {

                restartButton.textContent = "Restart";

                document.querySelector('#gameOverScreen h2').textContent = "Game Over";

            }

            startGame();

        });


    </script>

</body>

</html>

Post a Comment

0 Comments