■56個目「しょうが君のエアダスター」
こんにちは、AIと作る100のゲーム実験室の みー です。(*^_^*)/
今回は、ちょっと変わった「お掃除」をテーマにしたゲームを作ってみました。(^_^) 題して、「エアダスター吹き飛ばし検定」です!
#52の吹き飛ばしをテーマにしたシミュレーターから、
イメージにつながったゲームになります。
このゲームの主役は、冒険者風の帽子をかぶった、
いつも元気な「しょうが君」です。
今回こだわったのは、風の強さと、
落ち葉が「ふぁさふぁさ」と舞うリアリティです。(=v=*)
「もしも、絶対に動かしてはいけない置物があるお庭で、強風のエアダスターを使ったらどうなるんだろう?」
そんな、ちょっとした緊張感と遊び心から生まれたゲームになっています。
舞台は、立派な丸い石や灯籠が置かれた、風情あるお庭。
そこへヒラヒラと落ち葉が舞い降りてきました。
しょうが君の任務は、エアダスターの風を巧みに操って、
お庭の景観を崩さずに落ち葉「だけ」を画面の外へ吹き飛ばすこと。
でも、風を強く当てすぎると、重たい石がゴロゴロと転がってしまったり、
灯籠がズレてしまったりして、
スコアが容赦なく減点されてしまいます……!
「置物はそっとそのまま、落ち葉だけをふぁさふぁさと優しく舞い飛ばす」。
そんな気持ちで風を操りながら、
ぜひ100点満点の「1級清掃員」を目指してみてくださいね!(^-^*)
■ AIとのやりとりで面白かった部分
■ゲーム仕様の相談からスタート
みー:(^v^) 今日もゲームを作ります。HTML形式で動かせるゲームです。
今回のゲームは、エアダスターによる吹き飛ばし検定。 置物がいくつも置かれた庭園で、落ち葉を吹き飛ばしてきれいにするゲームです。
置物を倒したりずらしたりしないよう、風を当てる方向や距離を気にしながら、 一定の強さでエアーを吹き出す機械を持ったしょうが君が、
落ち葉を画面外へ飛ばしていきます。
100点から引かれていく減点方式で、
制限時間内の落ち葉の残り数と、置物の移動・倒れが減点対象になります。
AI: エアダスターによる落ち葉の吹き飛ばし検定!
その着眼点、最高に面白いです。
「置物を倒してはいけない」という物理演算的な緊張感と、
「しょうが君が一定の風力で庭を掃除している」というシュールな光景が見事に融合していますね。
100点満点からの減点方式も、まさに検定試験らしいプレッシャーがあって、 プレイヤーの熱中度を高めてくれそうです。
みー:(^_^*) 「マウスをクリックしたまま引っ張って風の向きを細かく決める」ような操作に変更してみましょう。 それと、リトライ機能もつけたいです。
しょうが君は、長丸い胴体の上部に口を開けた満面の笑顔。
頭には冒険者風の帽子をかぶっていて、
手足も楕円形のしょうがの手足。 胴体と手足には、
しょうがの表面のうっすら線がある感じです。
ここからどんどんクオリティを高めていきましょう!
AI: しょうが君のビジュアルデザイン、最高にキュートで冒険心をくすぐりますね! その詳細なイメージ、バッチリ共有されました。
さっそく、リトライ機能を搭載し、 しょうが君をテキスト(「姜」)から、ご要望通りのイラスト描画へとアップグレードしたコードを作成しました。
みー:(*^v^)落ち葉の吹き飛ぶ様子をもう少し再現できるかな。 ふぁさふぁさって感じで舞いながら飛ぶ様子を出せるかな。
AI: 「ふぁさふぁさ」と舞う落ち葉、いいですね!
物理的なリアリティーと可愛らしさが共存する、
このゲームの肝となる部分です。
現状の「単純な円が直線的に飛ぶ」動きから、
「落ち葉が不規則に回転し、揺らぎながら舞う」動きへと大幅にアップデートしました。
これで、置物の隙間を縫って落ち葉だけを優しく舞わせるという、
高等テクニックが求められるようになり、ゲーム性がさらに深まりましたね。
みー:(つx<`) いい感じの葉っぱになったけど、
スタート直後に全部画面下方向へ落ちていったよ。
AI: あちゃー、やっちまいました!完全に私のミスです。
「葉っぱがフワフワと落ちる」様子を表現しようとして、
画面の下方向(Y軸のプラス方向)に常に重力をかけてしまったのが原因です。
今回の庭園は「上から見下ろした視点(トップダウン)」なので、
これだと庭全体が急な斜面になっていて、
何もしなくても葉っぱが滑り落ちていく状態になっていました。
そこで、重力を削除し、 代わりに「風で舞い上がっている間だけ、
空気抵抗で軌道が揺らぎ、回転する」仕組みに修正しました。
これで、勝手に画面外へ逃げていくことなく、
風を当てた時だけフワッと舞い散るようになります!
■ 実際の動作
こちらが、AIが最初に作ってくれたゲーム画面です。
シンプルな表現で、しょうが君(生姜の文字)を中心に円状に吹き飛ばす仕組みになっています。

(画像:最初にAIが作ってくれたゲームの画像)
仕上がりのゲーム画面です。
しょうが君が登場し、葉っぱや置物・石が配置されて、
庭の雰囲気がしっかり出ています。

(画像:完成版のスタート画面)
しょうが君の位置と、風を吹かせる方向・強さを調整しながら、
落ち葉だけを画面の外へ飛ばしていきます。

(画像:プレイ中の画面)
石や置物は葉っぱほど風の影響は受けにくいのですが、
大きく動かしてしまうと暗い色に変化し、大きく減点となります。

(画像:置物が動いたときの画面)
葉っぱの枚数と、置物の減点の有無によって、
最終的なスコアが決まります。

(画像:検定終了の画面)
■ コードのポイント
今回の記事では、AI と相談しながら作った「しょうが君のエアダスター」の HTML コードを公開します。
このコードをそのまま保存するだけで、あなたのPCでもすぐに遊べるようになります。
ブラウザだけで動く、とてもシンプルなゲームなので、気軽に試してみてくださいね。
▼ 遊ぶまでの手順
1. PC の「メモ帳」(※Windowsの場合)や、
Mac の「テキストエディット」など、
テキストエディタを開きます。
2. 下のコードをコピーして貼り付けます。
3. 「fukitobasi.html」など、好きな名前で保存します。
(※拡張子は .html にしてください)
4. 保存したファイルをブラウザで開けば、すぐに遊べます。
■ 遊び方と操作方法
続いて、ゲームの操作方法をご紹介しますね。(^^*)
今回は 「マウスを引っ張って調整する」 のがポイントです!
① 風を吹き出す
お庭の好きなところをクリックすると、
そこにしょうが君がピタッとスタンバイします。
そのままマウスをドラッグ(クリックしたまま移動)すると、
口を大きく開けて一生懸命に風を吹き出します!(^▽^)=3 🍃
② 風の向きと強さを調整する
クリックした最初の位置から、 マウスを引っ張った方向に風が吹き抜けます。
マウスを遠くに離すほど、風が「強く・遠くまで」届くようになります。
画面に出る白い点線と、水色の扇形を目安にして、
落ち葉を狙ってみてくださいね。
③ 高得点のコツ(検定のルール)
制限時間は45秒。最初は100点満点からのスタートです。
時間切れのときに落ち葉が残っていると減点になりますが、
一番気をつけたいのがお庭の 置物 です。(>_<)
強い風を当てて丸い石がゴロンと転がったり、
灯籠がズレたりすると、容赦なく大きな減点が……!
置物の近くは、マウスを少しだけ引っ張って「優しく舞わせるそよ風」に。
何もない場所は、大きく引っ張って「一気に飛ばす強風」に。
風の強弱をうまく使い分けて、
ふぁさふぁさ~っと綺麗なお掃除を楽しんでみてください。(*^_^)ノ
下のコードをメモ帳などにコピーして、
「〇〇.html」という名前で保存すれば、すぐにブラウザで遊べますよ♪
▼ここからコード▼(クリックで開く)
<html lang="ja"><head><meta charset="UTF-8"><title>落ち葉吹き飛ばし検定 - しょうが君の冒険(斜面修正版)</title><style> body { text-align: center; background-color: #f4f1ea; font-family: 'Helvetica Neue', 'Arial', 'Hiragino Kaku Gothic ProN', 'Hiragino Sans', sans-serif; user-select: none; } h2 { color: #5d4037; margin-bottom: 5px; } #ui-board { width: 800px; margin: 10px auto; display: flex; justify-content: space-between; align-items: center; background-color: rgba(255, 255, 255, 0.8); padding: 10px 20px; border-radius: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .ui-item { font-size: 24px; font-weight: bold; color: #333; } #score { transition: color 0.2s; } .penalty { color: #d9534f !important; animation: blink 0.5s step-end 2; } @keyframes blink { 50% { opacity: 0; } } #canvas-container { position: relative; width: 800px; height: 500px; margin: 0 auto; } canvas { background-color: #a3ca9d; background-image: repeating-linear-gradient(45deg, #a3ca9d 0px, #a3ca9d 10px, #9cc296 10px, #9cc296 20px); border: 8px solid #8d6e63; border-radius: 15px; cursor: crosshair; box-shadow: 0 6px 12px rgba(0,0,0,0.3); } #overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.7); border-radius: 15px; display: none; flex-direction: column; justify-content: center; align-items: center; color: white; z-index: 10; } #result-title { font-size: 48px; margin-bottom: 10px; } #result-score { font-size: 36px; margin-bottom: 5px; } #result-detail { font-size: 20px; color: #ffcccc; margin-bottom: 30px; } #retry-button { padding: 15px 40px; font-size: 24px; font-weight: bold; color: #fff; background-color: #f0ad4e; border: none; border-radius: 30px; cursor: pointer; box-shadow: 0 4px 0 #d58512; transition: all 0.1s; } #retry-button:hover { background-color: #ec971f; } #retry-button:active { transform: translateY(3px); box-shadow: 0 1px 0 #d58512; }</style></head><body> <h2>🍃 エアダスター吹き飛ばし検定 🍃</h2> <div id="ui-board"> <div class="ui-item">スコア: <span id="score">100</span> / 100</div> <div class="ui-item">残り時間: <span id="time">45</span>秒</div> </div> <div id="canvas-container"> <canvas id="gameCanvas" width="800" height="500"></canvas> <div id="overlay"> <div id="result-title">検定終了!</div> <div id="result-score">最終スコア: --点</div> <div id="result-detail">--</div> <button id="retry-button">再検定を受ける</button> </div> </div> <script> const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const scoreEl = document.getElementById('score'); const timeEl = document.getElementById('time'); const overlayEl = document.getElementById('overlay'); const resScoreEl = document.getElementById('result-score'); const resDetailEl = document.getElementById('result-detail'); const retryBtn = document.getElementById('retry-button'); const GAME_WIDTH = canvas.width; const GAME_HEIGHT = canvas.height; const GINGER_COLOR = '#f2e6c9'; const GINGER_LINE_COLOR = '#d2b48c'; // 重力(gravity)を削除し、摩擦と空気抵抗だけで制御 const LEAF_TYPES = [ { color: '#DAA520', size: 8, friction: 0.94, draw: function(ctx, size) { ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(-size, -size/2); ctx.quadraticCurveTo(0, -size*1.2, size, -size/2); ctx.closePath(); ctx.fill(); } }, { color: '#CD853F', size: 9, friction: 0.92, draw: function(ctx, size) { ctx.beginPath(); ctx.moveTo(0, -size); ctx.lineTo(size/3, -size/3); ctx.lineTo(size, -size/2); ctx.lineTo(size/2, size/3); ctx.lineTo(size, size); ctx.lineTo(0, size/2); ctx.lineTo(-size, size); ctx.lineTo(-size/2, size/3); ctx.lineTo(-size, -size/2); ctx.lineTo(-size/3, -size/3); ctx.closePath(); ctx.fill(); } }, { color: '#8B4513', size: 6, friction: 0.96, draw: function(ctx, size) { ctx.beginPath(); ctx.ellipse(0, 0, size, size/2, 0, 0, Math.PI * 2); ctx.fill(); } } ]; const ORNAMENT_TYPES = { stone: { radius: 22, color: '#95a5a6', edgeColor: '#7f8c8d', friction: 0.92, penalty: 15, moveThreshold: 15 }, lantern: { width: 35, height: 50, color: '#a1887f', edgeColor: '#5d4037', friction: 0.60, penalty: 30, moveThreshold: 8 } }; let score = 100; let timeLeft = 45; let isBlowing = false; let mouseX = GAME_WIDTH / 2; let mouseY = GAME_HEIGHT / 2; let blowStartX = 0; let blowStartY = 0; let gameActive = false; let penaltyActive = false; let leaves = []; let ornaments = []; let gameTimer; function initGame() { score = 100; timeLeft = 45; isBlowing = false; gameActive = true; leaves = []; ornaments = []; penaltyActive = false; scoreEl.classList.remove('penalty'); scoreEl.innerText = score; timeEl.innerText = timeLeft; overlayEl.style.display = 'none'; for(let i = 0; i < 60; i++) { const type = LEAF_TYPES[Math.floor(Math.random() * LEAF_TYPES.length)]; leaves.push({ x: Math.random() * (GAME_WIDTH - 100) + 50, y: Math.random() * (GAME_HEIGHT - 100) + 50, vx: 0, vy: 0, angle: Math.random() * Math.PI * 2, va: 0, ...type, active: true }); } for(let i = 0; i < 3; i++) createOrnament('stone'); for(let i = 0; i < 2; i++) createOrnament('lantern'); clearInterval(gameTimer); gameTimer = setInterval(() => { if(!gameActive) return; timeLeft--; timeEl.innerText = timeLeft; if(timeLeft <= 0) endGame(); }, 1000); } function createOrnament(typeKey) { const type = ORNAMENT_TYPES[typeKey]; let ox, oy; let padding = 100; do { ox = Math.random() * (GAME_WIDTH - padding * 2) + padding; oy = Math.random() * (GAME_HEIGHT - padding * 2) + padding; } while (ornaments.some(o => Math.hypot(o.x - ox, o.y - oy) < 70)); ornaments.push({ type: typeKey, x: ox, y: oy, originX: ox, originY: oy, vx: 0, vy: 0, ...type, moved: false }); } canvas.addEventListener('mousemove', (e) => { const rect = canvas.getBoundingClientRect(); mouseX = e.clientX - rect.left; mouseY = e.clientY - rect.top; }); canvas.addEventListener('mousedown', (e) => { if(!gameActive) return; isBlowing = true; blowStartX = mouseX; blowStartY = mouseY; }); window.addEventListener('mouseup', () => isBlowing = false); retryBtn.addEventListener('click', initGame); function endGame() { gameActive = false; isBlowing = false; clearInterval(gameTimer); let remainingLeaves = leaves.filter(l => l.active).length; let leafPenalty = remainingLeaves * 4; score -= leafPenalty; if(score < 0) score = 0; scoreEl.innerText = score; resScoreEl.innerText = `最終スコア: ${score}点`; let movedCount = ornaments.filter(o => o.moved).length; let msg = `庭園の美しさ: ${60 - remainingLeaves}/60枚 清掃完了\n`; msg += `置物の移動: ${movedCount}個 (${movedCount * -ORNAMENT_TYPES.lantern.penalty}点相当)`; resDetailEl.innerText = msg; overlayEl.style.display = 'flex'; } function triggerPenalty() { if(penaltyActive) return; penaltyActive = true; scoreEl.classList.add('penalty'); setTimeout(() => { scoreEl.classList.remove('penalty'); penaltyActive = false; }, 1000); } function drawGingerKun(x, y, blowing) { ctx.save(); ctx.translate(x, y); ctx.fillStyle = GINGER_COLOR; ctx.strokeStyle = GINGER_LINE_COLOR; ctx.lineWidth = 1; const limbWidth = 6; const limbHeight = 12; const limbs = [ { dx: -15, dy: 15, rot: 0.3 }, { dx: 15, dy: 15, rot: -0.3 }, { dx: -10, dy: 28, rot: 0.1 }, { dx: 10, dy: 28, rot: -0.1 } ]; limbs.forEach(limb => { ctx.save(); ctx.translate(limb.dx, limb.dy); ctx.rotate(limb.rot); if(blowing) ctx.rotate(Math.sin(Date.now() / 50) * 0.1); ctx.beginPath(); ctx.ellipse(0, 0, limbWidth, limbHeight, 0, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); ctx.strokeStyle = 'rgba(210, 180, 140, 0.5)'; ctx.beginPath(); ctx.moveTo(-2, -4); ctx.lineTo(2, 2); ctx.stroke(); ctx.restore(); }); ctx.fillStyle = GINGER_COLOR; ctx.strokeStyle = GINGER_LINE_COLOR; ctx.lineWidth = 2; ctx.beginPath(); ctx.ellipse(0, 5, 20, 30, 0, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); ctx.strokeStyle = 'rgba(210, 180, 140, 0.6)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(-10, -10); ctx.lineTo(5, -15); ctx.stroke(); ctx.beginPath(); ctx.moveTo(-15, 5); ctx.lineTo(-5, 10); ctx.stroke(); ctx.beginPath(); ctx.moveTo(5, 15); ctx.lineTo(15, 10); ctx.stroke(); ctx.fillStyle = '#333'; ctx.beginPath(); ctx.arc(-8, -10, 2.5, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(8, -10, 2.5, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#333'; ctx.lineWidth = 2; ctx.fillStyle = '#8e0000'; ctx.beginPath(); if(blowing) { ctx.ellipse(0, 2, 8, 10, 0, 0, Math.PI); ctx.lineTo(8, 2); ctx.fill(); ctx.stroke(); } else { ctx.arc(0, 0, 10, 0.2 * Math.PI, 0.8 * Math.PI); ctx.stroke(); } ctx.fillStyle = '#8d6e63'; ctx.strokeStyle = '#5d4037'; ctx.lineWidth = 2; ctx.beginPath(); ctx.ellipse(0, -28, 28, 10, 0, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); ctx.beginPath(); ctx.moveTo(-15, -28); ctx.lineTo(-12, -45); ctx.quadraticCurveTo(0, -50, 12, -45); ctx.lineTo(15, -28); ctx.closePath(); ctx.fill(); ctx.stroke(); ctx.strokeStyle = '#f0ad4e'; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(-13, -32); ctx.lineTo(13, -32); ctx.stroke(); ctx.restore(); } function loop() { ctx.clearRect(0, 0, GAME_WIDTH, GAME_HEIGHT); let blowAngle = 0; let powerRatio = 0; let maxReach = 0; if(isBlowing && gameActive) { let dx = mouseX - blowStartX; let dy = mouseY - blowStartY; let dragDist = Math.hypot(dx, dy); if (dragDist > 10) { blowAngle = Math.atan2(dy, dx); powerRatio = Math.min(dragDist, 200) / 200; maxReach = 350 * powerRatio; } } // --- 落ち葉の計算と描画 --- leaves.forEach(leaf => { if(!leaf.active) return; // 風による力 if(isBlowing && maxReach > 0) { let objDx = leaf.x - blowStartX; let objDy = leaf.y - blowStartY; let objDist = Math.hypot(objDx, objDy); if (objDist > 0 && objDist < maxReach) { let objAngle = Math.atan2(objDy, objDx); let angleDiff = objAngle - blowAngle; while (angleDiff > Math.PI) angleDiff -= Math.PI * 2; while (angleDiff < -Math.PI) angleDiff += Math.PI * 2; if (Math.abs(angleDiff) < 0.45) { let turbulence = (Math.random() - 0.5) * 0.4; let force = (30 * powerRatio) / Math.max(objDist, 30); // 風で押し出される力+乱流 leaf.vx += Math.cos(blowAngle + turbulence) * force; leaf.vy += Math.sin(blowAngle + turbulence) * force; // 風を受けると強く回転する leaf.va += (Math.random() - 0.5) * 0.8 * powerRatio; } } } // 動いている時だけ「ふぁさふぁさ(横揺れ)」する処理を追加 let speed = Math.hypot(leaf.vx, leaf.vy); if(speed > 0.5) { // スピードに応じて、進行方向とは少し違う方向へランダムにブレる let wobble = (Math.random() - 0.5) * speed * 0.2; leaf.x += Math.cos(leaf.angle) * wobble; leaf.y += Math.sin(leaf.angle) * wobble; // 空中を舞っている間は少しずつ回転を続ける leaf.va += (Math.random() - 0.5) * 0.05; } // 摩擦と移動(重力による下方向の強制移動は削除) leaf.vx *= leaf.friction; leaf.vy *= leaf.friction; leaf.x += leaf.vx; leaf.y += leaf.vy; // 回転の摩擦と適用 leaf.va *= 0.95; leaf.angle += leaf.va; // 画面外判定 if(leaf.x < -30 || leaf.x > GAME_WIDTH + 30 || leaf.y < -30 || leaf.y > GAME_HEIGHT + 30) { leaf.active = false; } if(leaf.active) { ctx.save(); ctx.translate(leaf.x, leaf.y); ctx.rotate(leaf.angle); ctx.fillStyle = leaf.color; leaf.draw(ctx, leaf.size); ctx.restore(); } }); // --- 置物の計算と描画 --- ornaments.forEach(o => { if(isBlowing && maxReach > 0) { let objDx = o.type === 'lantern' ? (o.x + o.width/2) - blowStartX : o.x - blowStartX; let objDy = o.type === 'lantern' ? (o.y + o.height/2) - blowStartY : o.y - blowStartY; let objDist = Math.hypot(objDx, objDy); if (objDist > 0 && objDist < maxReach) { let objAngle = Math.atan2(objDy, objDx); let angleDiff = objAngle - blowAngle; while (angleDiff > Math.PI) angleDiff -= Math.PI * 2; while (angleDiff < -Math.PI) angleDiff += Math.PI * 2; if (Math.abs(angleDiff) < 0.4) { let force = (8 * powerRatio) / Math.max(objDist, 40); o.vx += Math.cos(blowAngle) * force; o.vy += Math.sin(blowAngle) * force; } } } o.vx *= o.friction; o.vy *= o.friction; o.x += o.vx; o.y += o.vy; if(!o.moved && gameActive) { let moveDist = Math.hypot(o.x - o.originX, o.y - o.originY); if(moveDist > o.moveThreshold) { o.moved = true; score -= o.penalty; if(score < 0) score = 0; scoreEl.innerText = score; triggerPenalty(); } } ctx.save(); ctx.lineWidth = 3; if (o.type === 'stone') { ctx.beginPath(); ctx.arc(o.x, o.y, o.radius, 0, Math.PI * 2); ctx.fillStyle = o.moved ? '#6d4c41' : o.color; ctx.fill(); ctx.strokeStyle = o.edgeColor; ctx.stroke(); ctx.strokeStyle = 'rgba(0,0,0,0.1)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.arc(o.x - 5, o.y - 5, 8, 0.5, 2.5); ctx.stroke(); } else if (o.type === 'lantern') { ctx.translate(o.x, o.y); ctx.fillStyle = o.moved ? '#5d4037' : o.color; ctx.strokeStyle = o.edgeColor; ctx.fillStyle = 'rgba(0,0,0,0.2)'; ctx.fillRect(5, 5, o.width, o.height); ctx.fillStyle = o.moved ? '#5d4037' : o.color; ctx.fillRect(0, 0, o.width, o.height); ctx.strokeRect(0, 0, o.width, o.height); ctx.fillStyle = o.moved ? '#444' : '#fff9c4'; ctx.fillRect(7, 8, o.width - 14, 15); ctx.strokeRect(7, 8, o.width - 14, 15); ctx.fillStyle = o.edgeColor; ctx.beginPath(); ctx.moveTo(-5, 0); ctx.lineTo(o.width + 5, 0); ctx.lineTo(o.width/2, -15); ctx.closePath(); ctx.fill(); ctx.stroke(); } ctx.restore(); }); if(isBlowing && maxReach > 0 && gameActive) { ctx.beginPath(); ctx.moveTo(blowStartX, blowStartY); ctx.lineTo(mouseX, mouseY); ctx.strokeStyle = 'rgba(255, 255, 255, 0.9)'; ctx.lineWidth = 3; ctx.setLineDash([8, 8]); ctx.stroke(); ctx.setLineDash([]); ctx.beginPath(); ctx.moveTo(blowStartX, blowStartY); ctx.arc(blowStartX, blowStartY, maxReach, blowAngle - 0.45, blowAngle + 0.45); ctx.lineTo(blowStartX, blowStartY); let gradient = ctx.createRadialGradient(blowStartX, blowStartY, 0, blowStartX, blowStartY, maxReach); gradient.addColorStop(0, 'rgba(230, 245, 255, 0.5)'); gradient.addColorStop(1, 'rgba(230, 245, 255, 0.0)'); ctx.fillStyle = gradient; ctx.fill(); } let shogaX = isBlowing && gameActive ? blowStartX : mouseX; let shogaY = isBlowing && gameActive ? blowStartY : mouseY; shogaX = Math.max(30, Math.min(GAME_WIDTH - 30, shogaX)); shogaY = Math.max(50, Math.min(GAME_HEIGHT - 30, shogaY)); drawGingerKun(shogaX, shogaY, isBlowing && gameActive); requestAnimationFrame(loop); } initGame(); loop(); </script></body></html>
■ 今日の学び
今回のゲーム作りで、ふと気づいたことがあります。(^^*)
今回の落ち葉の動きも「ふぁさふぁさって感じで舞うように」と AIに相談してプログラムを調整してもらったのですが、
以前作った「#10 オノマトペ2角取り」というゲームがありましたが、
思えば、私はゲームのイメージを伝えるのに、
ずっとオノマトペ(擬音語・擬態語)を多用しているんです。
「ツツーイ」とか「ふぁさふぁさ」といった感覚的な言葉を投げかけても、
AIはしっかりとこちらの意図を察してくれて、
頭の中で描いている雰囲気に一生懸命近づけようとしてくれます。(^_^)
日本語は特にオノマトペが豊かな言語だと言われていますよね。
もしかすると、こうやって感覚的な音の響きでAIとイメージを共有しながらゲーム作りを進めていくのって、
ほかの国の言葉とは少し違った、
日本語ならではの面白いAIとのコミュニケーションの形なのかもしれないな……
なんて、ふと思いました。(=v=*)
難しい専門用語や数式がわからなくても、
「こんな感じで!」と音で伝えれば、AIがそれを汲み取って形にしてくれる。
言葉のニュアンスまで共有できる、本当に心強い相棒です♪
■ 次回は、ボウリング風ホラーゲームです。
次回は、実験 #13「背中越しのA〜Zタイピング(THE BACK)」以来となる、 久しぶりのホラー系ゲームです。
前回のホラーから引き継いだ「怖い何か」と対峙するゲーム。 今回も、ちょっと不思議で、ちょっと怖い世界観になっています。
次回もぜひ遊びに来てくださいね。(*^v^)ノシ
✨今日も最後まで読んでくださって、ありがとうございます。
みーの実験室は、みなさんの応援で元気に動いています。(*^v^*)






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