シューティング
<!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>