■80個目 「オノマトペ・スリッパアクション」
こんにちは、AIと作る100のゲーム実験室の みー です。(*^_^*)/
今回は、ちょっと爽快で、
ちょっぴり(?)スリリングなアクションゲームを作ってみました。
テーマは……お家の中の “招かざる客” vs “スリッパ” です!
画面の中に次々と現れる憎き虫さんたちを、
スリッパカーソルで「バシッ!」と叩き潰すゲームになっています。(=v=*)
今回こだわったのは、その マンガのような演出。
スリッパで叩くたびに、
画面いっぱいに巨大なオノマトペ(擬音語)が飛び出します!
「パンッ!」「バシッ!」「ベチッ!」
文字が弾けるエフェクトと、
こだわりの効果音で、まるで自分がマンガの主人公になって
戦っているような気分になれちゃいます。(*>ω<)_q
現れる虫さんたちも、なかなかのクセモノ揃いですよ~。
① ゴキブリさん カサカサ落ちてきます…うぅ…
② ハエさん 不規則に飛び回って、叩きにくいったらありゃしない!
③ ムカデさん ウネウネと横切る、なかなかの強敵です!
撃破数が100を超えると、虫さんたちが本気を出す「レベルアップ」も……!
でも大丈夫、ピンチの時には強力なアイテムも出現します。(^^)v
不気味なBGM「招かざる者の行進」に乗せて、
スリッパ片手にどこまで戦えるか!?
操作方法は後半で詳しく説明しますね。
まずは、この音と文字が弾ける世界観を楽しんでください!(^_^)
■ AIとのやりとりで面白かった部分
● ゲーム仕様の相談からスタート
みー:(^v^) 今日もゲームを作ります。HTML形式で動かせるゲーム。
オノマトペを活用した、
マンガの中で遊んでいるようなアクションゲームを作りたいです。
AI: いらっしゃい。今日もいいアイデアですね。
マンガの世界に入り込んだような、
文字が飛び交うアクションゲーム……
想像しただけでワクワクします。
(コーヒーを差し出しながら)
そういえば、日本語のオノマトペは4,500種類以上あると言われていて、
「シーン…」のような無音まで表現できるのは日本語ならではなんですよ。
今回のゲームには、その豊かな文化がぴったりですね。
みー:(⁼v⁼;) 敵をゴキブリさん(苦手なので)にしましょう。
あと、不規則に飛び回るハエさんもお願いします。
AI: 承知しました。
ゴキブリさんは上からカサカサと落ちてきて、
ハエさんは予測不能な動きでブーンと飛び回るようにしておきます。
みー:(>_<;) ダメージ制にしましょう。
ゴキブリさんが画面下に到達するとアップで這い上がってきて1ダメージ。
ハエさんは10秒倒せないと、画面にピトッと張り付いて1ダメージ。
10ダメージでゲームオーバー。
AI: 画面に張り付いてくるペナルティ……
想像しただけでゾワッとしますが、
ゲームとしては最高の緊張感ですね(笑)。
みー:(>x<`) 難易度が高いので、
マウスの点じゃなくて“面”で叩けるようにしたいです。
スリッパで叩くイメージにできますか?
AI: なるほど、確かにピンポイントは難しいですよね。
スリッパで「バシッ!」と叩くのはマンガ的にも相性抜群です。
(使い古しのスリッパを取り出しながら)
実はスリッパって、しなりが叩く瞬間の速度を上げてくれるので、
虫退治に理にかなった武器なんですよ。
みー:(*`v´)ノ 左右にうねるムカデも追加したいです。
端まで到達すると2ダメージの強敵。
BGMは「招かざる者の行進」で。
AI: ムカデ……最高のボス枠ですね。
あのウネウネした動き、叩いてもなかなか倒れないしぶとさ。
テーマにもぴったりです。
みー:( ̄w ̄)ノ 100退治したらレベルアップして動きが変わります。
ゴキブリは急に飛び出して2秒間ハエのように暴れる。
ハエは残り3秒で加速。
ムカデはうねりが大きくなってスピードアップ。
AI: 撃破数100で凶悪化……
まさに修羅の道ですね。
でもそのカオス感、嫌いじゃないですよ。
ちなみにゴキブリが急に飛ぶのは、
極限状態になると“最後の手段”として飛ぶからなんです。
しかも飛ぶのが下手なので、人間に向かってくることも多い。
今回の挙動は、まさにそのパニック状態を再現しています。
みー:(=v=)ノ アイテムも追加します。
「シャッキリ」で回復3。
「スッッキリ」で画面内の虫を一掃。
「ノロノロ」で虫のスピード低下。
スリッパで触れるだけで取れるように。
オノマトペも増やして、ゴキブリの向きも上下逆に。
AI: 直感的で良いアイデアですね。
アイテムも追加して、オノマトペも増やしておきました。
ところで「ノロノロ」みたいに周りがスローモーションに見える現象、
実は現実にもあって「タキサイキア現象」と呼ばれています。
極度の緊張状態で脳が情報を高速処理するため、
時間がゆっくりに感じるんです。
レベル2の猛攻を捌いていると、
リアルに体験できるかもしれませんね(笑)。
■ 実際の動作
まずは、AIが最初に出してくれたゲーム画面です。

(画像:AIが最初に出してくれたゲーム画面)
こちらが仕上がったスタート画面。

(画像:スタート画面)
上から、横から、そして飛び回る害虫たち。
スタート直後から ヒエッ てなります。Σ(>o<;)ノ
スリッパで叩くと、画面いっぱいにオノマトペがドーンと表示されます。

(画像:ゲーム画面)
叩けずにいると、虫が画面中央にピトッと張り付きダメージに。
視界が遮られて連鎖的にダメージを受けたりして、
もう悲惨な状況に……(@_@;)

(画像:虫の攻撃)
そんなピンチを救ってくれるのがアイテムたち。
虫の動きをスローにしたり、画面内の虫を一掃したり、
受けたダメージを回復したり……
正直、これなしではクリアは無理です(^^;)

(画像:スッッキリ!)
ダメージ10でゲームオーバー。

(画像:ゲームオーバー)
100匹を超えるとレベル2に突入。
虫たちが今までと違う、危険な動きを始めます。
200匹を倒すとゲームクリア!

(画像:ゲームクリア)
■ コードのポイント
今回の記事では、AI と相談しながら作った 「オノマトペ・スリッパアクション」 の HTML コードを公開します。
このコードをそのまま保存するだけで、 あなたのPCでもすぐに遊べます。
ブラウザだけで動く、とてもシンプルなゲームなので、 「コードは初めて…」という方でも気軽に試してみてくださいね。
▼ 遊ぶまでの手順
①PC の「メモ帳」(※Windowsの場合)や
Mac の「テキストエディット」など、テキストエディタを開きます。
②下のコードをコピーして貼り付けます。
③「surippa.html」など、好きな名前で保存します。
(※拡張子は .html にしてください)
④保存したファイルをブラウザで開けば、すぐに遊べます。
■ 操作方法
ルールはとってもシンプルです!
上や横から迫ってくる虫さんたちを、
スリッパで 「バシッ!」 とやっつけて、
10ダメージ受ける前に 200匹退治できればクリア!(*>ω<)o
ダメージが10溜まると、ゲームオーバーになっちゃいます…(>_<)
■ 武器は「🩴スリッパ」だけ!
あなたのマウスポインタ(スマホなら指の場所)が、
そのままスリッパになります。
(アイキャッチ画像の女の子が持っている、あれです!)
- 移動: マウス(または指)を動かすと、スリッパもついてきます。
- 攻撃(バシッ!): クリック(またはタップ)すると、
スリッパを中心に大きな「判定の面(円形)」が発生します。
この範囲に虫さんが入っていれば、まとめて撃破できます!
大雑把に狙っても当たるので、爽快感バツグンです(^^)v
■ 招かざる客(虫さん)たち
- 𪦠 ゴキブリさん: 上からカサカサ落ちてきます。
油断すると画面に張り付いてダメージに。 - 𪦲 ハエさん: 不規則にブンブン飛び回ります。
倒し損ねると中央にピトッと張り付いて視界を奪ってきます。 - 👹 ムカデさん(強敵): 横からウネウネと横断してきます。
大きいので、判定の真ん中でしっかり捉えてくださいね!
■ 攻略のカギ!お助けアイテム💊
8秒ごとに便利なアイテムが飛んできます。
これはクリックしなくても、スリッパで触れるだけ で取れますよ!(^^)
- 💊 シャッキリ: ダメージを3回復します。
- 🧹 スッッキリ: 画面にいる虫さんを全員まとめてお掃除!
- 🐢 ノロノロ: 5秒間、虫さんの動きが超スローに。
画面が青くなるのが合図。この間に体勢を立て直しましょう(=v=*)
■ レベル2(覚醒)について
100匹退治すると、蟲たちの動きが凶悪になる レベル2 に突入します!
- ゴキブリさん:突然飛び出して暴れ回る
- ハエさん:残り3秒で急加速
- ムカデさん:うねりが大きくなり、スピードアップ
ここからが本番です(*>ω<)o
■ 最後に
ルールはこれだけ! 終わりのない戦いではなく、
200匹でクリア! というゴールがあるので、
ピンチになってもシャッキリで立て直して、
ぜひゴールを目指してみてくださいね。
さあ、音量を少し上げて、スリッパを握りしめて(?)、
カオスなマンガの世界へ飛び込みましょう!(^^*)ノシ
“コードは長いので折りたたんでいます。必要な方は開いてご覧ください。
▼ここからコード▼(クリックで開く)
<html lang="ja"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-select=none"><title>マンガ・オノマトペ・スリッパアクション 覚醒版 (クリアあり)</title><style> body { margin: 0; overflow: hidden; background-color: #fff; font-family: 'Impact', 'Yu Gothic', 'Hiragino Kaku Gothic ProN', sans-serif; user-select: none; -webkit-user-select: none; touch-action: none; cursor: none; } #game-container { position: relative; width: 100vw; height: 100vh; overflow: hidden; background-image: radial-gradient(#ccc 15%, transparent 16%), radial-gradient(#ccc 15%, transparent 16%); background-size: 20px 20px; background-position: 0 0, 10px 10px; display: none; } #start-screen { position: absolute; top: 0; left: 0; width: 100vw; height: 100vh; background: repeating-linear-gradient(45deg, #222, #222 10px, #111 10px, #111 20px); color: white; display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 2000; cursor: pointer; } #start-screen h1 { font-size: 80px; margin: 0; -webkit-text-stroke: 3px black; text-shadow: 6px 6px 0px #d32f2f; transform: rotate(-3deg); } #start-screen p { font-size: 30px; margin-top: 20px; animation: blink 1s infinite; } @keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } #ui { position: absolute; top: 20px; left: 20px; z-index: 200; background: white; border: 5px solid black; padding: 10px 25px; font-size: 24px; font-weight: 900; transform: skew(-5deg); box-shadow: 4px 4px 0px black; display: flex; flex-direction: column; gap: 5px; } .damage-text { color: #d32f2f; } .level-text { color: #1976d2; font-size: 28px; } #slipper-cursor { position: absolute; width: 120px; height: 120px; font-size: 100px; pointer-events: none; z-index: 1000; transform: translate(-50%, -50%) rotate(0deg); transition: transform 0.05s ease-out; display: flex; justify-content: center; align-items: center; } #slipper-cursor::before { content: '🩴'; } #slipper-cursor.smack { transform: translate(-50%, -50%) rotate(-45deg) scale(1.2); } .enemy { position: absolute; display: flex; justify-content: center; align-items: center; z-index: 50; transform: translate(-50%, -50%); transition: box-shadow 0.2s; } .enemy.cockroach { width: 80px; height: 80px; background: #3a2000; border-radius: 40% 40% 50% 50%; border: 3px solid black; box-shadow: 0 0 0 3px white, 0 0 0 6px black; } .enemy.cockroach::before { content: '🪳'; font-size: 50px; display: block; transform: rotate(180deg); } .enemy.fly { width: 80px; height: 80px; background: #111; border-radius: 50%; border: 3px solid black; box-shadow: 0 0 0 3px white, 0 0 0 6px black; } .enemy.fly::before { content: '🪰'; font-size: 40px; } .enemy.centipede { width: 160px; height: 50px; background: #5c1e06; border-radius: 25px; border: 3px solid black; box-shadow: 0 0 0 3px white, 0 0 0 6px black; background-image: repeating-linear-gradient(90deg, transparent, transparent 10px, black 10px, black 15px); } .enemy.centipede::before { content: '👹'; font-size: 40px; margin-left: 100px; transform: rotate(90deg); } .item { position: absolute; width: 60px; height: 60px; border-radius: 50%; display: flex; justify-content: center; align-items: center; font-size: 35px; border: 4px solid white; box-shadow: 0 0 0 4px black; z-index: 80; transform: translate(-50%, -50%); } .item.shakkiri { background-color: #4caf50; } .item.shakkiri::before { content: '💊'; } .item.sukkiri { background-color: #f44336; } .item.sukkiri::before { content: '🧹'; } .item.noronoro { background-color: #2196f3; } .item.noronoro::before { content: '🐢'; } .onomatopoeia { position: absolute; font-size: 80px; font-weight: 900; color: white; -webkit-text-stroke: 3px black; text-shadow: 5px 5px 0px black, 10px 10px 0px #ff3333; pointer-events: none; z-index: 100; white-space: nowrap; transform: translate(-50%, -50%); animation: pop 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; } .jumpscare { position: fixed; top: 50%; left: 50%; width: 100px; height: 100px; transform: translate(-50%, -50%) scale(0.1); z-index: 999; pointer-events: none; animation: attackScreen 0.8s ease-out forwards; } @keyframes attackScreen { 0% { transform: translate(-50%, -50%) scale(0.1); opacity: 0; } 20% { transform: translate(-50%, -50%) scale(5); opacity: 1; } 80% { transform: translate(-50%, -50%) scale(6); opacity: 1; } 100% { transform: translate(-50%, -50%) scale(8); opacity: 0; } } @keyframes pop { 0% { transform: translate(-50%, -50%) scale(0) rotate(-30deg); opacity: 1; } 40% { transform: translate(-50%, -50%) scale(1.3) rotate(10deg); opacity: 1; } 80% { transform: translate(-50%, -50%) scale(1) rotate(-5deg); opacity: 1; } 100% { transform: translate(-50%, -50%) scale(1) rotate(-5deg); opacity: 0; } } .shake { animation: shake 0.3s ease-in-out; } .heavy-shake { animation: heavyShake 0.5s ease-in-out; } @keyframes shake { 0%, 100% { transform: translate(0, 0) rotate(0deg); } 25% { transform: translate(-10px, -5px) rotate(-1deg); } 50% { transform: translate(10px, 5px) rotate(1deg); } 75% { transform: translate(-10px, 5px) rotate(0deg); } } @keyframes heavyShake { 0%, 100% { transform: translate(0, 0) rotate(0deg); } 20% { transform: translate(-30px, -20px) rotate(-3deg); background-color: #ffcccc; } 40% { transform: translate(30px, 20px) rotate(3deg); } 60% { transform: translate(-30px, 20px) rotate(-3deg); } 80% { transform: translate(30px, -20px) rotate(3deg); background-color: #fff; } } #game-over, #game-clear { display: none; position: absolute; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.9); z-index: 1000; color: white; flex-direction: column; justify-content: center; align-items: center; cursor: default; } #game-over h1 { font-size: 100px; margin: 0; color: red; -webkit-text-stroke: 4px white; transform: rotate(-5deg); } #game-clear h1 { font-size: 100px; margin: 0; color: #4caf50; -webkit-text-stroke: 4px white; transform: rotate(5deg); } #game-over button, #game-clear button { margin-top: 30px; padding: 15px 40px; font-size: 30px; font-weight: bold; background: white; border: 5px solid black; cursor: pointer; } .level-up-flash { animation: levelUpAnim 1.5s ease-in-out forwards; } @keyframes levelUpAnim { 0% { background-color: #fff; } 10% { background-color: #ffd700; } 100% { background-color: #fff; } }</style></head><body><div id="start-screen"> <h1>招かざる者の行進</h1> <p>画面をクリックしてスタート</p></div><div id="game-container"> <div id="slipper-cursor"></div> <div id="ui"> <div class="level-text">レベル: <span id="levelDisplay">1</span></div> <div>撃破数: <span id="scoreDisplay">0</span> / 200</div> <div class="damage-text">ダメージ: <span id="damageDisplay">0</span> / 10</div> </div></div><div id="game-over"> <h1>GAME OVER</h1> <p>撃破数: <span id="finalScoreDisplayOver">0</span> / 200</p> <button onclick="location.reload()">もう一回</button></div><div id="game-clear"> <h1>MISSION COMPLETE</h1> <p>撃破数: <span id="finalScoreDisplayClear">0</span> / 200</p> <button onclick="location.reload()">もう一度戦う</button></div><script> const startScreen = document.getElementById('start-screen'); const container = document.getElementById('game-container'); const slipper = document.getElementById('slipper-cursor'); const scoreDisplay = document.getElementById('scoreDisplay'); const damageDisplay = document.getElementById('damageDisplay'); const levelDisplay = document.getElementById('levelDisplay'); const gameOverScreen = document.getElementById('game-over'); const gameClearScreen = document.getElementById('game-clear'); const finalScoreDisplayOver = document.getElementById('finalScoreDisplayOver'); const finalScoreDisplayClear = document.getElementById('finalScoreDisplayClear'); const WIN_SCORE = 200; // クリアに必要なスコア const wordsSmack = ["パンッ!", "バシッ!", "ベチッ!", "ドスッ!", "スマッシュ!", "ペチィ!"]; const wordsCockroach = ["カサ…", "ズルッ…", "ギチィ!", "プチッ", "グチャ…", "ヒィィ!"]; const wordsFly = ["ブーン…", "ピブン…", "うるせぇ!", "パチン!", "ポトッ", "チィィ!"]; const wordsCentipede = ["ズバババァン!", "グチャァ…", "ド派手!", "キシャァァ!", "ボドォッ!"]; let score = 0; let damage = 0; let level = 1; let isGameOver = false; let isGameClear = false; let spawnInterval; let itemInterval; let mouseX = window.innerWidth / 2; let mouseY = window.innerHeight / 2; let slowMultiplier = 1.0; const gameElements = []; const HIT_RADIUS = 60; // ----- オーディオエンジン ----- let audioCtx; function initAudio() { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); startBGM(); } function startBGM() { const notes = [65.41, 69.30, 65.41, 61.74]; let step = 0; setInterval(() => { if (isGameOver || isGameClear) return; const currentTempo = (level === 1 ? 600 : 400) / slowMultiplier; const noteDuration = (level === 1 ? 0.5 : 0.3) / slowMultiplier; const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = 'square'; osc.frequency.setValueAtTime(notes[step % notes.length], audioCtx.currentTime); const filter = audioCtx.createBiquadFilter(); filter.type = 'lowpass'; filter.frequency.value = 150; osc.connect(filter); filter.connect(gain); gain.connect(audioCtx.destination); gain.gain.setValueAtTime(0.2, audioCtx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + noteDuration); osc.start(); osc.stop(audioCtx.currentTime + noteDuration); step++; }, 600); } function playSmackSound() { if (!audioCtx) return; const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = 'sawtooth'; osc.frequency.setValueAtTime(100, audioCtx.currentTime); osc.frequency.exponentialRampToValueAtTime(10, audioCtx.currentTime + 0.1); osc.connect(gain); gain.connect(audioCtx.destination); gain.gain.setValueAtTime(0.5, audioCtx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1); osc.start(); osc.stop(audioCtx.currentTime + 0.1); } function playDamageSound() { if (!audioCtx) return; [150, 160].forEach(freq => { const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = 'sawtooth'; osc.frequency.value = freq; osc.connect(gain); gain.connect(audioCtx.destination); gain.gain.setValueAtTime(0.5, audioCtx.currentTime); gain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + 0.5); osc.start(); osc.stop(audioCtx.currentTime + 0.5); }); } function playPowerUpSound(type) { if (!audioCtx) return; const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = 'sine'; if (type === 'shakkiri') osc.frequency.setValueAtTime(400, audioCtx.currentTime); else if (type === 'sukkiri') osc.frequency.setValueAtTime(800, audioCtx.currentTime); else osc.frequency.setValueAtTime(200, audioCtx.currentTime); osc.frequency.exponentialRampToValueAtTime(type === 'noronoro' ? 50 : 1200, audioCtx.currentTime + 0.5); osc.connect(gain); gain.connect(audioCtx.destination); gain.gain.setValueAtTime(0.4, audioCtx.currentTime); gain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + 0.5); osc.start(); osc.stop(audioCtx.currentTime + 0.5); } function playClearSound() { if (!audioCtx) return; const notes = [261.63, 329.63, 392.00, 523.25]; // Cコード notes.forEach((freq, i) => { const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = 'triangle'; osc.frequency.value = freq; osc.connect(gain); gain.connect(audioCtx.destination); gain.gain.setValueAtTime(0, audioCtx.currentTime + i * 0.1); gain.gain.linearRampToValueAtTime(0.3, audioCtx.currentTime + i * 0.1 + 0.1); gain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + i * 0.1 + 0.5); osc.start(audioCtx.currentTime + i * 0.1); osc.stop(audioCtx.currentTime + i * 0.1 + 0.5); }); } startScreen.addEventListener('click', () => { startScreen.style.display = 'none'; container.style.display = 'block'; initAudio(); spawnInterval = setInterval(spawnEnemy, 1000); itemInterval = setInterval(spawnItem, 8000); }); const updateMousePos = (e) => { mouseX = e.clientX || (e.touches && e.touches[0].clientX); mouseY = e.clientY || (e.touches && e.touches[0].clientY); if (!isGameOver && !isGameClear) { slipper.style.left = mouseX + 'px'; slipper.style.top = mouseY + 'px'; } }; window.addEventListener('mousemove', updateMousePos); window.addEventListener('touchmove', updateMousePos, { passive: false }); // ----- スリッパの攻撃 ----- const smack = (e) => { if (isGameOver || isGameClear || e.target === startScreen) return; e.preventDefault(); slipper.classList.add('smack'); setTimeout(() => slipper.classList.remove('smack'), 100); playSmackSound(); showOnomatopoeia(mouseX, mouseY, wordsSmack, true, '#ffeb3b'); for (let i = gameElements.length - 1; i >= 0; i--) { const elInfo = gameElements[i]; if (!elInfo.active) continue; const rect = elInfo.element.getBoundingClientRect(); const enemyX = rect.left + rect.width / 2; const enemyY = rect.top + rect.height / 2; const hitArea = elInfo.type === 'centipede' ? HIT_RADIUS * 1.5 : HIT_RADIUS; const dist = Math.sqrt(Math.pow(mouseX - enemyX, 2) + Math.pow(mouseY - enemyY, 2)); if (dist < hitArea) killEnemy(elInfo, mouseX, mouseY); } }; container.addEventListener('mousedown', smack); container.addEventListener('touchstart', smack, { passive: false }); // ----- アイテムの出現と挙動 ----- function spawnItem() { if (isGameOver || isGameClear) return; const itemTypes = ['shakkiri', 'sukkiri', 'noronoro']; const type = itemTypes[Math.floor(Math.random() * itemTypes.length)]; const item = document.createElement('div'); item.className = `item ${type}`; container.appendChild(item); let x = Math.random() * (window.innerWidth - 100) + 50; let y = Math.random() * (window.innerHeight - 100) + 50; let vx = (Math.random() * 4 + 4) * (Math.random() > 0.5 ? 1 : -1); let vy = (Math.random() * 4 + 4) * (Math.random() > 0.5 ? 1 : -1); let active = true; const moveItem = setInterval(() => { if (!active || isGameOver || isGameClear) { clearInterval(moveItem); return; } x += vx; y += vy; if (x < 30 || x > window.innerWidth - 30) vx *= -1; if (y < 30 || y > window.innerHeight - 30) vy *= -1; item.style.left = x + 'px'; item.style.top = y + 'px'; const dist = Math.sqrt(Math.pow(x - mouseX, 2) + Math.pow(y - mouseY, 2)); if (dist < HIT_RADIUS + 20) { active = false; item.remove(); applyItemEffect(type, x, y); } }, 16); setTimeout(() => { if (active) { active = false; item.remove(); } }, 5000); } function applyItemEffect(type, x, y) { playPowerUpSound(type); if (type === 'shakkiri') { damage = Math.max(0, damage - 3); damageDisplay.innerText = damage; showOnomatopoeia(x, y, ["シャッキリ!", "命拾い!"], true, '#4caf50'); } else if (type === 'sukkiri') { showOnomatopoeia(window.innerWidth/2, window.innerHeight/2, ["スッッキリ!", "一掃!!"], true, '#f44336'); shakeScreen(true); gameElements.forEach(elInfo => { if (elInfo.active) killEnemy(elInfo, elInfo.element.getBoundingClientRect().left, elInfo.element.getBoundingClientRect().top, true); }); } else if (type === 'noronoro') { showOnomatopoeia(x, y, ["ノロノロ〜", "スロー!"], true, '#2196f3'); slowMultiplier = 0.25; container.style.backgroundColor = '#e3f2fd'; setTimeout(() => { if(!isGameOver && !isGameClear) { slowMultiplier = 1.0; container.style.backgroundColor = '#fff'; } }, 5000); } } function checkLevelUp() { if (score >= 100 && level === 1) { level = 2; levelDisplay.innerText = level; const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = 'triangle'; osc.frequency.value = 880; osc.connect(gain); gain.connect(audioCtx.destination); gain.gain.setValueAtTime(0.3, audioCtx.currentTime); gain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + 0.3); osc.start(); osc.stop(audioCtx.currentTime + 0.3); showOnomatopoeia(window.innerWidth/2, window.innerHeight/2, ["覚醒!!", "LEVEL 2"], true, '#ffd700'); container.classList.add('level-up-flash'); setTimeout(() => container.classList.remove('level-up-flash'), 1500); clearInterval(spawnInterval); spawnInterval = setInterval(spawnEnemy, 750); } } function killEnemy(elInfo, smackX, smackY, isItemKill = false) { elInfo.active = false; elInfo.element.remove(); score += elInfo.type === 'centipede' ? 2 : 1; scoreDisplay.innerText = score; let words = wordsCockroach; if(elInfo.type === 'fly') words = wordsFly; if(elInfo.type === 'centipede') words = wordsCentipede; if(!isItemKill) { showOnomatopoeia(smackX + 30, smackY - 30, words, false); shakeScreen(false); } if (score >= WIN_SCORE && !isGameClear && !isGameOver) { triggerGameClear(); } else { checkLevelUp(); } } function takeDamage(type, damageAmount) { if (isGameOver || isGameClear) return; damage += damageAmount; damageDisplay.innerText = damage; playDamageSound(); const scare = document.createElement('div'); scare.className = `enemy ${type} jumpscare`; container.appendChild(scare); setTimeout(() => scare.remove(), 800); shakeScreen(true); if (damage >= 10) triggerGameOver(); } function spawnEnemy() { if (isGameOver || isGameClear) return; const enemy = document.createElement('div'); const rand = Math.random(); let type = 'cockroach'; if (rand < 0.15) type = 'centipede'; else if (rand < 0.5) type = 'fly'; enemy.className = `enemy ${type}`; container.appendChild(enemy); const elInfo = { element: enemy, type: type, active: true }; gameElements.push(elInfo); if (type === 'centipede') { const isLeftToRight = Math.random() > 0.5; let x = isLeftToRight ? -200 : window.innerWidth + 200; let y = Math.random() * (window.innerHeight - 300) + 150; const baseY = y; let tick = 0; enemy.style.transform = `translate(-50%, -50%) ${isLeftToRight ? '' : 'scaleX(-1)'}`; const crawl = setInterval(() => { if (!elInfo.active || isGameOver || isGameClear) { clearInterval(crawl); return; } tick += 0.1 * slowMultiplier; const currentSpeed = (level >= 2 ? 8 : 4) * slowMultiplier; const currentAmp = level >= 2 ? 140 : 60; x += isLeftToRight ? currentSpeed : -currentSpeed; y = baseY + Math.sin(tick) * currentAmp; enemy.style.left = x + 'px'; enemy.style.top = y + 'px'; if ((isLeftToRight && x > window.innerWidth + 150) || (!isLeftToRight && x < -150)) { elInfo.active = false; enemy.remove(); takeDamage('centipede', 2); } }, 16); } else if (type === 'cockroach') { let x = Math.random() * (window.innerWidth - 100) + 50; let y = -100; const baseSpeedY = Math.random() * 3 + 2; let flyMode = false; let flyTimer = 0; let hasFlown = false; let flyAngle = Math.random() * Math.PI * 2; const fall = setInterval(() => { if (!elInfo.active || isGameOver || isGameClear) { clearInterval(fall); return; } if (level >= 2 && !hasFlown && y > window.innerHeight * 0.3) { flyMode = true; hasFlown = true; flyTimer = 2000; } if (flyMode) { flyTimer -= 16 * slowMultiplier; if (flyTimer <= 0) { flyMode = false; enemy.style.boxShadow = "0 0 0 3px white, 0 0 0 6px black"; } else { const flySpeed = 10 * slowMultiplier; flyAngle += (Math.random() - 0.5) * 1.5; x += Math.cos(flyAngle) * flySpeed; y += Math.sin(flyAngle) * flySpeed; if (x < 50 || x > window.innerWidth - 50) flyAngle = Math.PI - flyAngle; if (y < 50 || y > window.innerHeight - 50) flyAngle = -flyAngle; enemy.style.boxShadow = "0 0 0 3px yellow, 0 0 0 6px black"; } } else { y += baseSpeedY * slowMultiplier; let wobble = Math.sin(y / 20) * 15; x += wobble * 0.1; } enemy.style.left = x + 'px'; enemy.style.top = y + 'px'; if (y > window.innerHeight + 100) { elInfo.active = false; enemy.remove(); takeDamage('cockroach', 1); } }, 16); } else { let x = Math.random() * window.innerWidth; let y = Math.random() * window.innerHeight; let angle = Math.random() * Math.PI * 2; let baseSpeed = 6; let flyTime = 0; const flyMove = setInterval(() => { if (!elInfo.active || isGameOver || isGameClear) { clearInterval(flyMove); return; } flyTime += 16 * slowMultiplier; let currentSpeed = baseSpeed * slowMultiplier; if (level >= 2 && flyTime >= 7000) { currentSpeed = baseSpeed * 2.5 * slowMultiplier; enemy.style.boxShadow = "0 0 0 3px red, 0 0 0 6px black"; } angle += (Math.random() - 0.5) * 1.2; x += Math.cos(angle) * currentSpeed; y += Math.sin(angle) * currentSpeed; if (x < 50 || x > window.innerWidth - 50) angle = Math.PI - angle; if (y < 50 || y > window.innerHeight - 50) angle = -angle; enemy.style.left = x + 'px'; enemy.style.top = y + 'px'; if (flyTime >= 10000) { elInfo.active = false; enemy.remove(); takeDamage('fly', 1); } }, 16); } } function showOnomatopoeia(x, y, words, isSpecial, customColor) { const text = document.createElement('div'); text.className = 'onomatopoeia'; text.innerText = words[Math.floor(Math.random() * words.length)]; if (isSpecial) { text.style.fontSize = '120px'; text.style.color = customColor || '#ffeb3b'; text.style.zIndex = '110'; } else { text.style.fontSize = '60px'; text.style.zIndex = '100'; } text.style.left = x + 'px'; text.style.top = y + 'px'; container.appendChild(text); setTimeout(() => text.remove(), 600); } function shakeScreen(isHeavy) { container.classList.remove('shake', 'heavy-shake'); void container.offsetWidth; container.classList.add(isHeavy ? 'heavy-shake' : 'shake'); } // ----- ゲームオーバー・クリア処理 ----- function cleanUpGame() { clearInterval(spawnInterval); clearInterval(itemInterval); gameElements.forEach(elInfo => { if (elInfo.active) elInfo.element.remove(); }); document.body.style.cursor = 'default'; slipper.style.display = 'none'; container.style.backgroundColor = '#fff'; // 画面の色を戻す } function triggerGameOver() { isGameOver = true; cleanUpGame(); finalScoreDisplayOver.innerText = score; gameOverScreen.style.display = 'flex'; if(audioCtx) audioCtx.suspend(); } function triggerGameClear() { isGameClear = true; cleanUpGame(); finalScoreDisplayClear.innerText = score; gameClearScreen.style.display = 'flex'; if(audioCtx) { audioCtx.suspend(); setTimeout(() => { audioCtx.resume(); playClearSound(); }, 500); // 少し置いてクリアファンファーレ } }</script></body></html>
■ 今日の学び
今回のゲーム実験を振り返って、一番の「学び」だったのは、
アイデアの種がどこに潜んでいるか分からない ということ、
そして AIとの会話の中でアイデアが形を変えていく面白さ でした。(^^)
最初は「オノマトペの手法を活かす」実験から始まりました。
AIが提案してくれた最初のオノマトペは……
“ドーン!!” “バキッ!” “ズバァァン!”
などなど、大迫力の格闘風!(=v=*) 敵もシンプルな黒い丸だけ。
でも、その黒丸の「敵」の影こそが、すべての始まりだったんですね。
そこから、
「敵といえばあの虫… 害虫退治ゲームにしよう!」
とアイデアが変化していきました。
そしてAIさんと相談しているうちに、
「じゃあ、やっぱりスリッパで叩くしかないよね!」 と、
武器も自然に決まっていったんです。
スリッパという具体的なアイテムが出てきたことで、
オノマトペも格闘風から「パンッ!」「バシッ!」といった
“スリッパ専用”の音に変わっていったのも面白いところです。
そこからはAIと一緒に盛り上がりすぎて、
ムカデさんを追加したり、アイテムを考えたり……。
気づけば、虫さんたちの「大行進」みたいな賑やかなゲームに。(^^;
苦手な方には、ちょっとドキドキする内容になってしまったかもしれません。
ごめんなさい m(_ _)m
でも、言葉の持つ力と、AIさんとの共同作業が重なって、
シンプルな丸い「敵」から始まった今回の実験は、
思いがけない方向へ広がっていく“オノマトペの大冒険”になりました。
■ 次回は、シュールな運搬ゲーム。
収穫したユズを、一輪運搬車(ねこぐるま)でコロコロ運びます。
中身をできるだけこぼさずにゴールを目指す、
不安定と不完全がそのままゲームになったような作品 です。
ちょっとした段差でユズが吹き飛んだり、
バランスを崩して全部こぼれたり……。
思わず「なんでそうなるの!?」とツッコミたくなる、
シュールでクセになる運搬アクションになっています。
次回もぜひ遊びに来てくださいね。(*^v^)ノシ
✨今日も最後まで読んでくださって、ありがとうございます。
みーの実験室は、みなさんの応援で元気に動いています。(*^v^*)






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