
【ゲーム】JavaScript:51 スペースインベーダー
「👾 スペースインベーダー」は、プレイヤーが自機を左右に動かし、上空から迫りくるインベーダー(👾)たちを撃ち落とす2Dシューティングゲームです。インベーダーをすべて倒すとステージクリア、より速くなった次ステージへ進めます。時々現れるUFO(🛸)を倒すとボーナス得点もゲットできます。
遊び方・操作方法
PCの場合
- 左右移動:キーボードの ←(左)・→(右)キー
- 弾を撃つ:スペースキー
スマートフォンの場合
- 画面下の「←」「スペース」「→」ボタンで操作
ゲームのルール
- 自機(🪆)を左右に動かし、インベーダー(👾)を全滅させるとステージクリア。
- スペースキーで**弾(🔺)**を発射。自機からは1発ずつしか撃てません。
- インベーダー(👾)は端まで到達すると一段下がります。
- 敵の弾(🔻)やインベーダー本体に自機が当たるとゲームオーバー。
- UFO(🛸)は一定時間ごとに登場し、撃破でボーナス点。
- ステージが進むごとに敵の動きが速くなります。
🎮ゲームプレイ
以下のリンク先から実際にプレイできます。
51 スペースインベーダー
素材のダウンロード
以下のリンクから使用する素材をダウンロードできます。
space_invaders_title.png | space_invaders_clear.png | space_invaders_bg.png |
---|---|---|
![]() | ![]() | ![]() |
ゲーム画面イメージ

プログラム全体(space_invaders.html)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>👾 スペースインベーダー 👾</title>
<style>
body {
background: url('space_invaders_bg.png') no-repeat center center fixed;
background-size: cover;
font-family: 'Segoe UI', 'Yu Gothic', sans-serif;
margin: 0;
padding: 0;
}
.container {
width: 800px;
margin: 40px auto 0 auto;
background: rgba(0,0,0,0.70);
border-radius: 20px;
box-shadow: 0 8px 24px rgba(0,0,0,0.3);
min-height: 600px;
padding-bottom: 20px;
position: relative;
}
.main-title {
font-size: 2.8rem;
color: #fff;
text-shadow: 2px 2px 6px #1e3a8a;
letter-spacing: 0.1em;
margin-top: 20px;
margin-bottom: 18px;
font-weight: bold;
text-align: center;
width: 100%;
}
.title-img, .clear-img {
display: block;
margin: 0 auto 20px auto;
max-width: 400px;
width: 75%;
}
.center {
text-align: center;
}
.subtitle {
font-size: 1.4rem;
color: #ffeb3b;
margin-bottom: 16px;
text-shadow: 1px 1px 2px #333;
}
.rule-title {
font-size: 1.3rem;
color: #3cf4fc;
text-align: center;
margin-bottom: 6px;
font-weight: bold;
}
.rules {
color: #fff;
font-size: 1.1rem;
text-align: left;
margin: 0 auto 20px auto;
max-width: 90%;
background: rgba(50,60,100,0.5);
border-radius: 10px;
padding: 16px;
box-shadow: 0 2px 8px #2225;
}
.start-btn, .back-btn {
display: block;
margin: 28px auto 0 auto;
padding: 14px 46px;
font-size: 1.2rem;
border: none;
border-radius: 12px;
background: linear-gradient(90deg, #50e3c2, #5f8df3);
color: #fff;
font-weight: bold;
cursor: pointer;
box-shadow: 0 2px 6px #2228;
transition: background 0.2s;
}
.start-btn:hover, .back-btn:hover {
background: linear-gradient(90deg, #5f8df3, #50e3c2);
}
.game-area {
background: rgba(25,32,45,0.9);
margin: 0 auto;
border-radius: 12px;
width: 760px;
height: 534px;
position: relative;
overflow: hidden;
box-shadow: 0 2px 12px #1117;
border: 2px solid #444;
margin-bottom: 16px;
}
.score {
color: #fff;
font-size: 1.1rem;
background: rgba(60,60,100,0.8);
border-radius: 8px;
padding: 6px 18px;
position: absolute;
left: 14px;
top: 14px;
z-index: 10;
letter-spacing: 0.1em;
text-shadow: 1px 1px 2px #000;
}
.stage {
color: #fff;
font-size: 1.1rem;
background: rgba(60,60,100,0.8);
border-radius: 8px;
padding: 6px 18px;
position: absolute;
right: 14px;
top: 14px;
z-index: 10;
letter-spacing: 0.1em;
text-shadow: 1px 1px 2px #000;
}
.msg-overlay {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
background: rgba(24,28,80,0.85);
display: flex; align-items: center; justify-content: center;
font-size: 2.1rem; color: #fff;
text-shadow: 2px 2px 8px #111a;
z-index: 50;
border-radius: 12px;
text-align: center;
flex-direction: column;
}
.invader, .player, .bullet, .enemy-bullet, .ufo {
position: absolute;
text-align: center;
user-select: none;
pointer-events: none;
}
.invader {
font-size: 2.3rem;
transition: transform 0.07s;
}
.player {
font-size: 2.3rem;
transition: transform 0.06s;
filter: drop-shadow(0 0 6px #0ff);
}
.bullet {
font-size: 1.6rem;
color: #ff5252;
filter: drop-shadow(0 0 4px #f00);
}
.enemy-bullet {
font-size: 1.6rem;
color: #1cf745;
filter: drop-shadow(0 0 4px #0f5);
}
.ufo {
font-size: 2.5rem;
text-shadow: 0 0 12px #f3f779;
filter: drop-shadow(0 0 12px #fff176);
z-index: 100;
}
.bonus {
font-size: 1.3rem;
color: #ffe931;
background: rgba(40, 45, 12, 0.9);
border-radius: 8px;
padding: 4px 18px;
box-shadow: 0 2px 8px #2228;
position: absolute;
left: 50%;
transform: translateX(-50%);
top: 80px;
z-index: 120;
animation: bonus-pop 1.2s linear;
}
@keyframes bonus-pop {
0% { opacity:0; top:120px; }
10% { opacity:1; top:80px; }
90% { opacity:1; top:80px;}
100% { opacity:0; top:60px;}
}
@media (max-width: 900px) {
.container { width: 98vw; min-width: 340px; }
.game-area { width: 96vw; height: 54vw; min-width: 260px; min-height: 180px; }
}
</style>
</head>
<body>
<div class="container" id="app"></div>
<script>
// ====== ゲーム設定 ======
const WIDTH = 760;
const HEIGHT = 534;
const INVADER_ROWS = 3;
const INVADER_COLS = 7;
const INVADER_SIZE = 62;
const PLAYER_SIZE = 60;
const PLAYER_Y = HEIGHT - 56;
const PLAYER_SPEED = 36;
const BULLET_SPEED = 19;
const ENEMY_BULLET_SPEED = 9;
const INVADER_INIT_SPEED = 19;
const INVADER_DROP = 38;
const GAME_FPS = 60;
const UFO_SPEED = 7;
const UFO_BONUS = 500;
let app = document.getElementById('app');
// ========== タイトル画面 ==========
function showTitleScreen() {
app.innerHTML = `
<div class="main-title">👾 スペースインベーダー 👾</div>
<img src="space_invaders_title.png" class="title-img" alt="タイトル画像">
<div class="subtitle center">~ 2Dシューティングゲーム ~</div>
<div class="rule-title">🎮 ルール説明</div>
<div class="rules">
・「🪆」を左右キー(← →)または「←」「→」ボタンで操作<br>
・スペースキーまたは「スペース」ボタンで弾を発射<br>
・「👾」が端に到達すると一段下がります<br>
・「👾」が全て倒れるとステージクリア、次ステージは敵が速くなります<br>
・「👾」や敵弾に当たるとゲームオーバー<br>
・時々1段目に「🛸」が登場。撃つとボーナス得点!
<br><br>
【スマホ操作】<br>
・「←」「→」「スペース」ボタンで操作できます
</div>
<button class="start-btn" onclick="startGame()">▶ ゲームスタート</button>
`;
}
// ========== ゲーム終了画面 ==========
function showGameOver(win, score, stage) {
app.innerHTML = `
<div class="main-title">👾 スペースインベーダー 👾</div>
<img src="${win ? 'space_invaders_clear.png' : 'space_invaders_title.png'}" class="${win ? 'clear-img' : 'title-img'}" alt="タイトル画像">
<div class="msg-overlay" style="position:relative; background:transparent; box-shadow:none; height:auto;">
<div style="font-size:2.2rem; color:#fff;">${win ? '🎉 ステージクリア!' : '💥 ゲームオーバー!'}</div>
<div style="margin:14px 0 6px 0; font-size:1.1rem;">スコア:<span style="color:#ffeb3b; font-weight:bold">${score}</span></div>
<div style="margin-bottom:18px;font-size:1.1rem;">ステージ:${stage}</div>
<button class="back-btn" onclick="showTitleScreen()">タイトル画面に戻る</button>
${win ? `<button class="start-btn" onclick="startGame(${stage+1},${score})">▶ 次のステージへ</button>` : ''}
</div>
`;
}
// ========== ゲーム開始処理 ==========
function startGame(startStage=1, startScore=0) {
// 変数宣言
let stage = startStage;
let score = startScore;
let invaderSpeed = INVADER_INIT_SPEED + (stage-1)*7;
let gameArea, player, invaders, bullets, enemyBullets, invaderDir, invaderDx, invaderDy;
let leftPressed = false, rightPressed = false, spacePressed = false;
let canShoot = true, isGameOver = false, isWin = false, canEnemyShoot = true;
// UFO関連
let ufo = null; // UFOオブジェクト
let ufoTimer = 0; // UFO出現タイマー
let ufoDir = 1; // 1:右移動, -1:左移動
let showBonus = false;
let bonusTime = 0;
// ゲーム画面描画
app.innerHTML = `
<div class="main-title">👾 スペースインベーダー 👾</div>
<div class="game-area" id="gameArea"></div>
<div class="score" id="score">スコア:${score}</div>
<div class="stage" id="stage">ステージ:${stage}</div>
`;
gameArea = document.getElementById('gameArea');
let scoreDiv = document.getElementById('score');
let stageDiv = document.getElementById('stage');
// プレイヤー初期化
player = {
x: WIDTH/2 - PLAYER_SIZE/2,
y: PLAYER_Y,
w: PLAYER_SIZE,
h: PLAYER_SIZE,
alive: true
};
// 敵の初期化
invaders = [];
for(let r=0; r<INVADER_ROWS; r++){
for(let c=0; c<INVADER_COLS; c++){
invaders.push({
x: 68 + c*92,
y: 48 + r*68,
w: INVADER_SIZE,
h: INVADER_SIZE,
alive: true,
id: 'invader-' + r + '-' + c
});
}
}
bullets = [];
enemyBullets = [];
invaderDx = invaderSpeed;
invaderDy = 0;
invaderDir = 1; // 1:右, -1:左
// UFO出現タイミングを設定(5〜13秒後ランダム)
function resetUfoTimer() {
ufoTimer = GAME_FPS * (5 + Math.random()*8);
}
resetUfoTimer();
// モバイル操作UI
drawMobileControls();
// キーイベント
window.onkeydown = function(e){
if(e.code === 'ArrowLeft'){ leftPressed = true; }
if(e.code === 'ArrowRight'){ rightPressed = true; }
if(e.code === 'Space'){ spacePressed = true; e.preventDefault(); }
};
window.onkeyup = function(e){
if(e.code === 'ArrowLeft'){ leftPressed = false; }
if(e.code === 'ArrowRight'){ rightPressed = false; }
if(e.code === 'Space'){ spacePressed = false; }
};
// ========== ゲームループ ==========
function gameLoop(){
// プレイヤー操作
if(leftPressed) player.x -= PLAYER_SPEED;
if(rightPressed) player.x += PLAYER_SPEED;
player.x = Math.max(8, Math.min(WIDTH-player.w-8, player.x));
// プレイヤー弾発射
if(spacePressed && canShoot && player.alive){
bullets.push({ x: player.x + player.w/2-8, y: player.y-22, w:16, h:24, from: 'player' });
canShoot = false;
setTimeout(()=>{ canShoot=true; }, 220);
}
// 弾移動
for(let b of bullets){
if(b.from === 'player') b.y -= BULLET_SPEED;
else b.y += ENEMY_BULLET_SPEED + stage;
}
bullets = bullets.filter(b => b.y + b.h > 0 && b.y < HEIGHT);
// 敵横移動・端到達時落下
let livingInvs = invaders.filter(v=>v.alive);
if(livingInvs.length > 0) {
let invLeft = Math.min(...livingInvs.map(v=>v.x));
let invRight = Math.max(...livingInvs.map(v=>v.x+v.w));
// 端に到達
if(invRight >= WIDTH-8 && invaderDir===1) { invaderDir=-1; invaderDy=INVADER_DROP; }
if(invLeft <= 8 && invaderDir===-1) { invaderDir=1; invaderDy=INVADER_DROP; }
for(let inv of invaders){
if(inv.alive){
inv.x += invaderDx * invaderDir / GAME_FPS;
inv.y += invaderDy / GAME_FPS;
}
}
invaderDy = 0;
}
// 敵弾発射:生存中の敵からランダム(一定間隔)
if(canEnemyShoot && livingInvs.length > 0){
// 最下段で生きてる敵のいずれかから発射
let cols = {};
for(let inv of livingInvs){
let col = Math.round((inv.x-68)/92);
if(!cols[col] || cols[col].y < inv.y) cols[col] = inv;
}
let shootInvaders = Object.values(cols);
let shooter = shootInvaders[Math.floor(Math.random() * shootInvaders.length)];
bullets.push({ x: shooter.x + shooter.w/2-6, y: shooter.y + shooter.h, w:12, h:20, from: 'enemy' });
canEnemyShoot = false;
setTimeout(()=>{ canEnemyShoot=true; }, 500 + Math.max(0, 1100 - stage*180) );
}
// ===== UFO出現制御 =====
if(!ufo && --ufoTimer <= 0) {
// 出現方向決定
ufoDir = Math.random() < 0.5 ? 1 : -1;
ufo = {
x: ufoDir === 1 ? -62 : WIDTH+12,
y: 6,
w: 68,
h: 44,
alive: true,
dir: ufoDir
};
resetUfoTimer();
}
// UFO移動
if(ufo && ufo.alive){
ufo.x += UFO_SPEED * ufo.dir;
// 画面外に出たら消去
if((ufo.dir === 1 && ufo.x > WIDTH+80) || (ufo.dir === -1 && ufo.x < -80)){
ufo = null;
}
}
// ===== 衝突判定 =====
// 弾と敵
for(let b of bullets){
if(b.from === 'player'){
for(let inv of invaders){
if(inv.alive && isHit(b, inv)){
inv.alive = false;
b.y = -100;
score += 50 + stage*10;
}
}
// UFOヒット判定
if(ufo && ufo.alive && isHit(b, ufo)){
ufo.alive = false;
b.y = -100;
score += UFO_BONUS;
showBonus = true;
bonusTime = 0;
}
}
}
// 敵弾とプレイヤー
for(let b of bullets){
if(b.from === 'enemy' && player.alive && isHit(b, player)){
player.alive = false;
isGameOver = true;
isWin = false;
}
}
// 敵が自機ライン到達→ゲームオーバー
for(let inv of invaders){
if(inv.alive && inv.y+inv.h > player.y+16){
isGameOver = true;
isWin = false;
}
}
// 敵全滅→クリア
if(invaders.every(v=>!v.alive)){
isGameOver = true;
isWin = true;
}
// ===== 描画 =====
drawGame();
// スコア・ステージ
scoreDiv.innerHTML = "スコア:" + score;
stageDiv.innerHTML = "ステージ:" + stage;
// UFOボーナス表示
if(showBonus){
bonusTime++;
if(bonusTime > 70) showBonus = false;
}
// ===== ゲーム終了判定 =====
if(isGameOver){
setTimeout(()=>{
showGameOver(isWin, score, stage);
}, 950);
return;
}
requestAnimationFrame(gameLoop);
}
// ========== 当たり判定 ==========
function isHit(a, b){
return a.x < b.x+b.w && a.x+a.w > b.x &&
a.y < b.y+b.h && a.y+a.h > b.y;
}
// ========== ゲーム画面の描画 ==========
function drawGame(){
gameArea.innerHTML = '';
// 敵
for(let inv of invaders){
if(inv.alive){
let d = document.createElement('div');
d.className = 'invader';
d.style.left = inv.x+'px';
d.style.top = inv.y+'px';
d.innerText = '👾';
gameArea.appendChild(d);
}
}
// プレイヤー
if(player.alive){
let p = document.createElement('div');
p.className = 'player';
p.style.left = player.x+'px';
p.style.top = player.y+'px';
p.innerText = '🪆';
gameArea.appendChild(p);
}
// プレイヤー・敵弾
for(let b of bullets){
let e = document.createElement('div');
e.className = b.from === 'player' ? 'bullet' : 'enemy-bullet';
e.style.left = b.x+'px';
e.style.top = b.y+'px';
e.innerText = b.from === 'player' ? '🔺' : '🔻';
gameArea.appendChild(e);
}
// UFO
if(ufo && ufo.alive){
let u = document.createElement('div');
u.className = 'ufo';
u.style.left = ufo.x+'px';
u.style.top = ufo.y+'px';
u.innerText = '🛸';
gameArea.appendChild(u);
}
// UFOボーナス
if(showBonus){
let b = document.createElement('div');
b.className = 'bonus';
b.innerText = `UFO撃破!+${UFO_BONUS}点`;
gameArea.appendChild(b);
}
}
// ========== スマホ用操作UI ==========
function drawMobileControls(){
if(window.innerWidth < 900){
let controls = document.createElement('div');
controls.style = "position:absolute; left:0; right:0; bottom:8px; width:98%; display:flex; justify-content:center; z-index:20;";
controls.innerHTML = `
<button id="leftBtn" style="margin-right:16px;font-size:1.7rem;border-radius:16px; width:66px; height:54px;">←</button>
<button id="spaceBtn" style="margin:0 12px;font-size:1.7rem;border-radius:16px; width:86px; height:54px;">スペース</button>
<button id="rightBtn" style="margin-left:16px;font-size:1.7rem;border-radius:16px; width:66px; height:54px;">→</button>
`;
app.querySelector('.game-area').appendChild(controls);
// タッチ操作
controls.querySelector('#leftBtn').addEventListener('touchstart',()=>{ leftPressed=true; }, false);
controls.querySelector('#leftBtn').addEventListener('touchend',()=>{ leftPressed=false; }, false);
controls.querySelector('#rightBtn').addEventListener('touchstart',()=>{ rightPressed=true; }, false);
controls.querySelector('#rightBtn').addEventListener('touchend',()=>{ rightPressed=false; }, false);
controls.querySelector('#spaceBtn').addEventListener('touchstart',()=>{
spacePressed=true; setTimeout(()=>{spacePressed=false;},100);
}, false);
}
}
// ゲーム開始
drawGame();
gameLoop();
}
// ====== 初期画面 ======
showTitleScreen();
</script>
</body>
</html>
アルゴリズムの流れ
ステップ | 処理内容 |
---|---|
1. 初期表示 | タイトル画面(ルール説明・画像・開始ボタン)を表示 |
2. ゲーム開始 | ステージ・スコア初期化、インベーダー・自機・弾・UFO情報を初期化 |
3. 入力処理 | キー入力(またはボタン)で自機左右移動、弾発射 |
4. ゲームループ | 各キャラクターの移動、弾の発射・移動、UFO出現など |
5. 当たり判定 | 弾と敵、自機と敵弾・敵、弾とUFOの衝突判定 |
6. 描画 | 各要素(自機・敵・弾・UFO)の位置に応じてDOM描画 |
7. ゲーム終了判定 | 敵全滅ならステージクリア、敵or弾で自機がやられたらゲームオーバー |
8. リザルト画面 | スコア・ステージ結果表示、タイトル画面or次ステージへボタン |
主要関数・命令の解説
関数・変数名 | 役割・説明 |
---|---|
showTitleScreen() | タイトル画面・ルール説明を表示 |
showGameOver(win, score, stage) | ゲームオーバー or ステージクリア画面表示 |
startGame(startStage, startScore) | ゲーム本体の初期化、ゲームループ開始 |
gameLoop() | 毎フレームごとの全体制御・衝突判定・描画更新 |
drawGame() | 画面内の自機・敵・弾・UFO・ボーナスのDOM描画 |
isHit(a, b) | 二つの矩形(オブジェクト)の当たり判定 |
drawMobileControls() | スマホ用ボタンの描画とタッチイベント登録 |
関数の主な処理内容
関数名 | 主な処理内容 |
---|---|
showTitleScreen() | ルール・画像・開始ボタンを描画 |
showGameOver(win, s, st) | ゲーム結果・次のステージorタイトル復帰ボタンを描画 |
startGame(stage, score) | ステージ・スコア初期化、キャラ情報リセット、ループ開始 |
gameLoop() | 各種移動/発射/当たり判定/敵UFO出現/ゲーム終了判定 |
drawGame() | ゲームエリアのDOM再生成、自機・敵・弾・UFO描画 |
isHit(a, b) | オブジェクト同士の当たり判定 |
drawMobileControls() | スマホ向けの操作ボタンの描画・イベント設定 |
改造のポイントとアドバイス
- 難易度調整
・INVADER_ROWS
やINVADER_COLS
で敵の数を変更できます。
・INVADER_INIT_SPEED
やINVADER_DROP
で敵の動きを調整可能。 - ライフ機能の追加
・プレイヤーのライフ(残機)を増やすと、初心者でも遊びやすくなります。 - サウンドや効果の強化
・SEやBGMを加えるとレトロ感&臨場感UP! - 新しい敵やパターンの追加
・特殊な動きの敵や、ボス、アイテム出現などを実装できます。 - スマホ最適化
・既にタッチボタン実装済みですが、見た目やレスポンス改善も面白いです。
アドバイス
このゲームはDOM描画ベースでシンプルに設計されており、カスタマイズも容易です。
初心者は弾や敵キャラの画像や、得点システムをアレンジしたり、上級者は自機・敵・UFOにアニメーションやエフェクトを追加してみましょう!
自分だけのスペースインベーダーを作ってみてください!