実験#112:画像からのアート再現(レトロ黒板アートメーカー)

■112個目 「レトロ黒板アートメーカー

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

今回は、放課後の教室にタイムスリップしたようなツールを作ってみました。

日直の目を気にせず、思う存分お絵かきができちゃう
「レトロ黒板アートメーカー」です!

深い緑色の黒板いっぱいに、
白・赤・黄色の定番チョークで自由に絵を描けます。

極細の線から、チョークの腹を使った太塗りまで……
本物みたいな「粉っぽさ」や「かすれ具合」にこだわりました。(=v=*)

そして今回の一番の魔法は…… なんと、お手持ちの写真を
黒板アート風に変換してポンッと貼り付けられる機能 です!(≧▽≦)

例えば、おやつの芋けんぴの写真や、
おうちでのんびりしている相棒のカメさんの画像を読み込むと、

あっという間に、チョークで丹念に描いたような
素敵な黒板アートに早変わりします。(´ω`)

貼り付けたあとは、レトロな黒板消しや「指」を使って、
ちょいちょいっと粉を舞わせながら微調整することもできます。

誰もがあっという間に黒板アートの巨匠になれる、
不思議な教室へようこそ!(^^)

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

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

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

チョークで黒板にお絵描きするゲーム。

黒板アートぐらい作れるイメージでお願い。

チョークの色は白・赤・青・黄色・緑など定番色で、
粉っぽい質感は大事。

角を使った尖り具合や、
腹で描く太塗りの書き心地も調整できるようにしたいです。

AI: 今回は「チョークの質感」と
「書き心地(角・腹)」の再現がキモですね。

単純な線ではなく、マウスの軌跡に沿って
“微小な粒子を散布する”アルゴリズムで、
黒板特有の粉っぽさや擦れ具合を表現しています。

● 黒板消しの粉と、日直の縦書き

みー:(^v^) 黒板消しはレトロな感じで、
消すときに粉が舞う雰囲気できるかな。

あと、右端に「月 日 日直:」の縦書きを入れて、
学校の黒板の雰囲気を出したいです。

AI: 学校の黒板、あのノスタルジックな雰囲気ですね!最高です。

  • 日直の縦書き表記
    キャンバス右端に白線で区切りを入れ、消しゴムでは消えないように
    「月 日」「日直:」を縦書き風に配置しました。
  • 粉が舞うエフェクト
    黒板消しや「すべて消す」ボタンを使ったときに、
    チョークの粉がふわっと舞い上がり、
    ゆっくり落ちていくアニメーションを追加しました。

こだわりポイント

  • 空気抵抗を感じる粉の舞い方
    粉が少し上に舞ってから重力で落ちるように調整し、
    レトロでエモい空気感を再現。
  • レイヤー構造の採用
    「月・日・日直」の文字は別レイヤーにして、
    黒板消しで擦っても消えないようにしました。

● 黒板の枠と余白、日直の改行問題

みー:(^_^) 絵としてのまとまりが欲しいので、
黒板の枠と少しの壁の余白が欲しいです。

それと、日直のところが改行されておかしくなってたよ。

AI: 壁の余白と木の枠をつけることで、
ぐっと“教室の黒板”らしい雰囲気になりますね。

日直の改行問題も、CSSでしっかり1行に収まるように修正しました。

● チョークカーソルと太さの種類を増やす

みー:( ̄v ̄) チョークも描く時に表示できるかな。

色を変えると持っているチョークの色も変わる感じで。

細い・太いの2種類だけなので、極細と中太も増やしたいです。

AI: さらに見栄えのするリッチな作品になりそうですね!

  • 選んだ色に合わせて、チョークのカーソルがリアルタイムで色変化
  • 太さは 極細・細・中太・太 の4段階に拡張
  • チョークカーソルは外部画像を使わず、JavaScriptでSVGを動的生成

● 画像読み込み機能の追加

みー:(^o^) このお絵描きゲームに、
画像から絵を表示する機能ってつけられるかな。

AI: もちろん追加できるぜ。
写真やイラストを黒板に貼り付けるようなイメージだ。

上からチョークで落書きしたり、
黒板消しでゴシゴシこすって緑色に塗りつぶして“消す”こともできる。
なかなか面白い効果が出るはずだ。


■ 実際の動作

こちらが、仕上がりのゲーム画面です。
まずは黒板に自由に描く練習から。

マウスで描くので毎回ちょっと難しいのですが、
その分とても楽しいです。(^v^*)

(画像:ゲーム画面⓵)

黒板の粉っぽい質感がしっかり出ていて、
描いているだけで放課後の教室にいるような気分になります。


貼り付けた画像キャラクター以外の部分は、
黒板消しで自由に消して描き直すことができます。

画像の上からチョークで落書きしたり、
周りを塗りつぶして雰囲気を変えたりするのも楽しいです。

(画像:ゲーム画面⓶)

黒板の上で写真とチョークが混ざり合う感じが、とても新鮮です。


少し夢中になって、もう一枚描いてみました。
黒板の粉のかすれ具合が、どんな絵でも“レトロな味”にしてくれます。

(画像:ゲーム画面⓷)

黒板アートは、描けば描くほど味が出てくるのが楽しいですね。(^_^


■ コードのポイント

今回の記事では、
AI と相談しながら作ったレトロ黒板アートメーカー の HTML コードを公開します。

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

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

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

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

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

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

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

■ 遊び方・操作方法

読者さんが迷わず、ワクワクしながら遊べるように工夫しています。

🎨 色と太さを選ぼう

チョークの色は、白・赤・青・黄色・緑の定番5色です。
太さは「極細・細・中太・太」の4段階!

チョークの角を使ってカリカリ細く書いたり、
腹を使ってザザッと広く塗ったり……
お好みの質感を使い分けてみてくださいね。(=v=*)

🧽 こだわりの消しツール

失敗しても大丈夫! 「黒板消し」を選ぶと、
カーソルがレトロな黒板消しに変身します。

ゴシゴシすると、チョークの粉がフワッと舞い散りますよ〜。(≧▽≦)

「はみ出しちゃった!」という細かいところは、
「👆 指」ツールでちょいちょいっとこすって消すのがおすすめです。(´ω`)

📸 写真をチョークアートに変換!

今回の目玉機能です!

「画像を選ぶ」ボタンからお手持ちの写真やイラストを読み込むと、
自動でチョーク画風に変換されて黒板に貼り付きます。

思い出の写真が、あっという間に黒板アートになっちゃいます。(^^*)

✨ すべて消す

「全消去」ボタンを押すと、
大量の粉を舞わせながら黒板がまっさらにリセットされます。

右端に書いてある「日直」の文字や線は消えないようになっているので、
安心して何度でも描き直せます。(^^)

黒板の粉のかすれ具合や、チョークの質感を楽しみながら、
ぜひあなただけの素敵な黒板アートを作ってみてくださいね!



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

▼ここからコード▼(クリックで開く)
<!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;
background-color: #e4e0d7;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
overflow: hidden;
font-family: sans-serif;
user-select: none;
}
.board-frame {
background-color: #6d4c33;
padding: 15px;
border-radius: 8px;
box-shadow: 0 10px 25px rgba(0,0,0,0.3);
margin-bottom: 20px;
}
.board-wrapper {
position: relative;
width: 90vw;
height: 75vh;
background-color: #1a3c23;
box-shadow: inset 0 0 40px rgba(0,0,0,0.8);
overflow: hidden;
}
canvas {
position: absolute;
top: 0;
left: 0;
}
#particle-canvas {
pointer-events: none;
z-index: 10;
}
.board-text {
position: absolute;
top: 40px;
right: 40px;
color: rgba(255, 255, 255, 0.75);
font-family: "Yu Mincho", "MS PMincho", serif;
font-size: 32px;
writing-mode: vertical-rl;
pointer-events: none;
border-left: 2px solid rgba(255, 255, 255, 0.4);
padding-left: 25px;
height: calc(100% - 80px);
}
.text-line {
margin-left: 45px;
white-space: nowrap;
display: inline-block;
}
.space-for-num {
display: inline-block;
height: 3.5em;
}
.toolbar {
display: flex;
justify-content: center;
gap: 15px;
flex-wrap: wrap;
background: #333;
padding: 12px 20px;
border-radius: 8px;
box-shadow: 0 4px 10px rgba(0,0,0,0.3);
color: #fff;
z-index: 20;
}
.panel {
display: flex;
gap: 8px;
align-items: center;
border-right: 1px solid #555;
padding-right: 15px;
}
.panel:last-child {
border-right: none;
padding-right: 0;
}
button {
background: #555;
color: white;
border: 2px solid transparent;
padding: 8px 10px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: 0.2s;
font-size: 0.9em;
}
button:hover { background: #666; }
button.active { border-color: #fff; background: #444; }
.color-btn { width: 30px; height: 30px; padding: 0; border-radius: 50%; box-shadow: 0 2px 4px rgba(0,0,0,0.5); }
#btn-eraser {
position: relative;
width: 60px;
height: 40px;
background: transparent;
border: none;
padding: 0;
margin-top: -5px;
overflow: hidden;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.5);
}
#btn-eraser::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0; height: 18px;
background: #a6704b;
}
#btn-eraser::after {
content: '';
position: absolute;
top: 18px; left: 0; right: 0; bottom: 0;
background: #444;
border-bottom: 4px solid #ccc;
}
#btn-eraser.active {
outline: 3px solid white;
outline-offset: 2px;
}
#btn-finger {
padding: 8px 12px;
font-size: 1em;
}
#image-upload {
display: none;
}
</style>
</head>
<body>
<div class="board-frame">
<div class="board-wrapper">
<canvas id="chalkboard"></canvas>
<canvas id="particle-canvas"></canvas>
<div class="board-text">
<div class="text-line">
<span class="space-for-num"></span>
<span class="space-for-num"></span>
</div>
<div class="text-line">
日直:
</div>
</div>
</div>
</div>
<div class="toolbar">
<div class="panel">
<span style="font-size: 0.85em;">色:</span>
<button class="color-btn active" style="background: #ffffff;" data-color="255, 255, 255" onclick="setColor(this)"></button>
<button class="color-btn" style="background: #ff8888;" data-color="255, 136, 136" onclick="setColor(this)"></button>
<button class="color-btn" style="background: #88ccff;" data-color="136, 204, 255" onclick="setColor(this)"></button>
<button class="color-btn" style="background: #ffff88;" data-color="255, 255, 136" onclick="setColor(this)"></button>
<button class="color-btn" style="background: #88ff88;" data-color="136, 255, 136" onclick="setColor(this)"></button>
</div>
<div class="panel">
<span style="font-size: 0.85em;">太さ:</span>
<button id="btn-super-corner" onclick="setMode('super-corner')">極細</button>
<button id="btn-corner" class="active" onclick="setMode('corner')"></button>
<button id="btn-medium-flat" onclick="setMode('medium-flat')">中太</button>
<button id="btn-flat" onclick="setMode('flat')"></button>
</div>
<div class="panel">
<button id="btn-eraser" onclick="setMode('eraser')" title="黒板消し"></button>
<button id="btn-finger" onclick="setMode('finger')" title="指でこする">👆 指</button>
</div>
<div class="panel">
<button onclick="document.getElementById('image-upload').click()" style="background: #4a6b8c;">🖼️ 画像をチョーク化</button>
<input type="file" id="image-upload" accept="image/*">
<button onclick="clearBoard()" style="background: #883333;">全消去</button>
</div>
</div>
<script>
const canvas = document.getElementById('chalkboard');
const ctx = canvas.getContext('2d');
const particleCanvas = document.getElementById('particle-canvas');
const pCtx = particleCanvas.getContext('2d');
const wrapper = document.querySelector('.board-wrapper');
let isDrawing = false;
let lastX = 0;
let lastY = 0;
let currentColor = '255, 255, 255';
let currentMode = 'corner';
let particles = [];
function resizeCanvas() {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = canvas.width || wrapper.clientWidth;
tempCanvas.height = canvas.height || wrapper.clientHeight;
if (canvas.width) tempCanvas.getContext('2d').drawImage(canvas, 0, 0);
canvas.width = wrapper.clientWidth;
canvas.height = wrapper.clientHeight;
particleCanvas.width = wrapper.clientWidth;
particleCanvas.height = wrapper.clientHeight;
if (tempCanvas.width) ctx.drawImage(tempCanvas, 0, 0);
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
const eraserSVG = `data:image/svg+xml;utf8,<svg width="80" height="50" xmlns="http://www.w3.org/2000/svg"><rect width="80" height="25" rx="4" fill="%23a6704b"/><rect y="25" width="80" height="20" fill="%23444"/><rect y="45" width="80" height="5" fill="%23ccc"/></svg>`;
const fingerSvgStr = `<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M14.5,10.5 L14.5,4 C14.5,2.5 13,2 12,2 C11,2 9.5,2.5 9.5,4 L9.5,11 L7,11 C5.5,11 4,12.5 5,14 L8,21 C8.5,22 10,22.5 11.5,22.5 L15,22.5 C16.5,22.5 18,21.5 18.5,20 L20.5,14 C21,12.5 19.5,11.5 18,11.5 L14.5,11.5 Z" fill="#ffccaa" stroke="#333" stroke-width="1.5"/>
</svg>`;
const encodedFingerSvg = encodeURIComponent(fingerSvgStr).replace(/'/g, "%27").replace(/"/g, "%22");
function updateCursor() {
if (currentMode === 'eraser') {
canvas.style.cursor = `url('${eraserSVG}') 40 25, auto`;
} else if (currentMode === 'finger') {
canvas.style.cursor = `url("data:image/svg+xml;utf8,${encodedFingerSvg}") 12 2, auto`;
} else {
const chalkSvgStr = `<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg">
<path d="M4,4 L14,2 L40,28 L30,40 Z" fill="rgb(${currentColor})" stroke="rgba(0,0,0,0.4)" stroke-width="1"/>
<path d="M4,4 L10,18 L36,40 L30,40 Z" fill="rgba(0,0,0,0.15)"/>
</svg>`;
const encodedSvg = encodeURIComponent(chalkSvgStr).replace(/'/g, "%27").replace(/"/g, "%22");
canvas.style.cursor = `url("data:image/svg+xml;utf8,${encodedSvg}") 4 4, crosshair`;
}
}
updateCursor();
// --- 画像をチョークアート風に変換して描画する処理(バグ修正版) ---
document.getElementById('image-upload').addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(event) {
const img = new Image();
img.onload = function() {
const scale = Math.min((canvas.width * 0.8) / img.width, (canvas.height * 0.8) / img.height);
// ★ 修正:サイズや座標を必ず整数(Math.floor)にする
const w = Math.floor(img.width * scale);
const h = Math.floor(img.height * scale);
const x = Math.floor((canvas.width - w) / 2);
const y = Math.floor((canvas.height - h) / 2);
const tempC = document.createElement('canvas');
tempC.width = w;
tempC.height = h;
const tCtx = tempC.getContext('2d');
tCtx.drawImage(img, 0, 0, w, h);
const imgData = tCtx.getImageData(0, 0, w, h);
const data = imgData.data;
const dataWidth = imgData.width; // 確実なデータ幅を取得
createParticles(canvas.width / 2, canvas.height / 2, 100);
const step = 2;
for (let py = 0; py < h; py += step) {
for (let px = 0; px < w; px += step) {
// ★ 修正:dataWidthを使って正確にインデックスを計算
const i = (py * dataWidth + px) * 4;
let r = data[i];
let g = data[i+1];
let b = data[i+2];
const a = data[i+3];
if (a < 128) continue;
const brightness = (r * 0.299 + g * 0.587 + b * 0.114);
if (brightness > 15) {
r = Math.min(255, r + 40);
g = Math.min(255, g + 40);
b = Math.min(255, b + 40);
const dustCount = Math.floor(brightness / 20) + 2;
for(let d = 0; d < dustCount; d++){
const offsetX = px + x + (Math.random() * step * 2 - step);
const offsetY = py + y + (Math.random() * step * 2 - step);
const alpha = (brightness / 255) * 0.7 + 0.2;
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${alpha})`;
const size = Math.random() > 0.8 ? 2 : 1.5;
ctx.fillRect(offsetX, offsetY, size, size);
}
}
}
}
}
img.src = event.target.result;
}
reader.readAsDataURL(file);
e.target.value = '';
});
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseout', stopDrawing);
canvas.addEventListener('touchstart', (e) => { e.preventDefault(); startDrawing(e.touches[0]); }, {passive: false});
canvas.addEventListener('touchmove', (e) => { e.preventDefault(); draw(e.touches[0]); }, {passive: false});
canvas.addEventListener('touchend', stopDrawing);
function startDrawing(e) {
isDrawing = true;
const pos = getPos(e);
lastX = pos.x;
lastY = pos.y;
draw(e);
}
function stopDrawing() {
isDrawing = false;
}
function getPos(e) {
const rect = canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
function draw(e) {
if (!isDrawing) return;
const pos = getPos(e);
const dist = Math.hypot(pos.x - lastX, pos.y - lastY);
const angle = Math.atan2(pos.y - lastY, pos.x - lastX);
const steps = Math.max(dist, 1);
let radius, density;
if (currentMode === 'super-corner') {
radius = 1.5;
density = 15;
} else if (currentMode === 'corner') {
radius = 3;
density = 25;
} else if (currentMode === 'medium-flat') {
radius = 9;
density = 40;
} else if (currentMode === 'flat') {
radius = 18;
density = 60;
} else if (currentMode === 'eraser') {
radius = 40;
density = 200;
} else if (currentMode === 'finger') {
radius = 6;
density = 50;
}
for (let i = 0; i <= steps; i++) {
const cx = lastX + Math.cos(angle) * i;
const cy = lastY + Math.sin(angle) * i;
for (let p = 0; p < density; p++) {
const r = Math.random() * radius;
const theta = Math.random() * 2 * Math.PI;
const px = cx + r * Math.cos(theta);
const py = cy + r * Math.sin(theta);
if (currentMode === 'eraser' || currentMode === 'finger') {
let alpha = (currentMode === 'finger') ? Math.random() * 0.4 + 0.15 : Math.random() * 0.3 + 0.1;
ctx.fillStyle = `rgba(26, 60, 35, ${alpha})`;
ctx.fillRect(px, py, 3, 3);
} else {
let alpha;
if (currentMode === 'super-corner') alpha = Math.random() * 0.6 + 0.4;
else if (currentMode === 'corner') alpha = Math.random() * 0.5 + 0.2;
else if (currentMode === 'medium-flat') alpha = Math.random() * 0.4 + 0.1;
else alpha = Math.random() * 0.3 + 0.05;
ctx.fillStyle = `rgba(${currentColor}, ${alpha})`;
ctx.fillRect(px, py, 1.5, 1.5);
}
}
if (currentMode === 'eraser' && Math.random() < 0.4) {
createParticles(cx, cy, 3);
} else if (currentMode === 'finger' && Math.random() < 0.15) {
createParticles(cx, cy, 1);
}
}
lastX = pos.x;
lastY = pos.y;
}
function createParticles(x, y, count) {
for (let i = 0; i < count; i++) {
particles.push({
x: x + (Math.random() - 0.5) * 50,
y: y + (Math.random() - 0.5) * 50,
vx: (Math.random() - 0.5) * 1.0,
vy: (Math.random() - 0.5) * 2 - 1.2,
life: 1.0,
decay: Math.random() * 0.02 + 0.01,
size: Math.random() * 1.5 + 0.5
});
}
}
function animateParticles() {
pCtx.clearRect(0, 0, particleCanvas.width, particleCanvas.height);
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.vy += 0.06;
p.life -= p.decay;
if (p.life <= 0) {
particles.splice(i, 1);
} else {
pCtx.fillStyle = `rgba(255, 255, 255, ${p.life * 0.4})`;
pCtx.beginPath();
pCtx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
pCtx.fill();
}
}
requestAnimationFrame(animateParticles);
}
animateParticles();
function setColor(btn) {
currentColor = btn.getAttribute('data-color');
if (currentMode === 'eraser' || currentMode === 'finger') setMode('corner');
updateCursor();
document.querySelectorAll('.color-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
}
function setMode(mode) {
currentMode = mode;
document.getElementById('btn-super-corner').classList.remove('active');
document.getElementById('btn-corner').classList.remove('active');
document.getElementById('btn-medium-flat').classList.remove('active');
document.getElementById('btn-flat').classList.remove('active');
document.getElementById('btn-eraser').classList.remove('active');
document.getElementById('btn-finger').classList.remove('active');
document.getElementById(`btn-${mode}`).classList.add('active');
updateCursor();
}
function clearBoard() {
createParticles(canvas.width / 2, canvas.height / 2, 300);
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
</script>
</body>
</html>


■ 今日の学び

いつものお約束のフレーズですが、
「私は絵を描くのは好きですが上手ではありません。」

せっかくの黒板アートゲームなので、
そんな自分でも「なんだかいい雰囲気」に仕上げられる機能を
どうしても入れたいと考えました。

そこで思い出したのが、未公開記事ゲームの一つ
「画像からイラストロジックを作るゲーム」です。

あの実験では、手持ちの写真や画像データを解析して
ゲームに反映させる処理を作ったことがありました。

「その時の学びを応用すれば、手持ちの画像を
黒板アート風の質感に変換して貼り付けられるのでは…?」

そう考えて試しに機能を追加してみたところ、
これが予想以上に素敵な仕上がりになりました!ദ്ദി ( Ꙭ )✧

読み込んだ写真が、チョークの粉のザラつきや、
擦れて黒板の地が見える質感まで伴って浮かび上がる様子は、
作っている自分でも感動するレベルです。

過去の実験で得た知識が、新しいゲームの表現として
ピタッとハマる瞬間はたまりませんね。

結果的に、いつもと同じくプログラムの調整そっちのけで、
ひたすら画像を読み込ませてはチョークで加筆する
「絵を描く方」に夢中になってしまったのは……
ここだけの秘密です。(^b^*)

■ 次回は、暗闇の宝探しゲームです。

舞台は真っ暗闇。

頼りになるのは、マウスの先から放たれる「ブラックライト」の怪しい光だけ。

画面を照らしながら探索し、
闇に紛れたお宝(顔文字)たちを見つけ出す、

新感覚の宝探しゲームです。

ライトを当て続けるほど、ネオングリーンに じわっと蓄光 していき、
見つけた瞬間には光の粒が弾ける気持ちいい演出つき!

全発見までの最速を目指す「タイムアタック」に挑むもよし、
どうしても見つからない時は
「ギブアップ機能」で一斉に発光させて答え合わせすることもできます。

ちょっと不思議でクセになる光の体験。

次回もぜひ楽しみにしていてくださいね。(*^v^)ノシ


次の実験はこちら
じわっと光る、顔文字
「ブラックライト宝探し」



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

~このブログについて~

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

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


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

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

続きを読む