<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>�� 像素小鸟 · 闯关星光版</title>
<style>
* {
box-sizing: border-box;
user-select: none;
margin: 0;
padding: 0;
}
body {
background: #1a2630;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Courier New', Courier, monospace;
padding: 8px;
}
.game-wrapper {
background: #2d4555;
padding: 12px 12px 16px;
border-radius: 24px;
box-shadow: 0 10px 0 #16222b, 0 16px 30px rgba(0,0,0,0.7);
border: 2px solid #5a7a8a;
max-width: 520px;
width: 100%;
}
canvas {
display: block;
margin: 0 auto;
width: 100%;
height: auto;
aspect-ratio: 480 / 640;
background: #80b9e0;
border-radius: 16px;
box-shadow: inset 0 0 0 3px #2a4355, 0 8px 0 #1a2f3a;
cursor: pointer;
touch-action: none;
image-rendering: pixelated;
}
/* 状态栏 - 更紧凑 */
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin: 8px 2px 0;
color: #f0ebd0;
text-shadow: 1px 1px 0 #1a2a33;
font-size: 0.85rem;
font-weight: bold;
flex-wrap: wrap;
gap: 4px 6px;
padding: 4px 4px 0;
}
.status-bar span {
background: #2a4355;
padding: 3px 12px;
border-radius: 30px;
box-shadow: inset 0 -2px 0 #14222b;
font-size: 0.8rem;
letter-spacing: 0.3px;
white-space: nowrap;
}
.status-bar .stars {
background: #5a3f2e;
box-shadow: inset 0 -2px 0 #3f2a1e;
}
.btn-reset {
background: #d68d3a;
border: none;
color: #1e2a2f;
font-weight: bold;
font-size: 0.8rem;
padding: 3px 14px;
border-radius: 30px;
box-shadow: 0 4px 0 #a86d2a;
cursor: pointer;
transition: 0.05s linear;
font-family: inherit;
white-space: nowrap;
}
.btn-reset:active {
transform: translateY(3px);
box-shadow: 0 1px 0 #a86d2a;
}
/* 状态栏内部数字保持可见 */
.status-bar span span {
background: none;
box-shadow: none;
padding: 0;
font-size: inherit;
}
@media (max-width: 480px) {
.game-wrapper { padding: 8px; border-radius: 16px; }
.status-bar { font-size: 0.7rem; gap: 3px; }
.status-bar span { padding: 2px 8px; font-size: 0.7rem; }
.btn-reset { font-size: 0.7rem; padding: 2px 10px; }
}
</style>
</head>
<body>
<div class="game-wrapper">
<canvas id="gameCanvas" width="480" height="640"></canvas>
<div class="status-bar">
<span>�� <span id="scoreDisplay">0</span></span>
<span>�� 第 <span id="levelDisplay">1</span> 关</span>
<span class="stars">⭐ <span id="starsDisplay">3</span></span>
<span>�� <span id="coinDisplay">0</span></span>
<button class="btn-reset" id="restartBtn">↺</button>
</div>
</div>
<script>
(function() {
// ---------- 画布 ----------
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const W = 480, H = 640;
// ---------- DOM ----------
const scoreSpan = document.getElementById('scoreDisplay');
const levelSpan = document.getElementById('levelDisplay');
const starsSpan = document.getElementById('starsDisplay');
const coinSpan = document.getElementById('coinDisplay');
// ---------- 常量 ----------
const GRAVITY = 0.28;
const FLAP_FORCE = -5.0;
const BIRD_X = 72;
const PIPE_W = 38;
const GROUND_H = 58;
const MIN_PIPE = 48;
// ---------- 游戏状态 ----------
let bird = { y: 280, vy: 0, radius: 13 };
let pipes = [];
let score = 0;
let level = 1;
let stars = 3;
let coins = 0;
let highScore = parseInt(localStorage.getItem('flappyBirdHighScore')) || 0;
let gameState = 'start';
let frame = 0;
let pipeSpawnCounter = 0;
let baseSpeed = 2.0;
let levelCompleteTimer = 0;
let showLevelUp = false;
let hitEffectTimer = 0;
let starLossEffect = 0;
let victoryParticles = [];
let isVictoryCelebration = false;
// ===== 皮肤系统 =====
const SKIN_LIST = [
{ name: '经典黄', color: '#f5c842', eye: '#1e2e3a', wing: '#e5b832' },
{ name: '烈焰红', color: '#e65c4a', eye: '#2d1a1a', wing: '#c94a3a' },
{ name: '冰霜蓝', color: '#6fc1e0', eye: '#0f2a3a', wing: '#4fa3c4' },
{ name: '暗影紫', color: '#8f6db5', eye: '#1f1a2a', wing: '#7a54a0' },
{ name: '翡翠绿', color: '#5fb57a', eye: '#1a2a1a', wing: '#3f9a5a' },
{ name: '暗夜黑', color: '#4a4a4a', eye: '#1a1a1a', wing: '#3a3a3a' },
{ name: '樱花粉', color: '#f7a8b8', eye: '#2d1a1a', wing: '#e8889a' },
{ name: '阳光橙', color: '#f5a842', eye: '#1e2a1a', wing: '#d48a30' },
];
let currentSkinIndex = 0;
let skinUnlocked = [1, 0, 0, 0, 0, 0, 0, 0];
const SKIN_COST = [0, 20, 30, 40, 50, 60, 35, 45];
// 云朵
let clouds = [];
for (let i = 0; i < 8; i++) {
clouds.push({
x: Math.random() * W,
y: Math.random() * 200 + 20,
w: 45 + Math.random() * 70,
speed: 0.15 + Math.random() * 0.3
});
}
// 背景小元素
let bgElements = [];
for (let i = 0; i < 30; i++) {
bgElements.push({
x: Math.random() * W,
y: Math.random() * (H - GROUND_H - 60) + 30,
w: 3 + Math.random() * 6,
h: 3 + Math.random() * 6,
color: `hsla(${200 + Math.random() * 40}, 60%, ${60 + Math.random() * 30}%, 0.3)`
});
}
// ---------- 辅助 ----------
function updateUI() {
scoreSpan.textContent = score;
levelSpan.textContent = level;
starsSpan.textContent = stars;
coinSpan.textContent = coins;
}
function updateHighScore() {
if (score > highScore) {
highScore = score;
localStorage.setItem('flappyBirdHighScore', String(highScore));
}
}
function getLevelConfig(lvl) {
switch (lvl) {
case 1: return { speed: 2.0, gap: 175, spawnRate: 95, target: 20 };
case 2: return { speed: 2.8, gap: 150, spawnRate: 78, target: 40 };
case 3: return { speed: 3.7, gap: 128, spawnRate: 62, target: 70 };
case 4: return { speed: 4.8, gap: 105, spawnRate: 44, target: 100 };
default: return { speed: 2.0, gap: 175, spawnRate: 95, target: 20 };
}
}
// ---------- 重置 ----------
function fullReset() {
level = 1;
baseSpeed = 2.0;
stars = 3;
score = 0;
bird.y = 280;
bird.vy = 0;
pipes = [];
frame = 0;
pipeSpawnCounter = 0;
showLevelUp = false;
levelCompleteTimer = 0;
hitEffectTimer = 0;
starLossEffect = 0;
victoryParticles = [];
isVictoryCelebration = false;
gameState = 'start';
updateUI();
}
function resetGame() {
stars = 3;
score = 0;
bird.y = 280;
bird.vy = 0;
pipes = [];
frame = 0;
pipeSpawnCounter = 0;
showLevelUp = false;
levelCompleteTimer = 0;
hitEffectTimer = 0;
starLossEffect = 0;
victoryParticles = [];
isVictoryCelebration = false;
const cfg = getLevelConfig(level);
baseSpeed = cfg.speed;
gameState = 'start';
updateUI();
}
// ---------- 管道 ----------
function spawnPipe() {
const cfg = getLevelConfig(level);
const gap = cfg.gap;
const minH = 48;
const maxH = H - GROUND_H - gap - 48;
const top = Math.floor(Math.random() * (maxH - minH + 1)) + minH;
pipes.push({
x: W,
topHeight: top,
gap: gap,
scored: false,
width: PIPE_W,
});
}
function checkCollision(pipe) {
const bx = BIRD_X, by = bird.y, r = bird.radius - 2;
const left = bx - r, right = bx + r, top = by - r, bottom = by + r;
const pLeft = pipe.x, pRight = pipe.x + pipe.width;
const topPipeBottom = pipe.topHeight;
const bottomPipeTop = pipe.topHeight + pipe.gap;
if (right > pLeft && left < pRight) {
if (top < topPipeBottom || bottom > bottomPipeTop) return true;
}
return false;
}
function loseStar() {
stars--;
starLossEffect = 20;
hitEffectTimer = 10;
updateUI();
if (stars <= 0) {
gameState = 'gameover';
updateHighScore();
for (let i = 0; i < 25; i++) {
victoryParticles.push({
x: BIRD_X + (Math.random() - 0.5) * 30,
y: bird.y + (Math.random() - 0.5) * 30,
vx: (Math.random() - 0.5) * 10,
vy: (Math.random() - 0.5) * 10 - 3,
life: 30 + Math.random() * 25,
color: `hsl(${Math.random() * 60 + 10}, 90%, 60%)`,
size: 3 + Math.random() * 7
});
}
}
}
function checkLevelComplete() {
const cfg = getLevelConfig(level);
if (score >= cfg.target) {
if (level === 4) {
gameState = 'victory';
isVictoryCelebration = true;
for (let i = 0; i < 100; i++) {
victoryParticles.push({
x: Math.random() * W,
y: Math.random() * H * 0.7,
vx: (Math.random() - 0.5) * 12,
vy: (Math.random() - 0.5) * 12 - 5,
life: 60 + Math.random() * 50,
color: `hsl(${Math.random() * 360}, 90%, 65%)`,
size: 4 + Math.random() * 9
});
}
updateHighScore();
} else {
gameState = 'levelComplete';
levelCompleteTimer = 70;
showLevelUp = true;
updateHighScore();
for (let i = 0; i < 35; i++) {
victoryParticles.push({
x: W/2 + (Math.random() - 0.5) * 250,
y: 180 + Math.random() * 120,
vx: (Math.random() - 0.5) * 7,
vy: (Math.random() - 0.5) * 7 - 4,
life: 30 + Math.random() * 25,
color: `hsl(${Math.random() * 80 + 30}, 90%, 60%)`,
size: 3 + Math.random() * 6
});
}
}
return true;
}
return false;
}
function goToNextLevel() {
if (level < 4) {
level++;
const cfg = getLevelConfig(level);
baseSpeed = cfg.speed;
pipes = [];
bird.y = 280;
bird.vy = 0;
score = 0;
gameState = 'playing';
showLevelUp = false;
updateUI();
}
}
// ---------- 更新 ----------
function update() {
for (let c of clouds) {
c.x += c.speed * 0.4;
if (c.x > W + 60) { c.x = -c.w - 20; c.y = Math.random() * 200 + 20; }
}
if (gameState === 'start') {
bird.y = 280 + Math.sin(frame * 0.05) * 8;
frame++;
return;
}
if (gameState === 'levelComplete') {
levelCompleteTimer--;
updateParticles();
if (levelCompleteTimer <= 0) goToNextLevel();
return;
}
if (gameState === 'victory') {
updateParticles();
return;
}
if (gameState === 'gameover') {
updateParticles();
if (starLossEffect > 0) starLossEffect--;
return;
}
// playing
bird.vy += GRAVITY;
bird.y += bird.vy;
if (bird.y + bird.radius > H - GROUND_H) {
bird.y = H - GROUND_H - bird.radius;
loseStar();
if (gameState === 'gameover') return;
}
if (bird.y - bird.radius < 0) {
bird.y = bird.radius;
bird.vy = 0;
}
const cfg = getLevelConfig(level);
pipeSpawnCounter++;
if (pipeSpawnCounter >= cfg.spawnRate) {
pipeSpawnCounter = 0;
spawnPipe();
}
for (let i = pipes.length - 1; i >= 0; i--) {
const p = pipes[i];
p.x -= baseSpeed;
if (checkCollision(p)) {
loseStar();
if (gameState === 'gameover') return;
pipes.splice(i, 1);
continue;
}
if (!p.scored && p.x + p.width < BIRD_X) {
p.scored = true;
score++;
if (score % 5 === 0) coins++;
updateUI();
if (checkLevelComplete()) return;
}
if (p.x + p.width < 0) {
pipes.splice(i, 1);
}
}
if (hitEffectTimer > 0) hitEffectTimer--;
if (starLossEffect > 0) starLossEffect--;
frame++;
}
function updateParticles() {
for (let i = victoryParticles.length - 1; i >= 0; i--) {
const p = victoryParticles[i];
p.x += p.vx;
p.y += p.vy;
p.vy += 0.08;
p.life--;
if (p.life <= 0) victoryParticles.splice(i, 1);
}
}
// ---------- 绘制 ----------
function draw() {
ctx.clearRect(0, 0, W, H);
// 天空 (像素色块)
for (let y = 0; y < H * 0.65; y += 4) {
let ratio = y / (H * 0.65);
let r = 60 + 40 * ratio;
let g = 130 + 60 * ratio;
let b = 200 - 20 * ratio;
ctx.fillStyle = `rgb(${r|0},${g|0},${b|0})`;
ctx.fillRect(0, y, W, 4);
}
ctx.fillStyle = '#aad0ee';
ctx.fillRect(0, H * 0.65, W, H * 0.35);
for (let el of bgElements) {
ctx.fillStyle = el.color;
ctx.fillRect(el.x, el.y, el.w, el.h);
}
// 云
for (let c of clouds) {
ctx.fillStyle = 'rgba(255,255,248,0.85)';
let cx = c.x, cy = c.y, cw = c.w;
const blocks = [
[0, 0, cw * 0.5, 14], [-cw * 0.2, -6, cw * 0.35, 12], [cw * 0.2, -4, cw * 0.3, 12],
[-cw * 0.1, 4, cw * 0.4, 10], [cw * 0.15, 6, cw * 0.35, 10]
];
for (let b of blocks) {
ctx.fillRect(cx + b[0] - b[2]/2, cy + b[1] - b[3]/2, b[2], b[3]);
}
}
// 城市剪影
const cityColors = ['#3d5f72', '#4a6f82', '#355568'];
for (let i = 0; i < 16; i++) {
let bx = i * 34 - 8;
let bh = 20 + Math.sin(i * 1.3 + 0.5) * 14 + 10;
ctx.fillStyle = cityColors[i % 3];
ctx.fillRect(bx, H - GROUND_H - bh, 24, bh);
ctx.fillStyle = 'rgba(255,220,120,0.2)';
for (let wy = 0; wy < bh - 8; wy += 8) {
for (let wx = 0; wx < 16; wx += 8) {
if (Math.random() > 0.35) continue;
ctx.fillRect(bx + 4 + wx, H - GROUND_H - bh + 4 + wy, 4, 4);
}
}
}
// 管道
for (let p of pipes) {
ctx.fillStyle = '#3b8540';
ctx.fillRect(p.x, 0, p.width, p.topHeight);
ctx.fillStyle = '#2b6a2a';
ctx.fillRect(p.x - 4, p.topHeight - 16, p.width + 8, 16);
ctx.fillStyle = 'rgba(180,255,180,0.15)';
ctx.fillRect(p.x + 4, 0, 6, p.topHeight - 16);
ctx.fillRect(p.x + p.width - 10, 0, 6, p.topHeight - 16);
const bottomY = p.topHeight + p.gap;
ctx.fillStyle = '#3b8540';
ctx.fillRect(p.x, bottomY, p.width, H - GROUND_H - bottomY);
ctx.fillStyle = '#2b6a2a';
ctx.fillRect(p.x - 4, bottomY, p.width + 8, 16);
ctx.fillStyle = 'rgba(180,255,180,0.15)';
ctx.fillRect(p.x + 4, bottomY + 16, 6, H - GROUND_H - bottomY - 20);
ctx.fillRect(p.x + p.width - 10, bottomY + 16, 6, H - GROUND_H - bottomY - 20);
}
// 地面
const gy = H - GROUND_H;
ctx.fillStyle = '#7d9b6d';
ctx.fillRect(0, gy, W, GROUND_H);
for (let i = 0; i < 30; i++) {
let gx = (i * 18 - (frame * 0.8) % 18 + 18) % W - 18;
ctx.fillStyle = i % 2 === 0 ? '#5d7b4d' : '#6d8b5d';
ctx.fillRect(gx, gy + 2, 8, 6);
ctx.fillRect(gx + 10, gy + 14, 8, 6);
ctx.fillRect(gx + 5, gy + 26, 8, 6);
}
ctx.fillStyle = '#9dbd8d';
ctx.fillRect(0, gy, W, 3);
// ---- 小鸟 ----
const skin = SKIN_LIST[currentSkinIndex] || SKIN_LIST[0];
ctx.shadowColor = 'rgba(0,0,0,0.25)';
ctx.shadowBlur = 8;
ctx.fillStyle = skin.color;
ctx.fillRect(BIRD_X - 12, bird.y - 12, 24, 24);
ctx.fillRect(BIRD_X - 6, bird.y - 4, 12, 14);
const wingAngle = Math.sin(frame * 0.2) * 0.4 + 0.3;
ctx.fillStyle = skin.wing;
ctx.fillRect(BIRD_X - 16, bird.y - 4 + wingAngle * 4, 8, 8);
ctx.fillRect(BIRD_X + 8, bird.y - 4 - wingAngle * 4, 8, 8);
ctx.fillStyle = '#1e2e3a';
ctx.fillRect(BIRD_X + 4, bird.y - 8, 6, 6);
ctx.fillRect(BIRD_X + 12, bird.y - 8, 6, 6);
ctx.fillStyle = 'white';
ctx.fillRect(BIRD_X + 6, bird.y - 6, 3, 3);
ctx.fillRect(BIRD_X + 14, bird.y - 6, 3, 3);
ctx.fillStyle = '#1e2e3a';
ctx.fillRect(BIRD_X + 8, bird.y - 4, 2, 2);
ctx.fillRect(BIRD_X + 16, bird.y - 4, 2, 2);
ctx.fillStyle = '#e8a030';
ctx.fillRect(BIRD_X + 18, bird.y - 2, 8, 4);
ctx.fillRect(BIRD_X + 22, bird.y + 2, 4, 2);
ctx.shadowBlur = 0;
if (hitEffectTimer > 0 && hitEffectTimer % 4 < 2) {
ctx.fillStyle = 'rgba(255,255,255,0.4)';
ctx.fillRect(BIRD_X - 14, bird.y - 14, 28, 28);
}
// 星星碎裂特效
if (starLossEffect > 0) {
ctx.font = '28px monospace';
ctx.fillStyle = '#f5d742';
ctx.shadowBlur = 16;
ctx.shadowColor = '#ffaa33';
ctx.fillText('��', BIRD_X - 18, bird.y - 28);
ctx.shadowBlur = 0;
for (let i = 0; i < 8; i++) {
const angle = frame * 0.25 + i * 1.0;
const dist = 14 + (22 - starLossEffect) * 1.5;
ctx.fillStyle = `hsl(${40 + i * 20}, 90%, 60%)`;
ctx.fillRect(BIRD_X + Math.cos(angle) * dist - 3, bird.y + Math.sin(angle) * dist - 10 - 3, 6, 6);
}
}
// 粒子
for (let p of victoryParticles) {
ctx.globalAlpha = Math.min(1, p.life / 20);
ctx.fillStyle = p.color;
ctx.fillRect(p.x - p.size/2, p.y - p.size/2, p.size, p.size);
}
ctx.globalAlpha = 1;
// ---- UI 文字 ----
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.shadowColor = 'rgba(0,0,0,0.6)';
ctx.shadowBlur = 10;
if (gameState === 'start') {
ctx.font = 'bold 40px "Courier New", monospace';
ctx.fillStyle = '#f7f2d6';
ctx.fillText('�� 像素小鸟', W/2, 100);
ctx.font = '20px "Courier New", monospace';
ctx.fillStyle = '#dfdbb0';
ctx.fillText('点击 / 空格 起飞', W/2, 160);
ctx.font = '16px "Courier New", monospace';
ctx.fillStyle = '#c0b88a';
ctx.fillText(`�� ${coins} | 皮肤: ${skin.name}`, W/2, 220);
ctx.font = '20px "Courier New", monospace';
ctx.fillStyle = '#d4c88a';
ctx.fillText('◀ 换皮肤 ▶', W/2, 290);
ctx.font = '14px "Courier New", monospace';
ctx.fillStyle = '#b0a87a';
let statusText = '已拥有';
if (currentSkinIndex !== 0 && skinUnlocked[currentSkinIndex] === 0) {
statusText = `需要 ${SKIN_COST[currentSkinIndex]} ��`;
}
ctx.fillText(statusText, W/2, 325);
ctx.font = '11px "Courier New", monospace';
ctx.fillStyle = '#90886a';
let skinNames = SKIN_LIST.map((s, i) => {
return skinUnlocked[i] === 1 ? s.name : `${s.name}��`;
}).join(' ');
ctx.fillText(skinNames, W/2, 380);
}
if (gameState === 'levelComplete' && showLevelUp) {
ctx.font = 'bold 38px "Courier New", monospace';
ctx.fillStyle = '#f5e56b';
ctx.shadowBlur = 20;
ctx.fillText(`�� 第 ${level+1} 关`, W/2, 180);
ctx.font = '22px "Courier New", monospace';
ctx.fillStyle = '#f0e8c0';
ctx.fillText('难度提升 !', W/2, 250);
ctx.shadowBlur = 0;
}
if (gameState === 'victory') {
ctx.font = 'bold 44px "Courier New", monospace';
ctx.fillStyle = '#f7d44a';
ctx.shadowBlur = 24;
ctx.fillText('�� 终极胜利', W/2, 160);
ctx.font = '26px "Courier New", monospace';
ctx.fillStyle = '#f5eab0';
ctx.fillText(`得分 ${score}`, W/2, 240);
ctx.shadowBlur = 0;
}
if (gameState === 'gameover') {
ctx.font = 'bold 48px "Courier New", monospace';
ctx.fillStyle = '#d94f4f';
ctx.shadowBlur = 18;
ctx.fillText('�� 坠毁', W/2, 150);
ctx.font = '22px "Courier New", monospace';
ctx.fillStyle = '#f0e8c0';
ctx.fillText(`第 ${level} 关 · 得分 ${score}`, W/2, 230);
if (score >= highScore && score > 0) {
ctx.fillStyle = '#f5d742';
ctx.fillText('✨ 新纪录!', W/2, 290);
}
ctx.shadowBlur = 0;
}
ctx.shadowBlur = 0;
}
// ---------- 循环 ----------
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
// ---------- 交互 ----------
function handleAction() {
if (gameState === 'start') {
gameState = 'playing';
bird.y = 280;
bird.vy = 0;
pipes = [];
score = 0;
stars = 3;
updateUI();
return;
}
if (gameState === 'playing') {
bird.vy = FLAP_FORCE;
return;
}
if (gameState === 'gameover' || gameState === 'victory') {
fullReset();
return;
}
}
function changeSkin(direction) {
if (gameState !== 'start') return;
let next = (currentSkinIndex + direction + SKIN_LIST.length) % SKIN_LIST.length;
if (skinUnlocked[next] === 0 && next !== 0) {
if (coins >= SKIN_COST[next]) {
coins -= SKIN_COST[next];
skinUnlocked[next] = 1;
currentSkinIndex = next;
updateUI();
}
return;
}
if (skinUnlocked[next] === 1 || next === 0) {
currentSkinIndex = next;
}
}
// 事件绑定
canvas.addEventListener('click', (e) => {
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const x = (e.clientX - rect.left) * scaleX;
const y = (e.clientY - rect.top) * scaleY;
if (gameState === 'start') {
if (y > 265 && y < 320) {
if (x < W/2 - 50) { changeSkin(-1); return; }
else if (x > W/2 + 50) { changeSkin(1); return; }
}
handleAction();
return;
}
handleAction();
});
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const touch = e.touches[0];
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const x = (touch.clientX - rect.left) * scaleX;
const y = (touch.clientY - rect.top) * scaleY;
if (gameState === 'start') {
if (y > 265 && y < 320) {
if (x < W/2 - 50) { changeSkin(-1); return; }
else if (x > W/2 + 50) { changeSkin(1); return; }
}
handleAction();
return;
}
handleAction();
});
document.addEventListener('keydown', (e) => {
if (e.key === ' ' || e.key === 'Space') {
e.preventDefault();
handleAction();
}
if (gameState === 'start') {
if (e.key === 'ArrowLeft') { changeSkin(-1); e.preventDefault(); }
if (e.key === 'ArrowRight') { changeSkin(1); e.preventDefault(); }
}
});
document.getElementById('restartBtn').addEventListener('click', () => {
fullReset();
});
fullReset();
loop();
})();
</script>
</body>
</html>
console