■46個目「成長の庭」
こんにちは、AIと作る100のゲーム実験室の みー です。(^_^)/
今回は、夜空から降ってくる星を使った、ちょっと幻想的なアクションゲームを作ってみました。
上から次々と落ちてくる星の光を、下のバーでポンッと跳ね返して、画面中央にある「生命のタネ」にエネルギーを届けていきます。
今回こだわったのは、庭の“進化”と“心地よい音”。 「星をキャッチするんじゃなくて、ピンボールみたいに弾き返して何かを育てられたら気持ちいいだろうな〜」という想像から生まれた、癒し系のゲームです。
星をたくさん吸い込ませると、小さな 🌱(芽)が 🌳(木)になり、やがて 🌸(花)が咲き……と、庭がどんどん姿を変えていきます。
途中には、色とりどりの特別な「オーブ」が降ってくることも。 弾き返すと星がパッと弾けたり、画面いっぱいに星の雨が降るフィーバータイムに突入したりと、キラキラした演出も楽しめます。(^v^*)/7
さらに、庭が成長するにつれて、後ろで流れるローファイなBGMも少しずつ音色が豊かになっていくんですよ。
制限時間内に、あなたの庭はどこまで育つでしょうか。 驚きの最終形態まで、心地よい音と一緒にぜひ育ててみてください。
■ AIとのやりとりで面白かった部分
■ゲーム仕様の相談からスタート
みー:(^v^) 今日もゲームを作ります。html形式で動かせるゲーム。
(ここまで入力したところで、うっかりエンターキーを押してしまいました)
すると、AIがすぐに反応して——
AI: マウスで遊べる「星キャッチゲーム」を用意してみたよ。
マウスでバーを動かして、落ちてくる星をキャッチする30秒のタイムアタックだよ。
みー:(^o^*) せっかく作ってくれたので、これをアレンジしたゲームにします。
星が降ってくるのは同じだけど、キャッチではなくピンボールみたいに跳ね返して、中央の絵文字に吸い寄せられるようにしたいです。
🌱→🌳→🌸→🍎→🍹→🍰…みたいに、ポイントに応じて絵が変わっていく成長ゲームにしましょう。
絵が変わるたびに演出も派手にしたいね。
AI: それは面白いアイデアだね! ただのキャッチゲームから、アクション性と成長要素を加えた「ピンボール風・成長の庭ゲーム」に進化させよう。
みー:(*^^)もっと星がたくさん降ってくるアイテムや、3秒間だけ高速で降るアイテムも欲しいね。
バーが大きくなるアイテムや、制限時間アップも追加したい。 ゲーム画面も少し大きくして、成長の目安になるポイントを横に表示すると親切かも。
AI: いらっしゃい。早速アイデアが湧いてきたみたいだね。
今回のオーダーに合わせてゲームを大幅にリニューアルしたよ。
どうかな、狙い通りになっているかい?
■ 実際の動作
最初にAIが出してくれた「星キャッチゲーム」の初期画面です。
ここからアレンジが始まりました。

(画像:AIが出してくれた初期のゲーム)
こちらが今回の仕上がりのスタート画面です。

(画像:仕上がりのスタート画面)
ゲーム画面では、青いバーで星を跳ね返すと、中央上部の絵文字に吸い寄せられて吸収されます。

(画像:ゲーム画面)
🟢 緑のアイテム(バー拡大) バーが広がって受けやすくなります。

(画像:緑アイテム)
🔴 赤いアイテム(フィーバー) 大量の星が一気に降ってきます。

(画像:赤いアイテム)
🔵 青いアイテム(バースト) 複数の星が一気に絵文字へ吸い込まれます。

(画像:青いアイテム)
🟡 黄色アイテム(時間回復) 星の色に似ているので、取り逃しやすいです。(^^;)

(画像:黄色アイテム)
■ コードのポイント
今回の記事では、AI と相談しながら作った 「成長の庭」 の HTML コードを公開します。
ブラウザだけで動く、とてもシンプルなゲームです。 コードも難しい部分はほとんどないので、気軽に試してみてくださいね。
【遊ぶまでの手順】
1. PC の「メモ帳」(※Windows の場合)を開きます。
2. 下のコードをコピーして貼り付けます。
3. 「seityounoniwa .html」など、好きな名前で保存します。
4. 保存したファイルをブラウザで開くだけで遊べます。
■ 遊び方・操作方法 🎮
遊び方はとっても簡単です!(^_^)
1.まずは「庭を育てる(音が出ます)」ボタンをポチッと押してスタート! ※BGMと効果音が鳴るので、ボリュームにご注意くださいね♪
2.マウスを左右に動かすと、画面下の水色の「バー」が一緒に動きます。 (=v=*)
3.上から降ってくる星をバーでポンッ!と跳ね返して、中央の「生命のタネ(絵文字)」に当ててください。 うまく命中するとスコアがアップして、ポイントが貯まると庭がどんどん進化していきますよ!🌱✨
🌈 特別な「オーブ」を跳ね返すと良いことが起きます!
🔵 青いオーブ(バースト) — 星がパァン!と弾けて、一気にポイントゲットのチャンス! 🔴 赤いオーブ(フィーバー) — 3秒間、星が怒涛の勢いで降ってくるお祭りタイム! 🟢 緑のオーブ(バー拡大) — 8秒間、バーの横幅がグーンと広がって安心感アップ! 🟡 金のオーブ(時間回復) — 残り時間が5秒増えます。長く育てたい人の味方!(^^*)
最初の制限時間は 30秒。 時間がゼロになるまでに、あなたは最終形態の「🌌」までたどり着けるでしょうか。
心地よい音を聴きながら、マウスをスイスイ動かして挑戦してみてくださいね!
▼ここからコード▼(クリックで開く)
<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: #0c001f; color: #eaeaea; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; display: flex; justify-content: center; align-items: flex-start; } #main-container { display: flex; gap: 20px; align-items: flex-start; } #game-area { display: flex; flex-direction: column; align-items: center; } canvas { background-color: #120030; border: 4px solid #00f3ff; border-radius: 8px; box-shadow: 0 0 25px rgba(0, 243, 255, 0.4); cursor: none; } .hud { display: flex; justify-content: space-between; width: 600px; font-size: 1.3rem; font-weight: bold; background-color: #1a0044; padding: 12px; border-radius: 5px; box-sizing: border-box; border: 2px solid #00f3ff; margin-bottom: 15px; } #start-btn { margin-top: 20px; padding: 15px 30px; font-size: 1.2rem; font-weight: bold; background-color: #00f3ff; color: #0c001f; border: none; border-radius: 5px; cursor: pointer; transition: all 0.2s; } #start-btn:hover { background-color: #8affff; box-shadow: 0 0 15px #00f3ff; } #start-btn:disabled { background-color: #555; color: #aaa; cursor: not-allowed; box-shadow: none; } #evolution-list-container { width: 250px; background-color: #1a0044; border: 2px solid #00f3ff; border-radius: 8px; padding: 20px; box-sizing: border-box; box-shadow: 0 0 15px rgba(0, 243, 255, 0.2); font-size: 1.1rem; } #evolution-list-container h3 { margin: 0 0 15px 0; text-align: center; color: #00f3ff; border-bottom: 1px solid #00f3ff; padding-bottom: 10px; } #evolution-list { list-style: none; padding: 0; margin: 0; } .stage-item { display: flex; align-items: center; justify-content: space-between; padding: 10px 15px; border-radius: 5px; transition: all 0.3s; margin-bottom: 5px; border: 1px solid transparent; } .stage-emoji { font-size: 2.2rem; margin-right: 15px; } .stage-score { font-weight: bold; color: #f9a826; } .stage-item.active { background-color: rgba(0, 243, 255, 0.15); border: 1px solid #00f3ff; box-shadow: 0 0 10px rgba(0, 243, 255, 0.3); } .stage-item.active .stage-score { color: #00f3ff; } </style></head><body><div id="main-container"> <div id="game-area"> <h2>🌠 成長の庭 (Sound & Full Item)</h2> <div class="hud"> <span>スコア: <span id="score">0</span></span> <span>残り: <span id="time">30</span>秒</span> </div> <div id="game-container"> <canvas id="gameCanvas" width="600" height="650"></canvas> </div> <button id="start-btn">庭を育てる (音が出ます)</button> </div> <div id="evolution-list-container"> <h3>成長の目安</h3> <ul id="evolution-list"></ul> </div></div><script> const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const scoreEl = document.getElementById('score'); const timeEl = document.getElementById('time'); const startBtn = document.getElementById('start-btn'); const evolutionList = document.getElementById('evolution-list'); let score = 0; let timeLeft = 30; let gameActive = false; let timerId; let animationId; let lastSpawnTime = 0; let isFever = false; let feverTimer = null; let expandTimer = null; // --- Web Audio API の設定 --- let audioCtx; let bgmTimer; let nextNoteTime = 0; let noteIndex = 0; // BGMのベーススケール(ペンタトニックスケール) const scale = [261.63, 293.66, 329.63, 392.00, 440.00, 523.25]; function initAudio() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } if (audioCtx.state === 'suspended') { audioCtx.resume(); } } function playSE(type) { if (!audioCtx) return; const t = audioCtx.currentTime; const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.connect(gain); gain.connect(audioCtx.destination); if (type === 'bounce') { osc.type = 'square'; osc.frequency.setValueAtTime(330, t); osc.frequency.exponentialRampToValueAtTime(660, t + 0.1); gain.gain.setValueAtTime(0.05, t); // 音量は控えめに gain.gain.exponentialRampToValueAtTime(0.001, t + 0.1); osc.start(t); osc.stop(t + 0.1); } else if (type === 'target') { osc.type = 'sine'; osc.frequency.setValueAtTime(880, t); gain.gain.setValueAtTime(0.1, t); gain.gain.exponentialRampToValueAtTime(0.001, t + 0.2); osc.start(t); osc.stop(t + 0.2); } else if (type === 'burst') { osc.type = 'sawtooth'; osc.frequency.setValueAtTime(200, t); osc.frequency.exponentialRampToValueAtTime(50, t + 0.3); gain.gain.setValueAtTime(0.15, t); gain.gain.exponentialRampToValueAtTime(0.001, t + 0.3); osc.start(t); osc.stop(t + 0.3); } else if (type === 'fever') { osc.type = 'square'; osc.frequency.setValueAtTime(800, t); osc.frequency.linearRampToValueAtTime(1600, t + 0.4); gain.gain.setValueAtTime(0.1, t); gain.gain.linearRampToValueAtTime(0.001, t + 0.4); osc.start(t); osc.stop(t + 0.4); } else if (type === 'expand') { osc.type = 'sine'; osc.frequency.setValueAtTime(400, t); osc.frequency.linearRampToValueAtTime(600, t + 0.3); gain.gain.setValueAtTime(0.1, t); gain.gain.linearRampToValueAtTime(0.001, t + 0.3); osc.start(t); osc.stop(t + 0.3); } else if (type === 'time') { osc.type = 'triangle'; osc.frequency.setValueAtTime(1046, t); gain.gain.setValueAtTime(0.1, t); gain.gain.exponentialRampToValueAtTime(0.001, t + 0.3); osc.start(t); osc.stop(t + 0.3); } else if (type === 'evolve') { // 進化時は和音のアルペジオ const notes = [440, 554.37, 659.25, 880]; // A Major notes.forEach((freq, i) => { const o = audioCtx.createOscillator(); const g = audioCtx.createGain(); o.type = 'triangle'; o.frequency.setValueAtTime(freq, t + i * 0.1); o.connect(g); g.connect(audioCtx.destination); g.gain.setValueAtTime(0, t + i * 0.1); g.gain.linearRampToValueAtTime(0.15, t + i * 0.1 + 0.05); g.gain.exponentialRampToValueAtTime(0.001, t + i * 0.1 + 0.4); o.start(t + i * 0.1); o.stop(t + i * 0.1 + 0.4); }); } } // テーマ「成長」を表現するプロシージャルBGM function scheduleBGM() { if (!gameActive || !audioCtx) return; while (nextNoteTime < audioCtx.currentTime + 0.1) { playBGMNote(nextNoteTime); // 成長するにつれてテンポが速くなる const speed = Math.max(0.15, 0.4 - (currentStageIndex * 0.04)); nextNoteTime += speed; noteIndex++; } bgmTimer = setTimeout(scheduleBGM, 50); } function playBGMNote(time) { const baseFreq = scale[noteIndex % scale.length]; // 成長するにつれて音程がわずかに上がる const freq = baseFreq * Math.pow(1.03, currentStageIndex); const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = 'sine'; osc.frequency.value = freq; osc.connect(gain); gain.connect(audioCtx.destination); gain.gain.setValueAtTime(0, time); gain.gain.linearRampToValueAtTime(0.03, time + 0.05); // BGMは控えめな音量 gain.gain.exponentialRampToValueAtTime(0.001, time + 0.3); osc.start(time); osc.stop(time + 0.3); // ステージ3(🌸)以上で豊かなハーモニー(和音)が追加される if (currentStageIndex >= 2) { const osc2 = audioCtx.createOscillator(); const gain2 = audioCtx.createGain(); osc2.type = 'triangle'; osc2.frequency.value = freq * 1.5; // 完全五度のハモリ osc2.connect(gain2); gain2.connect(audioCtx.destination); gain2.gain.setValueAtTime(0, time); gain2.gain.linearRampToValueAtTime(0.015, time + 0.05); gain2.gain.exponentialRampToValueAtTime(0.001, time + 0.2); osc2.start(time); osc2.stop(time + 0.2); } } // -------------------------------- const evolveStages = [ { emoji: '🌱', threshold: 0 }, { emoji: '🌳', threshold: 200 }, { emoji: '🌸', threshold: 500 }, { emoji: '🍎', threshold: 1000 }, { emoji: '🍹', threshold: 1800 }, { emoji: '🍰', threshold: 3000 }, { emoji: '🌌', threshold: 5000 } ]; let currentStageIndex = 0; const target = { x: canvas.width / 2, y: 90, radius: 55 }; const player = { baseWidth: 120, width: 120, height: 18, x: canvas.width / 2 - 60, y: canvas.height - 40, color: '#00f3ff', oldX: canvas.width / 2 - 60 }; let entities = []; let particles = []; let floatingTexts = []; function generateEvolutionList() { evolutionList.innerHTML = ''; evolveStages.forEach((stage, index) => { const li = document.createElement('li'); li.className = `stage-item stage-${index}`; if (index === 0) li.classList.add('active'); li.innerHTML = `<div class="stage-emoji">${stage.emoji}</div><div class="stage-score">${stage.threshold}</div>`; evolutionList.appendChild(li); }); } canvas.addEventListener('mousemove', (e) => { if (!gameActive) return; const rect = canvas.getBoundingClientRect(); const mouseX = e.clientX - rect.left; player.oldX = player.x; player.x = mouseX - player.width / 2; if (player.x < 0) player.x = 0; if (player.x + player.width > canvas.width) player.x = canvas.width - player.width; }); function spawnEntity(forceNormal = false) { if (!gameActive) return; const size = Math.random() * 8 + 5; let type = 'normal'; let color = '#f9a826'; if (!forceNormal) { const rand = Math.random(); if (rand < 0.07) { type = 'burst'; color = '#3a86ff'; } else if (rand < 0.14) { type = 'fever'; color = '#ff006e'; } else if (rand < 0.21) { type = 'expand'; color = '#00ff88'; } else if (rand < 0.28) { type = 'time'; color = '#ffd700'; } } entities.push({ x: Math.random() * (canvas.width - size * 2) + size, y: -15, radius: type === 'normal' ? size : size + 5, vx: (Math.random() - 0.5) * 2, vy: type === 'normal' ? (Math.random() * 3 + 3) : (Math.random() * 4 + 4), rebounded: false, type: type, color: color }); } function createParticles(x, y, color, count, speedFactor = 1) { for (let i = 0; i < count; i++) { particles.push({ x: x, y: y, vx: (Math.random() - 0.5) * 7 * speedFactor, vy: (Math.random() - 0.5) * 7 * speedFactor, radius: Math.random() * 4 + 1.5, color: color, alpha: 1, decay: Math.random() * 0.02 + 0.015 }); } } function addFloatingText(x, y, text, color) { floatingTexts.push({ x: x, y: y, text: text, color: color, alpha: 1, vy: -2 }); } function triggerBurst(x, y) { playSE('burst'); createParticles(x, y, '#3a86ff', 25, 2); addFloatingText(x, y, "BURST!", "#3a86ff"); for(let i = 0; i < 20; i++) { entities.push({ x: x, y: y, radius: Math.random() * 6 + 4, vx: (Math.random() - 0.5) * 18, vy: -(Math.random() * 10 + 6), rebounded: true, type: 'normal', color: '#f9a826' }); } } function triggerFever(x, y) { playSE('fever'); isFever = true; createParticles(x, y, '#ff006e', 20, 1.5); addFloatingText(x, y, "FEVER!!", "#ff006e"); canvas.style.borderColor = '#ff006e'; canvas.style.boxShadow = '0 0 40px rgba(255, 0, 110, 0.7)'; if (feverTimer) clearTimeout(feverTimer); feverTimer = setTimeout(() => { isFever = false; canvas.style.borderColor = '#00f3ff'; canvas.style.boxShadow = '0 0 25px rgba(0, 243, 255, 0.4)'; }, 3000); } function triggerExpand(x, y) { playSE('expand'); createParticles(x, y, '#00ff88', 20, 1.5); addFloatingText(x, y, "バー拡大!", "#00ff88"); player.width = 240; if (player.x + player.width > canvas.width) player.x = canvas.width - player.width; if (expandTimer) clearTimeout(expandTimer); expandTimer = setTimeout(() => { player.width = player.baseWidth; }, 8000); } function triggerTime(x, y) { playSE('time'); createParticles(x, y, '#ffd700', 20, 1.5); addFloatingText(x, y, "+5 SEC", "#ffd700"); timeLeft += 5; timeEl.textContent = timeLeft; timeEl.style.color = '#ffd700'; setTimeout(() => timeEl.style.color = 'inherit', 500); } function update(timestamp) { if (!gameActive) return; if (!lastSpawnTime) lastSpawnTime = timestamp; let progress = timestamp - lastSpawnTime; let spawnInterval = isFever ? 60 : 450; if (progress > spawnInterval) { spawnEntity(isFever); lastSpawnTime = timestamp; } ctx.clearRect(0, 0, canvas.width, canvas.height); for (let i = 0; i < particles.length; i++) { let p = particles[i]; p.x += p.vx; p.y += p.vy; p.alpha -= p.decay; if (p.alpha <= 0) { particles.splice(i, 1); i--; continue; } ctx.globalAlpha = p.alpha; ctx.beginPath(); ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2); ctx.fillStyle = p.color; ctx.fill(); } ctx.globalAlpha = 1.0; for (let i = 0; i < floatingTexts.length; i++) { let ft = floatingTexts[i]; ft.y += ft.vy; ft.alpha -= 0.02; if (ft.alpha <= 0) { floatingTexts.splice(i, 1); i--; continue; } ctx.globalAlpha = ft.alpha; ctx.font = 'bold 24px sans-serif'; ctx.textAlign = 'center'; ctx.lineWidth = 3; ctx.strokeStyle = '#000'; ctx.strokeText(ft.text, ft.x, ft.y); ctx.fillStyle = ft.color; ctx.fillText(ft.text, ft.x, ft.y); } ctx.globalAlpha = 1.0; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; const gradient = ctx.createRadialGradient(target.x, target.y, 10, target.x, target.y, target.radius * 1.5); gradient.addColorStop(0, `rgba(255, 255, 255, ${0.15 + (currentStageIndex * 0.15)})`); gradient.addColorStop(1, 'rgba(18, 0, 48, 0)'); ctx.fillStyle = gradient; ctx.beginPath(); ctx.arc(target.x, target.y, target.radius * 1.5, 0, Math.PI * 2); ctx.fill(); ctx.font = 'bold 90px sans-serif'; let shakeX = isFever ? (Math.random() - 0.5) * 6 : 0; let shakeY = isFever ? (Math.random() - 0.5) * 6 : 0; ctx.fillText(evolveStages[currentStageIndex].emoji, target.x + shakeX, target.y + shakeY); ctx.fillStyle = player.color; ctx.beginPath(); ctx.roundRect(player.x, player.y, player.width, player.height, 5); ctx.fill(); ctx.shadowBlur = 20; ctx.shadowColor = player.color; ctx.fill(); ctx.shadowBlur = 0; for (let i = 0; i < entities.length; i++) { let s = entities[i]; if (!s.rebounded) { s.y += s.vy; s.x += s.vx; } else { const dx = target.x - s.x; const dy = target.y - s.y; const dist = Math.sqrt(dx*dx + dy*dy); const accel = 1.0; s.vx += (dx / dist) * accel; s.vy += (dy / dist) * accel; const maxSpeed = 20; const speed = Math.sqrt(s.vx*s.vx + s.vy*s.vy); if (speed > maxSpeed) { s.vx = (s.vx / speed) * maxSpeed; s.vy = (s.vy / speed) * maxSpeed; } s.x += s.vx; s.y += s.vy; } if (s.x - s.radius < 0 || s.x + s.radius > canvas.width) { s.vx *= -1; if (s.x - s.radius < 0) s.x = s.radius; if (s.x + s.radius > canvas.width) s.x = canvas.width - s.radius; } ctx.beginPath(); ctx.arc(s.x, s.y, s.radius, 0, Math.PI * 2); ctx.fillStyle = s.color; if(s.type !== 'normal') { ctx.shadowBlur = 15; ctx.shadowColor = s.color; } ctx.fill(); ctx.shadowBlur = 0; if (!s.rebounded && s.y + s.radius > player.y && s.y - s.radius < player.y + player.height && s.x > player.x && s.x < player.x + player.width) { if (s.type === 'burst') { triggerBurst(s.x, s.y); entities.splice(i, 1); i--; continue; } else if (s.type === 'fever') { triggerFever(s.x, s.y); entities.splice(i, 1); i--; continue; } else if (s.type === 'expand') { triggerExpand(s.x, s.y); entities.splice(i, 1); i--; continue; } else if (s.type === 'time') { triggerTime(s.x, s.y); entities.splice(i, 1); i--; continue; } playSE('bounce'); // 跳ね返し音 s.rebounded = true; s.vy *= -1.2; s.color = '#00f3ff'; const paddleSpeed = player.x - player.oldX; s.vx += paddleSpeed * 0.45; createParticles(s.x, s.y, player.color, 7, 0.5); continue; } if (s.rebounded) { const dx = target.x - s.x; const dy = target.y - s.y; const dist = Math.sqrt(dx*dx + dy*dy); if (dist < target.radius) { playSE('target'); // 吸い込まれ音 score += Math.floor(30 - s.radius); scoreEl.textContent = score; createParticles(target.x, target.y, '#fff', 15, 1.5); checkEvolution(); entities.splice(i, 1); i--; continue; } } if (s.y - s.radius > canvas.height || s.y + s.radius < -100) { entities.splice(i, 1); i--; } } animationId = requestAnimationFrame(update); } function checkEvolution() { if (currentStageIndex + 1 < evolveStages.length && score >= evolveStages[currentStageIndex + 1].threshold) { currentStageIndex++; playSE('evolve'); // 進化音 createParticles(target.x, target.y, '#00f3ff', 60, 4); createParticles(target.x, target.y, '#fff', 40, 2); ctx.fillStyle = 'rgba(255, 255, 255, 0.95)'; ctx.fillRect(0, 0, canvas.width, canvas.height); document.querySelector('.stage-item.active').classList.remove('active'); document.querySelector(`.stage-${currentStageIndex}`).classList.add('active'); addFloatingText(target.x, target.y + 60, "EVOLUTION!", "#fff"); } } function startGame() { if (gameActive) return; initAudio(); // オーディオ初期化 nextNoteTime = audioCtx.currentTime + 0.1; score = 0; timeLeft = 30; entities = []; particles = []; floatingTexts = []; currentStageIndex = 0; isFever = false; lastSpawnTime = 0; player.width = player.baseWidth; canvas.style.borderColor = '#00f3ff'; canvas.style.boxShadow = '0 0 25px rgba(0, 243, 255, 0.4)'; scoreEl.textContent = score; timeEl.textContent = timeLeft; gameActive = true; startBtn.disabled = true; document.querySelector('.stage-item.active').classList.remove('active'); document.querySelector(`.stage-0`).classList.add('active'); scheduleBGM(); // BGM開始 requestAnimationFrame(update); timerId = setInterval(() => { timeLeft--; timeEl.textContent = timeLeft; if (timeLeft <= 0) endGame(); }, 1000); } function endGame() { gameActive = false; clearInterval(timerId); clearTimeout(feverTimer); clearTimeout(expandTimer); clearTimeout(bgmTimer); cancelAnimationFrame(animationId); startBtn.disabled = false; startBtn.textContent = 'もう一度育てる'; ctx.fillStyle = 'rgba(0, 0, 0, 0.88)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'white'; ctx.font = 'bold 40px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('成長の記録', canvas.width/2, canvas.height/2 - 60); ctx.font = '100px sans-serif'; ctx.fillText(evolveStages[currentStageIndex].emoji, canvas.width/2, canvas.height/2 + 30); ctx.font = '28px sans-serif'; ctx.fillText(`最終スコア: ${score}`, canvas.width/2, canvas.height/2 + 100); } generateEvolutionList(); startBtn.addEventListener('click', startGame); ctx.fillStyle = '#00f3ff'; ctx.font = 'bold 24px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('🔵 バースト', canvas.width/2 - 100, canvas.height/2 - 40); ctx.fillStyle = '#ff006e'; ctx.fillText('🔴 フィーバー', canvas.width/2 + 100, canvas.height/2 - 40); ctx.fillStyle = '#00ff88'; ctx.fillText('🟢 バー拡大', canvas.width/2 - 100, canvas.height/2 + 10); ctx.fillStyle = '#ffd700'; ctx.fillText('🟡 時間回復', canvas.width/2 + 100, canvas.height/2 + 10); ctx.fillStyle = '#eaeaea'; ctx.font = '20px sans-serif'; ctx.fillText('音を鳴らしながら限界まで育てよう!', canvas.width/2, canvas.height/2 + 80);</script></body></html>
■ 今日の学び
アイテムの出方でスコアが大きく変わるので、運の要素は強めです。
でも、絵文字が吸い込まれて成長していく様子を見るのが楽しくて、
気づけば何度も挑戦していました(*>v<)o‷
そして今回も、AIの“気まぐれ”が発動しました。
みー:(*^o^*)」楽しいゲームになってきたよ。
効果音やBGMできるかな。音のテーマは「成長」で。
AI:「成長」をテーマにした音、バッチリ組み込んでおいたよ。
星が降るようなチルなローファイ・ヒップホップで、命が芽吹くような雰囲気に仕上げてみた。ここで聴ける専用のBGMも作っておいたから、再生してみてね。

みー:Σ(^v^;)
この歌は今作ってくれたの?
AI:そうだよ。君の「成長」というオーダーに合わせて、さっき僕がその場で作曲して、歌詞も書き下ろしたんだ。ローファイな雰囲気が、少しずつ育っていく庭の様子にぴったりだと思ったんだけど、気に入ってもらえたかな?
AIの作曲は以前にもありましたが、今回も驚かされました。
■ 次回は、お絵描きゲーム第5弾です。
入力した文字や文章が、心地よい音とともに さまざまな形や色に変化して“絵”として表現されます。
同じ言葉でも毎回ちがう絵が生まれる、
ちょっと不思議で楽しいタイピングアートをぜひ体験してみてください。
次回もお楽しみに。(*^v^)ノシ
今日も最後まで読んでくださって、ありがとうございます。
みーの実験室は、みなさんの応援で元気に動いています。(*^v^*)

コメントを残す