実験 #11:集中力ブレイカー(目隠し波動砲)

■11個目「目隠し波動砲」

 今回はこの「目隠し波動砲」のゲームを作ってみました。ヽ(*^o^*)ノ

 このゲームは、時間を勘で当てるゲームです。
 シンプルで絵の少ないゲームを作ってみたいと思いました。そこでAIと相談しながら形にしていきました。

 最初の5秒はカウントが表示されます。ちょうど20秒と思ったところで発射ボタンを押します。20秒に近いほどスコアが高くなります。

・・・ただし、船長の独り言が頭の中のカウントの邪魔をしてきます。自分の感覚を信じてボタンを押してみましょう。(><;)

■ AIとのやりとりで面白かった部分

(^^*)今日もゲーム作ります。ゲームは目隠し波動砲ゲーム。SFの世界で、敵艦隊発見。敵の母艦へ必殺の波動砲を発射しようとするけど、20秒前から敵艦隊を隕石群が目隠しのように横切っていく。そこをちょうど20秒で発射ボタンを押す。早すぎても遅すぎても致命傷にはならないのでちょうどを狙う。ちょうどに近いほどポイントが高い。

(AI)常連さんもいよいよLv.2といったところでしょうか。少しずつ、あうんの呼吸でいけるようになってきた気がします。今回のゲーム、シンプルですが「見えない恐怖」と「体内時計」の勝負で熱くなりそうですね。
〈まだまだレベル低いです〉(つv<`)

(=v=*)船長の独り言は20秒を数えるプレイヤーの集中を削ぐような、気になる独り言を言い続ける感じが欲しい。

(AI)あはは、なるほど!船長は味方でありながら、最大の「お邪魔キャラ」というわけですね。Lv.2の常連さん、なかなかドSな仕様変更を提案されますね。
 20秒を数えている頭の中に、絶妙に「数字」を混ぜたり、どうでもいい世間話を振ってきたりして、体内時計を狂わせにくる……そんな**「集中力ブレイカー船長」**を実装しました。

■ 実際の動作

5秒までは、カウント見えます。

船長が意地の悪い独り言をつぶやきます。

惑わされずに、あてましょう(^^;)

途中AIの作ったイメージです。絵はかっこいいけど、誤字がひどいです。(^^;)

■ コードのポイント

 今回の記事では、AI と相談しながら作った「目隠し波動砲」の HTML コードを公開します。ブラウザだけで動く、とてもシンプルなゲームです。

PC の「メモ帳」(※Windowsの場合)を開き、下のコードをコピーして貼り付け、「mekakusi.html」など好きな名前で保存 → ブラウザで開く。これだけで遊べます。

操作はとても簡単で、20秒だと思ったタイミングで「発射ボタン」を押すだけ。

 今回も AI が作ってくれたコードをそのまま載せているので、気になるところを自分で改造したり、色や動きをアレンジして遊んでみてください(^^*)

 「※スマホでは動作しない場合があります」

 「※コードが長いので、必要な方だけコピーしてください」

▼ここからコード▼(クリックで開く)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>波動砲発射シミュレーター:精神汚染</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Noto+Sans+JP:wght@400;700&display=swap');
body {
background: #000; color: #fff; text-align: center;
font-family: 'Noto Sans JP', sans-serif; overflow: hidden;
margin: 0; display: flex; align-items: center; justify-content: center; min-height: 100vh;
}
#game-window {
position: relative; width: 95vw; max-width: 950px; height: 85vh;
background: radial-gradient(circle at center, #050510 0%, #000 100%);
border: 2px solid #0ff; box-shadow: 0 0 20px rgba(0,255,255,0.3);
display: flex; flex-direction: column; align-items: center; justify-content: center;
overflow: hidden;
}
.meteor-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: none; z-index: 10; pointer-events: none; }
.meteor { position: absolute; background: #444; border-radius: 50%; animation: slide linear infinite; }
@keyframes slide { from { transform: translateX(110vw); } to { transform: translateX(-20vw); } }
/* 独り言の演出:大きく、フェード付き */
#status-message {
height: 120px; font-size: 2.2em; color: #ffcc00; font-weight: bold;
margin: 20px 0; z-index: 20; width: 90%;
display: flex; align-items: center; justify-content: center;
text-shadow: 0 0 15px rgba(255, 204, 0, 0.7);
transition: opacity 0.8s ease-in-out; opacity: 0;
}
.fade-in { opacity: 1 !important; }
#timer { font-family: 'Orbitron', sans-serif; font-size: 8em; margin: 20px 0; text-shadow: 0 0 30px #fff; }
#result-display {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
background: rgba(0,0,0,0.98); border: 4px solid #ffd700; padding: 40px;
display: none; z-index: 100; border-radius: 15px; width: 75%;
}
.btn {
padding: 25px 70px; font-size: 2em; font-family: 'Noto Sans JP', sans-serif;
background: linear-gradient(to bottom, #f00, #800); color: #fff;
border: none; cursor: pointer; border-radius: 10px; box-shadow: 0 0 25px rgba(255,0,0,0.5);
z-index: 30;
}
.fire-shake { animation: fire-shake-anim 0.05s linear infinite; }
@keyframes fire-shake-anim {
0% { transform: translate(15px, 15px); }
50% { transform: translate(-20px, -10px); }
100% { transform: translate(0, 0); }
}
</style>
</head>
<body>
<div id="game-window">
<div class="meteor-container" id="meteor-field"></div>
<h1 style="font-family: 'Orbitron'; color: #0ff; letter-spacing: 5px;">WAVE MOTION CANNON</h1>
<div id="status-message"></div>
<div id="timer">0.00</div>
<div id="result-display">
<div id="score-rank" style="font-size: 3.5em; color: #ffd700; font-family: 'Orbitron';"></div>
<div id="error-info" style="font-size: 1.8em; margin: 15px 0;"></div>
<div id="comment-text" style="font-size: 1.5em; line-height: 1.6; color: #fff;"></div>
<button class="btn" style="margin-top: 40px;" onclick="resetGame()">再装填</button>
</div>
<button class="btn" id="fireBtn" onclick="fire()">波動砲 発射! (SPACE)</button>
</div>
<audio id="fireSound" src="https://www.soundjay.com/buttons/sounds/button-7.mp3"></audio>
<audio id="explosionSound" src="https://www.soundjay.com/explosions/sounds/explosion-02.mp3"></audio>
<script>
let startTime, animId, lastQuoteTime = 0, isFired = false;
const target = 20.00;
const captainQuotes = [
"「……15、16……。あ、悪い、今の私の独り言だ」",
"「昨日の昼飯、何食ったっけかなぁ……」",
"「おい、さっきの隕石、13個くらいあったか?」",
"「……そういえば、君の給料、来月から3%カットだってよ」",
"「7、8、9……いや、なんでもない。続けてくれ」",
"「……このボタン、押し心地が18倍くらい良くなってるな」",
"「今の振動、12段階評価でいうと5くらいか?」",
"「あ、書き置き忘れた。……ガス代払ったかな……」",
"「……宇宙の終わりって、あと何十億年後だっけ?」",
"「君、今……何秒だと思ってた?」",
"「……蚊がいるな、このコックピット。プ~ン……」",
"「19……あ、いや、まだかな。……どう思う?」",
"「……腹減ったな。牛丼屋、17時までだっけか」",
"「なあ、今の誤差1.4秒くらいズレてなかったか?」",
"「……12、13……。おっと、口に出てたか」",
"「君の故郷、今ちょうど18時くらいじゃないか?」",
"「……11、12、13……。よし、順調だな(笑)」",
"「あ、さっきの敵艦、19隻いた気がするな」",
"「……14、15、……。いや、数えてないぞ。断じてな」",
"「……20……。あ、これは私のラッキーナンバーだ」"
];
const timerDisp = document.getElementById('timer');
const msgDisp = document.getElementById('status-message');
const mField = document.getElementById('meteor-field');
const resDisp = document.getElementById('result-display');
const fireBtn = document.getElementById('fireBtn');
const windowEl = document.getElementById('game-window');
function createMeteors() {
mField.innerHTML = '';
for(let i=0; i<45; i++) {
let m = document.createElement('div');
m.className = 'meteor';
let size = Math.random() * 25 + 10;
m.style.width = size + 'px'; m.style.height = (size * 0.7) + 'px';
m.style.top = Math.random() * 100 + '%';
m.style.animationDuration = (Math.random() * 0.6 + 0.3) + 's';
m.style.animationDelay = (Math.random() * 2) + 's';
mField.appendChild(m);
}
}
function resetGame() {
isFired = false; lastQuoteTime = 0;
resDisp.style.display = 'none';
fireBtn.style.display = 'block';
timerDisp.style.visibility = 'visible';
mField.style.display = 'none';
windowEl.classList.remove('fire-shake');
msgDisp.innerText = "「20.00秒で発射だ。私語は厳禁だぞ」";
msgDisp.classList.add('fade-in');
startTime = performance.now();
update();
}
function update() {
if (isFired) return;
const elapsed = (performance.now() - startTime) / 1000;
if (elapsed >= 5.0) {
timerDisp.style.visibility = 'hidden';
mField.style.display = 'block';
if(mField.children.length === 0) createMeteors();
// 1.5秒ごとにフェードしながら更新
if (elapsed - lastQuoteTime > 1.8) {
msgDisp.classList.remove('fade-in');
setTimeout(() => {
if (!isFired) {
msgDisp.innerText = captainQuotes[Math.floor(Math.random() * captainQuotes.length)];
msgDisp.classList.add('fade-in');
}
}, 400);
lastQuoteTime = elapsed;
}
} else {
timerDisp.innerText = elapsed.toFixed(2);
}
animId = requestAnimationFrame(update);
}
function fire() {
if (isFired) return;
isFired = true;
cancelAnimationFrame(animId);
const finishTime = (performance.now() - startTime) / 1000;
const diff = Math.abs(target - finishTime);
document.getElementById('fireSound').play();
setTimeout(() => document.getElementById('explosionSound').play(), 400);
windowEl.classList.add('fire-shake');
setTimeout(() => windowEl.classList.remove('fire-shake'), 1200);
mField.style.display = 'none';
timerDisp.style.visibility = 'visible';
timerDisp.innerText = finishTime.toFixed(3) + "s";
fireBtn.style.display = 'none';
msgDisp.classList.remove('fade-in');
let rank, comment;
if (diff < 0.05) {
rank = "S: 神業"; comment = "「……チッ。私の独り言に惑わされないとはな。認めよう、君の勝ちだ」";
} else if (diff < 0.4) {
rank = "A: 直撃"; comment = "「ほう! 奇跡的に当たったな。今のは私の『20』が効いたか?」";
} else if (diff < 1.2) {
rank = "B: 命中"; comment = "「惜しいな、私の数え間違いに釣られたか? 悪い悪い(笑)」";
} else {
rank = "C: 失敗"; comment = "「……あ、今の失敗、全部君の責任ってことで報告書出しとくよ」";
}
document.getElementById('score-rank').innerText = rank;
document.getElementById('error-info').innerText = `誤差: ${diff.toFixed(3)}秒`;
document.getElementById('comment-text').innerText = comment;
resDisp.style.display = 'block';
}
window.addEventListener('keydown', (e) => { if (e.code === 'Space') { e.preventDefault(); fire(); } });
resetGame();
</script>
</body>
</html

■ 今日の学び

 船長の独り言は、目をつぶってプレイしてみると意外といい点が取れました。音声で読み上げる仕組みもAIから提案がありましたが、今回はここまでの仕上がりです。

 毎日1個のゲーム作りも順調で、R8.3.7の時点でいよいよ50個目前です。(^~^*)

ブログ作りもゲーム作りも、集中しすぎると時間があっという間に過ぎてしまい、つい夜更かししてしまいます。(><、)

 AIを有料版にしてからは、作っている途中で「思ってたのと違う」方向に行くことが減った気がします。こちらの意図が伝わりやすく、返ってくる答えも丁寧なので、無料版より頼りになる感覚があります。

 次回は「雪の妖精シマエナガ」ゲームです。
 かわいいので、どうぞお楽しみに(*^v^)ノシ

次の実験はこちら(横スクロール集団飛翔アクションゲーム): 「シマエナガ大好きゲーム」

コメントを残す