<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>アゲハチョウのダンス - Rhythm Game</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=M+PLUS+Rounded+1c:wght@700&family=Press+Start+2P&display=swap" rel="stylesheet">
<style>
body {
margin: 0;
padding: 0;
background-color: #0a0a0a;
color: white;
font-family: 'M PLUS Rounded 1c', sans-serif;
overflow: hidden;
touch-action: none; /* スマホでのスワイプなどの誤動作防止 */
}
#game-container {
position: relative;
width: 100vw;
height: 100vh;
max-width: 600px;
margin: 0 auto;
/* 背景の色相をCSS変数で管理して動的に変更できるようにする */
--bg-hue: 220;
background: linear-gradient(180deg,
hsl(var(--bg-hue), 58%, 28%) 0%,
hsl(var(--bg-hue), 56%, 38%) 50%,
hsl(var(--bg-hue), 47%, 16%) 100%);
box-shadow: 0 0 30px rgba(0, 0, 0, 0.8);
overflow: hidden;
}
/* 歌う女の子の背景 */
#singer-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* マイクを持って歌う女性のフリー画像 */
background-image: url('https://images.unsplash.com/photo-1516280440502-861f6c8d76a2?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80');
background-size: cover;
background-position: center;
opacity: 0.5;
/* luminosityにより、背景のグラデーションカラーが画像に反映され、叩くたびに色が変わる照明効果を生みます */
mix-blend-mode: luminosity;
z-index: 1;
pointer-events: none; /* クリック操作を妨げない */
}
canvas {
display: block;
width: 100%;
height: 100%;
position: relative;
z-index: 2;
}
/* 各種UIオーバーレイ */
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.8);
z-index: 10;
transition: opacity 0.5s;
}
.hidden {
display: none !important;
}
/* ヘッダーUI(スコアなど) */
#ui-header {
position: absolute;
top: 20px;
left: 20px;
right: 20px;
display: flex;
justify-content: space-between;
z-index: 5;
pointer-events: none;
font-family: 'Press Start 2P', cursive;
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
}
.text-glow {
text-shadow: 0 0 10px currentColor;
}
/* 判定テキストアニメーション */
#judgement-text {
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 2.5rem;
font-weight: bold;
font-style: italic;
opacity: 0;
pointer-events: none;
z-index: 5;
}
@keyframes popUp {
0% { transform: translate(-50%, -40%) scale(0.8); opacity: 0; }
20% { transform: translate(-50%, -50%) scale(1.1); opacity: 1; }
100% { transform: translate(-50%, -60%) scale(1); opacity: 0; }
}
.animate-judge {
animation: popUp 0.5s ease-out forwards;
}
/* ボタンデザイン */
.btn {
background: linear-gradient(45deg, #ff00ff, #00ffff);
border: none;
padding: 15px 40px;
border-radius: 30px;
color: white;
font-size: 1.5rem;
font-weight: bold;
cursor: pointer;
box-shadow: 0 0 15px rgba(255, 0, 255, 0.5);
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: scale(1.05);
box-shadow: 0 0 25px rgba(0, 255, 255, 0.8);
}
</style>
</head>
<body>
<div id="game-container">
<!-- 歌う女の子の背景画像 -->
<div id="singer-bg"></div>
<!-- ゲーム描画用キャンバス -->
<canvas id="gameCanvas" width="400" height="600"></canvas>
<!-- UI情報 -->
<div id="ui-header" class="hidden">
<div>
<div class="text-sm text-gray-300">スコア</div>
<div id="score-display" class="text-2xl text-white">0</div>
</div>
<div class="text-right">
<div class="text-sm text-gray-300">コンボ</div>
<div id="combo-display" class="text-2xl text-yellow-400">0</div>
</div>
</div>
<!-- 判定表示 -->
<div id="judgement-text"></div>
<!-- スタート画面 -->
<div id="start-screen" class="overlay">
<h1 class="text-3xl md:text-4xl font-bold mb-4 text-center px-4" style="color: #ffb7e6; text-shadow: 0 0 15px #ff00ff;">
アゲハチョウのダンス
</h1>
<p class="mb-8 text-gray-300 text-center px-6">
上から降ってくるノーツに合わせて<br>
<span class="text-white font-bold bg-gray-800 px-2 py-1 rounded">D</span>
<span class="text-white font-bold bg-gray-800 px-2 py-1 rounded">F</span>
<span class="text-white font-bold bg-gray-800 px-2 py-1 rounded">J</span>
<span class="text-white font-bold bg-gray-800 px-2 py-1 rounded">K</span><br>
キーを押すか、画面下部をタップしてください!
</p>
<div class="flex flex-col gap-4 w-64">
<button class="btn diff-btn" data-diff="EASY" style="background: linear-gradient(45deg, #4CAF50, #8BC34A);">かんたん</button>
<button class="btn diff-btn" data-diff="NORMAL" style="background: linear-gradient(45deg, #ff00ff, #00ffff);">ふつう</button>
<button class="btn diff-btn" data-diff="HARD" style="background: linear-gradient(45deg, #f44336, #E91E63);">むずかしい</button>
</div>
</div>
<!-- リザルト画面 -->
<div id="result-screen" class="overlay hidden">
<h2 class="text-4xl font-bold mb-6 text-white">終了!</h2>
<div class="text-center mb-8 bg-gray-900 p-6 rounded-xl border border-gray-700 shadow-2xl w-3/4 max-w-sm">
<div class="text-gray-400 mb-1">最終スコア</div>
<div id="final-score" class="text-4xl font-bold text-cyan-400 mb-4 font-[Press Start 2P]">0</div>
<div class="text-gray-400 mb-1">最大コンボ</div>
<div id="final-combo" class="text-2xl font-bold text-yellow-400 font-[Press Start 2P]">0</div>
</div>
<button id="retry-btn" class="btn">もう一度遊ぶ</button>
</div>
</div>
<!-- オーディオ要素 -->
<audio id="bgm" src="https://petitacode.web2050.jp/users/original/emma/%E3%82%A2%E3%82%B2%E3%83%8F%E3%83%81%E3%83%A7%E3%82%A6%E3%81%AE%E3%83%80%E3%83%B3%E3%82%B9.mp3" preload="auto"></audio>
<script>
// ゲーム設定(楽曲に合わせてここで調整可能です)
const CONFIG = {
BPM: 130, // 楽曲の想定テンポ(1分間のビート数)
START_OFFSET: 1.0, // 最初のノーツが来るまでの時間(秒)
TARGET_Y: 500, // 判定ラインのY座標(キャンバス高さ600に対して)
LANE_WIDTH: 70, // 各レーンの幅(ピクセル)
KEYS: ['d', 'f', 'j', 'k'] // 対応キーボード配列
};
// DOM要素の取得
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const bgm = document.getElementById('bgm');
const startScreen = document.getElementById('start-screen');
const resultScreen = document.getElementById('result-screen');
const uiHeader = document.getElementById('ui-header');
const scoreDisplay = document.getElementById('score-display');
const comboDisplay = document.getElementById('combo-display');
const judgementText = document.getElementById('judgement-text');
// ゲーム状態
let isPlaying = false;
let score = 0;
let combo = 0;
let maxCombo = 0;
let notes = [];
let particles = [];
let laneFlashes = [0, 0, 0, 0]; // レーンが光るエフェクト用
let animationId;
let currentSpeed = 600; // ノーツの落下速度
let currentDifficulty = 'NORMAL'; // 現在の難易度
let targetBgHue = 220; // 背景の目標色相
let currentBgHue = 220; // 現在の背景色相
// アゲハ蝶の背景エフェクト用
let butterflies = [];
const BUTTERFLY_COUNT = 15; // 周りを飛んでいる感を強調するため蝶の数を増加
// レーンの描画開始X座標(中央寄せ)
const TOTAL_LANE_WIDTH = CONFIG.LANE_WIDTH * 4;
const OFFSET_X = (canvas.width - TOTAL_LANE_WIDTH) / 2;
// レーンごとのテーマカラー (シアン, ピンク, イエロー, ライム)
const LANE_COLORS = ['#00e5ff', '#ff00a0', '#ffeb3b', '#00e676'];
// BPMから自動的にノーツ(譜面)を生成する関数
function generateChart(difficulty) {
notes = [];
const secPerBeat = 60 / CONFIG.BPM;
let currentTime = CONFIG.START_OFFSET;
// 約2分半(150秒)分のノーツを生成
while (currentTime < 150) {
const rand = Math.random();
if (difficulty === 'EASY') {
// EASY: 4分音符メイン、同時押しなし、休符多め
if (rand < 0.6) {
notes.push({ time: currentTime, lane: Math.floor(Math.random() * 4), active: true });
}
currentTime += secPerBeat;
} else if (difficulty === 'HARD') {
// HARD: 8分音符・16分音符多め、同時押し多め
if (rand < 0.3) {
// 同時押し
let l1 = Math.floor(Math.random() * 4);
let l2 = (l1 + 1 + Math.floor(Math.random() * 3)) % 4;
notes.push({ time: currentTime, lane: l1, active: true });
notes.push({ time: currentTime, lane: l2, active: true });
currentTime += secPerBeat;
} else if (rand < 0.7) {
// 8分音符連打
notes.push({ time: currentTime, lane: Math.floor(Math.random() * 4), active: true });
notes.push({ time: currentTime + secPerBeat / 2, lane: Math.floor(Math.random() * 4), active: true });
currentTime += secPerBeat;
} else {
// 16分音符の細かい連打
let l = Math.floor(Math.random() * 4);
notes.push({ time: currentTime, lane: l, active: true });
notes.push({ time: currentTime + secPerBeat / 4, lane: (l+1)%4, active: true });
notes.push({ time: currentTime + secPerBeat / 2, lane: (l+2)%4, active: true });
currentTime += secPerBeat;
}
} else {
// NORMAL: 標準パターン
if (rand < 0.5) {
// 単押し
notes.push({ time: currentTime, lane: Math.floor(Math.random() * 4), active: true });
currentTime += secPerBeat;
} else if (rand < 0.75) {
// 同時押し(2つ)
let l1 = Math.floor(Math.random() * 4);
let l2 = (l1 + 1 + Math.floor(Math.random() * 3)) % 4; // l1以外のレーン
notes.push({ time: currentTime, lane: l1, active: true });
notes.push({ time: currentTime, lane: l2, active: true });
currentTime += secPerBeat;
} else {
// 8分音符(半拍ずらし)
notes.push({ time: currentTime, lane: Math.floor(Math.random() * 4), active: true });
notes.push({ time: currentTime + secPerBeat / 2, lane: Math.floor(Math.random() * 4), active: true });
currentTime += secPerBeat;
}
}
}
}
// アゲハ蝶を生成する関数
function createButterfly() {
return {
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 2,
vy: -Math.random() * 2 - 0.5, // 基本的に上へ飛ぶ
size: Math.random() * 15 + 10, // 蝶の大きさ
// アゲハチョウをイメージした黄色や幻想的なネオンカラー
color: ['#ffeb3b', '#ffb7e6', '#00e5ff', '#ffff8d'][Math.floor(Math.random() * 4)],
flapPhase: Math.random() * Math.PI * 2, // 羽ばたきのタイミング
flapSpeed: Math.random() * 0.1 + 0.05, // 羽ばたきの速さ
wobble: Math.random() * Math.PI * 2 // ゆらゆら飛ぶための値
};
}
// 蝶の初期化
function initButterflies() {
butterflies = [];
for(let i=0; i<BUTTERFLY_COUNT; i++){
butterflies.push(createButterfly());
}
}
// 背景の蝶を更新・描画
function updateAndDrawButterflies() {
for (let i = 0; i < butterflies.length; i++) {
let b = butterflies[i];
// ゆらゆら動かす
b.wobble += 0.05;
b.x += b.vx + Math.sin(b.wobble) * 1.5;
b.y += b.vy;
b.flapPhase += b.flapSpeed; // 羽ばたき
// 画面外に出たら再配置
if (b.y < -50 || b.x < -50 || b.x > canvas.width + 50 || b.y > canvas.height + 50) {
butterflies[i] = createButterfly();
if (Math.random() > 0.5) {
butterflies[i].y = canvas.height + 30; // 下から
butterflies[i].vy = -Math.abs(butterflies[i].vy);
} else {
butterflies[i].x = Math.random() > 0.5 ? -30 : canvas.width + 30; // 横から
butterflies[i].vx = butterflies[i].x < 0 ? Math.abs(butterflies[i].vx) : -Math.abs(butterflies[i].vx);
}
}
// 進行方向の角度を計算
let targetAngle = Math.atan2(b.vy, b.vx + Math.sin(b.wobble)*1.5) + Math.PI/2;
ctx.save();
ctx.translate(b.x, b.y);
ctx.rotate(targetAngle);
// 羽ばたきのスケール変形(左右にパタパタ)
let flapScale = Math.sin(b.flapPhase);
ctx.scale(flapScale, 1);
// 蝶の描画
ctx.globalAlpha = 0.6;
ctx.fillStyle = b.color;
ctx.shadowBlur = 15;
ctx.shadowColor = b.color;
// 上羽
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.bezierCurveTo(-b.size*1.2, -b.size, -b.size*1.5, b.size*0.2, 0, b.size*0.3);
ctx.bezierCurveTo(b.size*1.5, b.size*0.2, b.size*1.2, -b.size, 0, 0);
ctx.fill();
// 下羽
ctx.beginPath();
ctx.moveTo(0, b.size*0.2);
ctx.bezierCurveTo(-b.size*0.8, b.size*0.6, -b.size*0.6, b.size*1.5, 0, b.size*0.8);
ctx.bezierCurveTo(b.size*0.6, b.size*1.5, b.size*0.8, b.size*0.6, 0, b.size*0.2);
ctx.fill();
// 胴体
ctx.fillStyle = '#222';
ctx.shadowBlur = 0;
ctx.beginPath();
ctx.ellipse(0, b.size*0.2, b.size*0.15, b.size*0.5, 0, 0, Math.PI*2);
ctx.fill();
ctx.restore();
}
}
// メインループ
function gameLoop() {
// --- 背景の色相をなめらかに変化させる ---
if (Math.abs(targetBgHue - currentBgHue) > 0.1) {
currentBgHue += (targetBgHue - currentBgHue) * 0.05; // 0.05の割合で徐々に目標の色に近づける
document.getElementById('game-container').style.setProperty('--bg-hue', currentBgHue % 360);
}
// 画面のクリア
ctx.clearRect(0, 0, canvas.width, canvas.height);
// --- 背景のアゲハ蝶の更新と描画 ---
updateAndDrawButterflies();
// プレイ中のみゲーム用の描画を行う
if (isPlaying) {
// 現在の音楽の再生時間を取得(ノーツの位置計算の基準)
const audioTime = bgm.currentTime;
// --- 背景/レーンの描画 ---
drawLanes();
// --- ノーツの更新と描画 ---
updateAndDrawNotes(audioTime);
// --- パーティクル(ヒットエフェクト)の更新と描画 ---
updateAndDrawParticles();
// --- 判定ラインの描画 ---
drawTargetLine();
// 楽曲が終了したらリザルト画面へ
if (bgm.ended || (audioTime > 0 && bgm.paused)) {
endGame();
}
}
animationId = requestAnimationFrame(gameLoop);
}
function drawLanes() {
for (let i = 0; i < 4; i++) {
const x = OFFSET_X + i * CONFIG.LANE_WIDTH;
// レーンの区切り線
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
// キーを押したときのレーンのフラッシュ
if (laneFlashes[i] > 0) {
const gradient = ctx.createLinearGradient(0, CONFIG.TARGET_Y, 0, 0);
gradient.addColorStop(0, `${LANE_COLORS[i]}${Math.floor(laneFlashes[i] * 120).toString(16).padStart(2, '0')}`); // 透過度付きHex
gradient.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = gradient;
ctx.fillRect(x, 0, CONFIG.LANE_WIDTH, canvas.height);
laneFlashes[i] -= 0.05; // フラッシュを徐々に減衰
}
}
// 右端の線
ctx.beginPath();
ctx.moveTo(OFFSET_X + 4 * CONFIG.LANE_WIDTH, 0);
ctx.lineTo(OFFSET_X + 4 * CONFIG.LANE_WIDTH, canvas.height);
ctx.stroke();
}
function updateAndDrawNotes(audioTime) {
// 最適化のため、画面外の下に落ちたノーツは描画しない
for (let i = 0; i < notes.length; i++) {
let note = notes[i];
if (!note.active) continue;
// 落下距離を計算:Y = 判定ライン - (ノーツのタイミング - 現在時間) * スピード
let y = CONFIG.TARGET_Y - (note.time - audioTime) * currentSpeed;
// 画面外(まだ上すぎる)場合は描画スキップ
if (y < -50) continue;
// ミス判定(判定ラインを一定以上通り過ぎたら)
if (audioTime - note.time > 0.15) {
note.active = false;
combo = 0;
updateUI();
displayJudgement("ミス", "#ff3333");
continue;
}
// ノーツの描画
const x = OFFSET_X + note.lane * CONFIG.LANE_WIDTH + 5; // 余白をとる
const width = CONFIG.LANE_WIDTH - 10;
const height = 25; // ノーツの厚みを20から25に増やして少し大きく
ctx.fillStyle = LANE_COLORS[note.lane];
ctx.shadowColor = LANE_COLORS[note.lane];
ctx.shadowBlur = 15;
// 角丸四角形の描画(最近のブラウザ標準機能)
ctx.beginPath();
if (ctx.roundRect) {
ctx.roundRect(x, y - height/2, width, height, 5);
} else {
ctx.fillRect(x, y - height/2, width, height); // フォールバック
}
ctx.fill();
// シャドウリセット
ctx.shadowBlur = 0;
}
}
function drawTargetLine() {
for (let i = 0; i < 4; i++) {
const x = OFFSET_X + i * CONFIG.LANE_WIDTH;
// ターゲットの枠(高さを30から50に拡大)
ctx.strokeStyle = laneFlashes[i] > 0 ? LANE_COLORS[i] : 'rgba(255, 255, 255, 0.4)';
ctx.lineWidth = laneFlashes[i] > 0 ? 4 : 2; // 光ったときの枠線も少し太く
ctx.strokeRect(x + 5, CONFIG.TARGET_Y - 25, CONFIG.LANE_WIDTH - 10, 50);
// ガイドのキー文字表示(文字サイズを16pxから20pxに拡大)
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
ctx.font = '20px "Press Start 2P"';
ctx.textAlign = 'center';
ctx.fillText(CONFIG.KEYS[i].toUpperCase(), x + CONFIG.LANE_WIDTH / 2, CONFIG.TARGET_Y + 8);
}
}
function createParticles(x, y, color) {
for (let i = 0; i < 15; i++) {
particles.push({
x: x,
y: y,
vx: (Math.random() - 0.5) * 10,
vy: (Math.random() - 0.5) * 10 - 2, // やや上向きに飛ぶ
life: 1.0,
color: color
});
}
}
function updateAndDrawParticles() {
for (let i = particles.length - 1; i >= 0; i--) {
let p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.life -= 0.03; // 寿命を減らす
if (p.life <= 0) {
particles.splice(i, 1);
continue;
}
ctx.fillStyle = p.color;
ctx.globalAlpha = p.life;
ctx.beginPath();
ctx.arc(p.x, p.y, 3, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1.0; // リセット
}
}
// 入力に対する判定処理
function handleInput(laneIndex) {
if (!isPlaying) return;
laneFlashes[laneIndex] = 1.0; // レーンを光らせる
targetBgHue += 8; // キーに触れる(叩く)ごとに背景の色を少し進める
const audioTime = bgm.currentTime;
let closestNote = null;
let minDiff = Infinity;
// 押したレーンで、現在時刻に一番近いアクティブなノーツを探す
for (let note of notes) {
if (note.active && note.lane === laneIndex) {
let diff = Math.abs(note.time - audioTime);
if (diff < minDiff) {
minDiff = diff;
closestNote = note;
}
}
}
// 判定基準(秒)
if (closestNote && minDiff <= 0.15) {
closestNote.active = false;
// エフェクト発生
const pX = OFFSET_X + laneIndex * CONFIG.LANE_WIDTH + CONFIG.LANE_WIDTH / 2;
createParticles(pX, CONFIG.TARGET_Y, LANE_COLORS[laneIndex]);
// 精度によるスコア計算
if (minDiff <= 0.05) {
score += 100;
combo++;
displayJudgement("パーフェクト", "#00ffff");
} else if (minDiff <= 0.10) {
score += 50;
combo++;
displayJudgement("グレート", "#00ff00");
} else {
score += 10;
combo++;
displayJudgement("グッド", "#ffff00");
}
if (combo > maxCombo) maxCombo = combo;
updateUI();
}
}
// キーボード入力リスナー
window.addEventListener('keydown', (e) => {
if (e.repeat) return; // 押しっぱなしによる連続発火を防止
const key = e.key.toLowerCase();
const laneIndex = CONFIG.KEYS.indexOf(key);
if (laneIndex !== -1) {
handleInput(laneIndex);
}
});
// タッチ(スマホ)入力リスナー
canvas.addEventListener('touchstart', (e) => {
e.preventDefault(); // スクロール防止
const rect = canvas.getBoundingClientRect();
for (let i = 0; i < e.changedTouches.length; i++) {
const touch = e.changedTouches[i];
// キャンバス内の相対X座標を計算
const x = touch.clientX - rect.left;
// キャンバスの描画スケールに合わせて調整
const scaleX = canvas.width / rect.width;
const canvasX = x * scaleX;
// どのレーンをタップしたか判定
if (canvasX >= OFFSET_X && canvasX <= OFFSET_X + TOTAL_LANE_WIDTH) {
const laneIndex = Math.floor((canvasX - OFFSET_X) / CONFIG.LANE_WIDTH);
handleInput(laneIndex);
}
}
}, { passive: false });
// UIの更新
function updateUI() {
scoreDisplay.innerText = score;
comboDisplay.innerText = combo;
}
// 判定テキストを画面中央にアニメーション表示
function displayJudgement(text, color) {
judgementText.innerText = text;
judgementText.style.color = color;
judgementText.style.textShadow = `0 0 20px ${color}`;
// アニメーションを再スタートさせるためにクラスを付け直す
judgementText.classList.remove('animate-judge');
void judgementText.offsetWidth; // DOMリフローを強制してアニメーションリセット
judgementText.classList.add('animate-judge');
}
// ゲーム開始処理
function startGame(difficulty) {
currentDifficulty = difficulty || 'NORMAL';
// 難易度に応じて落下スピードを変更
if (currentDifficulty === 'EASY') currentSpeed = 400;
else if (currentDifficulty === 'NORMAL') currentSpeed = 600;
else if (currentDifficulty === 'HARD') currentSpeed = 850;
startScreen.classList.add('hidden');
resultScreen.classList.add('hidden');
uiHeader.classList.remove('hidden');
score = 0;
combo = 0;
maxCombo = 0;
targetBgHue = 220; // 背景の色相をリセット(初期状態の青)
currentBgHue = 220;
document.getElementById('game-container').style.setProperty('--bg-hue', currentBgHue);
updateUI();
generateChart(currentDifficulty); // 難易度を渡して譜面生成
bgm.currentTime = 0;
bgm.play().then(() => {
isPlaying = true;
// gameLoopは常に回っているため、フラグをオンにするだけで開始
}).catch(e => {
console.error("Audio play failed:", e);
alert("音声の再生に失敗しました。ブラウザの自動再生制限の可能性があります。");
startScreen.classList.remove('hidden');
});
}
// ゲーム終了処理
function endGame() {
isPlaying = false;
bgm.pause();
uiHeader.classList.add('hidden');
resultScreen.classList.remove('hidden');
document.getElementById('final-score').innerText = score;
document.getElementById('final-combo').innerText = maxCombo;
}
// ボタンのイベントリスナー登録
document.querySelectorAll('.diff-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
startGame(e.target.getAttribute('data-diff'));
});
});
document.getElementById('retry-btn').addEventListener('click', () => {
startGame(currentDifficulty);
});
// 起動時に蝶を初期化し、アニメーションループをスタートしておく
initButterflies();
gameLoop();
</script>
</body>
</html>
この作品はアゲハチョウが周りを飛んでて、かんたん・ふつう・むずかしいが選べる作品です。