実験#69:ふわふわとは(究極のふわふわ)

■69個目 「究極のふわふわ」

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

今回は、いろいろな「ふわふわ」の感触をひとつの画面に
ぎゅっと集めたシミュレーターゲームを作ってみました。

「もし、画面の向こう側の質感を、マウス越しに指先で感じられたら……?」
そんな小さな願いから始まった今回の実験。

まずは「動物の毛並み」のなで心地を再現し、
次に「羽毛のほわほわ感」、
そして「水風船のようなぷにっとした弾力」。

3つの異なる心地よさを組み合わせて生まれたのが、
画面中央でぷかぷかと漂う不思議な「丸いふわふわ」です。

さらにおまけとして、この毛玉に「スヤスヤ眠るハムスター」のような、
ふんわり優しい表情も付けてみました。(^_^)

ただ浮かんでいるだけではなく、
あなたのアクションに合わせていろいろな反応を見せてくれます。

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

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

みー:(^v^) 今日もゲームを作ります。html形式で動かせるゲーム。
今回は「ふわふわの再現」をどこまでできるかの検証です。

動物の毛(ハムスターなど)、羽毛、植物の綿毛(たんぽぽ・ラムズイヤー)、
お菓子のふわふわ(シフォンケーキ・ホイップ・綿菓子)、
クッションやスポンジなど……。

まずは動物の体毛のふわふわから再現できそうかな。

AI: 「AIと作る100のゲーム実験室」、
本日のテーマは『ふわふわ』の再現ですね。

視覚とマウス操作だけで“触っている錯覚”を生み出す、
とても面白い挑戦です。

まずは王道の「動物の体毛」からいきましょう。

キャンバス上に数千本の毛を描画し、
マウスの動きに合わせて毛がなびいたり
起き上がったりする物理演算を組んでみました。

みー:(*^o^*)すごくたのしい。癒されます。
もう少し柔らかめとか、毛先を細くとかもできるのかな。

AI: もちろん可能です。
より極上のふわふわ感を出すために、コードの心臓部を少し調整しましょう。

ちなみに究極の柔らかさを持つ動物といえばチンチラ。
1つの毛穴から50〜80本も毛が生えているため、
あの圧倒的なふわふわ感が生まれるんですよ。

● 羽毛のふわふわへ

みー:(^v^) 続いて羽毛のふわふわ感のリアル再現。
触れて舞うイメージできるかな。

AI: 動物の毛の「撫でる」感触から一転、
今度は「空気をはらんで舞う」羽毛ですね。

羽毛のふわふわ感は「軽さ」と「空気抵抗」。
重力より空気の影響を強く受けるため、ゆっくり落ちたり、
少しの風(マウス)でふわりと舞い上がる物理演算を組み込みました。

● スポンジの「ぶにっ」とした柔らかさ

みー:(^v^) 続いて柔らかスポンジのふわふわ感も再現してみましょう。

AI: 毛や羽毛が「空気」や「しなり」の表現だったのに対し、
スポンジは「弾性体」のシミュレーションになります。

指で押し込むと「ぐっ」、離すと「ぽふっ」と戻る、あの感触ですね。

みー:(^v^) もっと大きくぶにっと感を出せるかな。
形も丸くしましょう。

AI: 了解です。 四角いグリッド構造だった前回から、
今回は「表面張力」と「水風船の弾力」を持つ
円形モデルへ大幅に変更しました。

内部にゼリーや水が詰まっているような質感を出すため、
円周上に60個の点を配置し、
互いに引っ張り合いながら丸い形を保つ計算式を使っています。

● 3つのふわふわを合体!

みー:(^v^) 最初の毛玉と羽毛と、水風船のようなの。
この3つのふわふわを1つにまとめてみましょう。

イメージとしては、毛玉+水風船の一体化で、周りに羽毛が漂う感じ。

AI: 毛玉、羽毛、水風船……3つの物理演算を統合する。
いよいよ本格的なゲームエンジンの領域に入りましたね。

「押し込むと変形するベース(水風船)」の表面座標をリアルタイム計算し、
そこに「何千本もの毛」の根元を追従させています。

さらに周囲には、独立した空気抵抗を持つ羽毛を漂わせました。

重くならないよう最適化も施してあります。
エディタに貼り付けて、クリックしたままぐりぐりと撫でてみてください。

(このあと、ふわふわな表情・動き・効果音も追加しました。)

■ 実際の動作

こちらが、AIが最初に作ってくれた「毛のふわふわシミュレーター」です。
なんだか、いがぐりみたいな見た目です。
(画像:最初のふわふわ)


そして、こちらが完成版。

上から羽毛がふわふわと降ってきて、
マウスカーソルの動きに合わせて舞い上がります。

(画像:羽毛が舞うふわふわ)

これは通常状態のふわふわ玉。
マウスを近づけると、ふわっと避けるように動きます。
画面中央に“ゆるい重力”を設定しているので、
離すと自然に中央へ戻っていきます。

(画像:ふわふわ玉)


掴んで動かすと、こんなふうに形が変わります。
水風船のように、むにっと伸びて、ゆっくり元に戻ります。

(画像:変形するふわふわ)


毛をなでると、動きに合わせてふわふわと毛が動きます。
そして、表情もふわふわな雰囲気です。

(画像:ふわふわ)


■ コードのポイント

今回の記事では、AI と相談しながら作った
「究極のふわふわ」 の HTML コードを公開します。

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

ブラウザだけで動く、とてもシンプルなゲームなので、
気軽に試してみてくださいね。

▼ 遊ぶまでの手順

①PC の「メモ帳」(※Windowsの場合)や  
 Mac の「テキストエディット」など、テキストエディタを開きます。

②下のコードをコピーして貼り付けます。

③「fuwafuwa .html」など、好きな名前で保存します。
 (※拡張子は .html にしてください)

④保存したファイルをブラウザで開けば、すぐに遊べます。

 

■ 遊び方・操作説明

基本はマウスを使うだけの、とってもシンプルな操作です。
ぜひ、いろんな触り方を試して癒やされてみてくださいね。

① そっと近づいてみる(マウスカーソルを近づける)

丸いふわふわにカーソルを近づけると、
空気の風圧に反応して ぽわ〜ん とゆっくり逃げていきます。

まるで風船のように、画面の中をふわふわ漂う姿を楽しめます。

マウスをサッと動かすと、周りを舞っている羽毛も フワッ
風にのって舞い上がります。

② 優しくなでる・つかむ(クリックしたまま動かす)

ふわふわの上でクリックしたままにすると、
逃げるのをやめて大人しくなります。
そのまま、ぐりぐりとなでてみてください。(^_^)

水風船のような ぶにっ とした弾力と、
毛並みが ふさっ と揺れる感触が同時に味わえます。

スヤスヤ眠るお顔もむにゅっと潰れるので、
お餅みたいに柔らかくなった姿も楽しめます。

③ じっと見守る(操作しない)

何もせずに放っておくと、ふわふわ玉は ゆっくりと
画面中央の“定位置”へ戻っていきます。

ただ漂いながら眠り続ける姿を眺めているだけでも、
とても癒やされます。(=v=*)

休日のリラックスタイムや、ちょっと疲れたときの息抜きに、
ぜひこの「究極のふわふわ」を触りにきてくださいね。

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

▼ここからコード▼(クリックで開く)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>究極のふわふわキメラ(サウンド付き・居眠りハムスター妖精)</title>
<style>
body {
margin: 0;
overflow: hidden;
background: linear-gradient(135deg, #a1c4fd 0%, #c2e9fb 100%);
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
color: #333;
font-family: sans-serif;
}
canvas {
background-color: transparent;
cursor: grab;
}
canvas:active {
cursor: grabbing;
}
#instructions {
position: absolute;
bottom: 20px;
text-align: center;
line-height: 1.5;
pointer-events: none;
color: #4a5568;
font-weight: bold;
text-shadow: 0 1px 3px rgba(255,255,255,0.8);
}
#start-screen {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
background: rgba(255, 255, 255, 0.85);
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
z-index: 100;
cursor: pointer;
font-size: 24px;
font-weight: bold;
color: #4a5568;
}
#start-screen span {
font-size: 16px;
font-weight: normal;
margin-top: 10px;
color: #718096;
}
</style>
</head>
<body>
<div id="start-screen">
画面をクリックしてスタート
<span>(ふんわりした音が出ます)</span>
</div>
<canvas id="canvas"></canvas>
<div id="instructions">
近づくと風圧で「ぽわんっ」と動きます。<br>
クリックしながら撫でると大人しくなり、「むにゅっ」と癒やしてくれます。
</div>
<script>
// ==========================================
// サウンドエンジン (Web Audio API)
// ==========================================
let audioCtx;
function initAudio() {
const AudioContext = window.AudioContext || window.webkitAudioContext;
audioCtx = new AudioContext();
playBGM();
}
// BGM:ゆったりとした三角波のオルゴール風アンビエント
function playBGM() {
if (!audioCtx) return;
const notes = [261.63, 329.63, 392.00, 523.25]; // C, E, G, C
let step = 0;
setInterval(() => {
let osc = audioCtx.createOscillator();
let gain = audioCtx.createGain();
osc.type = 'triangle'; // 角の丸い柔らかい波形
osc.frequency.value = notes[step % notes.length] / 2; // 1オクターブ下で落ち着きを
// ふんわり鳴って、ふんわり消える
gain.gain.setValueAtTime(0, audioCtx.currentTime);
gain.gain.linearRampToValueAtTime(0.08, audioCtx.currentTime + 1.0);
gain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + 3.0);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 3.0);
step++;
}, 2500); // 2.5秒間隔でゆっくり
}
// 効果音:逃げる時の「ぽわん」
function playFuwa() {
if (!audioCtx) return;
let osc = audioCtx.createOscillator();
let gain = audioCtx.createGain();
osc.type = 'sine'; // 一番丸い波形
osc.frequency.setValueAtTime(300, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(150, audioCtx.currentTime + 0.4);
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + 0.4);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.4);
}
// 効果音:掴んだ時の「むにゅっ」
function playBuni() {
if (!audioCtx) return;
let osc = audioCtx.createOscillator();
let gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(200, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(500, audioCtx.currentTime + 0.15); // ピッチが上がる
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.2);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.2);
}
// ==========================================
// ゲームメインロジック
// ==========================================
document.getElementById('start-screen').addEventListener('click', function() {
this.style.display = 'none';
initAudio();
});
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
let blobCenterX = centerX;
let blobCenterY = centerY;
let blobVX = 0;
let blobVY = 0;
let mouseX = -1000;
let mouseY = -1000;
let pmouseX = -1000;
let pmouseY = -1000;
let mouseVX = 0;
let mouseVY = 0;
let isMouseDown = false;
let wasHovered = false; // 音の連続再生を防ぐためのフラグ
window.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
mouseVX = mouseX - pmouseX;
mouseVY = mouseY - pmouseY;
pmouseX = mouseX;
pmouseY = mouseY;
});
window.addEventListener('mousedown', () => {
isMouseDown = true;
playBuni(); // 掴んだ時の効果音
});
window.addEventListener('mouseup', () => isMouseDown = false);
window.addEventListener('mouseleave', () => { isMouseDown = false; mouseX = -1000; });
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
const numPoints = 40;
const baseRadius = 130;
const blobPoints = [];
const stiffness = 0.03;
const surfaceTension = 0.2;
const frictionBlob = 0.85;
class BlobPoint {
constructor(angle) {
this.angle = angle;
this.ox = blobCenterX + Math.cos(angle) * baseRadius;
this.oy = blobCenterY + Math.sin(angle) * baseRadius;
this.x = this.ox;
this.y = this.oy;
this.vx = 0;
this.vy = 0;
}
updateTarget() {
this.ox = blobCenterX + Math.cos(this.angle) * baseRadius;
this.oy = blobCenterY + Math.sin(this.angle) * baseRadius;
}
}
for (let i = 0; i < numPoints; i++) {
blobPoints.push(new BlobPoint((i / numPoints) * Math.PI * 2));
}
function updateBlob() {
let dxCenter = centerX - blobCenterX;
let dyCenter = centerY - blobCenterY;
blobVX += dxCenter * 0.001;
blobVY += dyCenter * 0.001;
let dxMouse = blobCenterX - mouseX;
let dyMouse = blobCenterY - mouseY;
let distMouse = Math.sqrt(dxMouse * dxMouse + dyMouse * dyMouse);
let influenceRange = baseRadius + 140;
// 触れた瞬間に一度だけ「ぽわん」と鳴らす
if (distMouse < influenceRange && !isMouseDown) {
if (!wasHovered) {
playFuwa();
wasHovered = true;
}
let escapeForce = 0.0015;
let windForce = 0.006;
let force = (influenceRange - distMouse) * escapeForce;
blobVX += (dxMouse / distMouse) * force;
blobVY += (dyMouse / distMouse) * force;
blobVX += mouseVX * windForce;
blobVY += mouseVY * windForce;
} else if (distMouse > influenceRange + 50) {
wasHovered = false; // 離れたらフラグをリセット
}
blobVX *= 0.94;
blobVY *= 0.94;
blobCenterX += blobVX;
blobCenterY += blobVY;
for (let i = 0; i < numPoints; i++) {
blobPoints[i].updateTarget();
}
for (let i = 0; i < numPoints; i++) {
let p = blobPoints[i];
p.vx += (p.ox - p.x) * stiffness;
p.vy += (p.oy - p.y) * stiffness;
if (isMouseDown) {
let dx = p.x - mouseX;
let dy = p.y - mouseY;
let dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 120) {
let force = (120 - dist) * 0.04;
p.vx += (dx / dist) * force;
p.vy += (dy / dist) * force;
}
}
}
for (let i = 0; i < numPoints; i++) {
let p = blobPoints[i];
let pPrev = blobPoints[(i - 1 + numPoints) % numPoints];
let pNext = blobPoints[(i + 1) % numPoints];
let targetX = (pPrev.x + pNext.x) / 2;
let targetY = (pPrev.y + pNext.y) / 2;
p.vx += (targetX - p.x) * surfaceTension;
p.vy += (targetY - p.y) * surfaceTension;
}
for (let i = 0; i < numPoints; i++) {
let p = blobPoints[i];
p.vx *= frictionBlob;
p.vy *= frictionBlob;
p.x += p.vx;
p.y += p.vy;
}
}
const hairs = [];
const numHairs = 1800;
class Hair {
constructor() {
this.baseAngle = Math.random() * Math.PI * 2;
this.rRatio = Math.sqrt(Math.random());
this.length = 25 + Math.random() * 30;
this.targetAngle = this.baseAngle + (Math.random() - 0.5) * 0.5;
this.currentAngle = this.targetAngle;
this.stiffness = 0.01 + Math.random() * 0.02;
let hue = 35 + Math.random() * 10;
let sat = 30 + Math.random() * 20;
let light = 70 + Math.random() * 25;
this.color = `hsla(${hue}, ${sat}%, ${light}%, 0.8)`;
this.thickness = 1.5 + Math.random() * 2.0;
}
update() {
let idx = (this.baseAngle / (Math.PI * 2)) * numPoints;
let i1 = Math.floor(idx) % numPoints;
let i2 = (i1 + 1) % numPoints;
let t = idx - i1;
let sx = blobPoints[i1].x * (1 - t) + blobPoints[i2].x * t;
let sy = blobPoints[i1].y * (1 - t) + blobPoints[i2].y * t;
this.baseX = blobCenterX + (sx - blobCenterX) * this.rRatio;
this.baseY = blobCenterY + (sy - blobCenterY) * this.rRatio;
this.tipX = this.baseX + this.length * Math.cos(this.currentAngle);
this.tipY = this.baseY + this.length * Math.sin(this.currentAngle);
let dx = mouseX - this.tipX;
let dy = mouseY - this.tipY;
let dist = Math.sqrt(dx * dx + dy * dy);
let interactionRadius = isMouseDown ? 100 : 50;
if (dist < interactionRadius) {
let speed = Math.sqrt(mouseVX * mouseVX + mouseVY * mouseVY);
if (speed > 0.5) {
let moveAngle = Math.atan2(mouseVY, mouseVX);
let force = Math.min(speed * 0.015, 0.4);
let diff = moveAngle - this.currentAngle;
while (diff < -Math.PI) diff += Math.PI * 2;
while (diff > Math.PI) diff -= Math.PI * 2;
this.currentAngle += diff * force;
}
} else {
let diff = this.targetAngle - this.currentAngle;
while (diff < -Math.PI) diff += Math.PI * 2;
while (diff > Math.PI) diff -= Math.PI * 2;
this.currentAngle += diff * this.stiffness;
}
}
draw() {
this.tipX = this.baseX + this.length * Math.cos(this.currentAngle);
this.tipY = this.baseY + this.length * Math.sin(this.currentAngle);
let cpX = this.baseX + (this.length * 0.5) * Math.cos(this.currentAngle);
let cpY = this.baseY + (this.length * 0.5) * Math.sin(this.currentAngle);
let nx = Math.cos(this.currentAngle + Math.PI / 2);
let ny = Math.sin(this.currentAngle + Math.PI / 2);
let halfThick = this.thickness / 2;
ctx.beginPath();
ctx.moveTo(this.baseX + nx * halfThick, this.baseY + ny * halfThick);
ctx.quadraticCurveTo(cpX + nx * (halfThick * 0.4), cpY + ny * (halfThick * 0.4), this.tipX, this.tipY);
ctx.quadraticCurveTo(cpX - nx * (halfThick * 0.4), cpY - ny * (halfThick * 0.4), this.baseX - nx * halfThick, this.baseY - ny * halfThick);
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
}
}
for (let i = 0; i < numHairs; i++) {
hairs.push(new Hair());
}
const downs = [];
const numDowns = 60;
class Down {
constructor() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.vx = (Math.random() - 0.5) * 0.5;
this.vy = Math.random() * 0.5 + 0.1;
this.size = 10 + Math.random() * 15;
this.angle = Math.random() * Math.PI * 2;
this.spin = 0;
this.drag = 0.96 + Math.random() * 0.03;
this.barbs = [];
let barbsCount = 15 + Math.random() * 10;
for(let i = 0; i < barbsCount; i++) {
this.barbs.push({
angle: Math.random() * Math.PI * 2,
length: this.size * (0.6 + Math.random() * 0.6),
curve: (Math.random() - 0.5) * this.size * 1.5
});
}
}
update() {
this.vx *= this.drag;
this.vy *= this.drag;
this.vy += 0.015;
let dx = this.x - mouseX;
let dy = this.y - mouseY;
let dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 200) {
let force = (200 - dist) / 200;
this.vx += mouseVX * force * 0.05;
this.vy += mouseVY * force * 0.05;
this.vx += (dx / dist) * force * 1.5;
this.vy += (dy / dist) * force * 1.5;
this.spin += (mouseVX - mouseVY) * force * 0.005;
}
this.angle += this.spin;
this.spin *= 0.95;
this.x += this.vx;
this.y += this.vy;
if (this.y > canvas.height + 50) { this.y = -50; this.x = Math.random() * canvas.width; this.vy = Math.random() * 0.5; this.vx = 0; }
if (this.x > canvas.width + 50) this.x = -50;
if (this.x < -50) this.x = canvas.width + 50;
}
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.beginPath();
ctx.arc(0, 0, this.size * 0.2, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
ctx.fill();
ctx.lineWidth = 0.8;
this.barbs.forEach(barb => {
ctx.beginPath();
ctx.moveTo(0, 0);
let endX = Math.cos(barb.angle) * barb.length;
let endY = Math.sin(barb.angle) * barb.length;
let cpX = Math.cos(barb.angle) * (barb.length * 0.5) - Math.sin(barb.angle) * barb.curve;
let cpY = Math.sin(barb.angle) * (barb.length * 0.5) + Math.cos(barb.angle) * barb.curve;
ctx.quadraticCurveTo(cpX, cpY, endX, endY);
ctx.strokeStyle = `rgba(255, 255, 255, ${0.3 + Math.random() * 0.3})`;
ctx.stroke();
});
ctx.restore();
}
}
for (let i = 0; i < numDowns; i++) {
downs.push(new Down());
}
function drawHamsterFace() {
ctx.save();
ctx.translate(blobCenterX, blobCenterY);
let lookX = (mouseX - blobCenterX) * 0.08;
let lookY = (mouseY - blobCenterY) * 0.08;
lookX = Math.max(-12, Math.min(12, lookX));
lookY = Math.max(-12, Math.min(12, lookY));
ctx.translate(lookX, lookY);
ctx.fillStyle = 'rgba(255, 130, 130, 0.5)';
ctx.beginPath();
ctx.ellipse(-35, 12, 16, 10, 0, 0, Math.PI * 2);
ctx.ellipse(35, 12, 16, 10, 0, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.beginPath();
ctx.ellipse(0, 10, 22, 14, 0, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#5c4033';
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(-45, -2);
ctx.quadraticCurveTo(-30, 8, -15, -2);
ctx.moveTo(15, -2);
ctx.quadraticCurveTo(30, 8, 45, -2);
ctx.stroke();
ctx.fillStyle = '#ff8888';
ctx.beginPath();
ctx.ellipse(0, 6, 6, 4, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.moveTo(-8, 14);
ctx.quadraticCurveTo(0, 20, 8, 14);
ctx.stroke();
ctx.restore();
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
mouseVX *= 0.85;
mouseVY *= 0.85;
updateBlob();
ctx.beginPath();
let midX = (blobPoints[0].x + blobPoints[1].x) / 2;
let midY = (blobPoints[0].y + blobPoints[1].y) / 2;
ctx.moveTo(midX, midY);
for (let i = 1; i < blobPoints.length; i++) {
let nextI = (i + 1) % blobPoints.length;
let nextMidX = (blobPoints[i].x + blobPoints[nextI].x) / 2;
let nextMidY = (blobPoints[i].y + blobPoints[nextI].y) / 2;
ctx.quadraticCurveTo(blobPoints[i].x, blobPoints[i].y, nextMidX, nextMidY);
}
ctx.closePath();
ctx.fillStyle = '#eed9c4';
ctx.fill();
hairs.forEach(hair => {
hair.update();
hair.draw();
});
drawHamsterFace();
downs.forEach(down => {
down.update();
down.draw();
});
requestAnimationFrame(animate);
}
animate();
</script>
</body>
</html>


■ 今日の学び

「ふわふわ」というオノマトペをシミュレーションゲームにしたら、
一体どんな“手触り”になるんだろう?
今回は、そんな素朴な好奇心から始まった実験でした。(^_^)

私がハムスター好きということもあって、
途中でスヤスヤ眠る表情などを足していくうちに、
最終的にはすっかり“かわいいハムスターの
ふわふわ玉”のような姿になりました。(^^*)

でも、この作品の裏側にある本当のテーマは、
最初から最後まで一貫して 「究極のふわふわ」 を追求することでした。

今回の実験を通して得た気づきは、大きく分けてこの2つです。

  • AIと一緒にリアリティを探求する面白さ  
    毛先の細さや空気抵抗など、「もっとリアルにしたい!」と
    AIに相談しながら調整していく過程がとても刺激的でした。  

    ブラウザ上とは思えないほどの心地よさに近づけたのは、
    この対話のおかげです。(=v=*)
  • “感覚そのもの”を作品にする楽しさ  
    視覚・音・マウス操作を組み合わせるだけで、
    画面越しでも“触っている感覚”が生まれることに驚きました。
     
    ゲームというより、ひとつの“触感アート”に近い体験になった気がします。

明確なクリアやスコアはありませんが、
そのぶん「ふわふわに特化した世界観」を、
時間を忘れてゆったり楽しんでもらえたら嬉しいです。(´▽`)

■ 次回は、レトロな雰囲気のコレクションゲームです。

日常にひそむ “バグモン” を見つけていくゲームで、

クリックすると姿を現し、ちょっと不思議な形に変形します。

見つけたバグモンは図鑑にコレクションされていくので、

特徴的なバグたちをぜひコンプリートしてみてください。

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

▶ 次の実験はこちら
 (AIに作ってもらうバグ)「バグってもんすた」



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

~このブログについて~

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

🌈初めての方へ
🌱このブログについて
🌳実験一覧
🍃プライバシーポリシー


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

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

続きを読む