見出し画像

無限ランゲームはAIへの指示だけでどこまで作れるのか?

chatGPT-4oなら1行の指示だけで「もぐら叩きゲーム」を作ってくれたので、次は無限ランゲームは何処まで作れるのか、興味本位で試しました。


まずは前回同様にGPT君に『ジャバスクリプトで無限ランゲームを作りたいです、必要なプログラムを考えてファイルリストを作ってください』と指示します。

返答はダメなプログラムでしたが、最初と最後でどれくらいコードが変わるのかを比較するために記載しておきます。


無限ランゲームを作成するためには、複数のファイルを組み合わせて実装する必要があります。以下は、必要なファイルリストとその簡単な説明です。

ファイルリスト

  1. index.html

    • ゲームの基本的なHTML構造を定義します。

  2. styles.css

    • ゲームのスタイルを定義します。キャラクターや背景、障害物のデザインを含みます。

  3. game.js

    • ゲームのロジックを実装するメインのJavaScriptファイルです。

  4. character.js

    • ゲームのキャラクターに関するロジックを定義します。

  5. obstacle.js

    • 障害物に関するロジックを定義します。

  6. background.js

    • 背景のスクロールに関するロジックを定義します。

  7. score.js

    • スコアの計算と表示に関するロジックを定義します。

  8. assets/

    • ゲームで使用する画像や音声ファイルを保存するディレクトリです。

      • character.png:キャラクターの画像

      • obstacle.png:障害物の画像

      • background.png:背景の画像

ファイル内容の例

1. index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>無限ランゲーム</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div id="game-container">
        <canvas id="game-canvas"></canvas>
        <div id="score">Score: 0</div>
    </div>
    <script src="game.js"></script>
    <script src="character.js"></script>
    <script src="obstacle.js"></script>
    <script src="background.js"></script>
    <script src="score.js"></script>
</body>
</html>

2. styles.css

body {
    margin: 0;
    padding: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: #87CEEB;
}

#game-container {
    position: relative;
}

#game-canvas {
    border: 1px solid #000;
}

#score {
    position: absolute;
    top: 10px;
    left: 10px;
    font-size: 24px;
    color: #fff;
}

3. game.js

window.onload = function() {
    const canvas = document.getElementById('game-canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = 800;
    canvas.height = 400;

    let score = 0;

    // ゲームの初期化
    function init() {
        // 各モジュールの初期化処理を呼び出す
        character.init();
        obstacle.init();
        background.init();
        score.init();
        
        // ゲームループの開始
        gameLoop();
    }

    // ゲームループ
    function gameLoop() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        background.update();
        character.update();
        obstacle.update();

        background.draw(ctx);
        character.draw(ctx);
        obstacle.draw(ctx);

        score.update();
        score.draw();

        requestAnimationFrame(gameLoop);
    }

    init();
}

4. character.js

const character = (function() {
    let x = 50, y = 300, width = 50, height = 50, vy = 0;
    const gravity = 0.5;
    const jumpStrength = 10;
    const characterImage = new Image();
    characterImage.src = 'assets/character.png';

    function init() {
        document.addEventListener('keydown', function(e) {
            if (e.code === 'Space') {
                vy = -jumpStrength;
            }
        });
    }

    function update() {
        vy += gravity;
        y += vy;

        if (y + height > canvas.height) {
            y = canvas.height - height;
            vy = 0;
        }
    }

    function draw(ctx) {
        ctx.drawImage(characterImage, x, y, width, height);
    }

    return {
        init,
        update,
        draw
    };
})();

5. obstacle.js

const obstacle = (function() {
    let obstacles = [];
    const obstacleImage = new Image();
    obstacleImage.src = 'assets/obstacle.png';

    function init() {
        // 障害物の生成を一定間隔で行う
        setInterval(function() {
            obstacles.push({
                x: canvas.width,
                y: canvas.height - 50,
                width: 50,
                height: 50
            });
        }, 2000);
    }

    function update() {
        for (let i = 0; i < obstacles.length; i++) {
            obstacles[i].x -= 5;
            if (obstacles[i].x + obstacles[i].width < 0) {
                obstacles.splice(i, 1);
                i--;
            }
        }
    }

    function draw(ctx) {
        for (let obstacle of obstacles) {
            ctx.drawImage(obstacleImage, obstacle.x, obstacle.y, obstacle.width, obstacle.height);
        }
    }

    return {
        init,
        update,
        draw
    };
})();

6. background.js

const background = (function() {
    let x = 0;
    const speed = 2;
    const backgroundImage = new Image();
    backgroundImage.src = 'assets/background.png';

    function init() {
        // 特に初期化処理は必要ない
    }

    function update() {
        x -= speed;
        if (x <= -canvas.width) {
            x = 0;
        }
    }

    function draw(ctx) {
        ctx.drawImage(backgroundImage, x, 0, canvas.width, canvas.height);
        ctx.drawImage(backgroundImage, x + canvas.width, 0, canvas.width, canvas.height);
    }

    return {
        init,
        update,
        draw
    };
})();

7. score.js

const score = (function() {
    let currentScore = 0;

    function init() {
        // 特に初期化処理は必要ない
    }

    function update() {
        currentScore++;
    }

    function draw() {
        document.getElementById('score').textContent = 'Score: ' + currentScore;
    }

    return {
        init,
        update,
        draw
    };
})();

8. assets/

  • character.png

  • obstacle.png

  • background.png

これらのファイルとディレクトリを作成し、内容を記述することで、シンプルな無限ランゲームが完成します。各JavaScriptモジュールは、ゲームの特定の機能に対応しており、それぞれの役割を明確に分担することで、コードの管理と拡張が容易になります。


長くなりましたが、以上が出力された結果でした。
このコードで動かして適当な画像ファイルを任意の名前で配置します。
起動しても画面が青くなるだけでゲームが始まりませんでした。

まあそういう事もあるよね。GPT君にフィードバック
最初の画面で青くなったっきりフリーズして動かないんだプログラムを修正して欲しい。

これは修正したコードが出てきて動きました。

鬼が主人公で岡っ引きが障害物です。pngで透過処理してなかったです。
白い部分全部が当たり判定になります。
後ろのねーちゃんは背景です。

キャラのドット絵は昔打った奴で背景はAI生成です。

動いたので、一つ一つ要望を追加していきます。

  • 障害物とプレイヤーがぶつかるとゲームオーバーになる処理

  • 主人公が無限に飛べるのでジャンプは2段ジャンプまでに変更

  • 敵キャラが一定の間隔で出てくるのでランダムに出てくるように変更

  • スコアが1000点毎になると、敵のスピードが少しずつ早くする

  • 出てくる敵の数を増やしたい

  • ときどき、ものすごく早く迫ってくる障害物を出して欲しい

  • 早い障害物は別のグラフィックにしたい

  • 主人公を画面内を左右に移動できるようにしてください

  • 主人公はちゃんと左右に移動するんだけど時々、キー入力に反応しないんだ何故だろう改善してくれないか?

  • 主人公が一定時間無敵になるアイテムを追加してください。


はい。ここで問題発生!

無敵アイテムが実装できませんでした。何度かコードの修正を依頼したけど無理っぽいので、デバックは人間がした方が早いですね。

元のコードに戻して欲しいとリクエストしたのですが、微妙に仕様が違ったりしました。またそれを修正するなどして横滑りしていきます。

こうなると、もう一から作った方がいい感じですね。無敵アイテムの前のコードを晒しておきます。


1. index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>無限ランゲーム</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div id="game-container">
        <canvas id="game-canvas"></canvas>
        <div id="score">Score: 0</div>
        <div id="game-over" style="display: none;">Game Over</div>
    </div>
    <script src="character.js"></script>
    <script src="obstacle.js"></script>
    <script src="background.js"></script>
    <script src="score.js"></script>
    <script src="game.js"></script>
</body>
</html>

2. styles.css

body {
    margin: 0;
    padding: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: #87CEEB;
}

#game-container {
    position: relative;
}

#game-canvas {
    border: 1px solid #000;
}

#score {
    position: absolute;
    top: 10px;
    left: 10px;
    font-size: 24px;
    color: #fff;
}

#game-over {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 48px;
    color: red;
}


3. game.js

window.onload = function() {
    const canvas = document.getElementById('game-canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = 800;
    canvas.height = 400;

    let gameOver = false;

    // 各モジュールにcanvasとctxを渡す
    character.init(canvas, ctx);
    obstacle.init(canvas, ctx);
    background.init(canvas, ctx);
    score.init(canvas, ctx);

    // 衝突判定関数
    function checkCollision(rect1, rect2) {
        return rect1.x < rect2.x + rect2.width &&
               rect1.x + rect1.width > rect2.x &&
               rect1.y < rect2.y + rect2.height &&
               rect1.y + rect1.height > rect2.y;
    }

    // ゲームループの開始
    function gameLoop() {
        if (gameOver) {
            document.getElementById('game-over').style.display = 'block';
            return;
        }

        ctx.clearRect(0, 0, canvas.width, canvas.height);

        background.update();
        character.update();
        obstacle.update(score.getScore());

        background.draw(ctx);
        character.draw(ctx);
        obstacle.draw(ctx);

        score.update();
        score.draw();

        // 衝突判定
        const charRect = character.getRect();
        const obstacles = obstacle.getObstacles();
        for (let obs of obstacles) {
            if (checkCollision(charRect, obs)) {
                gameOver = true;
                break;
            }
        }

        requestAnimationFrame(gameLoop);
    }

    gameLoop();
}

4. character.js

const character = (function() {
    let x = 50, y = 300, width = 50, height = 50, vy = 0, vx = 0;
    const gravity = 0.5;
    const jumpStrength = 10;
    const moveSpeed = 5;
    const characterImage = new Image();
    characterImage.src = 'assets/character.png';

    let canvas, ctx;
    let jumpCount = 0;
    const maxJumps = 2;
    const keys = {};

    function init(c, context) {
        canvas = c;
        ctx = context;

        document.addEventListener('keydown', function(e) {
            keys[e.code] = true;
            if (e.code === 'Space' && jumpCount < maxJumps) {
                vy = -jumpStrength;
                jumpCount++;
            }
        });

        document.addEventListener('keyup', function(e) {
            keys[e.code] = false;
        });
    }

    function update() {
        if (keys['ArrowLeft']) {
            vx = -moveSpeed;
        } else if (keys['ArrowRight']) {
            vx = moveSpeed;
        } else {
            vx = 0;
        }

        vy += gravity;
        y += vy;
        x += vx;

        if (y + height > canvas.height) {
            y = canvas.height - height;
            vy = 0;
            jumpCount = 0; // 地面に着いたらジャンプ回数をリセット
        }

        // 画面外に出ないようにする
        if (x < 0) {
            x = 0;
        }
        if (x + width > canvas.width) {
            x = canvas.width - width;
        }
    }

    function draw() {
        ctx.drawImage(characterImage, x, y, width, height);
    }

    function getRect() {
        return { x, y, width, height };
    }

    return {
        init,
        update,
        draw,
        getRect
    };
})();

5. obstacle.js

const obstacle = (function() {
    let obstacles = [];
    const obstacleImage = new Image();
    obstacleImage.src = 'assets/obstacle.png';
    const fastObstacleImage = new Image();
    fastObstacleImage.src = 'assets/fast_obstacle.png';

    let canvas, ctx;
    const minInterval = 667; // 最小間隔約0.67秒
    const maxInterval = 2000; // 最大間隔2秒
    let speed = 5;
    const speedIncrement = 0.5;
    const fastSpeedMultiplier = 3; // 非常に速い障害物のスピード倍率
    const fastObstacleChance = 0.2; // 非常に速い障害物が出現する確率 (20%)

    function init(c, context) {
        canvas = c;
        ctx = context;

        // 初回の障害物生成
        generateObstacle();
    }

    function generateObstacle() {
        const isFast = Math.random() < fastObstacleChance;
        const obstacleSpeed = isFast ? speed * fastSpeedMultiplier : speed;

        obstacles.push({
            x: canvas.width,
            y: canvas.height - 50,
            width: 50,
            height: 50,
            speed: obstacleSpeed,
            isFast: isFast
        });

        // 次の障害物生成のためのランダムな間隔を設定
        const nextInterval = Math.random() * (maxInterval - minInterval) + minInterval;
        setTimeout(generateObstacle, nextInterval);
    }

    function update(currentScore) {
        // スコアが1000の倍数になるとスピードを増加させる
        if (currentScore % 1000 === 0 && currentScore !== 0) {
            speed += speedIncrement;
        }

        for (let i = 0; i < obstacles.length; i++) {
            obstacles[i].x -= obstacles[i].speed;
            if (obstacles[i].x + obstacles[i].width < 0) {
                obstacles.splice(i, 1);
                i--;
            }
        }
    }

    function draw() {
        for (let obstacle of obstacles) {
            if (obstacle.isFast) {
                ctx.drawImage(fastObstacleImage, obstacle.x, obstacle.y, obstacle.width, obstacle.height);
            } else {
                ctx.drawImage(obstacleImage, obstacle.x, obstacle.y, obstacle.width, obstacle.height);
            }
        }
    }

    function getObstacles() {
        return obstacles;
    }

    return {
        init,
        update,
        draw,
        getObstacles
    };
})();

6. background.js

const background = (function() {
    let x = 0;
    const speed = 2;
    const backgroundImage = new Image();
    backgroundImage.src = 'assets/background.png';

    let canvas, ctx;

    function init(c, context) {
        canvas = c;
        ctx = context;
    }

    function update() {
        x -= speed;
        if (x <= -canvas.width) {
            x = 0;
        }
    }

    function draw() {
        ctx.drawImage(backgroundImage, x, 0, canvas.width, canvas.height);
        ctx.drawImage(backgroundImage, x + canvas.width, 0, canvas.width, canvas.height);
    }

    return {
        init,
        update,
        draw
    };
})();


7. score.js

const score = (function() {
    let currentScore = 0;
    let canvas, ctx;

    function init(c, context) {
        canvas = c;
        ctx = context;
    }

    function update() {
        currentScore++;
    }

    function draw() {
        document.getElementById('score').textContent = 'Score: ' + currentScore;
    }

    function getScore() {
        return currentScore;
    }

    return {
        init,
        update,
        draw,
        getScore
    };
})();


背景はもぐら叩きゲームの時に使った奴にして
キックボードが早い障害物にしました。

そこそこは動くので、今回はトリマ完成とします。

作ってみた雑感

前回のもぐら叩きゲームに引き続き、無限ランゲームも作れました。まだまだ改善の余地はありますが。GPT-4oを使えば簡単なゲームならそれなりに作れるのが分かりました。

ただGPT3.5の時もそうでしたけど、一度横滑りした時の修正は泥沼に入りやすそうです。

機能を付け加えるのはある程度できるけど、デバックはまだまだ苦手そうです。ここは人間で直してあげた方が良さげです。



この記事が参加している募集

この記事が気に入ったらサポートをしてみませんか?