MidiSmash


・ダウンロードされる方はこちら。↓

https://drive.google.com/drive/folders/1vL4iwMBDM9Ibu4A5FEj3T-YGG20Wcfbb?usp=drive_link


<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>MIDI Smash 1.0</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
    <script src="https://unpkg.com/@tonejs/midi"></script>
    <style>
        body { margin: 0; background: #111; color: white; font-family: 'Segoe UI', sans-serif; overflow: hidden; user-select: none; }
        
        #gameContainer {
            position: relative;
            width: 100vw;
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            background: #000;
        }

        canvas { 
            background: #222; 
            box-shadow: 0 0 20px rgba(0,0,0,0.8);
        }

        #ui { 
            position: absolute; top: 0; left: 0; width: 100%;
            padding: 10px; box-sizing: border-box;
            background: rgba(0, 0, 0, 0.8);
            display: flex; justify-content: space-between; align-items: center;
            z-index: 10;
        }

        .hud-group { display: flex; gap: 15px; align-items: center; font-size: 14px; }
        .bar-container { width: 200px; height: 10px; background: #444; border: 1px solid #666; position: relative; }
        #hpBar { width: 100%; height: 100%; background: #0f0; transition: width 0.1s, background-color 0.2s; }
        
        button, input[type=file] { 
            background: #333; color: white; border: 1px solid #777; 
            padding: 5px 10px; cursor: pointer; border-radius: 4px; font-size: 12px;
        }
        button:hover { background: #555; }
        button:disabled { background: #222; color: #555; cursor: default; }

        #overlay {
            position: absolute; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.85);
            display: flex; flex-direction: column; justify-content: center; align-items: center;
            z-index: 20;
            display: none; /* 初期は非表示、JSで制御 */
        }
        #overlay h1 { font-size: 48px; color: #fff; text-shadow: 0 0 10px #f0f; margin-bottom: 20px; }
        #overlay p { font-size: 18px; color: #ccc; margin-bottom: 30px; text-align: center; line-height: 1.6; }
        #overlay .key { color: #ffeb3b; font-weight: bold; border: 1px solid #666; padding: 2px 6px; border-radius: 4px; }

        #comboText {
            position: absolute; top: 20%; left: 50%; transform: translate(-50%, -50%);
            font-size: 40px; font-weight: bold; color: rgba(255, 255, 255, 0.2);
            pointer-events: none; z-index: 5;
        }
    </style>
</head>
<body>

<div id="ui">
    <div class="hud-group">
        <div>HP:</div>
        <div class="bar-container"><div id="hpBar"></div></div>
        <div id="scoreDisplay">Score: 0</div>
    </div>
    <div class="hud-group">
        <input type="file" id="fileInput" accept=".mid,.midi">
        <button id="resetBtn">リセット</button>
    </div>
</div>

<div id="gameContainer">
    <canvas id="gameCanvas"></canvas>
    <div id="comboText"></div>
    
    <div id="overlay">
        <h1 id="overlayTitle">MIDI SMASH</h1>
        <p id="overlayMessage">
            MIDIファイルをロードしてください。<br><br>
            [矢印キー] 移動<br>
            [Z] 誘導ショット (同じ色の敵を狙う)<br>
            [X] ジャンプ (緊急回避)<br>
            [ESC] ポーズ
        </p>
        <button id="startBtn" style="font-size: 24px; padding: 10px 40px;" disabled>START</button>
    </div>
</div>

<script>
    // --- 定数・設定 ---
    const COLORS = ["#ff5252", "#448aff", "#69f0ae", "#ffd740", "#e040fb", "#536dfe", "#ff9800", "#00bcd4"];
    const PLAYER_SPEED = 4;
    const SHOT_SPEED = 12;
    const ENEMY_SPEED_BASE = 1.5;
    const STAR_LEAD_TIME = 1.5; // ノート発音の何秒前に星を出すか
    const MAX_HP = 100;

    // --- グローバル変数 ---
    const canvas = document.getElementById('gameCanvas');
    const ctx = canvas.getContext('2d');
    
    // ゲーム状態
    let gameState = 'init'; // init, ready, playing, paused, gameover, clear
    let lastTime = 0;
    let gameTime = 0;
    let score = 0;
    let parsedNotes = []; // MIDIデータ
    let spawnIndex = 0;   // 次に敵として出すノートのインデックス
    let starIndex = 0;    // 次に星として出すノートのインデックス
    let songDuration = 0;

    // キー入力管理
    const keys = { ArrowUp:false, ArrowDown:false, ArrowLeft:false, ArrowRight:false, z:false, x:false };
    
    // エンティティ
    let player = { x: 0, y: 0, r: 15, color: "#fff", hp: MAX_HP, jumpHeight: 0, isJumping: false, jumpVel: 0 };
    let enemies = [];
    let bullets = [];
    let stars = [];
    let particles = [];
    
    // 音源
    let synths = [];
    let isAudioInitialized = false;

    // UI要素
    const hpBar = document.getElementById('hpBar');
    const overlay = document.getElementById('overlay');
    const overlayTitle = document.getElementById('overlayTitle');
    const overlayMsg = document.getElementById('overlayMessage');
    const startBtn = document.getElementById('startBtn');
    const comboText = document.getElementById('comboText');

    // --- 初期化 ---
    function resize() {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        if(gameState === 'init' || gameState === 'ready') {
            player.x = canvas.width / 2;
            player.y = canvas.height / 2;
        }
    }
    window.addEventListener('resize', resize);
    resize();

    // --- Tone.js セットアップ ---
    async function initAudio() {
        if(isAudioInitialized) return;
        await Tone.start();
        
        // 簡易シンセ(トラックごとに少し音色を変える)
        const synthTypes = ['triangle', 'square', 'sawtooth'];
        for(let i=0; i<8; i++) {
            synths.push(new Tone.PolySynth(Tone.Synth, {
                volume: -8,
                oscillator: { type: synthTypes[i % 3] },
                envelope: { attack: 0.02, decay: 0.1, sustain: 0.1, release: 1 }
            }).toDestination());
        }
        isAudioInitialized = true;
    }

    // --- ゲームループ ---
    function gameLoop(timestamp) {
        if (!lastTime) lastTime = timestamp;
        const dt = (timestamp - lastTime) / 1000;
        lastTime = timestamp;

        if (gameState === 'playing') {
            update(dt);
            draw();
        } else if (gameState === 'paused') {
            // 描画だけ更新(止まっている様子を見せる)
            draw();
            ctx.fillStyle = "rgba(0,0,0,0.5)";
            ctx.fillRect(0,0,canvas.width, canvas.height);
            ctx.fillStyle = "white";
            ctx.font = "30px sans-serif";
            ctx.textAlign = "center";
            ctx.fillText("PAUSED", canvas.width/2, canvas.height/2);
        }

        requestAnimationFrame(gameLoop);
    }

    // --- 更新処理 (Update) ---
    function update(dt) {
        gameTime += dt;

        // 1. プレイヤー移動
        if (!player.isJumping) {
            if (keys.ArrowUp) player.y -= PLAYER_SPEED;
            if (keys.ArrowDown) player.y += PLAYER_SPEED;
            if (keys.ArrowLeft) player.x -= PLAYER_SPEED;
            if (keys.ArrowRight) player.x += PLAYER_SPEED;
        }

        // 画面端制限
        player.x = Math.max(player.r, Math.min(canvas.width - player.r, player.x));
        player.y = Math.max(player.r, Math.min(canvas.height - player.r, player.y));

        // ジャンプ処理
        if (keys.x && !player.isJumping) {
            player.isJumping = true;
            player.jumpVel = 10;
        }
        if (player.isJumping) {
            player.jumpHeight += player.jumpVel;
            player.jumpVel -= 0.6; // 重力
            if (player.jumpHeight <= 0) {
                player.jumpHeight = 0;
                player.isJumping = false;
            }
        }

        // 2. ショット発射 (Zキー押下時)
        // 押しっぱなし連射防止のため、単純なカウンタ等の制御は省略し、ここでは「押された瞬間」またはレート制御が必要
        // 今回は簡易的に「毎フレーム判定」だが、実用上はクールタイムを入れる
        if (keys.z && player.coolTime <= 0) {
            shootHomingBullet();
            player.coolTime = 0.15; // 連射速度
        }
        if (player.coolTime > 0) player.coolTime -= dt;

        // 3. スポーン処理 (MIDIデータに基づく)
        
        // 星の出現 (ノートより少し前)
        while (starIndex < parsedNotes.length && parsedNotes[starIndex].time - STAR_LEAD_TIME <= gameTime) {
            spawnStar(parsedNotes[starIndex]);
            starIndex++;
        }

        // 敵の出現 (ノートのタイミング ※ゲーム的調整で少し早めに出して、ヒット時に音が合うようにするのは難しいので、
        // 今回は「出現=ノートタイミング」とし、撃破時に「その音」を鳴らす方式にする)
        while (spawnIndex < parsedNotes.length && parsedNotes[spawnIndex].time <= gameTime) {
            spawnEnemy(parsedNotes[spawnIndex]);
            spawnIndex++;
        }

        // ゲームクリア判定
        if (spawnIndex >= parsedNotes.length && enemies.length === 0 && stars.length === 0) {
            gameClear();
        }
        // 曲が終わっても敵が残っている場合があるので、時間経過でもクリアにするか判定(今回は敵全滅を待つ)

        // 4. エンティティ更新 & 衝突判定
        
        // --- 星 ---
        for (let i = stars.length - 1; i >= 0; i--) {
            let s = stars[i];
            // プレイヤーに向かって飛ぶ
            let dx = player.x - s.x;
            let dy = player.y - s.y;
            let dist = Math.sqrt(dx*dx + dy*dy);
            
            s.x += (dx / dist) * s.speed;
            s.y += (dy / dist) * s.speed;

            // プレイヤーとの衝突 (ジャンプ中でも取れる)
            if (dist < player.r + s.r) {
                // 色変更
                player.color = s.color;
                createParticles(player.x, player.y, s.color, 5);
                stars.splice(i, 1);
                score += 100;
                continue;
            }
            // 画面外等での削除は今回はしない(必ずプレイヤーに向かう)
        }

        // --- 弾 ---
        for (let i = bullets.length - 1; i >= 0; i--) {
            let b = bullets[i];
            // ターゲットが生きていれば追尾
            if (b.target && !b.target.dead) {
                let dx = b.target.x - b.x;
                let dy = b.target.y - b.y;
                let angle = Math.atan2(dy, dx);
                b.vx = Math.cos(angle) * SHOT_SPEED;
                b.vy = Math.sin(angle) * SHOT_SPEED;
            }
            
            b.x += b.vx;
            b.y += b.vy;

            // 画面外削除
            if (b.x < 0 || b.x > canvas.width || b.y < 0 || b.y > canvas.height) {
                bullets.splice(i, 1);
                continue;
            }

            // 敵との衝突
            let hit = false;
            for (let j = 0; j < enemies.length; j++) {
                let e = enemies[j];
                let dx = e.x - b.x;
                let dy = e.y - b.y;
                if (Math.sqrt(dx*dx + dy*dy) < e.r + b.r) {
                    // ダメージ処理
                    if (b.color === e.color) {
                        e.hp -= 5; // 同色なら即死級ダメージ
                    } else {
                        e.hp -= 1; // 異色は1ダメージ
                    }

                    if (e.hp <= 0) {
                        killEnemy(e, true); // プレイヤーが倒した
                        enemies.splice(j, 1);
                    } else {
                        // ダメージエフェクト
                        createParticles(e.x, e.y, "#fff", 2);
                    }
                    
                    bullets.splice(i, 1);
                    hit = true;
                    break;
                }
            }
        }

        // --- 敵 ---
        for (let i = enemies.length - 1; i >= 0; i--) {
            let e = enemies[i];
            // プレイヤーに向かう
            let dx = player.x - e.x;
            let dy = player.y - e.y;
            let dist = Math.sqrt(dx*dx + dy*dy);
            
            // 少し揺らぎながら進む
            let angle = Math.atan2(dy, dx) + Math.sin(gameTime * 5 + e.id) * 0.2;
            e.x += Math.cos(angle) * e.speed;
            e.y += Math.sin(angle) * e.speed;

            // プレイヤーとの衝突 (ジャンプ中は回避)
            // ジャンプ高さ(z)を考慮していない簡易実装だが、"jumpHeight > 10"なら無敵とする
            if (player.jumpHeight < 20 && dist < player.r + e.r) {
                // ダメージ
                player.hp -= 10;
                updateHpBar();
                createParticles(player.x, player.y, "#f00", 10);
                
                // 敵消滅
                killEnemy(e, false); // 衝突死(音は鳴らない or 不協和音)
                enemies.splice(i, 1);

                if (player.hp <= 0) {
                    gameOver();
                }
            }
        }

        // --- パーティクル ---
        for (let i = particles.length - 1; i >= 0; i--) {
            let p = particles[i];
            p.x += p.vx;
            p.y += p.vy;
            p.life -= dt;
            if (p.life <= 0) particles.splice(i, 1);
        }
        
        // HP自然回復(微量)
        /* if(player.hp < MAX_HP) {
            player.hp += dt; 
            updateHpBar();
        } */
    }

    // --- 描画処理 (Draw) ---
    function draw() {
        ctx.fillStyle = "#111";
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        // 背景エフェクト(ビートに合わせて明滅させても面白いが今回は省略)

        // 星
        stars.forEach(s => {
            drawStarShape(s.x, s.y, s.r, s.color, gameTime * 5);
        });

        // 敵 (スライム)
        enemies.forEach(e => {
            drawSlime(e.x, e.y, e.r, e.color, gameTime + e.id);
        });

        // プレイヤー
        // ジャンプ中は拡大 & 影を表示
        let scale = 1 + (player.jumpHeight / 50);
        
        // 影
        ctx.fillStyle = "rgba(0,0,0,0.5)";
        ctx.beginPath();
        ctx.ellipse(player.x, player.y + 20, player.r, player.r * 0.5, 0, 0, Math.PI*2);
        ctx.fill();

        // 本体
        drawSlime(player.x, player.y - player.jumpHeight, player.r * scale, player.color, gameTime);
        
        // 弾
        bullets.forEach(b => {
            ctx.fillStyle = b.color;
            ctx.beginPath();
            ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2);
            ctx.fill();
            ctx.strokeStyle = "#fff";
            ctx.lineWidth = 1;
            ctx.stroke();
        });

        // パーティクル
        particles.forEach(p => {
            ctx.fillStyle = p.color;
            ctx.globalAlpha = p.life;
            ctx.beginPath();
            ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
            ctx.fill();
            ctx.globalAlpha = 1.0;
        });

        // UI更新
        document.getElementById('scoreDisplay').innerText = "Score: " + score;
    }

    // --- 図形描画ヘルパー ---
    function drawSlime(x, y, r, color, time) {
        ctx.fillStyle = color;
        ctx.strokeStyle = "rgba(255,255,255,0.3)";
        ctx.lineWidth = 2;
        
        ctx.beginPath();
        // 頂点を揺らしてスライムっぽく
        const points = 8;
        for (let i = 0; i <= points; i++) {
            let angle = (i / points) * Math.PI * 2;
            let wobble = Math.sin(time * 3 + i * 2) * 3; // 揺らぎ
            let px = x + Math.cos(angle) * (r + wobble);
            let py = y + Math.sin(angle) * (r * 0.8 + wobble); // 少し平べったく
            if (i === 0) ctx.moveTo(px, py);
            else ctx.lineTo(px, py);
        }
        ctx.closePath();
        ctx.fill();
        ctx.stroke();

        // 目
        ctx.fillStyle = "white";
        ctx.beginPath();
        ctx.arc(x - r*0.3, y - r*0.2, r*0.25, 0, Math.PI*2);
        ctx.arc(x + r*0.3, y - r*0.2, r*0.25, 0, Math.PI*2);
        ctx.fill();
        ctx.fillStyle = "black";
        ctx.beginPath();
        ctx.arc(x - r*0.3, y - r*0.2, r*0.1, 0, Math.PI*2);
        ctx.arc(x + r*0.3, y - r*0.2, r*0.1, 0, Math.PI*2);
        ctx.fill();
    }

    function drawStarShape(cx, cy, r, color, angle) {
        ctx.save();
        ctx.translate(cx, cy);
        ctx.rotate(angle);
        ctx.fillStyle = color;
        ctx.shadowBlur = 10;
        ctx.shadowColor = color;
        ctx.beginPath();
        for (let i = 0; i < 5; i++) {
            ctx.lineTo(Math.cos((18 + i * 72) * Math.PI / 180) * r,
                       Math.sin((18 + i * 72) * Math.PI / 180) * r);
            ctx.lineTo(Math.cos((54 + i * 72) * Math.PI / 180) * (r / 2),
                       Math.sin((54 + i * 72) * Math.PI / 180) * (r / 2));
        }
        ctx.closePath();
        ctx.fill();
        ctx.restore();
    }

    // --- ロジック関数 ---

    function spawnEnemy(note) {
        // 画面外のランダムな位置から出現
        const angle = Math.random() * Math.PI * 2;
        const dist = Math.max(canvas.width, canvas.height) / 2 + 50;
        
        let colorIndex = note.trackIndex % COLORS.length;
        let color = COLORS[colorIndex];

        enemies.push({
            x: canvas.width/2 + Math.cos(angle) * dist,
            y: canvas.height/2 + Math.sin(angle) * dist,
            r: 20,
            color: color,
            hp: 5, // HP 5
            speed: ENEMY_SPEED_BASE * (1.0 + Math.random() * 0.5), // 速度ゆらぎ
            note: note,
            id: Math.random(),
            dead: false
        });
    }

    function spawnStar(note) {
        // 同じ色の星を生成。プレイヤーより少し速く移動してくる
        const angle = Math.random() * Math.PI * 2;
        const dist = Math.max(canvas.width, canvas.height) / 2 + 50;
        let colorIndex = note.trackIndex % COLORS.length;
        
        stars.push({
            x: canvas.width/2 + Math.cos(angle) * dist,
            y: canvas.height/2 + Math.sin(angle) * dist,
            r: 10,
            color: COLORS[colorIndex],
            speed: PLAYER_SPEED * 1.5,
        });
    }

    function shootHomingBullet() {
        // 現在の色と同じ色の敵を探す
        let target = null;
        let minDist = Infinity;

        // 同色の敵を優先検索
        for (let e of enemies) {
            if (e.color === player.color) {
                let d = Math.pow(e.x - player.x, 2) + Math.pow(e.y - player.y, 2);
                if (d < minDist) {
                    minDist = d;
                    target = e;
                }
            }
        }

        // いなければ一番近い敵(色は違う)
        if (!target) {
            minDist = Infinity;
            for (let e of enemies) {
                let d = Math.pow(e.x - player.x, 2) + Math.pow(e.y - player.y, 2);
                if (d < minDist) {
                    minDist = d;
                    target = e;
                }
            }
        }

        // それでもいなければ適当な方向に撃つ
        let vx = 0, vy = -SHOT_SPEED;
        if (!target) {
            // 移動方向へ撃つ
            if(keys.ArrowUp) { vx=0; vy=-SHOT_SPEED; }
            else if(keys.ArrowDown) { vx=0; vy=SHOT_SPEED; }
            else if(keys.ArrowLeft) { vx=-SHOT_SPEED; vy=0; }
            else if(keys.ArrowRight) { vx=SHOT_SPEED; vy=0; }
        }

        bullets.push({
            x: player.x, y: player.y,
            vx: vx, vy: vy,
            r: 5,
            color: player.color,
            target: target
        });
    }

    function killEnemy(enemy, killedByPlayer) {
        enemy.dead = true;
        
        if (killedByPlayer) {
            // 音を鳴らす
            let trackId = enemy.note.trackIndex % synths.length;
            let vel = enemy.note.velocity;
            
            // 色違いで倒した場合はベロシティ(音量・強さ)を下げる
            if (player.color !== enemy.color) {
                vel *= 0.3; 
                score += 10;
            } else {
                score += 500;
                displayCombo("PERFECT!");
            }

            synths[trackId].triggerAttackRelease(enemy.note.name, enemy.note.duration, Tone.now(), vel);
            
            createParticles(enemy.x, enemy.y, enemy.color, 10);
        }
    }

    function createParticles(x, y, color, count) {
        for(let i=0; i<count; i++) {
            let angle = Math.random() * Math.PI * 2;
            let speed = Math.random() * 5;
            particles.push({
                x: x, y: y,
                vx: Math.cos(angle) * speed,
                vy: Math.sin(angle) * speed,
                color: color,
                life: 1.0,
                size: Math.random() * 3 + 1
            });
        }
    }

    function updateHpBar() {
        let pct = (player.hp / MAX_HP) * 100;
        if(pct < 0) pct = 0;
        hpBar.style.width = pct + "%";
        if (pct < 30) hpBar.style.backgroundColor = "#f00";
        else if (pct < 60) hpBar.style.backgroundColor = "#ff0";
        else hpBar.style.backgroundColor = "#0f0";
    }

    function displayCombo(text) {
        comboText.innerText = text;
        comboText.style.opacity = 1;
        setTimeout(() => { comboText.style.opacity = 0; }, 500);
    }

    // --- ゲーム進行管理 ---

    function gameOver() {
        gameState = 'gameover';
        overlayTitle.innerText = "GAME OVER";
        overlayMsg.innerText = "HPが尽きました...";
        overlayTitle.style.color = "#f00";
        overlay.style.display = "flex";
        startBtn.innerText = "もう一度";
        startBtn.disabled = false;
    }

    function gameClear() {
        gameState = 'clear';
        overlayTitle.innerText = "GAME CLEAR!!";
        overlayMsg.innerText = `Score: ${score}`;
        overlayTitle.style.color = "#0ff";
        overlay.style.display = "flex";
        startBtn.innerText = "もう一度";
        startBtn.disabled = false;
    }

    function resetGame() {
        gameTime = 0;
        score = 0;
        spawnIndex = 0;
        starIndex = 0;
        player.hp = MAX_HP;
        player.x = canvas.width/2;
        player.y = canvas.height/2;
        player.jumpHeight = 0;
        player.color = "#fff";
        enemies = [];
        bullets = [];
        stars = [];
        particles = [];
        updateHpBar();
    }

    // --- イベントリスナー ---

    document.getElementById('fileInput').addEventListener('change', async (e) => {
        const file = e.target.files[0];
        if (!file) return;

        overlay.style.display = "flex";
        overlayTitle.innerText = "LOADING...";
        startBtn.disabled = true;

        const reader = new FileReader();
        reader.onload = function(ev) {
            try {
                const midiData = new Midi(ev.target.result);
                parsedNotes = [];
                
                midiData.tracks.forEach((track, i) => {
                    track.notes.forEach(n => {
                        parsedNotes.push({
                            name: n.name,
                            time: n.time,
                            duration: n.duration,
                            velocity: n.velocity,
                            trackIndex: i
                        });
                    });
                });
                
                // 時間順にソート
                parsedNotes.sort((a,b) => a.time - b.time);
                songDuration = midiData.duration;

                overlayTitle.innerText = "READY";
                overlayMsg.innerHTML = "ファイルを読み込みました。<br>音量に注意してスタートしてください。";
                overlayTitle.style.color = "#fff";
                startBtn.disabled = false;
                gameState = 'ready';

            } catch(err) {
                console.error(err);
                alert("MIDI解析エラー");
            }
        };
        reader.readAsArrayBuffer(file);
    });

    startBtn.addEventListener('click', async () => {
        await initAudio();
        resetGame();
        gameState = 'playing';
        overlay.style.display = "none";
        requestAnimationFrame(gameLoop);
    });

    document.getElementById('resetBtn').addEventListener('click', () => {
        gameState = 'init';
        overlay.style.display = "flex";
        overlayTitle.innerText = "MIDI SLIME SURVIVOR";
        overlayMsg.innerHTML = "MIDIファイルをロードしてください。";
        startBtn.disabled = true;
    });

    window.addEventListener('keydown', (e) => {
        if(keys.hasOwnProperty(e.key)) keys[e.key] = true;
        if(e.key === 'z' || e.key === 'Z') keys.z = true;
        if(e.key === 'x' || e.key === 'X') keys.x = true;
        
        if(e.key === 'Escape') {
            if(gameState === 'playing') {
                gameState = 'paused';
                overlay.style.display = 'flex';
                overlayTitle.innerText = "PAUSED";
                overlayMsg.innerText = "ESCキーで再開";
                startBtn.innerText = "RESUME";
                startBtn.onclick = () => {
                    gameState = 'playing';
                    overlay.style.display = 'none';
                    lastTime = 0; // デルタタイムのリセット
                    requestAnimationFrame(gameLoop);
                };
            } else if (gameState === 'paused') {
                gameState = 'playing';
                overlay.style.display = 'none';
                lastTime = 0;
                requestAnimationFrame(gameLoop);
            }
        }
    });

    window.addEventListener('keyup', (e) => {
        if(keys.hasOwnProperty(e.key)) keys[e.key] = false;
        if(e.key === 'z' || e.key === 'Z') keys.z = false;
        if(e.key === 'x' || e.key === 'X') keys.x = false;
    });

</script>
</body>
</html>

いいなと思ったら応援しよう!

コメント

コメントするには、 ログイン または 会員登録 をお願いします。
MidiSmash|古井和雄
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1