実験#86:虹とパーティクル(虹色リボン)

■86個目 「虹色リボン」

こんにちは、AIと作る100のゲーム実験室の みー です。(*^_^*)/

今回は、都会の夜を舞台にした、とっても幻想的なゲームを作ってみました。

主役は……
そう、新体操のようにひらひらと舞う「虹色のリボン」です!

影絵のように真っ黒な建物が並ぶ静かな夜の街。

その闇の中を、マウスに合わせて虹色のリボンがスーッと滑るように動き、
煌めく光の軌跡を描いていきます。

今回こだわったのは、その “動かしていて気持ちいい” という感覚。(=v=*)

静かな都会の夜に、色と光がふわっと広がる瞬間を作りたかったんです。

忙しないイメージのある都会も、
影絵にしてみると意外と静かで、
光がとても綺麗に見えるんですよね。(^.^)

そんな静寂の中で、虹色のリボンを自由に操りながら、
夜空に煌めく星たちを集める……。

まるで、
自分が都会を彩る小さな魔法使いになったような気分になれるゲームです。

マウスを自由に動かして、自分だけの虹を描いてみてくださいね。
どんな煌めきが生まれるのか、楽しみにしています。


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

● ゲーム仕様の相談からスタート

みー:(^v^)
今日もゲームを作ります。HTML形式で動かせるゲーム。

新体操のリボンのように虹を自由に動かしたいです。

背景は影絵風の都会で、虹が映える感じにします。

AI: 新体操のリボンのような虹が、
影絵風の夜景に美しく映えるゲーム画面をイメージして作成しました。

このビジュアルを基に、HTML5 Canvasで虹の軌跡を描き、
マウス操作で動かせるようにすると雰囲気が出そうですね。

みー:(^v^*)
まずはリボンの動きの再現から進めましょう。

AI: リボンの動きから作るのは大正解。
一番「動かして気持ちいい」部分だからね。

マウスに追従して滑らかに動くリボンと、
影絵風の夜景を組んでみたよ。

みー:(*」 ̄v ̄)ノ∼*
リボンは持ち手の棒の先端についている感じにしましょう。
クリックしたときに星屑パーティクルが出るようにしたいです。

AI: 持ち手の棒を追加して、
クリックで星屑が弾ける演出だね。
一気に華やかさが増す良いアイデアだよ。

みー:(^v^)
クリックを押し続けると、
パーティクルがどんどん激しくなるようにできますか?

AI: 押しっぱなしでエネルギーがチャージされる演出は気持ちいいよね。
夜景にも映えるし、魔法の杖みたいで楽しいと思う。

みー:(^v^)
少しゲーム性を足したいので、
ランダムに現れるポイントを追いかける感じにしましょう。

ポイントにマウスが重なると次の位置に現れるように。

AI: ついにゲーム性の追加だね。
シンプルだけど、リボンの動きと組み合わせると中毒性が出る良いアイデア。

虹色のリボンで光のポイントを集める姿は、
夜空を彩る魔法使いみたいだよ。


■ 実際の動作

こちらが、仕上がったゲーム画面です。

画面の中に次々と現れる“ひかりの点”を、
虹色のリボンで追いかけてつないでいきます。

夜の街に小さな光が増えていく感じが、とても気持ちいいんです。

(仕上がりのゲーム画面)



マウスをクリックすると、星屑のパーティクルがふわっと弾けます。

(仕上がりのゲーム画面)



クリックを押しっぱなしにすると、
小さなパーティクルが連続して出続けて、
まるで魔法の杖を振っているような感覚になります。

(仕上がりのゲーム画面)


■ コードのポイント

今回の記事では、
AI と相談しながら作った 「虹色リボン」 の HTML コードを公開します。

このコードをそのまま保存するだけで、
あなたの PC でもすぐに遊べます。

ブラウザだけで動く、とてもシンプルなゲームなので、
「コードは初めて…」という方でも気軽に試してみてくださいね。

▼ 遊ぶまでの手順(むずかしさ:やさしい)
① テキストエディタを開く

Windows の場合は「メモ帳」、
Mac の場合は「テキストエディット」を使います。
(※検索バーで「メモ帳」と入力するとすぐ出てきます)

② 下のコードをコピーして貼り付けます。
③ 「niji.html」など、好きな名前で保存します。

(※拡張子は .html にしてください)

④ 保存したファイルをダブルクリックすると、
ブラウザでゲームが起動します。

■ 操作方法

世界観を楽しんでいただいたところで、
さっそくこの「虹色リボン」の操り方をご紹介しますね。

操作はとってもシンプルですが、
動かしているだけで癒やされる、
不思議な感覚になれると思います。(^^*)

基本の操作は、あなたの相棒「マウス」だけです!

🎀 虹色の軌跡を描こう

画面の中でマウスを動かすと、
それがそのままリボンの持ち手(スティック)になります。

あなたの手の動きに合わせて、
虹色のリボンがツツーイと滑らかに夜空を舞います。(^.^)

円を描いたり、波を打たせたり……。
自由に動かして、あなただけの光のアートを楽しんでみてくださいね。(=v=*)

✨ 魔法の星屑を散りばめて

そして、マウスをクリックすると、さらに素敵な魔法が使えます!

クリックの仕方で、
星屑(パーティクル)の出方が変わるんですよ。(’▽’)/.・*”

ワンクリックで「キラキラ爆発」
マウスをポンッと一度だけクリックして指を離すと、
スティックの先から大きめの光の欠片が飛び出し、
空中で細かくキラキラと弾けます。

まるで小さな花火みたいで、とっても綺麗ですよ。(^^*)

長押しで「じわじわチャージ」
マウスを押しっぱなしにすると、星屑がじわじわと出続けます。

押す時間が長いほど、
星屑の量も勢いも粒の大きさもどんどん激しくなっていきます!(>ω<)

「ここぞ!」というときにパワーを溜めて、
画面いっぱいを光で満たしてみてくださいね。

🌟 夜空のポイントを集めよう

ゲームの目的は、夜空にランダムに現れる
「光のポイント(ターゲット)」を集めることです。

リボンの動きを楽しみながら、
マウスポインタをその光のポイントに重ねてみてください。(^^)

うまく重なると、ポイントが虹色に弾けてスコアがアップ!
そしてまた、次の場所に新しいポイントが現れます。

静かな都会の夜空で、虹色のリボンを操り、光の粒を集める……。

操作に慣れてくると、まるで流れるような演技をしているような、
心地よいリズムが生まれてくるはずです。(=v=*)

ぜひ、あなたらしい虹色の魔法で、
夜空をいっぱいの光で満たしてあげてくださいね。(^_^)


“コードは長いので折りたたんでいます。必要な方は開いてご覧ください。

▼ここからコード▼(クリックで開く)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Rainbow Ribbon in the City - Step 8 (Target Collection Game)</title>
<style>
body { margin: 0; overflow: hidden; background-color: #000; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;
// --- 影絵の街並みデータ ---
const buildings = [];
function createBuildings() {
buildings.length = 0;
let currentX = 0;
while(currentX < width) {
let bw = 30 + Math.random() * 60;
let bh = 50 + Math.random() * (height * 0.4);
buildings.push({ x: currentX, w: bw, h: bh });
currentX += bw + (Math.random() * 10 - 5);
}
}
// --- リボンの関節データ ---
const points = [];
const numPoints = 60;
for(let i = 0; i < numPoints; i++) {
points.push({ x: width / 2, y: height / 2 });
}
// --- パーティクルデータ ---
const particles = [];
// --- ★追加:ゲーム性のための変数 ---
let score = 0;
// ターゲット(光のポイント)のデータ
const target = { x: 0, y: 0, r: 25, active: true }; // rは当たり判定の半径
// ターゲットをランダムな位置に再配置する関数
function resetTarget() {
// 画面の端すぎないようにマージンを設ける
const margin = target.r + 50;
target.x = margin + Math.random() * (width - margin * 2);
target.y = margin + Math.random() * (height - margin * 2);
}
// ヒット(収集)時の演出パーティクル
function triggerHitExplosion(x, y) {
for(let i = 0; i < 15; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 3 + Math.random() * 5;
const hue = (i * 20) % 360; // ヒット時は虹色に弾けさせる
particles.push({
x: x,
y: y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
life: 0.8,
decay: 0.02 + Math.random() * 0.02,
size: Math.random() * 3 + 2,
hue: hue,
type: 'hit' // 新しいタイプ
});
}
}
// 初期化処理
function initGame() {
createBuildings();
resetTarget();
score = 0;
}
initGame();
// マウス座標
let mouse = { x: width / 2, y: height / 2 };
window.addEventListener('mousemove', (e) => {
mouse.x = e.clientX;
mouse.y = e.clientY;
});
// --- マウスクリックの状態とチャージ量を管理 ---
let isMouseDown = false;
let clickCharge = 0;
const stickLength = 60;
const stickAngle = -Math.PI / 4;
window.addEventListener('mousedown', () => {
isMouseDown = true;
});
window.addEventListener('mouseup', () => {
isMouseDown = false;
const tipX = mouse.x + Math.cos(stickAngle) * stickLength;
const tipY = mouse.y + Math.sin(stickAngle) * stickLength;
// チャージが溜まっていない(短押し)なら、多段階爆発
if (clickCharge < 5) {
triggerSingleClickExplosion(tipX, tipY);
}
clickCharge = 0;
});
window.addEventListener('mouseleave', () => {
isMouseDown = false;
clickCharge = 0;
});
window.addEventListener('resize', () => {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
initGame(); // リサイズ時にターゲットも再配置
});
// ワンクリック用の強力な多段階爆発
function triggerSingleClickExplosion(x, y) {
for(let i = 0; i < 20; i++) {
particles.push({
x: x,
y: y,
vx: (Math.random() - 0.5) * 14,
vy: (Math.random() - 0.5) * 16 - 6,
life: 1.0,
decay: 0.008 + Math.random() * 0.01,
size: Math.random() * 4 + 4,
hue: 45,
type: 'parent',
splitTime: 0.3 + Math.random() * 0.2
});
}
}
function animate() {
// 1. 背景
const grad = ctx.createLinearGradient(0, 0, 0, height);
grad.addColorStop(0, '#0a0a2a');
grad.addColorStop(1, '#2a1a4a');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, width, height);
// 2. 影絵の街並み
ctx.fillStyle = '#050505';
buildings.forEach(b => {
ctx.fillRect(b.x, height - b.h, b.w, b.h);
});
// --- ★追加:ターゲットのロジックと描画 ---
if (target.active) {
// 当たり判定:マウスポインタとターゲットの距離を計算
const dx = mouse.x - target.x;
const dy = mouse.y - target.y;
const distSq = dx * dx + dy * dy; // 計算軽量化のため距離の2乗で判定
if (distSq < (target.r * target.r)) {
// ヒット!
score++;
triggerHitExplosion(target.x, target.y);
resetTarget(); // 新しい位置へ移動
}
// ターゲットの描画(煌めく虹色の光球)
ctx.shadowBlur = 20;
ctx.shadowColor = `hsla(${Date.now() * 0.1 % 360}, 100%, 70%, 0.8)`; // 影で発光表現
// 中心が白く、外側が虹色のグラデーション
const gradient = ctx.createRadialGradient(target.x, target.y, target.r * 0.2, target.x, target.y, target.r);
gradient.addColorStop(0, 'white');
gradient.addColorStop(0.3, `hsl(${Date.now() * 0.1 % 360}, 100%, 85%)`);
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)'); // 外側は透明に
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(target.x, target.y, target.r, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0; // 影をリセット
}
// --- ★追加:スコアの描画 ---
ctx.fillStyle = 'white';
ctx.font = 'bold 28px Arial';
ctx.fillText('SCORE: ' + score, 30, 50);
// 3. 持ち手の棒(スティック)
const tipX = mouse.x + Math.cos(stickAngle) * stickLength;
const tipY = mouse.y + Math.sin(stickAngle) * stickLength;
ctx.beginPath();
ctx.moveTo(mouse.x, mouse.y);
ctx.lineTo(tipX, tipY);
ctx.strokeStyle = '#aaaaaa';
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.stroke();
// 長押し処理
if (isMouseDown) {
if (clickCharge < 30) {
clickCharge += 0.5;
}
const numNewParticles = Math.floor(1 + clickCharge / 8);
for(let i = 0; i < numNewParticles; i++) {
const spread = 4 + clickCharge * 0.3;
particles.push({
x: tipX,
y: tipY,
vx: (Math.random() - 0.5) * spread,
vy: (Math.random() - 0.5) * spread - 1,
life: 1.0,
decay: 0.02 + Math.random() * 0.03,
size: Math.random() * 2 + 1.0 + (clickCharge * 0.05),
hue: 40 + Math.random() * 20,
type: 'normal'
});
}
}
// 4. リボンの動きを計算
points[0].x += (tipX - points[0].x) * 0.4;
points[0].y += (tipY - points[0].y) * 0.4;
for(let i = 1; i < numPoints; i++) {
points[i].x += (points[i-1].x - points[i].x) * 0.45;
points[i].y += (points[i-1].y - points[i].y) * 0.45;
}
// 5. リボンの描画
ctx.lineJoin = 'round';
for(let i = 1; i < numPoints; i++) {
ctx.beginPath();
ctx.moveTo(points[i-1].x, points[i-1].y);
ctx.lineTo(points[i].x, points[i].y);
const hue = (i * 5 - Date.now() * 0.1) % 360;
ctx.strokeStyle = `hsl(${hue}, 100%, 60%)`;
ctx.lineWidth = Math.max(1, 25 - (i / numPoints) * 25);
ctx.stroke();
}
// 6. 星屑パーティクルの計算と描画
for (let i = particles.length - 1; i >= 0; i--) {
let p = particles[i];
p.x += p.vx;
p.y += p.vy;
// ヒット演出パーティクルは重力を弱く、ふわっと煌めかせる
if (p.type === 'hit') {
p.vy += 0.1; // 弱い重力
p.vx *= 0.98; // 少ない空気抵抗
p.vy *= 0.98;
} else {
p.vy += 0.35; // 通常の重力
p.vx *= 0.96; // 空気の抵抗
p.vy *= 0.96;
}
p.life -= p.decay;
if (p.type === 'parent' && p.life <= p.splitTime && p.life > 0) {
particles.splice(i, 1);
const numChildren = Math.floor(10 + Math.random() * 5);
for(let j = 0; j < numChildren; j++) {
const angle = Math.random() * Math.PI * 2;
const speed = 1.5 + Math.random() * 3;
particles.push({
x: p.x,
y: p.y,
vx: p.vx * 0.3 + Math.cos(angle) * speed,
vy: p.vy * 0.3 + Math.sin(angle) * speed,
life: 0.3 + Math.random() * 0.1,
decay: 0.02 + Math.random() * 0.03,
size: Math.random() * 2 + 2.5,
hue: p.hue + (Math.random() - 0.5) * 10,
type: 'child'
});
}
} else if (p.life <= 0) {
particles.splice(i, 1);
} else {
// 描画。ヒット演出は明るく
if (p.type === 'hit') {
ctx.fillStyle = `hsla(${p.hue}, 100%, 80%, ${p.life})`; // 明るい虹色
} else {
ctx.fillStyle = `hsla(${p.hue}, 100%, ${p.type === 'child' ? 90 : 70}%, ${p.life})`;
}
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
}
}
requestAnimationFrame(animate);
}
animate();
</script>
</body>
</html>


■ 今日の学び

(^^*)さて、今回のゲーム作りを通して感じた「学び」や、
込めた想いについて少しお話ししますね。

ゲームを作るたびに、毎回ちょっとした気づきがあって、
それがまた次のアイデアにつながっていきます。

今回のゲームは、「虹を自由自在に動かせたらいいな」という、
ふとした空想から始まりました。(^_^)

皆さんも、空に虹を見つけると、なんだか嬉しくなりませんか?
実際の虹って、彩雲やハロ、ダブルレインボーなど、
いろんな姿で現れてくれます。

その中でも、以前見た“色がくっきりした見事な虹”が、
まるで空を舞う新体操のリボンのように見えたことがあって……。

今回のゲームは、その時の記憶がそっと背中を押してくれました。(0゚*u0゚)

虹って、その時、その場所、その瞬間にしか見られない、とても特別なもの。

だからこそ、見つけた瞬間に胸がふわっと温かくなるんですよね。

今回のゲームでは、マウス操作によって、その瞬間にしか生まれない
「あなただけの虹の軌跡」を描けるようにしました。

クリックで弾ける星屑も、その場限りの煌めきです。

「その時、その場所だけしか見られない、特別な瞬間」

デジタルの中ではありますが、そんな一期一会の感覚を、
リボンの動きと光の演出で少しでも表現できていたら嬉しいです。(`・ω・´)ゞ

ぜひ、自分だけの特別な虹を、都会の夜空に描いてみてくださいね。


画像は、夕方の薄暗い時間にふと現れた虹です。

一日の終わりに、こんな優しい色に出会えると、
それだけで少し救われた気持ちになります。

(画像:夕焼け虹)

■ 次回は、癒され涼しいゲームです

不思議なドーナッツ状の小川の真ん中の島では、
狸が気持ちよさそうにお昼寝をしています。

川の流れに、いろんなものをそっと浮かべて流していく……
そんな、見ているだけでも涼しくなる癒しゲームです。

次回もぜひ遊びに来てくださいね。(*^v^)ノシ

次の実験はこちら  
  (流れるプール、浮かべて流す自然)
「自然を浮かべる小川 – お昼寝狸」



 ✨今日も最後まで読んでくださって、ありがとうございます。
みーの実験室は、みなさんの応援で元気に動いています。(*^v^*)

~このブログについて~

AIと一緒に遊び、
100の実験でゲームを育てるブログ

🌈初めての方へ
🌱このブログについて
🌳実験一覧
🍃プライバシーポリシー
🐣未記事ゲーム展示室


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

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

続きを読む