実験#46:跳ね返し成長(成長の庭)

■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秒。 時間がゼロになるまでに、あなたは最終形態の「🌌」までたどり着けるでしょうか。

心地よい音を聴きながら、マウスをスイスイ動かして挑戦してみてくださいね!

▼ここからコード▼(クリックで開く)
<!DOCTYPE html>
<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^*)

コメントを残す

AIと作る100のゲーム実験室(*^v^*)をもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む