2D Racer - Version 1
by ChromeMeteor64519 lines10.1 KB
<!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: ChromeMeteor64
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-chromemeteor64-mu0ut419" to link back to the original. Then publish at arcadelab.ai/publish.