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

534 lines
19 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>Sky Glide - Flappy Example</title>
<style>
* { box-sizing: border-box; }
body { margin: 0; overflow: hidden; background: #1a1a2e; touch-action: none; }
canvas { display: block; }
#ui {
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
color: white;
font-family: Arial, sans-serif;
font-size: 36px;
font-weight: bold;
text-shadow: 2px 2px 4px #000;
z-index: 10;
}
#loading {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: linear-gradient(135deg, #87CEEB, #4682B4);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
font-family: Arial, sans-serif;
z-index: 100;
}
#loading h1 { margin-bottom: 1rem; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); }
.loading-bar {
width: 200px;
height: 8px;
background: rgba(255,255,255,0.3);
border-radius: 4px;
overflow: hidden;
}
.loading-bar-fill {
height: 100%;
background: white;
width: 0%;
transition: width 0.3s;
}
#startScreen {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
font-family: Arial, sans-serif;
z-index: 50;
}
#startScreen h2 { font-size: 2rem; margin-bottom: 1rem; }
#startScreen p { font-size: 1.25rem; opacity: 0.9; }
</style>
</head>
<body>
<div id="loading">
<h1>Sky Glide</h1>
<div class="loading-bar"><div class="loading-bar-fill" id="loadingFill"></div></div>
<p id="loadingText">Loading assets...</p>
</div>
<div id="startScreen">
<h2>Tap or Press Space to Start</h2>
<p>Avoid the pipes!</p>
</div>
<canvas id="gameCanvas"></canvas>
<div id="ui"><span id="score">0</span></div>
<!-- Asset Libraries -->
<script src="/assets/backgrounds/backgroundEngine.js"></script>
<script src="/assets/backgrounds/effects.js"></script>
<script src="/assets/backgrounds/themes/sky.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>
// ═══════════════════════════════════════════════════════════════════════
// FLAPPY EXAMPLE - Head Only Avatar Demo
// ═══════════════════════════════════════════════════════════════════════
// Demonstrates: HEAD_ONLY avatar, flappy-style context,
// sky/clouds background, Playful music mood
const GAME_CONFIG = {
title: "Sky Glide",
assets: {
music: { mood: "Playful", energy: 5, enabled: true },
background: { theme: "sky", variant: "clouds", animated: true },
avatarContext: { mode: "HEAD_ONLY", context: "flappy-style", showAccessories: true }
},
gameplay: {
gravity: 1200,
flapForce: 400,
pipeSpeed: 200,
pipeGap: 180,
pipeWidth: 70,
pipeSpawnRate: 1800
}
};
const playerAvatar = {
faceShape: 'round',
skinTone: 'light',
hairStyle: 'spiky',
hairColor: '#FFD700',
eyeShape: 'round',
eyeColor: '#3498db',
noseShape: 'small',
mouthShape: 'smile'
};
// ═══════════════════════════════════════════════════════════════════════
// 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 = {
playerY: 0,
playerVelY: 0,
playerRotation: 0,
animTime: 0,
score: 0,
highScore: parseInt(localStorage.getItem('skyglide_high') || '0'),
gameOver: false,
started: false,
paused: false,
lastTime: 0,
pipes: [],
lastPipeTime: 0,
clouds: []
};
// ═══════════════════════════════════════════════════════════════════════
// LOADING
// ═══════════════════════════════════════════════════════════════════════
function updateLoading(progress, text) {
document.getElementById('loadingFill').style.width = progress + '%';
document.getElementById('loadingText').textContent = text;
}
function hideLoading() {
document.getElementById('loading').style.display = 'none';
document.getElementById('startScreen').style.display = 'flex';
}
async function loadAssets() {
updateLoading(25, 'Loading sky...');
await new Promise(r => setTimeout(r, 200));
updateLoading(50, 'Loading music...');
await new Promise(r => setTimeout(r, 200));
updateLoading(75, 'Loading avatar...');
await new Promise(r => setTimeout(r, 200));
updateLoading(100, 'Ready!');
await new Promise(r => setTimeout(r, 300));
hideLoading();
initGame();
state.lastTime = performance.now();
requestAnimationFrame(gameLoop);
}
// ═══════════════════════════════════════════════════════════════════════
// GAME LOGIC
// ═══════════════════════════════════════════════════════════════════════
function initGame() {
state.playerY = canvas.height / 2;
state.playerVelY = 0;
state.playerRotation = 0;
state.pipes = [];
state.lastPipeTime = 0;
state.score = 0;
state.gameOver = false;
updateScoreDisplay();
// Initialize clouds for parallax
state.clouds = [];
for (let i = 0; i < 5; i++) {
state.clouds.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height * 0.6,
size: 50 + Math.random() * 100,
speed: 20 + Math.random() * 30
});
}
}
function flap() {
if (state.gameOver) {
state.score = 0;
state.gameOver = false;
state.started = false;
document.getElementById('startScreen').style.display = 'flex';
initGame();
return;
}
if (!state.started) {
state.started = true;
document.getElementById('startScreen').style.display = 'none';
// Start music
if (window.MusicEngine && GAME_CONFIG.assets.music.enabled) {
try {
window.MusicEngine.playByMood(GAME_CONFIG.assets.music.mood, GAME_CONFIG.assets.music.energy);
} catch(e) {}
}
}
state.playerVelY = -GAME_CONFIG.gameplay.flapForce;
}
function updateGame(dt) {
if (!state.started || state.gameOver) {
// Bob animation when not playing
state.playerY = canvas.height / 2 + Math.sin(state.animTime / 300) * 20;
state.animTime += dt * 1000;
return;
}
const { gravity, pipeSpeed, pipeGap, pipeWidth, pipeSpawnRate } = GAME_CONFIG.gameplay;
// Apply gravity
state.playerVelY += gravity * dt;
state.playerY += state.playerVelY * dt;
// Rotation based on velocity
state.playerRotation = Math.min(Math.max(state.playerVelY / 10, -30), 90);
// Spawn pipes
state.lastPipeTime += dt * 1000;
if (state.lastPipeTime > pipeSpawnRate) {
state.lastPipeTime = 0;
const gapY = 150 + Math.random() * (canvas.height - 350);
state.pipes.push({
x: canvas.width + pipeWidth,
gapY: gapY,
passed: false
});
}
// Update pipes
state.pipes = state.pipes.filter(pipe => {
pipe.x -= pipeSpeed * dt;
// Score when passing
if (!pipe.passed && pipe.x + pipeWidth < canvas.width / 3) {
pipe.passed = true;
state.score++;
updateScoreDisplay();
}
return pipe.x > -pipeWidth;
});
// Update clouds
state.clouds.forEach(cloud => {
cloud.x -= cloud.speed * dt;
if (cloud.x + cloud.size < 0) {
cloud.x = canvas.width + cloud.size;
cloud.y = Math.random() * canvas.height * 0.6;
}
});
// Collision detection
const playerX = canvas.width / 3;
const playerSize = 32;
// Ground/ceiling
if (state.playerY < playerSize || state.playerY > canvas.height - playerSize) {
endGame();
return;
}
// Pipes
state.pipes.forEach(pipe => {
const pipeLeft = pipe.x;
const pipeRight = pipe.x + pipeWidth;
if (playerX + playerSize > pipeLeft && playerX - playerSize < pipeRight) {
// Check top pipe
if (state.playerY - playerSize < pipe.gapY - pipeGap / 2) {
endGame();
}
// Check bottom pipe
if (state.playerY + playerSize > pipe.gapY + pipeGap / 2) {
endGame();
}
}
});
state.animTime += dt * 1000;
}
function renderGame() {
// Sky gradient background
const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
gradient.addColorStop(0, '#87CEEB');
gradient.addColorStop(0.5, '#B0E0E6');
gradient.addColorStop(1, '#E0F7FA');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Clouds (parallax)
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
state.clouds.forEach(cloud => {
drawCloud(cloud.x, cloud.y, cloud.size);
});
// Pipes
state.pipes.forEach(pipe => {
const { pipeWidth, pipeGap } = GAME_CONFIG.gameplay;
const topHeight = pipe.gapY - pipeGap / 2;
const bottomY = pipe.gapY + pipeGap / 2;
// Top pipe
ctx.fillStyle = '#2ECC71';
ctx.fillRect(pipe.x, 0, pipeWidth, topHeight);
ctx.fillStyle = '#27AE60';
ctx.fillRect(pipe.x - 5, topHeight - 30, pipeWidth + 10, 30);
// Bottom pipe
ctx.fillStyle = '#2ECC71';
ctx.fillRect(pipe.x, bottomY, pipeWidth, canvas.height - bottomY);
ctx.fillStyle = '#27AE60';
ctx.fillRect(pipe.x - 5, bottomY, pipeWidth + 10, 30);
// Pipe highlights
ctx.fillStyle = '#58D68D';
ctx.fillRect(pipe.x + 5, 0, 10, topHeight - 30);
ctx.fillRect(pipe.x + 5, bottomY + 30, 10, canvas.height - bottomY - 30);
});
// Ground
ctx.fillStyle = '#DEB887';
ctx.fillRect(0, canvas.height - 30, canvas.width, 30);
ctx.fillStyle = '#8B4513';
ctx.fillRect(0, canvas.height - 35, canvas.width, 5);
// Player
const playerX = canvas.width / 3;
ctx.save();
ctx.translate(playerX, state.playerY);
ctx.rotate(state.playerRotation * Math.PI / 180);
if (window.ContextRenderer) {
// Use context renderer for flappy style
ctx.translate(-40, -40);
window.ContextRenderer.render(
ctx,
GAME_CONFIG.assets.avatarContext.context,
playerAvatar,
40, 40,
'idle',
state.animTime
);
} else if (window.AvatarSystem) {
ctx.translate(-32, -32);
window.AvatarSystem.render(
ctx,
playerAvatar,
'HEAD_ONLY',
'idle',
state.animTime
);
} else {
// Fallback bird
ctx.fillStyle = '#FFD700';
ctx.beginPath();
ctx.ellipse(0, 0, 30, 25, 0, 0, Math.PI * 2);
ctx.fill();
// Eye
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(10, -5, 10, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(12, -5, 5, 0, Math.PI * 2);
ctx.fill();
// Beak
ctx.fillStyle = '#FF6B00';
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(40, 5);
ctx.lineTo(25, 10);
ctx.closePath();
ctx.fill();
// Wing
ctx.fillStyle = '#FFA500';
ctx.beginPath();
ctx.ellipse(-5, 5, 15, 10, Math.sin(state.animTime / 50) * 0.3, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
// Game over overlay
if (state.gameOver) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.font = 'bold 48px Arial';
ctx.textAlign = 'center';
ctx.fillText('Game Over', canvas.width / 2, canvas.height / 2 - 60);
ctx.font = '32px Arial';
ctx.fillText(`Score: ${state.score}`, canvas.width / 2, canvas.height / 2);
ctx.fillText(`Best: ${state.highScore}`, canvas.width / 2, canvas.height / 2 + 40);
ctx.font = '24px Arial';
ctx.fillText('Tap to Restart', canvas.width / 2, canvas.height / 2 + 100);
}
}
function drawCloud(x, y, size) {
ctx.beginPath();
ctx.arc(x, y, size * 0.5, 0, Math.PI * 2);
ctx.arc(x + size * 0.35, y - size * 0.15, size * 0.4, 0, Math.PI * 2);
ctx.arc(x + size * 0.6, y, size * 0.35, 0, Math.PI * 2);
ctx.arc(x + size * 0.3, y + size * 0.1, size * 0.3, 0, Math.PI * 2);
ctx.fill();
}
// ═══════════════════════════════════════════════════════════════════════
// UTILITIES
// ═══════════════════════════════════════════════════════════════════════
function updateScoreDisplay() {
document.getElementById('score').textContent = state.score;
}
function endGame() {
state.gameOver = true;
if (state.score > state.highScore) {
state.highScore = state.score;
localStorage.setItem('skyglide_high', state.highScore);
}
if (window.parent !== window) {
window.parent.postMessage({ type: 'gameOver', score: state.score }, '*');
}
}
// ═══════════════════════════════════════════════════════════════════════
// INPUT
// ═══════════════════════════════════════════════════════════════════════
document.addEventListener('keydown', e => {
if (e.code === 'Space') {
e.preventDefault();
flap();
}
if (e.code === 'Escape') {
state.paused = !state.paused;
if (!state.paused) {
state.lastTime = performance.now();
requestAnimationFrame(gameLoop);
}
}
});
canvas.addEventListener('click', flap);
canvas.addEventListener('touchstart', e => {
e.preventDefault();
flap();
}, { passive: false });
// ═══════════════════════════════════════════════════════════════════════
// GAME LOOP
// ═══════════════════════════════════════════════════════════════════════
function gameLoop(time) {
if (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>