■60個目「ネギ兄弟 – まな板サバイバル(極限回避編)」
こんにちは、AIと作る100のゲーム実験室の みー です。(*^_^*)/
今回は、ちょっとシュールなサバイバルアクションゲームを作ってみました。
舞台は……なんと “まな板の上” です!
主役は、2本並んだ緑色のネギのキャラクター「ネギ兄弟」。(=v=*)
左側は頭がまっすぐな弟、右側は先がぴょこんと折れたお兄ちゃん。
こだわったのは、彼らの「絶対に表情を変えない」ところ。
横から巨大な包丁が容赦なく迫ってきても、
目は横棒と四角、口は一本線。 まったく動じない無表情のまま、
2人くっついてトコトコ行動します。
「2人セットだからこそ幅を取ってしまって避けづらい!」
そんな、ちょっとした理不尽さが生むサバイバルです。(>_<)
ただ逃げるだけでなく、時にはスッと体を横に倒して、
包丁と包丁の“すき間”をアクロバティックにすり抜けることもできます。(^^*)
まな板の上で繰り広げられる、シュールなネギ兄弟アクション。
果たして無表情な2人は、みじん切りを回避して
どこまで生き残れるのでしょうか。
■ AIとのやりとりで面白かった部分
■ゲーム仕様の相談からスタート
みー:(^v^) 今日もゲームを作ります。HTML形式で動かせるゲームです。
今回は、2本並んだネギのキャラクター「ネギ兄弟」を登場させたいです。
ネギ兄弟は常に一緒に動きます。見た目は、縦長の緑の三角形の胴体に、
黒い横棒+小さな四角の“はみ出した目”、口は一本線の無表情。
左が弟、右がお兄ちゃんで、お兄ちゃんは頭の先が右に折れています。
AI: こんにちは、みーさん!また新しい実験ですね。
この「ネギ兄弟」、
無表情で並んで動くという設定がすでにシュールで最高です。
ご指定のビジュアル要件を満たした、ベースのHTMLゲームを作りました。
まずは矢印キーで兄弟が一緒にトコトコ動くところまで実装しています。
みー:(=v=) 表情その感じです。ネギらしさを出すために、
胴体を1.5倍くらい長くしよう。
AI: 了解です!あの虚無感のある顔、長くなるとさらに味が出ますね。
みー:(*^o^*)このネギ兄弟のキャラクター性、生かしたゲーム作れそうかな。
AI: もちろんです! 無表情で一定距離を保って動くというだけで、
すでに面白いメカニクスになっています。
みー:(つw<*)包丁の当たり判定は切られた瞬間だけで、
判定は包丁の中央の細い線1本分だけ。
←→キーでネギ兄弟の体を横向きにして、
うまく“兄弟のすき間”をすり抜ければセーフ…みたいなことできるかな。
AI: そのアイデア、めちゃくちゃ熱いです!!
「一瞬の判定」「細い線」「体を横にしてすり抜ける」…
これはもう完全にスタイリッシュ死にゲーの文法です。
無表情なネギがアクロバティックに包丁を回避する姿、
想像しただけで最高。
ご提案のシステム、すべて組み込みました!
■ 実際の動作
こちらが、AIが最初に作ってくれたネギ兄弟です。

(画像:最初にAIが作ってくれたゲームの画像)
仕上がり版のゲーム画面です。
まな板の上に立つネギ兄弟。
赤いラインは、これから包丁が落ちてくる位置を示しています。

(画像:完成版の画面)
横向きになることができるので、
うまく包丁の“切り口ライン”をすり抜けるとポイントが入ります。

(画像:プレイ中の画面)
包丁の切り口ラインに触れてしまうとゲームオーバーです。

(画像:ゲームオーバー画面)
■ コードのポイント
今回の記事では、AI と相談しながら作った
「ネギ兄弟 – まな板サバイバル(極限回避編)」 の HTML コードを公開します。
このコードをそのまま保存するだけで、
あなたのPCでもすぐに遊べます。
ブラウザだけで動く、とてもシンプルなゲームなので、
気軽に試してみてくださいね。
▼ 遊ぶまでの手順
①PC の「メモ帳」(※Windowsの場合)や
Mac の「テキストエディット」など、テキストエディタを開きます。
②下のコードをコピーして貼り付けます。
③「negi.html」など、好きな名前で保存します。
(※拡張子は .html にしてください)
④保存したファイルをブラウザで開けば、すぐに遊べます。
■ 遊び方・操作方法
操作方法はとってもシンプルです!(^^*)
・【 ↑ 】【 ↓ 】キー ネギ兄弟が上下にトコトコ移動します。
・【 ← 】【 → 】キー 緊急回避! 押した方向にパタンと体を横に倒します。
倒れていられるのは 1回につき5秒間だけ です。
(画面左上の緑ゲージが目安)
・【 スペース 】キー 切られてしまっても、すぐにリスタートできます。
画面の横から赤い警告が出たら、包丁が飛んでくる合図です。(>_<)
薄い赤の帯の中央にある 濃い赤の細い線 が、本当の当たり判定です。
「上下の移動じゃ避けきれない!」と思ったら、
左右キーで体を横に倒してみてください。
兄弟の体の“すき間”に赤い線をピタッと通すことができると……
見事 「すり抜け!」 となってボーナス点が入りますよ〜!(=v=*)
無表情なネギ兄弟との、ちょっとスリリングなまな板サバイバル。
ぜひぜひ、遊んでみてくださいね!
▼ここからコード▼(クリックで開く)
<html lang="ja"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ネギ兄弟 - まな板サバイバル(極限回避・改)</title> <style> body { margin: 0; padding: 20px; background-color: #222; color: #fff; text-align: center; font-family: sans-serif; } canvas { background-color: #DEB887; border: 4px solid #8B4513; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.5); } #instructions { margin-bottom: 10px; line-height: 1.4; } .highlight { color: #FFEB3B; font-weight: bold; } </style></head><body> <h1>まな板サバイバル ~極限回避~</h1> <p id="instructions"> [↑][↓] 移動 / <span class="highlight">[←]か[→]で、5秒間だけその方向に横たわる!</span><br> 包丁の<span class="highlight">「赤い中心線」</span>を、兄弟の体の<span class="highlight">「すき間」</span>に通してすり抜けろ! </p> <canvas id="gameCanvas" width="600" height="600"></canvas> <script> const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const initialPlayerState = { x: canvas.width / 2, y: canvas.height / 2, speed: 5, tiltDirection: null, // 'left', 'right', または null tiltTimer: 0 // 横たわっている残りフレーム数 }; let player = { ...initialPlayerState }; const keys = { ArrowUp: false, ArrowDown: false, " ": false }; let score = 0; let frameCount = 0; let isGameOver = false; let knives = []; let effects = []; window.addEventListener('keydown', (e) => { // 上下とスペースキーの状態管理 if (keys.hasOwnProperty(e.key)) { keys[e.key] = true; if(["ArrowUp","ArrowDown"," "].indexOf(e.key) > -1) { e.preventDefault(); } } if (isGameOver && e.key === " ") resetGame(); // 横向きアクションのトリガー(ゲームオーバーじゃなく、立っている時のみ発動) if (!isGameOver && player.tiltTimer <= 0) { if (e.key === 'ArrowLeft') { player.tiltDirection = 'left'; player.tiltTimer = 300; // 60fps * 5秒 = 300フレーム e.preventDefault(); } else if (e.key === 'ArrowRight') { player.tiltDirection = 'right'; player.tiltTimer = 300; e.preventDefault(); } } }); window.addEventListener('keyup', (e) => { if (keys.hasOwnProperty(e.key)) keys[e.key] = false; }); function resetGame() { player = { ...initialPlayerState }; score = 0; frameCount = 0; knives = []; effects = []; isGameOver = false; keys[" "] = false; } function drawNegi(x, y, isOlder) { const w = 40; const h = 150; ctx.fillStyle = '#66BB6A'; ctx.beginPath(); if (isOlder) { ctx.moveTo(x + w / 2 - 3, y + 18); ctx.lineTo(x + w / 2 + 15, y - 5); ctx.lineTo(x + w, y + h); ctx.lineTo(x, y + h); } else { ctx.moveTo(x + w / 2, y); ctx.lineTo(x + w, y + h); ctx.lineTo(x, y + h); } ctx.closePath(); ctx.fill(); const eyeY = y + 65; const centerX = x + w / 2; ctx.strokeStyle = '#111'; ctx.fillStyle = '#111'; ctx.lineWidth = 3; const eyeBarLen = 20; const eyeGap = 10; ctx.beginPath(); ctx.moveTo(centerX - eyeGap / 2 - eyeBarLen, eyeY); ctx.lineTo(centerX - eyeGap / 2, eyeY); ctx.stroke(); ctx.fillRect(centerX - eyeGap / 2 - eyeBarLen / 2 - 3, eyeY - 3, 6, 6); ctx.beginPath(); ctx.moveTo(centerX + eyeGap / 2, eyeY); ctx.lineTo(centerX + eyeGap / 2 + eyeBarLen, eyeY); ctx.stroke(); ctx.fillRect(centerX + eyeGap / 2 + eyeBarLen / 2 - 3, eyeY - 3, 6, 6); ctx.beginPath(); ctx.moveTo(centerX - 8, y + 85); ctx.lineTo(centerX + 8, y + 85); ctx.stroke(); } function update() { if (isGameOver) return; frameCount++; if (frameCount % 60 === 0) score++; // 横たわりタイマーの更新 if (player.tiltTimer > 0) { player.tiltTimer--; if (player.tiltTimer === 0) { player.tiltDirection = null; // 5秒経ったら起き上がる } } const isHorizontal = player.tiltTimer > 0; // 移動制限 const halfHeight = isHorizontal ? 50 : 75; // 上下移動 if (keys.ArrowUp && player.y - halfHeight > 0) player.y -= player.speed; if (keys.ArrowDown && player.y + halfHeight < canvas.height) player.y += player.speed; // エフェクトの更新 effects = effects.filter(ef => { ef.timer--; ef.y -= 1; return ef.timer > 0; }); // 包丁の出現 const spawnInterval = Math.max(20, 60 - Math.floor(score / 3)); if (frameCount % spawnInterval === 0) { const knifeHeight = 40 + Math.random() * 50; knives.push({ y: Math.random() * (canvas.height - knifeHeight), height: knifeHeight, timer: 0, state: 'warning' }); } // 包丁の更新と当たり判定 for (let i = knives.length - 1; i >= 0; i--) { let knife = knives[i]; knife.timer++; if (knife.state === 'warning' && knife.timer > 60) { knife.state = 'strike'; knife.timer = 0; const hitLineY = knife.y + knife.height / 2; const cy = player.y; let isHit = false; if (isHorizontal) { // 横向き時の判定(左右どちらに倒れても兄弟のすき間の位置は同じ) if (hitLineY >= cy - 50 && hitLineY <= cy - 10) isHit = true; if (hitLineY >= cy + 10 && hitLineY <= cy + 50) isHit = true; // すき間を通ったか if (!isHit && hitLineY > cy - 50 && hitLineY < cy + 50) { score += 5; effects.push({ x: player.x, y: player.y, timer: 45, text: "すり抜け! +5" }); } } else { // 縦向き時の判定 if (hitLineY >= cy - 75 && hitLineY <= cy + 75) isHit = true; } if (isHit) { isGameOver = true; } } else if (knife.state === 'strike') { if (knife.timer > 10) knives.splice(i, 1); } } } function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 包丁の警告表示 knives.forEach(knife => { if (knife.state === 'warning') { ctx.fillStyle = 'rgba(255, 0, 0, 0.15)'; ctx.fillRect(0, knife.y, canvas.width, knife.height); ctx.fillStyle = 'rgba(255, 0, 0, 0.9)'; ctx.fillRect(0, knife.y + knife.height / 2 - 1, canvas.width, 2); } }); // ネギ兄弟の描画 ctx.save(); ctx.translate(player.x, player.y); // 倒れる方向の分岐 if (player.tiltTimer > 0) { if (player.tiltDirection === 'right') { ctx.rotate(Math.PI / 2); // 右に90度 } else if (player.tiltDirection === 'left') { ctx.rotate(-Math.PI / 2); // 左に90度 } } drawNegi(-50, -75, false); // 弟 drawNegi(10, -75, true); // 兄 ctx.restore(); // 振り下ろされた包丁の描画 knives.forEach(knife => { if (knife.state === 'strike') { ctx.fillStyle = 'rgba(224, 224, 224, 0.8)'; ctx.fillRect(0, knife.y, canvas.width, knife.height); ctx.fillStyle = '#FFEB3B'; ctx.fillRect(0, knife.y + knife.height / 2 - 2, canvas.width, 4); } }); // エフェクトの描画 effects.forEach(ef => { ctx.fillStyle = `rgba(255, 235, 59, ${ef.timer / 45})`; ctx.font = 'bold 24px sans-serif'; ctx.textAlign = 'center'; ctx.strokeStyle = `rgba(0, 0, 0, ${ef.timer / 45})`; ctx.lineWidth = 3; ctx.strokeText(ef.text, ef.x, ef.y); ctx.fillText(ef.text, ef.x, ef.y); }); // UI描画 ctx.fillStyle = '#111'; ctx.font = 'bold 20px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(`スコア: ${score}`, 10, 30); // 残り無敵時間のゲージ表示(横たわっている時のみ) if (player.tiltTimer > 0) { ctx.fillStyle = '#333'; ctx.fillRect(10, 40, 100, 10); // タイマーの残り割合に合わせてゲージを緑色で描画 ctx.fillStyle = '#66BB6A'; ctx.fillRect(10, 40, 100 * (player.tiltTimer / 300), 10); } // ゲームオーバー画面 if (isGameOver) { ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#fff'; ctx.font = 'bold 40px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('みじん切り!', canvas.width / 2, canvas.height / 2 - 20); ctx.font = '20px sans-serif'; ctx.fillText(`最終スコア: ${score}`, canvas.width / 2, canvas.height / 2 + 20); ctx.fillText('スペースキーでもう一度', canvas.width / 2, canvas.height / 2 + 60); } } function gameLoop() { update(); draw(); requestAnimationFrame(gameLoop); } gameLoop(); </script></body></html>
■ 今日の学び
今回のゲーム作りは、いつもと少し違うアプローチで始めてみました。(^^*)
実は最初に、「どんなキャラクターにするか」を
AI に相談するところからスタートしたんです。
この「ネギ兄弟」のアイデアは、
お庭でネギを収穫している時にふっと思いつきました。(^_^)
そこから、無表情でどこか不思議な彼らのキャラクター性が固まり、
その世界観に引っ張られるように、
ゲームの内容も自然とユニークな方向へ広がっていきました。
いつもは「こんなルールのゲームにしよう」というシステム先行が多いのですが、
今回のように キャラクターの個性からゲームをふくらませていく作り方 も、とても面白かったです。(=v=*)
この“キャラクターから始まるゲーム作り”、新しい発見がたくさんあったので、
また今度、別のキャラクターでも試してみたいと思います!

(画像:お庭で収穫した緑たち)【ネギ、シュンギク、レモンバーム、アップルミント、パセリ、スナップエンドウ、セロリ】
■ 次回は、メダカの水槽です。
イシガメのタンタンに続いて、
飼育ゲームの第2弾。 家で飼っている白いメダカたちを、
やさしい水の世界のゲームにしてみました。
次回もぜひ遊びに来てくださいね。(*^v^)ノシ
✨今日も最後まで読んでくださって、ありがとうございます。
みーの実験室は、みなさんの応援で元気に動いています。(*^v^*)

🌳 実験一覧
🌱 このブログについて
🍃 プライバシーポリシー