這樓用來發一些AI寫的小遊戲~
仿懷舊太空射擊遊戲

<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dragon Space Shooter</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(circle at top, #1a2342 0%, #090d18 55%, #03050a 100%);
color: #f3f4f6;
font-family: Arial, "Noto Sans TC", sans-serif;
}
.wrap {
text-align: center;
padding: 16px;
}
h1 {
margin: 0 0 10px;
font-size: 28px;
letter-spacing: 1px;
}
.hint {
margin: 0 0 12px;
color: #cbd5e1;
font-size: 14px;
}
canvas {
display: block;
margin: 0 auto;
border: 3px solid #7dd3fc;
border-radius: 12px;
background: linear-gradient(to bottom, #07111f, #08101a 45%, #03060d);
box-shadow: 0 0 24px rgba(125, 211, 252, 0.25);
image-rendering: pixelated;
}
.controls {
margin-top: 14px;
display: flex;
justify-content: center;
gap: 12px;
flex-wrap: wrap;
}
button {
border: none;
background: #f97316;
color: white;
padding: 10px 18px;
border-radius: 999px;
font-size: 15px;
cursor: pointer;
font-weight: bold;
box-shadow: 0 6px 14px rgba(249, 115, 22, 0.3);
}
button:hover { filter: brightness(1.08); }
.note {
margin-top: 10px;
font-size: 13px;
color: #94a3b8;
}
</style>
</head>
<body>
<div class="wrap">
<h1>🐉 Dragon Space Shooter</h1>
<p class="hint">操作:← → 移動,空白鍵發射火球</p>
<canvas id="game" width="480" height="640"></canvas>
<div class="controls">
<button id="restartBtn">重新開始</button>
</div>
<div class="note">復古 2D 太空射擊風格,單一 HTML 即可運行</div>
</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const restartBtn = document.getElementById('restartBtn');
const WIDTH = canvas.width;
const HEIGHT = canvas.height;
const keys = {
ArrowLeft: false,
ArrowRight: false,
Space: false,
};
let stars = [];
let player;
let bullets;
let enemies;
let score;
let gameOver;
let enemySpawnTimer;
let enemySpawnInterval;
let fireCooldown;
let animationId;
function resetGame() {
player = {
x: WIDTH / 2 - 20,
y: HEIGHT - 90,
width: 40,
height: 48,
speed: 5.5,
};
bullets = [];
enemies = [];
score = 0;
gameOver = false;
enemySpawnTimer = 0;
enemySpawnInterval = 42;
fireCooldown = 0;
stars = Array.from({ length: 70 }, () => ({
x: Math.random() * WIDTH,
y: Math.random() * HEIGHT,
size: Math.random() * 2 + 1,
speed: Math.random() * 1.4 + 0.6,
}));
}
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function rectsOverlap(a, b) {
return (
a.x < b.x + b.width &&
a.x + a.width > b.x &&
a.y < b.y + b.height &&
a.y + a.height > b.y
);
}
function spawnEnemy() {
const size = 26 + Math.random() * 18;
enemies.push({
x: Math.random() * (WIDTH - size),
y: -size,
width: size,
height: size,
speed: 1.6 + Math.random() * 2.3 + Math.min(score * 0.02, 2.2),
wobble: Math.random() * Math.PI * 2,
wobbleSpeed: 0.03 + Math.random() * 0.04,
});
}
function shoot() {
bullets.push({
x: player.x + player.width / 2 - 4,
y: player.y + 8,
width: 8,
height: 18,
speed: 8.5,
});
}
function update() {
updateStars();
if (gameOver) return;
if (keys.ArrowLeft) player.x -= player.speed;
if (keys.ArrowRight) player.x += player.speed;
player.x = clamp(player.x, 0, WIDTH - player.width);
if (fireCooldown > 0) fireCooldown--;
if (keys.Space && fireCooldown <= 0) {
shoot();
fireCooldown = 10;
}
bullets.forEach((bullet) => {
bullet.y -= bullet.speed;
});
bullets = bullets.filter((bullet) => bullet.y + bullet.height > 0);
enemySpawnTimer++;
if (enemySpawnTimer >= enemySpawnInterval) {
spawnEnemy();
enemySpawnTimer = 0;
enemySpawnInterval = Math.max(18, 42 - Math.floor(score / 5));
}
enemies.forEach((enemy) => {
enemy.y += enemy.speed;
enemy.wobble += enemy.wobbleSpeed;
enemy.x += Math.sin(enemy.wobble) * 1.2;
enemy.x = clamp(enemy.x, 0, WIDTH - enemy.width);
});
for (let i = enemies.length - 1; i >= 0; i--) {
const enemy = enemies[i];
if (rectsOverlap(player, enemy)) {
gameOver = true;
break;
}
let destroyed = false;
for (let j = bullets.length - 1; j >= 0; j--) {
if (rectsOverlap(bullets[j], enemy)) {
bullets.splice(j, 1);
enemies.splice(i, 1);
score += 10;
destroyed = true;
break;
}
}
if (destroyed) continue;
}
enemies = enemies.filter((enemy) => enemy.y < HEIGHT + enemy.height);
}
function updateStars() {
for (const star of stars) {
star.y += star.speed;
if (star.y > HEIGHT) {
star.y = -star.size;
star.x = Math.random() * WIDTH;
star.size = Math.random() * 2 + 1;
star.speed = Math.random() * 1.4 + 0.6;
}
}
}
function drawBackground() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
for (const star of stars) {
ctx.fillStyle = '#ffffff';
ctx.fillRect(star.x, star.y, star.size, star.size);
}
}
function drawPlayer(p) {
const x = p.x;
const y = p.y;
// wings
ctx.fillStyle = '#7c3aed';
ctx.beginPath();
ctx.moveTo(x + 8, y + 22);
ctx.lineTo(x - 4, y + 40);
ctx.lineTo(x + 10, y + 36);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(x + p.width - 8, y + 22);
ctx.lineTo(x + p.width + 4, y + 40);
ctx.lineTo(x + p.width - 10, y + 36);
ctx.closePath();
ctx.fill();
// body
ctx.fillStyle = '#22c55e';
ctx.beginPath();
ctx.moveTo(x + p.width / 2, y);
ctx.lineTo(x + p.width - 2, y + 18);
ctx.lineTo(x + p.width - 7, y + 42);
ctx.lineTo(x + p.width / 2, y + p.height);
ctx.lineTo(x + 7, y + 42);
ctx.lineTo(x + 2, y + 18);
ctx.closePath();
ctx.fill();
// belly
ctx.fillStyle = '#fde68a';
ctx.beginPath();
ctx.moveTo(x + p.width / 2, y + 10);
ctx.lineTo(x + p.width - 11, y + 28);
ctx.lineTo(x + p.width / 2, y + p.height - 6);
ctx.lineTo(x + 11, y + 28);
ctx.closePath();
ctx.fill();
// horns
ctx.fillStyle = '#f59e0b';
ctx.fillRect(x + 10, y + 6, 4, 8);
ctx.fillRect(x + p.width - 14, y + 6, 4, 8);
// eyes
ctx.fillStyle = '#111827';
ctx.fillRect(x + 14, y + 18, 4, 4);
ctx.fillRect(x + p.width - 18, y + 18, 4, 4);
// fire tail glow
ctx.fillStyle = '#fb923c';
ctx.beginPath();
ctx.moveTo(x + p.width / 2, y + p.height);
ctx.lineTo(x + p.width / 2 - 6, y + p.height + 14);
ctx.lineTo(x + p.width / 2 + 6, y + p.height + 14);
ctx.closePath();
ctx.fill();
}
function drawBullets() {
bullets.forEach((bullet) => {
ctx.fillStyle = '#f97316';
ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
ctx.fillStyle = '#fde68a';
ctx.fillRect(bullet.x + 2, bullet.y - 4, bullet.width - 4, 6);
});
}
function drawEnemy(enemy) {
const x = enemy.x;
const y = enemy.y;
const w = enemy.width;
const h = enemy.height;
ctx.fillStyle = '#ef4444';
ctx.beginPath();
ctx.moveTo(x + w / 2, y);
ctx.lineTo(x + w, y + h * 0.4);
ctx.lineTo(x + w * 0.8, y + h);
ctx.lineTo(x + w * 0.2, y + h);
ctx.lineTo(x, y + h * 0.4);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#fecaca';
ctx.fillRect(x + w * 0.28, y + h * 0.34, w * 0.14, h * 0.14);
ctx.fillRect(x + w * 0.58, y + h * 0.34, w * 0.14, h * 0.14);
ctx.fillStyle = '#7f1d1d';
ctx.fillRect(x + w * 0.42, y + h * 0.54, w * 0.16, h * 0.12);
}
function drawHUD() {
ctx.fillStyle = 'rgba(15, 23, 42, 0.7)';
ctx.fillRect(12, 12, 130, 42);
ctx.strokeStyle = '#7dd3fc';
ctx.strokeRect(12, 12, 130, 42);
ctx.fillStyle = '#e2e8f0';
ctx.font = 'bold 22px Arial';
ctx.fillText('Score: ' + score, 24, 40);
}
function drawGameOver() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
ctx.fillRect(0, 0, WIDTH, HEIGHT);
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.font = 'bold 42px Arial';
ctx.fillText('GAME OVER', WIDTH / 2, HEIGHT / 2 - 24);
ctx.font = '24px Arial';
ctx.fillText('最終分數:' + score, WIDTH / 2, HEIGHT / 2 + 18);
ctx.font = '18px Arial';
ctx.fillText('按下「重新開始」再玩一次', WIDTH / 2, HEIGHT / 2 + 58);
ctx.textAlign = 'start';
}
function draw() {
drawBackground();
drawHUD();
drawBullets();
enemies.forEach(drawEnemy);
drawPlayer(player);
if (gameOver) {
drawGameOver();
}
}
function gameLoop() {
update();
draw();
animationId = requestAnimationFrame(gameLoop);
}
window.addEventListener('keydown', (e) => {
if (e.code === 'ArrowLeft' || e.code === 'ArrowRight' || e.code === 'Space') {
e.preventDefault();
}
if (e.code in keys) {
keys[e.code] = true;
}
});
window.addEventListener('keyup', (e) => {
if (e.code in keys) {
keys[e.code] = false;
}
});
restartBtn.addEventListener('click', () => {
resetGame();
});
resetGame();
cancelAnimationFrame(animationId);
gameLoop();
</script>
</body>
</html>
有 2 位朋友喜欢这篇文章:shiningdracon, 闪耀
←目前頭像感謝安雅贈圖。

Quit, dont quit... Noodles, dont noodles...
There is a saying: yesterday is history, tomorrow is a mystery, but today is a gift. That is why it is called the present.
离线