Files
2026-04-30 02:14:25 +00:00

703 lines
24 KiB
HTML

<!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>Night Racer - Racing Example</title>
<style>
* { box-sizing: border-box; }
body { margin: 0; overflow: hidden; background: #0a0a15; touch-action: none; }
canvas { display: block; }
#ui {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-family: 'Courier New', monospace;
font-size: 16px;
text-shadow: 0 0 10px #0ff;
z-index: 10;
}
#ui div { margin-bottom: 5px; }
#speedometer {
position: absolute;
bottom: 20px;
right: 20px;
color: #0ff;
font-family: 'Courier New', monospace;
font-size: 48px;
font-weight: bold;
text-shadow: 0 0 20px #0ff;
}
#loading {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: linear-gradient(135deg, #0a0a15, #1a1a2e);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #0ff;
font-family: 'Courier New', monospace;
z-index: 100;
}
#loading h1 {
margin-bottom: 1rem;
text-shadow: 0 0 20px #0ff;
font-size: 2.5rem;
}
.loading-bar {
width: 250px;
height: 4px;
background: #1a1a2e;
border: 1px solid #0ff;
}
.loading-bar-fill {
height: 100%;
background: #0ff;
width: 0%;
transition: width 0.3s;
box-shadow: 0 0 10px #0ff;
}
</style>
</head>
<body>
<div id="loading">
<h1>NIGHT RACER</h1>
<div class="loading-bar"><div class="loading-bar-fill" id="loadingFill"></div></div>
<p id="loadingText">Initializing...</p>
</div>
<canvas id="gameCanvas"></canvas>
<div id="ui">
<div>SCORE: <span id="score">0</span></div>
<div>DISTANCE: <span id="distance">0</span>m</div>
</div>
<div id="speedometer"><span id="speed">0</span> km/h</div>
<!-- Asset Libraries -->
<script src="/assets/backgrounds/backgroundEngine.js"></script>
<script src="/assets/backgrounds/effects.js"></script>
<script src="/assets/backgrounds/themes/urban.js"></script>
<script src="/assets/backgrounds/backgroundCatalog.js"></script>
<script src="/assets/music/moodCategories.js"></script>
<script src="/assets/music/musicEngine.js"></script>
<script src="/assets/music/library/songs1-80.js"></script>
<script src="/assets/avatar/avatarData.js"></script>
<script src="/assets/avatar/accessories.js"></script>
<script src="/assets/avatar/animations.js"></script>
<script src="/assets/avatar/avatarRenderer.js"></script>
<script src="/assets/avatar/accessoryRenderer.js"></script>
<script src="/assets/avatar/avatarSystem.js"></script>
<script src="/assets/avatar/contexts/vehicles.js"></script>
<script src="/assets/avatar/contextRenderer.js"></script>
<script>
// ═══════════════════════════════════════════════════════════════════════
// RACER EXAMPLE - Vehicle Context Demo
// ═══════════════════════════════════════════════════════════════════════
// Demonstrates: HEAD_AND_SHOULDERS avatar, race-car context,
// urban/neonDistrict background, Intense music mood
const GAME_CONFIG = {
title: "Night Racer",
assets: {
music: { mood: "Intense", energy: 9, enabled: true },
background: { theme: "urban", variant: "neonDistrict", animated: true },
avatarContext: { mode: "HEAD_AND_SHOULDERS", context: "race-car", showAccessories: true }
},
gameplay: {
baseSpeed: 300,
maxSpeed: 600,
acceleration: 150,
deceleration: 200,
laneWidth: 100,
lanes: 3,
obstacleSpawnRate: 1200,
coinSpawnRate: 800
}
};
const playerAvatar = {
faceShape: 'square',
skinTone: 'tan',
hairStyle: 'short',
hairColor: '#1a1a1a',
eyeShape: 'sharp',
eyeColor: '#2ecc71',
noseShape: 'pointed',
mouthShape: 'serious'
};
// ═══════════════════════════════════════════════════════════════════════
// SETUP
// ═══════════════════════════════════════════════════════════════════════
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);
let state = {
playerLane: 1,
playerX: 0,
targetX: 0,
speed: 0,
distance: 0,
animTime: 0,
score: 0,
gameOver: false,
paused: false,
lastTime: 0,
obstacles: [],
coins: [],
roadLines: [],
buildings: [],
lastObstacle: 0,
lastCoin: 0
};
const keys = {};
// ═══════════════════════════════════════════════════════════════════════
// LOADING
// ═══════════════════════════════════════════════════════════════════════
function updateLoading(progress, text) {
document.getElementById('loadingFill').style.width = progress + '%';
document.getElementById('loadingText').textContent = text;
}
function hideLoading() {
document.getElementById('loading').style.display = 'none';
}
async function loadAssets() {
updateLoading(20, 'Loading city...');
await new Promise(r => setTimeout(r, 200));
updateLoading(40, 'Loading music...');
await new Promise(r => setTimeout(r, 200));
updateLoading(60, 'Loading vehicle...');
await new Promise(r => setTimeout(r, 200));
updateLoading(80, 'Starting engine...');
await new Promise(r => setTimeout(r, 200));
updateLoading(100, 'GO!');
await new Promise(r => setTimeout(r, 300));
hideLoading();
initGame();
state.lastTime = performance.now();
requestAnimationFrame(gameLoop);
if (window.MusicEngine && GAME_CONFIG.assets.music.enabled) {
try {
window.MusicEngine.playByMood(GAME_CONFIG.assets.music.mood, GAME_CONFIG.assets.music.energy);
} catch(e) {}
}
}
// ═══════════════════════════════════════════════════════════════════════
// GAME LOGIC
// ═══════════════════════════════════════════════════════════════════════
function initGame() {
const { laneWidth, lanes } = GAME_CONFIG.gameplay;
const roadWidth = laneWidth * lanes;
const roadX = canvas.width / 2 - roadWidth / 2;
state.playerLane = 1;
state.playerX = roadX + laneWidth * state.playerLane + laneWidth / 2;
state.targetX = state.playerX;
state.speed = GAME_CONFIG.gameplay.baseSpeed;
state.distance = 0;
state.score = 0;
state.obstacles = [];
state.coins = [];
state.lastObstacle = 0;
state.lastCoin = 0;
// Initialize road lines
state.roadLines = [];
for (let i = 0; i < 15; i++) {
state.roadLines.push({
y: i * 80,
lane: 0
});
state.roadLines.push({
y: i * 80,
lane: 1
});
}
// Initialize buildings
state.buildings = [];
for (let i = 0; i < 20; i++) {
state.buildings.push({
x: Math.random() > 0.5 ? -50 - Math.random() * 100 : canvas.width + 50 + Math.random() * 100,
y: i * 100 - 500,
width: 60 + Math.random() * 100,
height: 150 + Math.random() * 300,
color: `hsl(${260 + Math.random() * 40}, 50%, ${15 + Math.random() * 15}%)`
});
}
updateUI();
}
function getLaneX(lane) {
const { laneWidth, lanes } = GAME_CONFIG.gameplay;
const roadWidth = laneWidth * lanes;
const roadX = canvas.width / 2 - roadWidth / 2;
return roadX + laneWidth * lane + laneWidth / 2;
}
function updateGame(dt) {
const { baseSpeed, maxSpeed, acceleration, deceleration, laneWidth, lanes, obstacleSpawnRate, coinSpawnRate } = GAME_CONFIG.gameplay;
// Speed control
if (keys['ArrowUp'] || keys['KeyW']) {
state.speed = Math.min(state.speed + acceleration * dt, maxSpeed);
} else {
state.speed = Math.max(state.speed - deceleration * dt * 0.3, baseSpeed);
}
// Lane switching
if (keys['ArrowLeft'] || keys['KeyA']) {
if (!keys._leftPressed) {
keys._leftPressed = true;
state.playerLane = Math.max(0, state.playerLane - 1);
state.targetX = getLaneX(state.playerLane);
}
} else {
keys._leftPressed = false;
}
if (keys['ArrowRight'] || keys['KeyD']) {
if (!keys._rightPressed) {
keys._rightPressed = true;
state.playerLane = Math.min(lanes - 1, state.playerLane + 1);
state.targetX = getLaneX(state.playerLane);
}
} else {
keys._rightPressed = false;
}
// Smooth lane movement
state.playerX += (state.targetX - state.playerX) * 10 * dt;
// Update distance
state.distance += state.speed * dt * 0.01;
// Spawn obstacles
state.lastObstacle += dt * 1000;
if (state.lastObstacle > obstacleSpawnRate * (baseSpeed / state.speed)) {
state.lastObstacle = 0;
const lane = Math.floor(Math.random() * lanes);
state.obstacles.push({
x: getLaneX(lane),
y: -100,
lane: lane,
type: Math.random() > 0.7 ? 'truck' : 'car',
color: `hsl(${Math.random() * 360}, 70%, 50%)`
});
}
// Spawn coins
state.lastCoin += dt * 1000;
if (state.lastCoin > coinSpawnRate) {
state.lastCoin = 0;
const lane = Math.floor(Math.random() * lanes);
// Don't spawn on obstacles
const blocked = state.obstacles.some(o => o.lane === lane && o.y < 200);
if (!blocked) {
state.coins.push({
x: getLaneX(lane),
y: -50,
lane: lane,
rotation: 0
});
}
}
// Update obstacles
state.obstacles = state.obstacles.filter(obs => {
obs.y += state.speed * dt;
// Collision check
const playerY = canvas.height - 150;
if (Math.abs(obs.x - state.playerX) < 40 &&
Math.abs(obs.y - playerY) < 60) {
endGame();
}
return obs.y < canvas.height + 150;
});
// Update coins
state.coins = state.coins.filter(coin => {
coin.y += state.speed * dt;
coin.rotation += dt * 5;
// Collection check
const playerY = canvas.height - 150;
if (Math.abs(coin.x - state.playerX) < 50 &&
Math.abs(coin.y - playerY) < 50) {
state.score += 10;
return false;
}
return coin.y < canvas.height + 50;
});
// Update road lines
state.roadLines.forEach(line => {
line.y += state.speed * dt;
if (line.y > canvas.height) {
line.y -= 15 * 80;
}
});
// Update buildings
state.buildings.forEach(b => {
b.y += state.speed * dt * 0.3;
if (b.y > canvas.height + 200) {
b.y -= 20 * 100 + 500;
b.height = 150 + Math.random() * 300;
}
});
// Score over time
state.score += Math.floor(state.speed * dt * 0.05);
state.animTime += dt * 1000;
updateUI();
}
function renderGame() {
// Dark background
ctx.fillStyle = '#0a0a15';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// City skyline (buildings)
state.buildings.forEach(b => {
// Building
ctx.fillStyle = b.color;
const bx = b.x < 0 ? b.x : b.x;
ctx.fillRect(bx, b.y, b.width, b.height);
// Windows
ctx.fillStyle = Math.random() > 0.3 ? '#ffff88' : '#333';
for (let wy = b.y + 20; wy < b.y + b.height - 20; wy += 30) {
for (let wx = bx + 10; wx < bx + b.width - 10; wx += 20) {
if (Math.random() > 0.4) {
ctx.fillRect(wx, wy, 10, 15);
}
}
}
});
const { laneWidth, lanes } = GAME_CONFIG.gameplay;
const roadWidth = laneWidth * lanes + 40;
const roadX = canvas.width / 2 - roadWidth / 2;
// Road
ctx.fillStyle = '#1a1a2a';
ctx.fillRect(roadX, 0, roadWidth, canvas.height);
// Road edges (neon)
ctx.strokeStyle = '#ff00ff';
ctx.lineWidth = 3;
ctx.shadowColor = '#ff00ff';
ctx.shadowBlur = 15;
ctx.beginPath();
ctx.moveTo(roadX, 0);
ctx.lineTo(roadX, canvas.height);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(roadX + roadWidth, 0);
ctx.lineTo(roadX + roadWidth, canvas.height);
ctx.stroke();
ctx.shadowBlur = 0;
// Lane dividers
ctx.strokeStyle = '#0ff';
ctx.lineWidth = 2;
ctx.shadowColor = '#0ff';
ctx.shadowBlur = 10;
state.roadLines.forEach(line => {
const x1 = roadX + 20 + laneWidth * (line.lane + 1);
ctx.beginPath();
ctx.moveTo(x1, line.y);
ctx.lineTo(x1, line.y + 40);
ctx.stroke();
});
ctx.shadowBlur = 0;
// Coins
state.coins.forEach(coin => {
ctx.save();
ctx.translate(coin.x, coin.y);
ctx.rotate(coin.rotation);
ctx.fillStyle = '#FFD700';
ctx.shadowColor = '#FFD700';
ctx.shadowBlur = 15;
ctx.beginPath();
ctx.arc(0, 0, 15, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#FFA500';
ctx.beginPath();
ctx.arc(-4, -4, 5, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
ctx.restore();
});
// Obstacles (other cars)
state.obstacles.forEach(obs => {
ctx.save();
ctx.translate(obs.x, obs.y);
if (obs.type === 'truck') {
// Truck
ctx.fillStyle = obs.color;
ctx.fillRect(-25, -60, 50, 100);
ctx.fillStyle = '#333';
ctx.fillRect(-20, -55, 40, 25);
// Wheels
ctx.fillStyle = '#111';
ctx.fillRect(-28, -40, 8, 20);
ctx.fillRect(20, -40, 8, 20);
ctx.fillRect(-28, 20, 8, 20);
ctx.fillRect(20, 20, 8, 20);
} else {
// Car
ctx.fillStyle = obs.color;
ctx.fillRect(-20, -40, 40, 70);
ctx.fillStyle = '#333';
ctx.fillRect(-15, -35, 30, 20);
// Tail lights
ctx.fillStyle = '#ff0000';
ctx.shadowColor = '#ff0000';
ctx.shadowBlur = 10;
ctx.fillRect(-18, 25, 8, 5);
ctx.fillRect(10, 25, 8, 5);
ctx.shadowBlur = 0;
}
ctx.restore();
});
// Player car with avatar
const playerY = canvas.height - 150;
ctx.save();
ctx.translate(state.playerX, playerY);
// Car body
ctx.fillStyle = '#e74c3c';
ctx.shadowColor = '#e74c3c';
ctx.shadowBlur = 20;
// Main body
ctx.beginPath();
ctx.moveTo(-30, 50);
ctx.lineTo(-35, 20);
ctx.lineTo(-30, -30);
ctx.lineTo(-20, -50);
ctx.lineTo(20, -50);
ctx.lineTo(30, -30);
ctx.lineTo(35, 20);
ctx.lineTo(30, 50);
ctx.closePath();
ctx.fill();
ctx.shadowBlur = 0;
// Windshield
ctx.fillStyle = '#1a1a2e';
ctx.beginPath();
ctx.moveTo(-20, -45);
ctx.lineTo(-25, -15);
ctx.lineTo(25, -15);
ctx.lineTo(20, -45);
ctx.closePath();
ctx.fill();
// Avatar in car (head and shoulders visible)
if (window.ContextRenderer) {
ctx.save();
ctx.translate(-32, -65);
window.ContextRenderer.render(
ctx,
GAME_CONFIG.assets.avatarContext.context,
playerAvatar,
32, 32,
'idle',
state.animTime
);
ctx.restore();
} else if (window.AvatarSystem) {
ctx.save();
ctx.translate(-32, -65);
window.AvatarSystem.render(
ctx,
playerAvatar,
'HEAD_AND_SHOULDERS',
'idle',
state.animTime
);
ctx.restore();
}
// Headlights
ctx.fillStyle = '#fff';
ctx.shadowColor = '#fff';
ctx.shadowBlur = 30;
ctx.beginPath();
ctx.arc(-18, -45, 5, 0, Math.PI * 2);
ctx.arc(18, -45, 5, 0, Math.PI * 2);
ctx.fill();
// Light beams
ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
ctx.beginPath();
ctx.moveTo(-20, -50);
ctx.lineTo(-40, -200);
ctx.lineTo(0, -200);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(20, -50);
ctx.lineTo(0, -200);
ctx.lineTo(40, -200);
ctx.closePath();
ctx.fill();
ctx.shadowBlur = 0;
ctx.restore();
// Speed lines effect at high speed
if (state.speed > 400) {
ctx.strokeStyle = `rgba(0, 255, 255, ${(state.speed - 400) / 400})`;
ctx.lineWidth = 2;
for (let i = 0; i < 10; i++) {
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x, y + 50 + state.speed * 0.2);
ctx.stroke();
}
}
}
// ═══════════════════════════════════════════════════════════════════════
// UTILITIES
// ═══════════════════════════════════════════════════════════════════════
function updateUI() {
document.getElementById('score').textContent = state.score;
document.getElementById('distance').textContent = Math.floor(state.distance);
document.getElementById('speed').textContent = Math.floor(state.speed * 0.6);
}
function endGame() {
state.gameOver = true;
if (window.parent !== window) {
window.parent.postMessage({ type: 'gameOver', score: state.score }, '*');
}
setTimeout(() => {
if (confirm(`GAME OVER\n\nScore: ${state.score}\nDistance: ${Math.floor(state.distance)}m\n\nRace again?`)) {
state.gameOver = false;
initGame();
state.lastTime = performance.now();
requestAnimationFrame(gameLoop);
}
}, 100);
}
// ═══════════════════════════════════════════════════════════════════════
// INPUT
// ═══════════════════════════════════════════════════════════════════════
document.addEventListener('keydown', e => {
keys[e.code] = true;
if (e.code === 'Escape') {
state.paused = !state.paused;
if (!state.paused) {
state.lastTime = performance.now();
requestAnimationFrame(gameLoop);
}
}
});
document.addEventListener('keyup', e => {
keys[e.code] = false;
});
// Touch controls
let touchStartX = 0;
canvas.addEventListener('touchstart', e => {
e.preventDefault();
touchStartX = e.touches[0].clientX;
keys['ArrowUp'] = true;
}, { passive: false });
canvas.addEventListener('touchmove', e => {
e.preventDefault();
const x = e.touches[0].clientX;
const dx = x - touchStartX;
if (dx < -50) {
keys['ArrowLeft'] = true;
keys['ArrowRight'] = false;
touchStartX = x;
} else if (dx > 50) {
keys['ArrowRight'] = true;
keys['ArrowLeft'] = false;
touchStartX = x;
}
}, { passive: false });
canvas.addEventListener('touchend', e => {
keys['ArrowUp'] = false;
keys['ArrowLeft'] = false;
keys['ArrowRight'] = false;
});
// ═══════════════════════════════════════════════════════════════════════
// GAME LOOP
// ═══════════════════════════════════════════════════════════════════════
function gameLoop(time) {
if (state.gameOver || state.paused) return;
const dt = Math.min((time - state.lastTime) / 1000, 0.05);
state.lastTime = time;
updateGame(dt);
renderGame();
requestAnimationFrame(gameLoop);
}
window.pauseGame = () => { state.paused = true; };
window.resumeGame = () => {
state.paused = false;
state.lastTime = performance.now();
requestAnimationFrame(gameLoop);
};
loadAssets();
</script>
</body>
</html>