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>Forest Jumper - Platformer 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: 10px;
color: white;
font-family: Arial, sans-serif;
font-size: 18px;
text-shadow: 2px 2px 4px #000;
z-index: 10;
}
#loading {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: linear-gradient(135deg, #1a1a2e, #16213e);
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; }
.loading-bar {
width: 200px;
height: 8px;
background: #333;
border-radius: 4px;
overflow: hidden;
}
.loading-bar-fill {
height: 100%;
background: #6366f1;
width: 0%;
transition: width 0.3s;
}
</style>
</head>
<body>
<div id="loading">
<h1>Forest Jumper</h1>
<div class="loading-bar"><div class="loading-bar-fill" id="loadingFill"></div></div>
<p id="loadingText">Loading assets...</p>
</div>
<canvas id="gameCanvas"></canvas>
<div id="ui">
<div>Score: <span id="score">0</span></div>
<div>Coins: <span id="coins">0</span></div>
</div>
<!-- Asset Libraries -->
<script src="/assets/backgrounds/backgroundEngine.js"></script>
<script src="/assets/backgrounds/effects.js"></script>
<script src="/assets/backgrounds/themes/nature.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/pure.js"></script>
<script src="/assets/avatar/contextRenderer.js"></script>
<script>
// ═══════════════════════════════════════════════════════════════════════
// PLATFORMER EXAMPLE - Full Body Avatar Demo
// ═══════════════════════════════════════════════════════════════════════
// Demonstrates: FULL_BODY avatar, platformer-standard context,
// nature/forest background, Playful music mood
const GAME_CONFIG = {
title: "Forest Jumper",
assets: {
music: { mood: "Playful", energy: 6, enabled: true },
background: { theme: "nature", variant: "forest", animated: true },
avatarContext: { mode: "FULL_BODY", context: "platformer-standard", showAccessories: true }
},
gameplay: {
gravity: 1800,
jumpForce: 650,
moveSpeed: 280,
platformCount: 8,
coinSpawnRate: 2000
}
};
// Player avatar configuration
const playerAvatar = {
faceShape: 'round',
skinTone: 'medium',
hairStyle: 'short',
hairColor: '#3d2314',
eyeShape: 'round',
eyeColor: '#4a7c59',
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 = {
playerX: 100,
playerY: 300,
playerVelX: 0,
playerVelY: 0,
isGrounded: false,
facingRight: true,
currentAnim: 'idle',
animTime: 0,
score: 0,
coins: 0,
gameOver: false,
paused: false,
lastTime: 0,
platforms: [],
collectedCoins: [],
particles: []
};
const keys = {};
// ═══════════════════════════════════════════════════════════════════════
// LOADING
// ═══════════════════════════════════════════════════════════════════════
let assetsLoaded = false;
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 backgrounds...');
await new Promise(r => setTimeout(r, 200));
updateLoading(40, 'Loading music...');
await new Promise(r => setTimeout(r, 200));
updateLoading(60, 'Loading avatars...');
await new Promise(r => setTimeout(r, 200));
updateLoading(80, 'Initializing game...');
await new Promise(r => setTimeout(r, 200));
updateLoading(100, 'Ready!');
await new Promise(r => setTimeout(r, 300));
assetsLoaded = true;
hideLoading();
initGame();
state.lastTime = performance.now();
requestAnimationFrame(gameLoop);
// 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) { console.log('Music not available'); }
}
}
// ═══════════════════════════════════════════════════════════════════════
// GAME LOGIC
// ═══════════════════════════════════════════════════════════════════════
function initGame() {
state.playerX = 100;
state.playerY = canvas.height - 200;
// Generate platforms
state.platforms = [];
const groundY = canvas.height - 50;
// Ground
state.platforms.push({ x: 0, y: groundY, w: canvas.width, h: 50, isGround: true });
// Floating platforms
for (let i = 0; i < GAME_CONFIG.gameplay.platformCount; i++) {
state.platforms.push({
x: 100 + i * (canvas.width / GAME_CONFIG.gameplay.platformCount),
y: groundY - 100 - Math.random() * 250,
w: 100 + Math.random() * 80,
h: 20
});
}
// Add coins on platforms
state.collectedCoins = [];
state.platforms.forEach((p, i) => {
if (!p.isGround && Math.random() > 0.3) {
state.collectedCoins.push({
x: p.x + p.w / 2,
y: p.y - 30,
collected: false,
bounce: Math.random() * Math.PI * 2
});
}
});
}
function updateGame(dt) {
const { gravity, jumpForce, moveSpeed } = GAME_CONFIG.gameplay;
// Horizontal movement
if (keys['ArrowLeft'] || keys['KeyA']) {
state.playerVelX = -moveSpeed;
state.facingRight = false;
if (state.isGrounded) state.currentAnim = 'walk';
} else if (keys['ArrowRight'] || keys['KeyD']) {
state.playerVelX = moveSpeed;
state.facingRight = true;
if (state.isGrounded) state.currentAnim = 'walk';
} else {
state.playerVelX *= 0.85;
if (state.isGrounded && Math.abs(state.playerVelX) < 10) {
state.currentAnim = 'idle';
}
}
// Jump
if ((keys['Space'] || keys['ArrowUp'] || keys['KeyW']) && state.isGrounded) {
state.playerVelY = -jumpForce;
state.isGrounded = false;
state.currentAnim = 'jump';
}
// Apply gravity
state.playerVelY += gravity * dt;
// Move player
state.playerX += state.playerVelX * dt;
state.playerY += state.playerVelY * dt;
// Platform collision
state.isGrounded = false;
const playerRect = {
x: state.playerX - 20,
y: state.playerY - 80,
w: 40,
h: 80
};
state.platforms.forEach(p => {
// Check if player is above platform and falling
if (state.playerVelY >= 0 &&
playerRect.x + playerRect.w > p.x &&
playerRect.x < p.x + p.w &&
playerRect.y + playerRect.h >= p.y &&
playerRect.y + playerRect.h <= p.y + p.h + state.playerVelY * dt + 5) {
state.playerY = p.y - 80;
state.playerVelY = 0;
state.isGrounded = true;
if (state.currentAnim === 'jump') state.currentAnim = 'idle';
}
});
// Animation when in air
if (!state.isGrounded) {
state.currentAnim = state.playerVelY < 0 ? 'jump' : 'jump';
}
// Screen bounds
if (state.playerX < 30) state.playerX = 30;
if (state.playerX > canvas.width - 30) state.playerX = canvas.width - 30;
// Fall death
if (state.playerY > canvas.height + 100) {
endGame();
}
// Coin collection
state.collectedCoins.forEach(coin => {
if (!coin.collected) {
coin.bounce += dt * 3;
const coinY = coin.y + Math.sin(coin.bounce) * 5;
const dx = state.playerX - coin.x;
const dy = (state.playerY - 40) - coinY;
if (Math.sqrt(dx*dx + dy*dy) < 35) {
coin.collected = true;
state.coins++;
state.score += 10;
updateScore(0);
// Particle effect
for (let i = 0; i < 8; i++) {
state.particles.push({
x: coin.x,
y: coinY,
vx: (Math.random() - 0.5) * 200,
vy: -Math.random() * 150 - 50,
life: 1,
color: '#FFD700'
});
}
}
}
});
// Update particles
state.particles = state.particles.filter(p => {
p.x += p.vx * dt;
p.y += p.vy * dt;
p.vy += 400 * dt;
p.life -= dt * 2;
return p.life > 0;
});
// Score over time
state.score += Math.floor(dt * 5);
updateScore(0);
state.animTime += dt * 1000;
}
function renderGame() {
// Background
if (window.BackgroundEngine) {
window.BackgroundEngine.render(
ctx,
GAME_CONFIG.assets.background.theme,
GAME_CONFIG.assets.background.variant,
canvas.width,
canvas.height,
state.animTime
);
} else {
ctx.fillStyle = '#228B22';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// Platforms
state.platforms.forEach(p => {
if (p.isGround) {
ctx.fillStyle = '#3d2817';
ctx.fillRect(p.x, p.y, p.w, p.h);
ctx.fillStyle = '#4a7c59';
ctx.fillRect(p.x, p.y, p.w, 10);
} else {
// Floating platform
ctx.fillStyle = '#5d4037';
ctx.fillRect(p.x, p.y, p.w, p.h);
ctx.fillStyle = '#795548';
ctx.fillRect(p.x + 2, p.y + 2, p.w - 4, 6);
}
});
// Coins
state.collectedCoins.forEach(coin => {
if (!coin.collected) {
const coinY = coin.y + Math.sin(coin.bounce) * 5;
ctx.fillStyle = '#FFD700';
ctx.beginPath();
ctx.arc(coin.x, coinY, 12, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#FFA500';
ctx.beginPath();
ctx.arc(coin.x - 3, coinY - 3, 4, 0, Math.PI * 2);
ctx.fill();
}
});
// Particles
state.particles.forEach(p => {
ctx.globalAlpha = p.life;
ctx.fillStyle = p.color;
ctx.fillRect(p.x - 3, p.y - 3, 6, 6);
});
ctx.globalAlpha = 1;
// Player avatar
ctx.save();
ctx.translate(state.playerX, state.playerY);
if (!state.facingRight) {
ctx.scale(-1, 1);
}
ctx.translate(-32, -160);
if (window.AvatarSystem) {
window.AvatarSystem.render(
ctx,
playerAvatar,
GAME_CONFIG.assets.avatarContext.mode,
state.currentAnim,
state.animTime
);
} else {
// Fallback
ctx.fillStyle = '#4CAF50';
ctx.fillRect(12, 100, 40, 60);
ctx.fillStyle = '#FFCC80';
ctx.beginPath();
ctx.arc(32, 80, 25, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
// ═══════════════════════════════════════════════════════════════════════
// UTILITIES
// ═══════════════════════════════════════════════════════════════════════
function updateScore(points) {
document.getElementById('score').textContent = state.score;
document.getElementById('coins').textContent = state.coins;
}
function endGame() {
state.gameOver = true;
if (window.parent !== window) {
window.parent.postMessage({ type: 'gameOver', score: state.score }, '*');
}
setTimeout(() => {
if (confirm(`Game Over!\nScore: ${state.score}\nCoins: ${state.coins}\n\nPlay again?`)) {
state.score = 0;
state.coins = 0;
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;
const y = e.touches[0].clientY;
// Top half = jump
if (y < canvas.height / 2) {
keys['Space'] = true;
}
}, { passive: false });
canvas.addEventListener('touchmove', e => {
e.preventDefault();
const x = e.touches[0].clientX;
const dx = x - touchStartX;
keys['ArrowLeft'] = dx < -30;
keys['ArrowRight'] = dx > 30;
}, { passive: false });
canvas.addEventListener('touchend', e => {
keys['ArrowLeft'] = false;
keys['ArrowRight'] = false;
keys['Space'] = 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);
}
// Parent window controls
window.pauseGame = () => { state.paused = true; };
window.resumeGame = () => {
state.paused = false;
state.lastTime = performance.now();
requestAnimationFrame(gameLoop);
};
// Start loading
loadAssets();
</script>
</body>
</html>