
【ゲーム】JavaScript:72 フロッグクロッシング
「🚦🐸 フロッグクロッシング 🐸🚦」は、十字キーで操作するカエルを道路と歩道の障害物を避けながら、画面上部の安全地帯へ導くアクションゲームです。プレイヤーは車や歩行者に衝突しないようにタイミングよく移動し、クリアするたびにスコアが増加、速度が上がることで難易度が上がっていきます。
遊び方と操作方法
- 操作方法: キーボードの十字キー(↑↓←→)でカエルを上下左右に移動。
- 目的: 画面上部(SAFE_ZONE_H)に到達すると +1 スコア。次のラウンドでは移動速度が上がる。
- ゲームオーバー: カエル(🐸)が車(🚗/🚙)または歩行者(🚶)に衝突した瞬間。
ルール
- カエルは画面下部の安全地帯からスタート。
- 道路レーン(LANE_COUNT = 5)には一定間隔で車が出現。
- 歩道レーン(SIDEWALK_LANE_INDEX)には歩行者が出現。
- 衝突判定は矩形(a.x < b.x + b.w かつ a.x + a.w > b.x ...)で厳密に行う。
- クリア時に速度(speed)を
speed += SPEED_INC
だけ上昇。
🎮ゲームプレイ
以下のリンク先から実際にプレイできます。
72 フロッグクロッシング
素材のダウンロード
以下のリンクから使用する素材をダウンロードできます。
frog_crossing_title.png | frog_crossing_bg.png |
---|---|
![]() | ![]() |
ゲーム画面イメージ

プログラム全体(frog_crossing.html)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>🚦🐸 フロッグクロッシング 🐸🚦</title>
<style>
html,body{
width:100%; height:100%; margin:0; padding:0;
background:url('frog_crossing_bg.png') no-repeat center center fixed;
background-size:cover;
font-family:'Yu Gothic', 'Meiryo', sans-serif;
overflow:auto;
}
#wrapper{
width:800px;
margin:20px auto 0 auto;
position:relative;
box-sizing:border-box;
}
.screen{display:none; width:100%; height:100%; box-sizing:border-box;}
#titleScreen{display:block; text-align:center; color:#fff; padding-top:20px;}
#titleScreen img{display:block; margin:0 auto 20px auto; max-width:70%; height:auto;}
.main-title {
font-size:2.0rem;
font-weight:bold;
line-height:1.1;
letter-spacing:0.08em;
margin-bottom:18px;
text-shadow:
0 0 8px #fff,
0 0 2px #fff,
2px 2px 0 #000,
-2px 2px 0 #000,
2px -2px 0 #000,
-2px -2px 0 #000;
background:rgba(0,0,0,0.28);
border-radius:12px;
padding:0.1em 0.4em;
display:inline-block;
}
.rule-panel{background:rgba(0,0,0,0.6); padding:20px; border-radius:10px; margin:0 auto; max-width:90%;}
.rule-panel h2{margin-top:0; text-align:center;}
.rule-panel p{text-align:left; margin:0 0 10px 0; line-height:1.6;}
.btn{display:inline-block; padding:12px 24px; margin-top:20px; font-size:1.1rem; font-weight:bold; color:#fff; background:#4CAF50; border:none; border-radius:8px; cursor:pointer; transition:opacity .2s;}
.btn:hover{opacity:0.8;}
#gameScreen{position:relative; color:#fff; user-select:none;}
#gameCanvas{display:block; margin:0 auto; background:transparent; border:4px solid #fff; border-radius:4px;}
#scoreBoard{position:absolute; top:10px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,0.6); padding:6px 12px; border-radius:6px; font-size:1.1rem;}
#gameOverScreen{text-align:center; color:#fff; padding-top:20px;}
#gameOverScreen .message{font-size:2rem; font-weight:bold; margin-bottom:20px;}
#rankingBoard{
margin:24px auto 0 auto; background:rgba(0,0,0,0.6);
border-radius:10px; padding:16px 32px; max-width:350px;
}
#rankingBoard h2{margin-top:0; color:#FFD700;}
#rankingBoard ul{margin:0; padding-left:0; text-align:left; list-style:none;}
#rankingBoard li{font-size:1.15em; margin-bottom:2px;}
</style>
</head>
<body>
<div id="wrapper">
<!-- ==================== タイトル画面 ==================== -->
<div id="titleScreen" class="screen">
<img src="frog_crossing_title.png" alt="ゲームタイトル">
<h1 class="main-title">🚦🐸 フロッグクロッシング 🐸🚦</h1>
<div class="rule-panel">
<h2>📜 ルール説明 📜</h2>
<p>・カエル (<span style="font-size:1.2em;">🐸</span>) を十字キーで操作し、道路と歩道を渡って上端の安全地帯へたどり着きましょう。</p>
<p>・車 (<span style="font-size:1.2em;">🚗</span>, <span style="font-size:1.2em;">🚙</span>) および 歩行者 (<span style="font-size:1.2em;">🚶</span>) に触れるとゲームオーバーです。</p>
<p>・無事に渡るとスコア +1。時間が経つにつれて車や歩行者の速度が上がります。</p>
</div>
<button id="startBtn" class="btn">▶️ スタート</button>
</div>
<!-- ==================== ゲーム画面 ==================== -->
<div id="gameScreen" class="screen">
<div id="scoreBoard">スコア: <span id="score">0</span></div>
<canvas id="gameCanvas" width="800" height="500"></canvas>
</div>
<!-- ==================== ゲーム終了画面 ==================== -->
<div id="gameOverScreen" class="screen">
<div class="message" id="resultMessage"></div>
<div id="rankingBoard"></div>
<button id="returnBtn" class="btn">🔙 タイトル画面に戻る</button>
</div>
</div>
<script>
/*******************************************
* 🚦🐸 フロッグクロッシング メイン処理 *
*******************************************/
(() => {
// ========== 要素取得 ==========
const titleScreen = document.getElementById('titleScreen');
const gameScreen = document.getElementById('gameScreen');
const gameOverScreen= document.getElementById('gameOverScreen');
const startBtn = document.getElementById('startBtn');
const returnBtn = document.getElementById('returnBtn');
const scoreSpan = document.getElementById('score');
const resultMessage = document.getElementById('resultMessage');
const rankingBoard = document.getElementById('rankingBoard');
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// ========== 定数設定 ==========
const LANE_COUNT = 5;
const LANE_HEIGHT = 60;
const SAFE_ZONE_H = 60;
const FROG_SIZE = 40;
const INITIAL_SPEED = 2;
const SPEED_INC = 0.2;
const CAR_SPAWN_GAP = 200;
// 歩道レーン
const SIDEWALK_LANE_INDEX = LANE_COUNT;
const SIDEWALK_HEIGHT = 60;
const SIDEWALK_Y = canvas.height - SAFE_ZONE_H - SIDEWALK_HEIGHT;
// ========== ゲーム用変数 ==========
let frog, cars, pedestrians, score, speed, frameId, running, carSpawnTimers, pedestrianSpawnTimer;
// ========== ランキング管理 ==========
const RANKING_KEY = "frog_crossing_rankings";
function saveScore(newScore) {
let rankings = JSON.parse(localStorage.getItem(RANKING_KEY) || "[]");
rankings.push(newScore);
rankings = rankings.sort((a,b)=>b-a).slice(0,5);
localStorage.setItem(RANKING_KEY, JSON.stringify(rankings));
return rankings;
}
function loadRanking() {
return JSON.parse(localStorage.getItem(RANKING_KEY) || "[]");
}
function showRankingBoard(rankings) {
const rankLabel = ["1位", "2位", "3位", "4位", "5位"];
let html = `<h2>🏆 スコアランキング 🏆</h2><ul>`;
for(let i=0; i<5; i++) {
html += `<li>${rankLabel[i]}: ${rankings[i] !== undefined ? rankings[i] : "0"}</li>`;
}
html += `</ul>`;
rankingBoard.innerHTML = html;
}
// ========== オブジェクト生成関数 ==========
function createFrog(){
return {
x: (canvas.width - FROG_SIZE)/2,
// 歩道より下(最下部安全地帯の中央)
y: canvas.height - SAFE_ZONE_H - FROG_SIZE/2 + (SAFE_ZONE_H - FROG_SIZE)/2,
w: FROG_SIZE,
h: FROG_SIZE
};
}
function createCar(lane){
const dir = lane % 2 === 0 ? 1 : -1;
const y = SAFE_ZONE_H + lane * LANE_HEIGHT + (LANE_HEIGHT - 40)/2;
const w = 80;
const x = dir === 1 ? -w : canvas.width + w;
const emoji = dir === 1 ? "🚗" : "🚙";
return {x, y, w, h:40, dir, emoji};
}
function createPedestrian(){
const y = SIDEWALK_Y + 10;
const w = 38, h = 38;
const x = canvas.width + 20;
return {x, y, w, h, dir: -1, emoji: "🚶"};
}
// ========== ゲーム初期化 ==========
function initGame(){
frog = createFrog();
cars = [];
pedestrians = [];
score = 0;
speed = INITIAL_SPEED;
carSpawnTimers = Array(LANE_COUNT).fill(0);
pedestrianSpawnTimer = 0;
scoreSpan.textContent = score;
running = true;
cancelAnimationFrame(frameId);
frameId = requestAnimationFrame(gameLoop);
}
// ========== メインループ ==========
function gameLoop(){
update();
render();
if(running) frameId = requestAnimationFrame(gameLoop);
}
// ========== 更新処理 ==========
function update(){
for(const car of cars){
if(rectHit(frog, car)){ gameOver(); return; }
}
for(const p of pedestrians){
if(rectHit(frog, p)){ gameOver(); return; }
}
for(let lane=0; lane<LANE_COUNT; lane++){
carSpawnTimers[lane] -= 1;
if(carSpawnTimers[lane] <= 0){
cars.push(createCar(lane));
carSpawnTimers[lane] = CAR_SPAWN_GAP / speed + Math.random()*100;
}
}
cars.forEach(car => car.x += car.dir * speed);
cars = cars.filter(car => car.x < canvas.width + car.w && car.x > -car.w);
pedestrianSpawnTimer -= 1;
if(pedestrianSpawnTimer <= 0){
pedestrians.push(createPedestrian());
pedestrianSpawnTimer = 200 / speed + Math.random()*100;
}
pedestrians.forEach(p => p.x += p.dir * (speed*0.9));
pedestrians = pedestrians.filter(p => p.x > -p.w);
checkClear();
}
// ========== 描画処理 ==========
function render(){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = '#555';
ctx.fillRect(0, SAFE_ZONE_H, canvas.width, LANE_COUNT * LANE_HEIGHT);
ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.setLineDash([10,10]);
for(let i=1;i<LANE_COUNT;i++){
const y = SAFE_ZONE_H + i*LANE_HEIGHT;
ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(canvas.width,y); ctx.stroke();
}
ctx.setLineDash([]);
ctx.fillStyle = '#2E7D32';
ctx.fillRect(0,0,canvas.width,SAFE_ZONE_H);
ctx.fillRect(0,canvas.height-SAFE_ZONE_H,canvas.width,SAFE_ZONE_H);
ctx.fillStyle = '#eee';
ctx.fillRect(0, SIDEWALK_Y, canvas.width, SIDEWALK_HEIGHT);
for(let i=0; i<canvas.width; i+=48){
ctx.fillStyle = '#fff';
ctx.fillRect(i, SIDEWALK_Y+16, 24, 12);
}
cars.forEach(car => {
ctx.font = "40px serif";
ctx.textBaseline = "top";
ctx.textAlign = "left";
ctx.fillText(car.emoji, car.x+10, car.y);
});
pedestrians.forEach(p => {
ctx.font = "38px serif";
ctx.textBaseline = "top";
ctx.textAlign = "left";
ctx.fillText(p.emoji, p.x, p.y);
});
ctx.font = "38px serif";
ctx.textBaseline = "top";
ctx.textAlign = "left";
ctx.fillText("🐸", frog.x+2, frog.y);
}
// ========== 衝突 / クリア判定 ==========
function rectHit(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 checkClear(){
if(frog.y < SAFE_ZONE_H){
score++;
scoreSpan.textContent = score;
speed += SPEED_INC;
frog = createFrog();
}
}
// ========== 入力処理 ==========
window.addEventListener('keydown', e => {
if(!running) return;
const MOVE = 40;
switch(e.code){
case 'ArrowUp': frog.y -= MOVE; break;
case 'ArrowDown': frog.y += MOVE; break;
case 'ArrowLeft': frog.x -= MOVE; break;
case 'ArrowRight':frog.x += MOVE; break;
}
frog.x = Math.max(0, Math.min(canvas.width - frog.w, frog.x));
frog.y = Math.max(0, Math.min(canvas.height - frog.h, frog.y));
// 歩道の下まで行けないよう制限(ただし安全地帯内はOK)
if(frog.y > canvas.height - frog.h) frog.y = canvas.height - frog.h;
});
// ========== ゲームオーバー ==========
function gameOver(){
running = false;
cancelAnimationFrame(frameId);
resultMessage.innerHTML = `💥 ゲームオーバー 💥<br>あなたのスコア: <strong>${score}</strong>`;
const rankings = saveScore(score);
showRankingBoard(rankings);
gameScreen.style.display = 'none';
gameOverScreen.style.display = 'block';
}
// ========== 画面遷移ボタン ==========
startBtn.addEventListener('click', () => {
titleScreen.style.display = 'none';
gameOverScreen.style.display = 'none';
gameScreen.style.display = 'block';
initGame();
});
returnBtn.addEventListener('click', () => {
gameOverScreen.style.display = 'none';
titleScreen.style.display = 'block';
const rankings = loadRanking();
showRankingBoard(rankings);
});
document.addEventListener('DOMContentLoaded',()=>{
showRankingBoard(loadRanking());
});
})();
</script>
</body>
</html>
アルゴリズムの流れ
以下の表では、メインの処理ステップと関連関数をまとめています。
ステップ | 処理内容 | 関連関数 |
---|---|---|
1. 初期化 | カエル・車・歩行者の配列を生成し、スコアと速度をリセット | initGame() |
2. メインループ | 毎フレーム update() → render() を実行し継続判定 | gameLoop() |
3. 衝突判定 | カエルと車/歩行者の矩形重なりを判定し、衝突ならゲーム終了 | rectHit() |
4. 出現処理 | 各レーンに応じたタイマー管理で車/歩行者を定周期で生成 | createCar() , createPedestrian() |
5. 移動更新 | 生成オブジェクトの座標を速度・方向(dir)に応じて更新 | update 内 |
6. 画面描画 | 背景・レーンライン・カエル・車・歩行者を canvas API で描画 | render() |
7. クリア判定 | カエルが画面上部の安全地帯に到達したかをチェックし、クリア時にスコアと速度増加 | checkClear() |
8. ゲーム終了 | 衝突検出後にアニメーションを停止し、スコア保存・ランキング表示を行う | gameOver() , saveScore() , showRankingBoard() |
関数の解説
関数名 | 引数 | 戻り値 | 処理内容 |
---|---|---|---|
createFrog() | なし | {x,y,w,h} | カエルの初期位置(画面下部中央)、サイズを設定して返す。 |
createCar(lane) | lane (0〜4) | {x,y,w,h,dir,emoji} | 指定レーンに合わせて左右どちらかから出現する車のオブジェクトを生成。ランダム間隔で呼び出し。 |
createPedestrian() | なし | {x,y,w,h,dir,emoji} | 歩道レーン端から歩行者を生成。速度は車よりやや遅い。 |
rectHit(a,b) | a,b (矩形情報) | boolean | a と b の矩形重なりを論理演算で判定。 |
checkClear() | なし | なし | カエルの y 座標が安全地帯上部より小さいか確認。クリアならスコア増加と速度アップ。 |
saveScore(newScore) | newScore (数値) | rankings (配列) | localStorage へスコアを追加・ソート・上位5件に絞り込み保存し、最新ランキングを返す。 |
loadRanking() | なし | rankings (配列) | localStorage から保存済みランキングを取得して返す。存在しなければ空配列。 |
showRankingBoard(ranking) | ranking (配列) | なし | HTML を組み立ててスコアランキングを画面に表示。テンプレート文字列を活用。 |
改造のポイント
- カエルや車のデザイン変更:
createFrog()
,createCar()
のemoji
プロパティを任意の画像や絵文字に変更可能。 - レーン数や速度調整:
LANE_COUNT
,INITIAL_SPEED
,SPEED_INC
を変えることでゲームの難易度やスピード感を調節できます。 - 出現間隔やパターン:
CAR_SPAWN_GAP
やpedestrianSpawnTimer
の計算式に乱数やレベル別の制御を加えると多彩な演出が可能。 - UI強化: スコアボードやゲームオーバーメッセージを CSS アニメーションやトランジションで装飾する。
アドバイス
まずは小さなパラメータ(速度増加量やレーン数)を変えて挙動を確認し、徐々にエフェクトやBGMを追加するとバランス良く拡張できます。