■66個目 「アカメカッパの自転車暴走ゲーム」
こんにちは、AIと作る100のゲーム実験室の みー です。(*^_^*)/
今回も、ちょっと(いや、かなり?)やんちゃなカッパが主役の
爆走アクションゲームを紹介します。
主人公は、赤い目が特徴の「アカメカッパ」。
ある日、川の土手に置いてあった「ママチャリ」を偶然見つけてしまいます。
「よし、これに乗って隣の川まで爆走だ!」
そんな無謀な思いつきから、アカメカッパの暴走劇が幕を開けます。
今回こだわったのは、アカメカッパの“爆走スタイル”。
ただ速いだけではありません。 舌を出したり、腕を大きく広げたり、
挑発的なポーズを決めながら 超高速で土手道を駆け抜けていきます。
見ているこちらがハラハラしてしまうような、
シュールで笑える光景が魅力です。
舞台は、人里近くの田舎道。 道端には木が生え、
お地蔵さんが優しく(?)見守ってくれています。
「ガッシャン!」
もし道から外れてしまったら……大変なことに。
隣の川まで無事にたどり着けるのか、ハラハラドキドキの爆走劇です。
そしてゴールに到着すると、アカメカッパは勢いよく川へダイブ!(^^*)
無事にたどり着けたら、次のステージへ進めるかもしれません。
■ AIとのやりとりで面白かった部分
■ゲーム仕様の相談からスタート
みー:(^v^) 今日もゲームを作ります。html形式で動かせるゲーム。
アカメカッパの自転車暴走ゲームです。
いたずら好きのアカメカッパが、
川のそばに置いてあったママチャリに乗り、隣の川まで爆走します。
ゲームとしては、左から右へ向かって
ランダム生成された曲がりくねった1本道のコースに沿って、
制限時間内にマウスで線を引きます。
その線の上を、自転車に乗ったアカメカッパが
挑発的なポーズをとりながら高速で走ります。
線がコースから外れていると「ガッシャン!」でゲームオーバー。
川までたどり着くと次のステージへ。難易度は徐々に上がっていきます。
AI: いらっしゃい、みーさん。今日も元気そうだね。
例のアカメカッパがママチャリで爆走するゲーム、作ってみたよ。
「なぞった線を爆走する」「外れたらガッシャン」「挑発ポーズ」っていう組み合わせ、なかなかシュールで笑える仕上がりになった。
テストプレイ中に思わず笑っちゃったよ。
みー:(^^;) このゲームの主人公なので、
アカメカッパと自転車は絵文字じゃなくて表現したいです。
前に作ったアカメカッパのコードからキャラクター作れますか?
貼り付けてみますね。 〈アカメカッパのコード〉
AI: お、前のコードを持ってきたんだね。
なるほど、Canvas APIで描いていたアカメカッパを、
今回の自転車ゲーム用に「画像ファイル(PNG)」として使いたいってことか。
過去の資産を再利用して新しいゲームに組み込むなんて、さすがみーさん。
そのまま使えるように、各ポーズをキャンバスに描画して、
クリック一つで保存できる『画像生成ツール』を作ってみたよ。
みー:( ̄v ̄*)
コードだけで完結するゲームにしたいです。
移動の様子がもう少し見たいので、アカメカッパのスピードを少し落とします。
背景が殺風景なので、木を生やしたり、
各ステージにランダムでお地蔵さんを置いたりしましょう。
AI: 了解。カッパのスピードは半分くらいにして、
ポーズや動きがしっかり見えるように調整したよ。
背景にはランダムに木を生やして、コース脇のどこかに毎回1体だけ
「お地蔵さん」をそっと配置するようにした。
そういえば、お地蔵さんって赤いよだれかけや帽子をつけてることが多いよね。
あれは昔から「赤」が魔除けの色とされていて、
子どもの無事を願う気持ちが込められているんだって。
今回のお地蔵さんにも、ちゃんと赤いよだれかけを描いておいたよ。
……まあ、ママチャリで爆走するカッパの安全まで
守ってくれるかは分からないけど(笑)。
■ 実際の動作
こちらが、AIが最初に作ってくれたゲームの画像です。

(画像:最初にAIが作ってくれたゲームの画像) そしてこちらが、調整を重ねて仕上がったスタート画面です。

(画像:スタート画面)
右側の川まで、時間内にコースから外れないように線を引きます。

(画像:ゲーム画面)
アカメカッパは、引いた線の通りに動きます。

(画像:移動時の挑発ポーズ)
時間切れになったり、コースアウトするとゲームオーバー。

(画像:ゲームオーバー)
ステージ10あたりからは、まさに“難所の峠”レベルの難易度に突入します。
こちらはステージ21の様子です。

(画像:ステージ21)
■ コードのポイント
今回の記事では、AI と相談しながら作った
「アカメカッパの自転車暴走ゲーム」 の HTML コードを公開します。
このコードをそのまま保存するだけで、
あなたのPCでもすぐに遊べます。
ブラウザだけで動く、とてもシンプルなゲームなので、
気軽に試してみてくださいね。
▼ 遊ぶまでの手順
①PC の「メモ帳」(※Windowsの場合)や
Mac の「テキストエディット」など、テキストエディタを開きます。
②下のコードをコピーして貼り付けます。
③「bakusou .html」など、好きな名前で保存します。
(※拡張子は .html にしてください)
④保存したファイルをブラウザで開けば、すぐに遊べます。
■ 遊び方・操作説明
こんにちは!後半では、このゲームの遊び方を詳しく説明しますね。(^^*) アカメカッパが隣の川へたどり着けるかどうかは、あなたの“なぞる力”にかかっています!
① 制限時間内に「道」をなぞろう!
ゲームが始まると、ランダムに曲がりくねった1本道が表示されます。 まずは、その道からはみ出さないように、左から右へマウスで線を引いてください。(^_^*)
制限時間はわずか! 焦らず、でも急いでなぞるのがポイントです。
もし線を引く前に制限時間が終わってしまったら……
アカメカッパは走り出せません。
② カッパが爆走開始!コースアウトに注意!
線を引き終わると、アカメカッパがその線の上を高速で移動し始めます。
この時、カッパは舌を出したり、ポーズをとったりと、
あなたを挑発してきます。(=v=*)
でも、それに惑わされてはいけません!
もし引いた線が少しでも元のコースから外れていたら…… 「ガッシャン!」とクラッシュしてゲームオーバー。(>_<)
③ 無事にゴールして、次の川へ!
見事、コースアウトせずに隣の川(右端)までたどり着けばステージクリア! アカメカッパは豪快に川へダイブします。(^^*)
クリアすると、すぐに次のステージが始まります。 ステージが進むにつれて、道はさらに曲がりくねり、制限時間も短くなっていきます。
あなたはいくつのステージをクリアできるでしょうか? アカメカッパの挑発に負けず、ハラハラドキドキの爆走を楽しんでくださいね!
さあ、レッツ爆走!(^^)
“コードは長いので折りたたんでいます”
▼ここからコード▼(クリックで開く)
<html lang="ja"><head> <meta charset="UTF-8"> <title>アカメカッパの自転車暴走ゲーム</title> <style> body { margin: 0; padding: 0; background-color: #2c3e50; color: #ecf0f1; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; } h1 { margin-bottom: 10px; font-size: 24px; color: #f1c40f; } canvas { background-color: #27ae60; border: 4px solid #bdc3c7; box-shadow: 0 0 20px rgba(0,0,0,0.5); cursor: crosshair; } p { margin-top: 10px; font-size: 14px; } </style></head><body> <h1>アカメカッパの自転車暴走ゲーム</h1> <canvas id="gameCanvas" width="800" height="600"></canvas> <p>左の川からスタート!制限時間内にドラッグしてコース上に線を引け!離すと爆走開始だ!</p> <script> const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); // ゲームの状態管理 let state = 'START'; let stage = 1; let gameOver = false; // コースと線のデータ let coursePoints = []; let drawnPoints = []; let isDrawing = false; // 背景の装飾データ let trees = []; let jizoPos = null; // 難易度調整用変数 let courseWidth = 80; let maxTime = 5000; let timeLeft = maxTime; // アニメーション用変数 let kappaIndex = 0; let frameCount = 0; let currentLane = 1; const runLanes = [1, 4, 0, 2]; // ゲーム初期化 function initStage() { state = 'DRAWING'; drawnPoints = []; isDrawing = false; gameOver = false; courseWidth = Math.max(30, 90 - stage * 8); maxTime = Math.max(2000, 6000 - stage * 400); timeLeft = maxTime; coursePoints = []; let amp1 = 30 + stage * 8; let amp2 = 20 + stage * 5; let freq1 = 0.004 + stage * 0.001; let freq2 = 0.009 + stage * 0.0015; let phase1 = Math.random() * Math.PI * 2; let phase2 = Math.random() * Math.PI * 2; for (let x = 0; x <= 800; x += 5) { let y = 300 + Math.sin(x * freq1 + phase1) * amp1 + Math.sin(x * freq2 + phase2) * amp2; y = Math.max(80, Math.min(520, y)); coursePoints.push({ x, y }); } // 背景の木をランダムに生成 trees = []; for (let i = 0; i < 15; i++) { trees.push({ x: 60 + Math.random() * 680, y: 30 + Math.random() * 540, scale: 0.6 + Math.random() * 0.6 }); } // お地蔵さんをコース脇のランダムな位置に1体配置 let jizoIndex = Math.floor(coursePoints.length * 0.2 + Math.random() * (coursePoints.length * 0.6)); let baseP = coursePoints[jizoIndex]; jizoPos = { x: baseP.x, y: Math.max(30, baseP.y - courseWidth / 2 - 25) }; } // マウスイベント canvas.addEventListener('mousedown', (e) => { if (state === 'START') { initStage(); } else if (state === 'DRAWING') { isDrawing = true; drawnPoints = [{ x: e.offsetX, y: e.offsetY }]; } else if (state === 'GAMEOVER') { stage = 1; initStage(); } }); canvas.addEventListener('mousemove', (e) => { if (state === 'DRAWING' && isDrawing) { let lastP = drawnPoints[drawnPoints.length - 1]; let dist = Math.hypot(e.offsetX - lastP.x, e.offsetY - lastP.y); if (dist > 5) { drawnPoints.push({ x: e.offsetX, y: e.offsetY }); } } }); canvas.addEventListener('mouseup', () => { if (state === 'DRAWING' && isDrawing) startRunning(); }); canvas.addEventListener('mouseleave', () => { if (state === 'DRAWING' && isDrawing) startRunning(); }); // 爆走開始 function startRunning() { isDrawing = false; if (drawnPoints.length < 2) { state = 'GAMEOVER'; gameOver = true; return; } state = 'RUNNING'; gameOver = false; kappaIndex = 0; frameCount = 0; currentLane = 1; } function isOffCourse(px, py) { let minDist = 9999; for (let p of coursePoints) { let d = Math.hypot(p.x - px, p.y - py); if (d < minDist) minDist = d; } return minDist > courseWidth / 2; } // 背景オブジェクトの描画関数 function drawTree(x, y, scale) { ctx.save(); ctx.translate(x, y); ctx.scale(scale, scale); ctx.fillStyle = '#5c4033'; ctx.fillRect(-10, 0, 20, 40); ctx.fillStyle = '#228b22'; ctx.beginPath(); ctx.arc(0, -10, 30, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.arc(-20, -30, 25, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.arc(20, -30, 25, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.arc(0, -50, 25, 0, Math.PI*2); ctx.fill(); ctx.restore(); } function drawJizo(x, y) { ctx.save(); ctx.translate(x, y); ctx.fillStyle = '#95a5a6'; ctx.beginPath(); ctx.ellipse(0, 10, 12, 16, 0, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.arc(0, -12, 11, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = '#c0392b'; ctx.beginPath(); ctx.arc(0, 0, 9, 0, Math.PI, false); ctx.fill(); ctx.strokeStyle = '#2c3e50'; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.arc(-4, -14, 3, 0, Math.PI); ctx.stroke(); ctx.beginPath(); ctx.arc(4, -14, 3, 0, Math.PI); ctx.stroke(); ctx.restore(); } // ママチャリ描画関数 function drawBicycle(x, y, isCrash) { ctx.save(); ctx.translate(x, y); if (isCrash) { ctx.rotate(Math.PI / 3); ctx.translate(0, 15); } ctx.strokeStyle = '#2c3e50'; ctx.lineWidth = 6; ctx.beginPath(); ctx.arc(-30, 0, 15, 0, Math.PI*2); ctx.stroke(); ctx.beginPath(); ctx.arc(30, 0, 15, 0, Math.PI*2); ctx.stroke(); ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(-45, 0); ctx.lineTo(-15, 0); ctx.moveTo(-30, -15); ctx.lineTo(-30, 15); ctx.moveTo(15, 0); ctx.lineTo(45, 0); ctx.moveTo(30, -15); ctx.lineTo(30, 15); ctx.stroke(); ctx.strokeStyle = '#bdc3c7'; ctx.lineWidth = 4; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.beginPath(); ctx.moveTo(-30, 0); ctx.lineTo(-10, -25); ctx.lineTo(20, -25); ctx.lineTo(30, 0); ctx.moveTo(-10, -25); ctx.lineTo(0, 0); ctx.lineTo(20, -25); ctx.stroke(); ctx.fillStyle = '#2c3e50'; ctx.beginPath(); ctx.ellipse(-12, -28, 10, 4, 0, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.moveTo(20, -25); ctx.lineTo(15, -40); ctx.lineTo(25, -45); ctx.stroke(); ctx.strokeStyle = '#7f8c8d'; ctx.lineWidth = 2; ctx.strokeRect(22, -40, 12, 10); ctx.restore(); } // アカメカッパ描画関数 function drawKappa(x, y, lane) { const kColor = '#3a6332'; const beakColor = '#ffcc00'; const shellColor = '#1d3d1a'; ctx.save(); ctx.translate(x, y); let armLeft = { angle: 0, x: -30, y: -50, width: 15 }; let armRight = { angle: 0, x: 30, y: -50, width: 15 }; let legLeft = { x: -20, y: -10, width: 10, height: 15 }; let legRight = { x: 10, y: -10, width: 10, height: 15 }; let headTilt = 0; let headShiftY = 0; let bodyScale = { w: 1, h: 1 }; let dishTilt = 0; let beakTilt = 0; let bodyAngle = 0; let showTongue = false; if (gameOver) { const flicker = Date.now() % 300 < 100 ? beakColor : (Date.now() % 300 < 200 ? 'white' : 'black'); bodyScale.w *= (0.9 + Math.random() * 0.2); bodyScale.h *= (0.9 + Math.random() * 0.2); ctx.fillStyle = flicker; } else { ctx.fillStyle = kColor; } switch(lane) { case 0: bodyAngle = -0.1; armLeft.y = -60; armLeft.x = -15; armLeft.angle = -0.8; armRight.y = -50; armRight.x = 35; armRight.angle = 1.0; headTilt = -0.3; dishTilt = -0.3; beakTilt = -0.2; showTongue = true; break; case 1: bodyAngle = 0.3; bodyScale.h = 0.8; armLeft.y = -40; armLeft.x = -10; armLeft.angle = 1.2; armRight.y = -40; armRight.x = 10; armRight.angle = 1.2; headShiftY = 10; headTilt = 0.2; dishTilt = 0.1; break; case 2: bodyScale.w = 1.1; armLeft.y = -55; armLeft.x = -35; armLeft.angle = -0.3; armRight.y = -65; armRight.x = 40; armRight.angle = -1.2; break; case 3: bodyScale.w = 0.9; bodyScale.h = 0.9; armLeft.y = -60; armLeft.x = -10; armLeft.angle = 1.2; armRight.y = -60; armRight.x = 10; armRight.angle = 1.2; headTilt = -0.1; break; case 4: bodyScale.w = 0.8; armLeft.y = -40; armLeft.x = -25; armLeft.angle = 0.5; armRight.y = -40; armRight.x = 25; armRight.angle = 0.5; headTilt = 0.2; dishTilt = 0.2; break; } if (!gameOver) ctx.fillStyle = kColor; ctx.beginPath(); ctx.ellipse(legLeft.x, legLeft.y, legLeft.width, legLeft.height, 0, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.ellipse(legRight.x, legRight.y, legRight.width, legRight.height, 0, 0, Math.PI*2); ctx.fill(); const bodyW = 60 * bodyScale.w; const bodyH = 60 * bodyScale.h; ctx.save(); ctx.rotate(bodyAngle); ctx.fillRect(-bodyW / 2, -65 + headShiftY, bodyW, bodyH); if (!gameOver) ctx.fillStyle = shellColor; if (gameOver) ctx.globalAlpha = 0.5; const shellW = 44 * bodyScale.w; const shellH = 50 * bodyScale.h; ctx.fillRect(-shellW / 2, -60 + headShiftY, shellW, shellH); ctx.restore(); if (gameOver) ctx.globalAlpha = 1; if (!gameOver) ctx.strokeStyle = kColor; if (gameOver) ctx.strokeStyle = ctx.fillStyle; ctx.lineWidth = armLeft.width; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(armLeft.x, armLeft.y + headShiftY); ctx.lineTo(armLeft.x - 15, armLeft.y + 10 + headShiftY); ctx.lineTo(armLeft.x - 5 + Math.sin(armLeft.angle)*15, armLeft.y + 25 + Math.cos(armLeft.angle)*15 + headShiftY); ctx.stroke(); ctx.lineWidth = armRight.width; ctx.beginPath(); ctx.moveTo(armRight.x, armRight.y + headShiftY); ctx.lineTo(armRight.x + 15, armRight.y + 10 + headShiftY); ctx.lineTo(armRight.x + 5 + Math.sin(armRight.angle)*15, armRight.y + 25 + Math.cos(armRight.angle)*15 + headShiftY); ctx.stroke(); ctx.translate(0, -75 + headShiftY); ctx.rotate(headTilt); if (!gameOver) ctx.fillStyle = kColor; if (gameOver) ctx.fillStyle = ctx.fillStyle; ctx.beginPath(); ctx.arc(0, 0, 25, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = 'red'; ctx.beginPath(); ctx.arc(-10, -5, 7, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.arc(10, -5, 7, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = beakColor; ctx.save(); ctx.rotate(beakTilt); ctx.beginPath(); ctx.moveTo(0, 5); ctx.lineTo(-10, 15); ctx.lineTo(10, 15); ctx.closePath(); ctx.fill(); ctx.fillStyle = '#bfa100'; ctx.beginPath(); ctx.ellipse(0, 15, 10, 3, 0, 0, Math.PI*2); ctx.fill(); if (showTongue && !gameOver) { ctx.fillStyle = '#ff6688'; ctx.beginPath(); ctx.ellipse(5, 18, 4, 8, -0.2, 0, Math.PI*2); ctx.fill(); } ctx.restore(); ctx.fillStyle = '#b3ffb3'; ctx.beginPath(); ctx.ellipse(0, -22, 18, 5, dishTilt, 0, Math.PI*2); ctx.fill(); if (gameOver) { ctx.strokeStyle = beakColor; ctx.lineWidth = 3; ctx.lineCap = 'round'; for (let i = 0; i < 12; i++) { const angle = (Math.PI * 2 / 12) * i + Math.random() * 0.3; const startX = Math.sin(angle) * 35; const startY = Math.cos(angle) * 35; const sparkLen = 15 + Math.random() * 25; ctx.beginPath(); ctx.moveTo(startX, startY); ctx.lineTo(startX + Math.sin(angle) * sparkLen * 0.5, startY + Math.cos(angle) * sparkLen * 0.5 + Math.random() * 5); ctx.lineTo(startX + Math.sin(angle + 0.2) * sparkLen * 0.8, startY + Math.cos(angle + 0.2) * sparkLen * 0.8 + Math.random() * 8); ctx.lineTo(startX + Math.sin(angle - 0.1) * sparkLen, startY + Math.cos(angle - 0.1) * sparkLen + Math.random() * 10); ctx.stroke(); } } ctx.restore(); } // メインループ function update() { // 背景(草地) ctx.fillStyle = '#27ae60'; ctx.fillRect(0, 0, 800, 600); // 背景の木を描画 if (state !== 'START') { for (let t of trees) { drawTree(t.x, t.y, t.scale); } } // 川 ctx.fillStyle = '#3498db'; ctx.fillRect(0, 0, 50, 600); ctx.fillRect(750, 0, 50, 600); if (state !== 'START') { // コース描画 ctx.beginPath(); ctx.moveTo(coursePoints[0].x, coursePoints[0].y); for (let p of coursePoints) { ctx.lineTo(p.x, p.y); } ctx.lineWidth = courseWidth; ctx.strokeStyle = '#d35400'; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.stroke(); // お地蔵さんを描画 if (jizoPos) drawJizo(jizoPos.x, jizoPos.y); // 引いた線の描画 if (drawnPoints.length > 0) { ctx.beginPath(); ctx.moveTo(drawnPoints[0].x, drawnPoints[0].y); for (let p of drawnPoints) { ctx.lineTo(p.x, p.y); } ctx.lineWidth = 4; ctx.strokeStyle = '#f1c40f'; ctx.stroke(); } } if (state === 'START') { drawText('クリックしてゲームスタート!', 400, 300, 40, '#fff'); } else if (state === 'DRAWING') { timeLeft -= 1000 / 60; if (timeLeft <= 0) startRunning(); drawText(`STAGE: ${stage}`, 100, 40, 24, '#fff'); drawTimerBar(); ctx.save(); ctx.translate(30, coursePoints[0].y); ctx.scale(0.5, 0.5); drawBicycle(0, 25, false); drawKappa(0, 0, 3); ctx.restore(); } else if (state === 'RUNNING') { drawText(`STAGE: ${stage}`, 100, 40, 24, '#fff'); // ★ スピードをさらに半分に調整(4 -> 2) kappaIndex += 2; if (kappaIndex >= drawnPoints.length - 1) { kappaIndex = drawnPoints.length - 1; let lastP = drawnPoints[kappaIndex]; if (lastP.x >= 740) { state = 'CLEAR'; setTimeout(() => { stage++; initStage(); }, 1000); } else { state = 'GAMEOVER'; gameOver = true; } } else { let px = drawnPoints[Math.floor(kappaIndex)].x; let py = drawnPoints[Math.floor(kappaIndex)].y; if (isOffCourse(px, py)) { state = 'GAMEOVER'; gameOver = true; } } frameCount++; if (frameCount % 12 === 0) { // ポーズ切り替えも少しゆったりに currentLane = runLanes[Math.floor(Math.random() * runLanes.length)]; } if (state === 'RUNNING') { let currentP = drawnPoints[Math.floor(kappaIndex)]; ctx.save(); ctx.translate(currentP.x, currentP.y); ctx.rotate(Math.sin(frameCount * 0.2) * 0.15); // フラフラの周期もまったりに ctx.scale(0.5, 0.5); drawBicycle(0, 25, false); drawKappa(0, 0, currentLane); ctx.restore(); } } if (state === 'GAMEOVER') { drawText('ガッシャン!', 400, 280, 60, '#e74c3c'); drawText(`記録:ステージ ${stage}`, 400, 350, 30, '#fff'); drawText('クリックで最初からやり直す', 400, 420, 20, '#bdc3c7'); let crashP = drawnPoints[Math.min(Math.floor(kappaIndex), drawnPoints.length - 1)] || coursePoints[0]; ctx.save(); ctx.translate(crashP.x, crashP.y); ctx.scale(0.5, 0.5); drawBicycle(10, 25, true); drawKappa(-10, 10, 1); ctx.restore(); } else if (state === 'CLEAR') { drawText('川へダイブ!', 400, 300, 50, '#3498db'); let endP = drawnPoints[drawnPoints.length - 1]; ctx.save(); ctx.translate(endP.x, endP.y); ctx.scale(0.5, 0.5); drawBicycle(0, 40, false); drawKappa(0, 0, 0); ctx.restore(); } requestAnimationFrame(update); } function drawText(text, x, y, size, color) { ctx.fillStyle = color; ctx.font = `bold ${size}px sans-serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.lineWidth = 4; ctx.strokeStyle = '#000'; ctx.strokeText(text, x, y); ctx.fillText(text, x, y); } function drawTimerBar() { let barWidth = 300 * (timeLeft / maxTime); ctx.fillStyle = timeLeft < maxTime * 0.3 ? '#e74c3c' : '#f1c40f'; ctx.fillRect(250, 20, barWidth, 20); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.strokeRect(250, 20, 300, 20); ctx.fillStyle = '#fff'; ctx.font = '16px sans-serif'; ctx.fillText('TIME', 220, 30); } initStage(); update(); </script></body></html>
■ 今日の学び
今回のゲーム作りには、
実はもうひとつ裏のテーマ(実験)がありました。
それは、「前に作ったゲームのキャラクターコードを、
そのまま新しいゲームに引っ越しさせられるか?」という検証です。
これまで作ってきた「しょうが君」シリーズでも、
同じキャラクターを毎回登場させていましたが、
あの時は毎回AIに特徴を言葉で説明して、ゼロから描いてもらっていました。
そのため、しょうが君の見た目が毎回ほんの少しずつ違っていて、
それはそれで“手作り感”があって楽しかったのですが……(=v=*)
今回は、以前のゲームで実際に使っていた
「アカメカッパの描画プログラム(Canvasコード)」を、
そのままAIにバトンタッチしてみました。
すると、言葉だけでは再現が難しかった
あの独特な「がっちり体格」や
「火花が散るゲームオーバー演出」が、
新しいママチャリゲームへ 一発で、寸分たがわず移植成功!
「過去のコードは、新しいゲームの最高のスプライト(素材)になる」
言葉で特徴を伝えるだけでなく、
すでに動いているコードを“部品”として渡すことで、
キャラクターの個性や世界観をそのまま保ったままシリーズ化できる。
これは、とても大きな発見でした。(^^)
全く違うルールのアクションゲームの中で、 あの時の姿のまま、
アカメカッパがママチャリを漕いで挑発しているのを見たときは、
ちょっと感動してしまいました。(>_<、)
こうして過去の資産を組み合わせながら、
また新しい表現に挑戦していけるのが、
AIと一緒に作るゲーム開発の本当に面白いところですね!
■ 次回は、無限の挑戦。筋肉トレ クリックゲームです。
指先だけで挑む、シンプルなのにクセになる筋トレゲームです。
スタートすると……なんだかやめられなくなります。
次回もぜひ遊びに来てくださいね。(*^v^)ノシ
✨今日も最後まで読んでくださって、ありがとうございます。
みーの実験室は、みなさんの応援で元気に動いています。(*^v^*)






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