プログラムコード

シューティング

<!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>Neon Homing Barrage</title>
    <style>
        * { margin: 0; padding: 0; }
        body { 
            background: #000; 
            overflow: hidden; 
            touch-action: none; 
            display: flex; 
            justify-content: center; 
            align-items: center;
            height: 100vh;
        }
        #gameContainer {
            position: relative;
            width: 100vw;
            height: 177.77vw; 
            max-height: 100vh;
            max-width: 56.25vh; 
            background: radial-gradient(circle at center, #001 0%, #000 100%);
            border: 1px solid #333;
        }
        canvas { width: 100%; height: 100%; display: block; }
        #ui {
            position: absolute; top: 15px; left: 15px;
            color: #0ff; font-family: 'Courier New', monospace; font-size: 14px;
            pointer-events: none; text-shadow: 0 0 8px #0ff;
            z-index: 10;
        }
    </style>
</head>
<body>
    <div id="gameContainer">
        <div id="ui">BOSS: <span id="hp">100</span>%<br>PHASE: <span id="phase">1</span></div>
        <canvas id="gameCanvas"></canvas>
    </div>

<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const hpDisplay = document.getElementById('hp');
const phaseDisplay = document.getElementById('phase');

let width, height;
let player = { x: 0, y: 0, r: 6, speed: 6 };
let boss = { x: 0, y: 0, hp: 15000, maxHp: 15000 };
let homingMissiles = [];
let enemyBullets = [];
let frame = 0;
let keys = {};

function init() {
    const dpr = window.devicePixelRatio || 1;
    width = canvas.clientWidth;
    height = canvas.clientHeight;
    canvas.width = width * dpr;
    canvas.height = height * dpr;
    ctx.scale(dpr, dpr);
    player.x = width / 2;
    player.y = height * 0.85;
}

// 虹色の生成
function getRainbowColor(offset) {
    return `HSL(${(frame + offset) % 360}, 100%, 70%)`;
}

function spawnBarrage() {
    const phaseIdx = Math.floor((frame / 300) % 4);
    phaseDisplay.innerText = phaseIdx + 1;

    // 美しい多色弾幕
    if (frame % 2 === 0) {
        let color = getRainbowColor(frame);
        switch(phaseIdx) {
            case 0: // スパイラルフラワー
                for(let i=0; i<2; i++) {
                    let a = frame * 0.1 + (i * Math.PI);
                    enemyBullets.push({ x: boss.x, y: boss.y, vx: Math.cos(a)*4, vy: Math.sin(a)*4, c: color, r: 3 });
                }
                break;
            case 1: // レインボーシャワー
                if (frame % 20 === 0) {
                    for(let i=0; i<15; i++) {
                        let a = (i/15) * Math.PI + Math.sin(frame*0.05);
                        enemyBullets.push({ x: boss.x, y: boss.y, vx: Math.cos(a)*5, vy: Math.sin(a)*5, c: getRainbowColor(i*20), r: 4 });
                    }
                }
                break;
            case 2: // 万華鏡
                let a2 = Math.sin(frame * 0.05) * 5;
                enemyBullets.push({ x: boss.x, y: boss.y, vx: a2, vy: 4, c: color, r: 3 });
                enemyBullets.push({ x: boss.x, y: boss.y, vx: -a2, vy: 4, c: color, r: 3 });
                break;
            case 3: // ギャラクシーリング
                if (frame % 30 === 0) {
                    for(let i=0; i<30; i++) {
                        let a = (i/30) * Math.PI * 2;
                        let spd = 2 + Math.sin(frame*0.1)*1;
                        enemyBullets.push({ x: boss.x, y: boss.y, vx: Math.cos(a)*spd, vy: Math.sin(a)*spd, c: getRainbowColor(i*12), r: 2 });
                    }
                }
                break;
        }
    }
}

function update() {
    frame++;

    // キーボード移動
    if (keys['ArrowLeft'] || keys['a']) player.x -= player.speed;
    if (keys['ArrowRight'] || keys['d']) player.x += player.speed;
    if (keys['ArrowUp'] || keys['w']) player.y -= player.speed;
    if (keys['ArrowDown'] || keys['s']) player.y += player.speed;
    player.x = Math.max(player.r, Math.min(width - player.r, player.x));
    player.y = Math.max(player.r, Math.min(height - player.r, player.y));

    // ボスの動き
    boss.x = width/2 + Math.sin(frame * 0.04) * (width * 0.35);
    boss.y = height * 0.2 + Math.cos(frame * 0.03) * 30;

    // 自機:大量ホーミング弾
    if (frame % 4 === 0) {
        homingMissiles.push({ 
            x: player.x, y: player.y, 
            vx: (Math.random()-0.5)*10, vy: (Math.random()-0.5)*5, 
            life: 100 
        });
    }

    homingMissiles.forEach((m, i) => {
        // ボスへの追尾計算
        let dx = boss.x - m.x;
        let dy = boss.y - m.y;
        let dist = Math.hypot(dx, dy);
        m.vx += (dx / dist) * 0.8;
        m.vy += (dy / dist) * 0.8;
        // 速度制限
        m.vx *= 0.92; m.vy *= 0.92;
        m.x += m.vx; m.y += m.vy;

        if (dist < 30) {
            boss.hp -= 4;
            homingMissiles.splice(i, 1);
        }
        if (m.y < -10 || m.life-- < 0) homingMissiles.splice(i, 1);
    });

    // 敵弾更新
    enemyBullets.forEach((b, i) => {
        b.x += b.vx; b.y += b.vy;
        if (Math.hypot(b.x - player.x, b.y - player.y) < 4) {
             // 被弾演出(省略)
        }
        if (b.y > height + 20 || b.y < -20 || b.x < -20 || b.x > width + 20) enemyBullets.splice(i, 1);
    });

    hpDisplay.innerText = Math.max(0, Math.ceil((boss.hp / boss.maxHp) * 100));
    spawnBarrage();
}

function draw() {
    ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
    ctx.fillRect(0, 0, width, height);

    // ボス(ネオン・ワイヤー)
    ctx.shadowBlur = 15;
    ctx.strokeStyle = '#0ff';
    ctx.shadowColor = '#0ff';
    ctx.beginPath();
    let sides = 8;
    for(let i=0; i<=sides; i++) {
        let a = (i/sides) * Math.PI * 2 + (frame * 0.02);
        let r = 40 + Math.sin(frame*0.1)*5;
        let x = boss.x + Math.cos(a) * r;
        let y = boss.y + Math.sin(a) * r;
        i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
    }
    ctx.stroke();

    // ホーミング弾(青白い光の粒)
    ctx.shadowBlur = 10;
    ctx.shadowColor = '#0af';
    ctx.fillStyle = '#fff';
    homingMissiles.forEach(m => {
        ctx.beginPath();
        ctx.arc(m.x, m.y, 2, 0, Math.PI*2);
        ctx.fill();
    });

    // 敵弾(カラフル)
    ctx.shadowBlur = 8;
    enemyBullets.forEach(b => {
        ctx.shadowColor = b.c;
        ctx.fillStyle = b.c;
        ctx.beginPath();
        ctx.arc(b.x, b.y, b.r, 0, Math.PI*2);
        ctx.fill();
    });

    // 自機
    ctx.shadowBlur = 15;
    ctx.shadowColor = '#fff';
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(player.x, player.y, player.r, 0, Math.PI*2);
    ctx.fill();
    ctx.shadowBlur = 0;
}

function loop() {
    update();
    draw();
    requestAnimationFrame(loop);
}

// 操作関連
window.addEventListener('keydown', e => keys[e.key] = true);
window.addEventListener('keyup', e => keys[e.key] = false);

const handleTouch = (e) => {
    e.preventDefault();
    const rect = canvas.getBoundingClientRect();
    const touch = e.touches[0];
    player.x = (touch.clientX - rect.left) * (width / rect.width);
    player.y = (touch.clientY - rect.top) * (height / rect.height) - 40;
};
canvas.addEventListener('touchstart', handleTouch, {passive: false});
canvas.addEventListener('touchmove', handleTouch, {passive: false});

window.addEventListener('load', () => { init(); loop(); });
window.addEventListener('resize', init);
</script>
</body>
</html>