<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flying Car Race</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="game-container">
<div id="sky">
<div id="player-car"></div>
</div>
<div id="ui">
<div id="score-display">Score: <span id="score">0</span></div>
<div id="start-screen">
<h1>Flying Car Race</h1>
<p>Press SPACE or TAP to Fly Up!</p>
<button id="start-button">Start Game</button>
</div>
<div id="game-over-screen" class="hidden">
<h2>Game Over!</h2>
<p>Your Score: <span id="final-score">0</span></p>
<button id="restart-button">Restart</button>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #add8e6; /* Light blue background */
font-family: 'Arial', sans-serif;
overflow: hidden; /* Prevent scrollbars if game container is slightly off */
}
#game-container {
width: 800px;
height: 500px;
border: 5px solid #333;
position: relative; /* Crucial for absolute positioning inside */
background-color: #87ceeb; /* Sky blue */
overflow: hidden; /* Keep elements inside */
}
#sky {
width: 100%;
height: 100%;
position: relative;
/* Add a subtle gradient or background image later if desired */
/* background: linear-gradient(to bottom, #87ceeb, #add8e6); */
background-image: url('https://www.transparenttextures.com/patterns/subtle-clouds.png'); /* Example cloud texture */
animation: scroll-sky 20s linear infinite; /* Animate background */
}
/* Simple scrolling animation for the background */
@keyframes scroll-sky {
0% { background-position: 0 0; }
100% { background-position: -800px 0; } /* Scroll width of container */
}
#player-car {
width: 60px;
height: 30px;
background-color: #ff4500; /* Orangey-red car */
border-radius: 5px 5px 0 0; /* Simple car shape */
position: absolute;
bottom: 50px; /* Initial position */
left: 50px;
z-index: 10;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
transition: bottom 0.1s ease-out; /* Smooth vertical movement */
}
/* Style for obstacles */
.obstacle {
width: 50px; /* Width of the obstacle */
background-color: #666; /* Dark grey obstacles */
border: 2px solid #444;
position: absolute;
right: -60px; /* Start off-screen */
z-index: 5;
box-shadow: 2px 2px 5px rgba(0,0,0,0.3);
}
/* UI Elements Styling */
#ui {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
z-index: 20; /* Above game elements */
}
#score-display {
position: absolute;
top: 10px;
left: 10px;
font-size: 1.5em;
color: white;
text-shadow: 1px 1px 2px black;
}
#start-screen, #game-over-screen {
margin-top: 100px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 30px;
border-radius: 10px;
text-align: center;
display: flex; /* Use flexbox for content alignment */
flex-direction: column;
align-items: center;
}
#start-screen h1, #game-over-screen h2 {
margin-top: 0;
}
#start-screen p, #game-over-screen p {
font-size: 1.1em;
margin-bottom: 20px;
}
button {
padding: 10px 20px;
font-size: 1.2em;
cursor: pointer;
background-color: #4CAF50; /* Green */
color: white;
border: none;
border-radius: 5px;
transition: background-color 0.2s;
}
button:hover {
background-color: #45a049;
}
/* Utility class to hide elements */
.hidden {
display: none !important; /* Use important to override other display styles */
}
document.addEventListener('DOMContentLoaded', () => {
// Game elements
const sky = document.getElementById('sky');
const playerCar = document.getElementById('player-car');
const gameContainer = document.getElementById('game-container');
const scoreDisplay = document.getElementById('score');
const finalScoreDisplay = document.getElementById('final-score');
const startScreen = document.getElementById('start-screen');
const gameOverScreen = document.getElementById('game-over-screen');
const startButton = document.getElementById('start-button');
const restartButton = document.getElementById('restart-button');
// Game variables
let isGameOver = true; // Start in game over state (show start screen)
let score = 0;
let gameLoopInterval;
let obstacleInterval;
let playerBottom = 50; // Initial vertical position matches CSS
const gravity = 0.9;
const jumpAmount = 25; // How much the car moves up per jump
const gameSpeed = 5; // How fast obstacles move left
const obstacleFrequency = 1500; // Milliseconds between new obstacles
let obstacles = []; // Array to track obstacle elements
// --- Controls ---
function jump(event) {
if (!isGameOver) {
// Only allow jump if Spacebar (32) or ArrowUp (38) is pressed
if (event.keyCode === 32 || event.keyCode === 38 || event.type === 'touchstart') {
event.preventDefault(); // Prevent spacebar scrolling or default touch behavior
if (playerBottom < gameContainer.offsetHeight - playerCar.offsetHeight - jumpAmount/3) { // Prevent going off top
playerBottom += jumpAmount;
playerCar.style.bottom = playerBottom + 'px';
}
}
}
}
// --- Game Loop ---
function gameLoop() {
if (isGameOver) return;
// Apply Gravity
playerBottom -= gravity;
playerCar.style.bottom = playerBottom + 'px';
// Check Floor Collision
if (playerBottom <= 0) {
endGame();
return; // Stop loop execution for this frame
}
// Move & Check Obstacles
moveObstacles();
// Update Score (simple time-based scoring)
score++;
scoreDisplay.textContent = score;
// Optional: Increase difficulty over time
// if (score % 500 === 0) { /* Increase speed */ }
}
// --- Obstacles ---
function generateObstacle() {
if (isGameOver) return;
const obstacleHeight = Math.random() * (gameContainer.offsetHeight / 2.5) + 50; // Random height, min 50px
const positionChoice = Math.random(); // Determine if top or bottom obstacle
const obstacle = document.createElement('div');
obstacle.classList.add('obstacle');
obstacle.style.width = '50px'; // Match CSS if needed, ensures consistency
obstacle.style.height = obstacleHeight + 'px';
if (positionChoice > 0.5) {
// Bottom obstacle
obstacle.style.bottom = '0px';
} else {
// Top obstacle
obstacle.style.bottom = gameContainer.offsetHeight - obstacleHeight + 'px';
}
obstacle.style.right = '-60px'; // Start off-screen right
sky.appendChild(obstacle);
obstacles.push(obstacle); // Add to our tracking array
}
function moveObstacles() {
obstacles.forEach((obstacle, index) => {
let obstacleRight = parseFloat(obstacle.style.right);
obstacleRight += gameSpeed;
obstacle.style.right = obstacleRight + 'px';
// Collision Detection
const carRect = playerCar.getBoundingClientRect();
const obsRect = obstacle.getBoundingClientRect();
// Basic AABB collision check
if (
carRect.left < obsRect.right &&
carRect.right > obsRect.left &&
carRect.top < obsRect.bottom &&
carRect.bottom > obsRect.top
) {
endGame();
return; // Stop checking other obstacles once collision detected
}
// Remove obstacle if it's off-screen left
if (obstacleRight > gameContainer.offsetWidth + parseFloat(obstacle.style.width)) {
obstacle.remove(); // Remove from DOM
obstacles.splice(index, 1); // Remove from array
}
});
}
// --- Game State ---
function startGame() {
if (!isGameOver) return; // Prevent starting if already running
isGameOver = false;
score = 0;
playerBottom = 150; // Reset position slightly higher
playerCar.style.bottom = playerBottom + 'px';
scoreDisplay.textContent = score;
obstacles.forEach(obstacle => obstacle.remove()); // Clear old obstacles
obstacles = []; // Reset array
// Hide screens, show score
startScreen.classList.add('hidden');
gameOverScreen.classList.add('hidden');
scoreDisplay.parentElement.classList.remove('hidden'); // Show score container if hidden
// Add controls
document.addEventListener('keydown', jump);
document.addEventListener('touchstart', jump); // Basic touch support
// Start loops
gameLoopInterval = setInterval(gameLoop, 20); // Run game loop ~50fps
obstacleInterval = setInterval(generateObstacle, obstacleFrequency);
// Start background animation
sky.style.animationPlayState = 'running';
}
function endGame() {
if (isGameOver) return; // Prevent ending multiple times
isGameOver = true;
clearInterval(gameLoopInterval);
clearInterval(obstacleInterval);
// Remove controls
document.removeEventListener('keydown', jump);
document.removeEventListener('touchstart', jump);
// Show game over screen
finalScoreDisplay.textContent = score;
gameOverScreen.classList.remove('hidden');
// Pause background animation
sky.style.animationPlayState = 'paused';
}
// --- Event Listeners ---
startButton.addEventListener('click', startGame);
restartButton.addEventListener('click', startGame);
// Initial setup: Pause background animation
sky.style.animationPlayState = 'paused';
}); // End DOMContentLoaded
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.