ChatGPT 自動プログラミング実験: 「マリオ風ゲーム」を作らせてみた!
自分が今まで理系の学問系中心に、内容を深く理解するためのシミュレータープログラムを作成してきました。このシミュレーター、簡単なものなら結構短いプログラムで書けてしまうこともあります。
「それなら、ChatGPTに全てプログラムを書かせるのも有り?」とふと思いました。ということで、様々なブラウザで動くようなシミュレーターやゲームをChatGPTに作成させる実験をしていきたいと思っています!
目次
今回のお題: マリオ風ゲームの作成
今回のお題は、マリオです。一番有名なゲームでもありますし、ちょうど2023年GW現在、マリオの映画も公開中ですので。
記念して、どれだけマリオっぽいゲームが自動で作れるか実験してみました!
ChatGPT 初回命令
ChatGPTへの命令
まずは↓の命令でプログラムを出力させてみました。
コード
生成されたプログラムが↓ですね。3つわけて出ていました。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Simple Platformer</title> <link rel="stylesheet" href="styles.css"> </head> <body> <canvas id="gameScreen"></canvas> <script src="game.js"></script> </body> </html>
body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #222; } canvas { border: 1px solid #fff; }
const canvas = document.getElementById('gameScreen'); const ctx = canvas.getContext('2d'); canvas.width = 800; canvas.height = 400; class Player { constructor(x, y, width, height, color) { this.x = x; this.y = y; this.width = width; this.height = height; this.color = color; this.velocityX = 0; this.velocityY = 0; this.onGround = false; } draw() { ctx.fillStyle = this.color; ctx.fillRect(this.x, this.y, this.width, this.height); } update() { this.x += this.velocityX; this.y += this.velocityY; if (this.y + this.height < canvas.height) { this.velocityY += 1; } else { this.y = canvas.height - this.height; this.velocityY = 0; this.onGround = true; } } } const player = new Player(100, 100, 50, 50, 'blue'); function gameLoop() { ctx.clearRect(0, 0, canvas.width, canvas.height); player.update(); player.draw(); requestAnimationFrame(gameLoop); } gameLoop(); document.addEventListener('keydown', (event) => { if (event.code === 'Space' && player.onGround) { player.velocityY = -15; player.onGround = false; } }); document.addEventListener('keydown', (event) => { if (event.code === 'ArrowRight') { player.velocityX = 5; } else if (event.code === 'ArrowLeft') { player.velocityX = -5; } }); document.addEventListener('keyup', (event) => { if (event.code === 'ArrowRight' || event.code === 'ArrowLeft') { player.velocityX = 0; } });
生成されたゲーム
↓のようなゲームができました。全然マリオじゃないし、ゲームではないけどとりあえず、、。最低限はできている。キーボードの左右で移動、空白でジャンプができます。勝手にそういう設定になりました。
*画面内クリックしてから、左右と空白で操作できます
改変1: 敵を出現させて踏めるようにする
改変命令
さすがに敵がでないとマリオではないので、指導。
生成コード
↓のように変更点だけ、提示してくれる方式になりました。
// 1. 敵キャラクターを表すEnemyクラスを作成 class Enemy { constructor(x, y, width, height, color) { this.x = x; this.y = y; this.width = width; this.height = height; this.color = color; } draw() { ctx.fillStyle = this.color; ctx.fillRect(this.x, this.y, this.width, this.height); } } // 2. 敵キャラクターを生成し、画面に表示 const enemy = new Enemy(400, 300, 50, 50, 'red'); // 敵キャラクターをゲームループに追加 function gameLoop() { ctx.clearRect(0, 0, canvas.width, canvas.height); player.update(); player.draw(); enemy.draw(); // 追加 checkCollision(); // 追加 requestAnimationFrame(gameLoop); } // 3. プレイヤーと敵キャラクターの衝突判定 function checkCollision() { const playerX = player.x; const playerY = player.y; const playerWidth = player.width; const playerHeight = player.height; const enemyX = enemy.x; const enemyY = enemy.y; const enemyWidth = enemy.width; const enemyHeight = enemy.height; if ( playerX < enemyX + enemyWidth && playerX + playerWidth > enemyX && playerY < enemyY + enemyHeight && playerY + playerHeight > enemyY ) { // 4. 衝突が発生した場合、プレイヤーが敵キャラクターの上にいるか判定 if (playerY + playerHeight < enemyY + enemyHeight / 2) { enemy.x = -100; // 敵を画面外に移動して倒す } } }
生成されたゲーム
敵出現!でもなんか横からぶつかっても倒せてしまう、、、
*画面内クリックしてから、左右キーと空白キーで操作できます
改変2: 敵に横からぶつかったら、プレイヤーが死ぬようにする
改変命令
さすがにこれではプレイヤーが死なないので、改変
生成コード
↓のように変更点提示されました。結構ながくなってくるので、プログラム書いたことない人だと、どこを変えればよいか分からなくなってきそう。
class Player { // ... 既存のコード ... constructor(x, y, width, height, color) { // ... 既存のコード ... this.isDead = false; // 1. isDeadプロパティを追加 } // ... 既存のコード ... } // ... 既存のコード ... function checkCollision() { // ... 既存のコード ... if ( playerX < enemyX + enemyWidth && playerX + playerWidth > enemyX && playerY < enemyY + enemyHeight && playerY + playerHeight > enemyY ) { // 2. 衝突が発生した場合、プレイヤーが敵キャラクターの上にいるか判定 if (playerY + playerHeight < enemyY + enemyHeight / 2) { enemy.x = -100; // 敵を画面外に移動して倒す } else { player.isDead = true; // プレイヤーを死亡状態にする } } } function gameLoop() { // プレイヤーが死んでいない場合のみ、ゲームループを続ける if (!player.isDead) { ctx.clearRect(0, 0, canvas.width, canvas.height); player.update(); player.draw(); enemy.draw(); checkCollision(); requestAnimationFrame(gameLoop); } else { // プレイヤーが死んだ場合、ゲームオーバーのメッセージを表示 ctx.font = '48px sans-serif'; ctx.fillStyle = 'white'; ctx.fillText('Game Over', canvas.width / 2 - 100, canvas.height / 2); } }
生成されたシミュレーター
ちゃんと横からぶつかると、ゲームオーバーになるようになりました。ただ、プレイヤーのほうが敵より上なら、、、と判定してるので、上からぶつかっても倒せてしまう、、
*画面内クリックしてから、左右キーと空白キーで操作できます
改変3: 敵を定期的に出現させて、スコアを表示させる
改変命令
これだと敵一体倒したら終わりだし、スコアもなにもないので改変
生成コード
↓のように変更点だけ、提示してくれる方式になりました。
// 1. スコアを保持する変数を追加 let score = 0; // ... 既存のコード ... function checkCollision() { // ... 既存のコード ... if ( playerX < enemyX + enemyWidth && playerX + playerWidth > enemyX && playerY < enemyY + enemyHeight && playerY + playerHeight > enemyY ) { if (playerY + playerHeight < enemyY + enemyHeight / 2) { enemy.x = -100; // 敵を画面外に移動して倒す score += 10; // 2. 敵を倒したときにスコアを加算 spawnEnemy(); // 3. 敵を倒したら、3秒後に次の敵が現れるようにする } else { player.isDead = true; // プレイヤーを死亡状態にする } } } // 2. スコアを表示する関数を追加 function drawScore() { ctx.font = '24px sans-serif'; ctx.fillStyle = 'white'; ctx.fillText(`Score: ${score}`, canvas.width - 150, 30); } // 3. 敵を倒したら、3秒後に次の敵が現れるようにする function spawnEnemy() { setTimeout(() => { enemy.x = canvas.width - 50; enemy.y = canvas.height - 50; }, 3000); } function gameLoop() { if (!player.isDead) { ctx.clearRect(0, 0, canvas.width, canvas.height); player.update(); player.draw(); enemy.draw(); checkCollision(); drawScore(); // スコアを表示 requestAnimationFrame(gameLoop); } else { // ... 既存のコード ... } }
生成されたシミュレーター
いいですね。ゲームっぽくなってきました
*画面内クリックしてから、左右と空白で操作できます
改変4: 地面を表示させる
改変命令
さすがに風景の見た目がわるいので、指示
生成コード
↓のような感じになりました。いいですね。曖昧な指示でも意図が伝わる。
function drawBackground() { // 1. キャンバスの背景色を空色に設定 ctx.fillStyle = 'skyblue'; ctx.fillRect(0, 0, canvas.width, canvas.height); } function drawGround() { // 2. 地面を表す矩形を描画し、茶色に塗る const groundHeight = 50; ctx.fillStyle = 'saddlebrown'; ctx.fillRect(0, canvas.height - groundHeight, canvas.width, groundHeight); } function gameLoop() { if (!player.isDead) { // ... 既存のコード ... drawBackground(); // 背景を描画 drawGround(); // 地面を描画 player.update(); player.draw(); enemy.draw(); checkCollision(); drawScore(); requestAnimationFrame(gameLoop); } else { // ... 既存のコード ... } }
生成されたシミュレーター
あれ、表示はいいけどなんでプレイヤーが地面の下にいるんだろう、、、
と思って色々指示しましたが、ここはなぜか簡単には直せませんでした、、。修正によってプレイヤー出現位置は地面上に指定できるけど、最初に設定した着地位置が変えられないので結局プレイヤーが↓の位置まで下がってしまう感じですね。こういった文脈が長くなってくると、なかなか日本語指示だけでなおさせるのは難しくなります。
*画面内クリックしてから、左右キーと空白キーで操作できます
改変5: 敵が同じところばかりでるので修正
敵の位置が毎回同じはきついので、ランダム出現するように依頼しました。
ChatGPTへの命令
生成コード
↓のような改変命令がでました。ちなみに初回はなぜかy軸しかランダムにしてくれなかったので、x軸ランダムも追加依頼実はしてます。
function getRandomEnemyY(height) { const minY = canvas.height - GROUND_HEIGHT - height * 2; const maxY = canvas.height - GROUND_HEIGHT - height; return Math.floor(Math.random() * (maxY - minY + 1)) + minY; } function getRandomEnemyX(width) { const minX = canvas.width / 2; const maxX = canvas.width - width; return Math.floor(Math.random() * (maxX - minX + 1)) + minX; } function spawnEnemy() { setTimeout(() => { // 2. 敵をスポーンする関数で、x座標をランダムに設定 enemy.x = getRandomEnemyX(enemy.width); enemy.y = getRandomEnemyY(enemy.height); }, 3000); }
生成されたゲーム
なんか厳しい位置に敵が現れるし、ぶつかって倒してるように見えるし、いまだにプレイヤーが地面下にいるのは気になるけど。とりあえずゲームとしては最低限のレベルになりましたね、、
*画面内クリックしてから、左右キーと空白キーで操作できます
結論:何度も修正は必要だが、最低限のゲームの骨組みなら作れる!
今回は何回かの指示で、マリオ風ゲームをつくっていきました。やっぱりいきなり出てきたゲームは別物であったため、少しずつ指示してつくっていきました。基本的には少しずつなおせばいいんですが、今回の地面下に配置されるのとか直りにくいバグもあるので、そしたら手でやっぱり修正する必要がありますね、、、
ということで完全に全自動は現状きついかと思います><
⇒「ChatGPTプログラム生成実験」カテゴリ記事一覧
その他関連カテゴリ