APK FET
Retro Road Racer: Combat Velocity
Retro Road Racer is a high-speed, 16-bit inspired combat racing game that pays homage to the legendary arcade "super-scaler" titles of the early 90s. Combining pixel-perfect aesthetics with modern fluid performance, it places players in the seat of a heavy-duty racing motorcycle on a lawless stretch of highway.
Core Gameplay
The objective is simple but brutal: reach the finish line of a 500-segment track while navigating hazardous curves, elevation changes, and aggressive rivals. Unlike traditional racers, finishing first requires more than just speed—it requires physical dominance.
Key Features
Heavy-Bore Combat: Your bike is equipped for more than just racing. Use the Kick mechanic to physically displace rivals as you pull alongside them. Timing is critical; a well-placed strike can send an opponent careening off the track, while a miss leaves you vulnerable.
Pseudo-3D Engine: Experience a custom-built "super-scaler" rendering engine that simulates 3D depth using 2D sprites. The track features dynamic hills, valleys, and sharp curves that challenge your bike's lean physics and traction.
Dynamic HUD & Feedback: * Integrity Meter: Monitors your bike’s health. Sustaining damage from collisions or environmental hazards will lead to total mechanical failure.
Precision Gauges: Real-time MPH tracking and distance-to-finish markers keep you informed in the heat of the race.
Responsive Physics: Master the lean. High-speed cornering requires careful positioning. Over-steering or failing to hit the apex of a curve will slow your momentum and leave you open to counter-attacks.
Old-School Aesthetics: * Vibrant, pixelated environments with parallax mountain ranges.
Detailed "heavy" bike sprites featuring visible engine blocks, aerodynamic cowls, and riders in full racing leathers.
A high-contrast color palette optimized for that classic arcade feel.
Controls
Action
Keyboard
Touch (Mobile)
Accelerate
Up Arrow
"GO" Button
Brake
Down Arrow
—
Steer
Left / Right Arrows
"L" / "R" Buttons
Kick Attack
Spacebar
"K" Button
Retro Road Racer is an unforgiving test of reflexes and aggression. Whether you’re weaving through traffic or kicking your way to the front of the pack, only the most ruthless riders will see the checkered flag.
<!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>Retro Road Racer - Pixel Perfect</title>
<style>
@font-face {
font-family: 'Retro';
src: url('https://fonts.cdnfonts.com/s/16219/PressStart2P-Regular.woff');
}
body {
margin: 0;
padding: 0;
background: #000;
color: #fff;
font-family: 'Press Start 2P', 'Courier New', monospace;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
image-rendering: pixelated;
}
#game-wrapper {
position: relative;
width: 800px;
height: 800px;
display: flex;
flex-direction: column;
background: #000;
}
canvas {
width: 800px;
height: 500px;
background: #111;
image-rendering: pixelated;
border-top: 4px solid #fbbf24;
border-bottom: 4px solid #fbbf24;
}
.hud-top {
height: 80px;
display: flex;
justify-content: space-around;
align-items: center;
background: #000;
}
.hud-bottom {
flex-grow: 1;
background: #000;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.retro-box {
border: 3px solid #fbbf24;
border-radius: 12px;
padding: 8px 15px;
background: #000;
color: #fff;
font-size: 14px;
font-weight: bold;
display: flex;
align-items: center;
gap: 10px;
}
.integrity-section {
display: flex;
flex-direction: column;
gap: 5px;
}
.bar-outer {
width: 150px;
height: 12px;
border: 2px solid #fff;
background: #222;
}
#health-bar {
width: 100%;
height: 100%;
background: linear-gradient(to bottom, #f97316, #ea580c);
}
.center-title {
text-align: center;
}
.center-title h1 {
color: #fbbf24;
margin: 0;
font-size: 28px;
font-style: italic;
}
.speed-gauge {
border: 3px solid #fbbf24;
border-radius: 10px;
padding: 10px;
display: flex;
align-items: center;
gap: 10px;
}
#speed-val { font-size: 32px; }
#overlay {
position: absolute;
inset: 80px 0 220px 0;
background: rgba(0,0,0,0.6);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 100;
}
.btn-start {
background: #fbbf24;
color: #000;
border: none;
padding: 15px 40px;
font-family: inherit;
font-size: 20px;
cursor: pointer;
box-shadow: 4px 4px 0 #b45309;
}
.touch-controls {
position: fixed;
bottom: 20px;
width: 100%;
display: none;
justify-content: space-between;
padding: 0 40px;
pointer-events: none;
}
.t-btn {
width: 60px;
height: 60px;
border-radius: 50%;
background: rgba(255,255,255,0.2);
border: 3px solid #fff;
pointer-events: auto;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
}
@media (max-width: 800px) {
.touch-controls { display: flex; }
#game-wrapper { width: 100vw; height: 100vh; }
canvas { width: 100%; height: auto; }
}
</style>
</head>
<body>
<div id="game-wrapper">
<div class="hud-top">
<div class="retro-box">LEVEL 1</div>
<div class="retro-box" id="hud-pos">POS 1/12</div>
<div class="retro-box" id="hud-finish">FINISH 0m</div>
<div class="retro-box" id="hud-dist">DIST 0m</div>
</div>
<canvas id="gameCanvas"></canvas>
<div class="hud-bottom">
<div class="integrity-section">
<span style="font-size: 10px;">INTEGRITY</span>
<div class="bar-outer"><div id="health-bar"></div></div>
</div>
<div class="center-title">
<h1>ROAD RASH</h1>
<div style="font-size: 8px; color: #aaa; margin-top: 5px;">sub-tile, and survive!</div>
<button class="btn-start" id="startBtn" onclick="startGame()" style="margin-top: 15px; font-size: 12px; padding: 10px 20px;">Start Race</button>
</div>
<div class="speed-gauge">
<span id="speed-val">0</span>
<span style="font-size: 10px;">MPH</span>
</div>
</div>
<div id="overlay"></div>
<div class="touch-controls">
<div style="display:flex; gap:10px;"><div class="t-btn" id="t-l">L</div><div class="t-btn" id="t-r">R</div></div>
<div style="display:flex; gap:10px;"><div class="t-btn" id="t-k" style="color:red">K</div><div class="t-btn" id="t-g" style="color:green">GO</div></div>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const width = 400, height = 250;
canvas.width = width; canvas.height = height;
const roadWidth = 1800;
const segmentLength = 200;
const cameraHeight = 1000;
const cameraDepth = 1 / Math.tan((80 * Math.PI / 180) / 2);
const drawDistance = 300;
const playerZ = cameraHeight * cameraDepth;
let segments = [];
let player = { x: 0, z: 0, speed: 0, maxSpeed: 16000, accel: 4500, health: 100, kick: 0, kickT: 0, lean: 0 };
let trackLength = 0;
let keys = {};
let enemies = [];
let gameState = 'menu';
let lastTime = 0;
function addSegment(curve, y) {
let n = segments.length;
let startY = n > 0 ? segments[n-1].p2.world.y : 0;
segments.push({
index: n,
p1: { world: { x: 0, y: startY, z: n * segmentLength }, camera: {}, screen: {} },
p2: { world: { x: 0, y: y, z: (n + 1) * segmentLength }, camera: {}, screen: {} },
curve: curve,
grass: Math.floor(n / 3) % 2 ? '#e2e8f0' : '#cbd5e1',
road: Math.floor(n / 3) % 2 ? '#444' : '#3d3d3d',
objects: []
});
}
function resetTrack() {
segments = [];
for(let i=0; i<500; i++) {
addSegment(Math.sin(i/30)*3, Math.sin(i/40)*1500);
if (i % 5 === 0) {
segments[i].objects.push({ x: -2.5 - Math.random(), type: 'tree' });
segments[i].objects.push({ x: 2.5 + Math.random(), type: 'tree' });
}
}
trackLength = segments.length * segmentLength;
enemies = [];
const enemyColors = ['#ef4444', '#3b82f6', '#10b981', '#a855f7', '#f97316', '#ec4899'];
for(let i=0; i<11; i++) {
enemies.push({
z: 2000 + i*4000,
x: (Math.random()*1.4)-0.7,
speed: 8000 + Math.random()*4000,
color: enemyColors[i % enemyColors.length],
dead: false
});
}
}
function project(p, cameraX, cameraY, cameraZ) {
p.camera.x = p.world.x - cameraX;
p.camera.y = p.world.y - cameraY;
p.camera.z = p.world.z - cameraZ;
if (p.camera.z <= 0) {
p.screen.scale = 0;
return;
}
p.screen.scale = cameraDepth / p.camera.z;
p.screen.x = Math.round((width / 2) + (p.screen.scale * p.camera.x * width / 2));
p.screen.y = Math.round((height / 2) - (p.screen.scale * p.camera.y * height / 2));
p.screen.w = Math.round(p.screen.scale * roadWidth * width / 2);
}
function drawTree(x, y, scale) {
const w = 150 * scale, h = 400 * scale;
if (h < 1) return;
ctx.fillStyle = '#2d3748';
ctx.fillRect(x - w*0.1, y - h*0.2, w*0.2, h*0.2);
ctx.fillStyle = '#065f46';
ctx.beginPath();
ctx.moveTo(x - w*0.6, y - h*0.2);
ctx.lineTo(x + w*0.6, y - h*0.2);
ctx.lineTo(x, y - h);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.fillRect(x - w*0.4, y - h*0.22, w*0.8, h*0.05);
}
function drawBike(x, y, scale, color, kick = 0, isDead = false, lean = 0) {
ctx.save();
ctx.translate(x, y);
const s = scale * 160; // Increased size to match "heavy" look
if (s < 1) { ctx.restore(); return; }
if(isDead) { ctx.rotate(Math.PI/2); ctx.translate(0, -s*0.3); }
else { ctx.rotate(lean * 0.15); }
// Ground Shadow
ctx.fillStyle = 'rgba(0,0,0,0.4)';
ctx.beginPath(); ctx.ellipse(0, 0, s*0.45, s*0.1, 0, 0, Math.PI*2); ctx.fill();
// REAR WHEEL (Thick and dark)
ctx.fillStyle = '#000';
ctx.fillRect(-s*0.25, -s*0.4, s*0.5, s*0.4);
ctx.fillStyle = '#1a1a1a';
ctx.fillRect(-s*0.15, -s*0.3, s*0.3, s*0.2);
// REAR SWINGARM & EXHAUST
ctx.fillStyle = '#333';
ctx.fillRect(-s*0.3, -s*0.45, s*0.15, s*0.2); // Muffler
ctx.fillStyle = '#444';
ctx.fillRect(s*0.1, -s*0.35, s*0.15, s*0.08); // Exhaust tip
// ENGINE BLOCK
ctx.fillStyle = '#222';
ctx.fillRect(-s*0.2, -s*0.65, s*0.4, s*0.25);
ctx.fillStyle = '#333';
ctx.fillRect(-s*0.15, -s*0.6, s*0.3, s*0.1); // Fins
// MAIN BODYWORK (The heavy aerodynamic shell)
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(-s*0.35, -s*0.85); // Back seat
ctx.lineTo(s*0.35, -s*0.85); // Front cowl
ctx.lineTo(s*0.25, -s*0.4); // Bottom front
ctx.lineTo(-s*0.25, -s*0.4); // Bottom back
ctx.fill();
// FRONT WINDSCREEN / DEFLECTOR
ctx.fillStyle = '#111'; // Dark plastic
ctx.fillRect(s*0.05, -s*0.92, s*0.25, s*0.1);
// HANDLEBARS / FORKS
ctx.fillStyle = '#111';
ctx.fillRect(s*0.1, -s*1.05, s*0.25, s*0.05); // Bars
ctx.fillStyle = '#333';
ctx.fillRect(s*0.2, -s*0.8, s*0.05, s*0.5); // Fork tube
// RIDER (Tucked in)
ctx.fillStyle = '#111'; // Leathers
ctx.beginPath();
ctx.moveTo(-s*0.25, -s*0.85);
ctx.lineTo(s*0.1, -s*0.85);
ctx.lineTo(s*0.05, -s*1.3);
ctx.lineTo(-s*0.2, -s*1.2);
ctx.fill();
// HELMET
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(-s*0.05, -s*1.4, s*0.14, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#000'; // Visor
ctx.fillRect(-s*0.1, -s*1.45, s*0.2, s*0.08);
// KICK ATTACK
if(kick > 0) {
ctx.fillStyle = '#000'; // Leg
ctx.fillRect(s*0.2, -s*0.7, s*0.8, s*0.15);
ctx.fillStyle = '#111'; // Boot
ctx.fillRect(s*0.85, -s*0.75, s*0.15, s*0.2);
}
ctx.restore();
}
function update(dt) {
if (gameState !== 'playing') return;
if (keys['ArrowUp'] || keys['gas']) player.speed += player.accel * dt;
else if (keys['ArrowDown']) player.speed -= 10000 * dt;
else player.speed -= 2000 * dt;
player.lean = 0;
if (keys['ArrowLeft'] || keys['left']) { player.x -= 2.5 * dt; player.lean = -1; }
if (keys['ArrowRight'] || keys['right']) { player.x += 2.5 * dt; player.lean = 1; }
if ((keys[' '] || keys['kick']) && player.kickT <= 0) { player.kick = 1; player.kickT = 0.4; }
if (player.kickT > 0) { player.kickT -= dt; if(player.kickT <= 0) player.kick = 0; }
player.speed = Math.max(0, Math.min(player.speed, player.maxSpeed));
player.z += player.speed * dt;
if (player.z >= trackLength) {
player.z = 0; // Win condition or loop
}
let playerPos = 12;
enemies.forEach(e => {
if(!e.dead) {
e.z += e.speed * dt;
if(e.z > trackLength) e.z -= trackLength;
if(e.z < player.z) playerPos--;
if(Math.abs(player.z - e.z) < 250 && Math.abs(player.x - e.x) < 0.4 && player.kick) e.dead = true;
} else {
playerPos--;
}
});
document.getElementById('speed-val').innerText = Math.floor(player.speed / 100);
document.getElementById('health-bar').style.width = player.health + '%';
document.getElementById('hud-finish').innerText = `FINISH ${Math.max(0, Math.floor((trackLength - player.z)/100))}m`;
document.getElementById('hud-dist').innerText = `DIST ${Math.floor(player.z/100)}m`;
document.getElementById('hud-pos').innerText = `POS ${playerPos}/12`;
if (player.health <= 0) {
gameState = 'menu';
document.getElementById('overlay').style.display = 'flex';
document.getElementById('startBtn').style.display = 'block';
}
}
function render() {
ctx.clearRect(0,0,width,height);
// Sky & Mountains
ctx.fillStyle = '#7dd3fc'; ctx.fillRect(0,0,width,height/2);
for(let i=0; i<6; i++) {
ctx.fillStyle = '#94a3b8';
ctx.beginPath();
ctx.moveTo(i*100-80, height/2);
ctx.lineTo(i*100+20, height/2-80);
ctx.lineTo(i*100+120, height/2);
ctx.fill();
}
if (segments.length === 0) return;
let baseIdx = Math.floor(player.z / segmentLength) % segments.length;
let percent = (player.z % segmentLength) / segmentLength;
let startSeg = segments[baseIdx];
if (!startSeg) return;
let playerY = startSeg.p1.world.y + (startSeg.p2.world.y - startSeg.p1.world.y) * percent;
let x = 0, dx = -(startSeg.curve * percent);
for (let n = 0; n < drawDistance; n++) {
let s = segments[(baseIdx + n) % segments.length];
if (!s) continue;
project(s.p1, player.x * roadWidth - x, playerY + cameraHeight, player.z);
project(s.p2, player.x * roadWidth - x - dx, playerY + cameraHeight, player.z);
x += dx;
dx += s.curve;
if (s.p1.camera.z <= 0) continue;
ctx.fillStyle = s.grass;
ctx.fillRect(0, s.p2.screen.y, width, s.p1.screen.y - s.p2.screen.y);
let p1 = s.p1.screen, p2 = s.p2.screen;
ctx.fillStyle = s.road;
ctx.beginPath();
ctx.moveTo(p1.x-p1.w, p1.y);
ctx.lineTo(p2.x-p2.w, p2.y);
ctx.lineTo(p2.x+p2.w, p2.y);
ctx.lineTo(p1.x+p1.w, p1.y);
ctx.fill();
if (s.index % 6 < 3) {
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.moveTo(p1.x-p1.w*0.02, p1.y);
ctx.lineTo(p2.x-p2.w*0.02, p2.y);
ctx.lineTo(p2.x+p2.w*0.02, p2.y);
ctx.lineTo(p1.x+p1.w*0.02, p1.y);
ctx.fill();
}
}
for (let n = drawDistance - 1; n >= 0; n--) {
let s = segments[(baseIdx + n) % segments.length];
if (!s) continue;
let scale = s.p1.screen.scale * width/2;
s.objects.forEach(o => {
if (o.type === 'tree') drawTree(s.p1.screen.x + (s.p1.screen.w * o.x), s.p1.screen.y, scale/1000);
});
enemies.forEach(e => {
if (Math.floor(e.z / segmentLength) === s.index) {
let p = { world: { x: e.x*roadWidth, y: s.p1.world.y, z: e.z }, camera: {}, screen: {} };
project(p, player.x * roadWidth, playerY + cameraHeight, player.z);
if (p.camera.z > 0) drawBike(p.screen.x, p.screen.y, p.screen.scale * width/2, e.color, 0, e.dead);
}
});
}
drawBike(width/2, height - 10, (cameraDepth/playerZ)*(width/2), '#fbbf24', player.kick, false, player.lean);
}
function startGame() {
resetTrack();
player.z = 0;
player.health = 100;
player.speed = 0;
gameState = 'playing';
document.getElementById('overlay').style.display = 'none';
document.getElementById('startBtn').style.display = 'none';
lastTime = performance.now();
gameLoop();
}
function gameLoop() {
let now = performance.now();
update((now-lastTime)/1000);
render();
lastTime = now;
if(gameState === 'playing') requestAnimationFrame(gameLoop);
}
window.addEventListener('keydown', e => keys[e.key] = true);
window.addEventListener('keyup', e => keys[e.key] = false);
const touch = (id, k) => {
const el = document.getElementById(id);
if (!el) return;
el.addEventListener('touchstart', e => { e.preventDefault(); keys[k] = true; });
el.addEventListener('touchend', e => { e.preventDefault(); keys[k] = false; });
}
touch('t-l', 'left'); touch('t-r', 'right'); touch('t-g', 'gas'); touch('t-k', 'kick');
resetTrack();
render();
</script>
</body>
</html>