見出し画像

GPT-4にモダンなゲームデザインを実装させようとした話

GPT-4に、CSSでモダンなデザインを実装してほしいと依頼すると、いい感じのゲームのデザインを実装してくれるというコメントをいただいたので、以前こちらで公開したGPT-4にサクッと作らせたゲームに対して試してみました。

本記事の作成目的

  • ゲームデザイン向上に関するプロンプトエンジニアリングの1つの知識を共有すること
    具体的には、javascript(+html、css)で開発したこちらの6つの単純なゲームに対して、GPT-4に「モダンなデザインを実装してください」という類の依頼を出した結果に関する知識を共有することです。

対象読者

  • ゲーム開発者

  • ChatGPTに興味がある人

使用言語モデル

GPT-4

質問1(アクションゲームにモダンなデザインの実装依頼)

質問

Q. 以下は簡単なジャンプ回避アクションゲームです。モダンなデザインを実装してください。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Side Scroller Game with Score</title>
    <style>
        canvas {
            display: block;
            margin: 0 auto;
            background-color: #87CEEB;
        }
    </style>
</head>
<body>
    <canvas id="gameCanvas" width="480" height="320"></canvas>
    <script>
        const canvas = document.getElementById("gameCanvas");
        const ctx = canvas.getContext("2d");

        let player = {
            x: 50,
            y: canvas.height / 2,
            size: 20,
            dy: 0,
            gravity: 0.5
        };

        let obstacles = [];
        let obstacleSpeed = 3;
        let gameOver = false;
        let gameStartTime = new Date().getTime();
        let score = 0;

        function drawPlayer() {
            ctx.beginPath();
            ctx.rect(player.x, player.y, player.size, player.size);
            ctx.fillStyle = "black";
            ctx.fill();
            ctx.closePath();
        }

        function createObstacle() {
            const obstacle = {
                x: canvas.width,
                y: canvas.height - 20,
                width: 20,
                height: 20,
                passed: false
            };
            obstacles.push(obstacle);
        }

        function drawObstacles() {
            for (let i = 0; i < obstacles.length; i++) {
                ctx.beginPath();
                ctx.rect(obstacles[i].x, obstacles[i].y, obstacles[i].width, obstacles[i].height);
                ctx.fillStyle = "red";
                ctx.fill();
                ctx.closePath();
            }
        }

        function updateObstacles() {
            for (let i = 0; i < obstacles.length; i++) {
                obstacles[i].x -= obstacleSpeed;

                if (checkCollision(player, obstacles[i])) {
                    gameOver = true;
                    break;
                }

                if (!obstacles[i].passed && obstacles[i].x + obstacles[i].width < player.x) {
                    obstacles[i].passed = true;
                    // スコアに障害物の速度を加算
                    score += obstacleSpeed;
                }
            }
            obstacles = obstacles.filter(o => o.x + o.width > 0);
        }

        function increaseObstacleSpeed() {
            let elapsedTime = new Date().getTime() - gameStartTime;
            obstacleSpeed = 3 + Math.floor(elapsedTime / 10000);
        }

        function drawScore() {
            ctx.font = "20px Arial";
            ctx.fillStyle = "black";
            ctx.fillText("Score: " + score, 10, 30);
        }

        function checkCollision(player, obstacle) {
            return player.x < obstacle.x + obstacle.width &&
                   player.x + player.size > obstacle.x &&
                   player.y < obstacle.y + obstacle.height &&
                   player.y + player.size > obstacle.y;
        }

        function updatePlayer() {
            player.dy += player.gravity;
            player.y += player.dy;

            if (player.y + player.size > canvas.height) {
                player.dy = 0;
                player.y = canvas.height - player.size;
            }
        }

        function jump() {
            if (!gameOver && player.y + player.size >= canvas.height) {
                player.dy = -10;
            }
        }

        function gameLoop() {
            if (!gameOver) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                increaseObstacleSpeed();

                drawPlayer();
                drawObstacles();
                drawScore();
                updatePlayer();
                updateObstacles();

                requestAnimationFrame(gameLoop);
            } else {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.font = "30px Arial";
                ctx.fillStyle = "black";
                ctx.fillText("Game Over", canvas.width / 2 - 70, canvas.height / 2);
                ctx.fillText("Final Score: " + score, canvas.width / 2 - 90, canvas.height / 2 + 40);
            }
        }

        // クリックでジャンプ
        canvas.addEventListener("click", jump);

        // 2秒ごとに新しい障害物を作成
        setInterval(createObstacle, 2000);

        gameLoop();

    </script>
</body>
</html>

回答

A. 以下は、モダンなデザインを実装したジャンプ回避アクションゲームのコードです。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Side Scroller Game with Score</title>
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
    <style>
        body {
            font-family: 'Roboto', sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
        }
        canvas {
            display: block;
            border: 5px solid #333;
            border-radius: 10px;
            background-color: #87CEEB;
        }
    </style>
</head>
<body>
    <canvas id="gameCanvas" width="480" height="320"></canvas>
    <script>
        const canvas = document.getElementById("gameCanvas");
        const ctx = canvas.getContext("2d");

        let player = {
            x: 50,
            y: canvas.height / 2,
            size: 20,
            dy: 0,
            gravity: 0.5
        };

        let obstacles = [];
        let obstacleSpeed = 3;
        let gameOver = false;
        let gameStartTime = new Date().getTime();
        let score = 0;

        function drawPlayer() {
            ctx.beginPath();
            ctx.rect(player.x, player.y, player.size, player.size);
            ctx.fillStyle = "#333";
            ctx.fill();
            ctx.closePath();
        }

        function createObstacle() {
            const obstacle = {
                x: canvas.width,
                y: canvas.height - 20,
                width: 20,
                height: 20,
                passed: false
            };
            obstacles.push(obstacle);
        }

        function drawObstacles() {
            for (let i = 0; i < obstacles.length; i++) {
                ctx.beginPath();
                ctx.rect(obstacles[i].x, obstacles[i].y, obstacles[i].width, obstacles[i].height);
                ctx.fillStyle = "#FF4500";
                ctx.fill();
                ctx.closePath();
            }
        }

        function updateObstacles() {
            for (let i = 0; i < obstacles.length; i++) {
                obstacles[i].x -= obstacleSpeed;

                if (checkCollision(player, obstacles[i])) {
                    gameOver = true;
                    break;
                }

                if (!obstacles[i].passed && obstacles[i].x + obstacles[i].width < player.x) {
                    obstacles[i].passed = true;
                    // スコアに障害物の速度を加算
                    score += obstacleSpeed;
                }
            }
            obstacles = obstacles.filter(o => o.x + o.width > 0);
        }

        function increaseObstacleSpeed() {
            let elapsedTime = new Date().getTime() - gameStartTime;
            obstacleSpeed = 3 + Math.floor(elapsedTime / 10000);
        }

        function drawScore() {
            ctx.font = "20px Roboto";
            ctx.fillStyle = "#333";
            ctx.fillText("Score: " + score, 10, 30);
        }

        function checkCollision(player, obstacle) {
            return player.x < obstacle.x + obstacle.width &&
                   player.x + player.size > obstacle.x &&
                   player.y < obstacle.y + obstacle.height &&
                   player.y + player.size > obstacle.y;
        }

        function updatePlayer() {
            player.dy += player.gravity;
            player.y += player.dy;

            if (player.y + player.size > canvas.height) {
                player.dy = 0;
                player.y = canvas.height - player.size;
            }
        }

        function jump() {
            if (!gameOver && player.y + player.size >= canvas.height) {
                player.dy = -10;
            }
        }

        function gameLoop() {
            if (!gameOver) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                increaseObstacleSpeed();

                drawPlayer();
                drawObstacles();
                drawScore();
                updatePlayer();
                updateObstacles();

                requestAnimationFrame(gameLoop);
            } else {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.font = "30px Roboto";
                ctx.fillStyle = "#333";
                ctx.fillText("Game Over", canvas.width / 2 - 70, canvas.height / 2);
                ctx.fillText("Final Score: " + score, canvas.width / 2 - 90, canvas.height / 2 + 40);
            }
        }

        // クリックでジャンプ
        canvas.addEventListener("click", jump);

        // 2秒ごとに新しい障害物を作成
        setInterval(createObstacle, 2000);

        gameLoop();

    </script>
</body>
</html>

このコードでは、以下の変更を行っています。

  1. Google FontsからRobotoフォントをインポートして、モダンなフォントを使用します。

  2. bodyタグにスタイルを追加して、ゲーム画面を中央に配置します。

  3. canvasのスタイルを更新し、ボーダーと角丸を追加します。

  4. プレイヤー、障害物、テキストの色を変更して、見た目を向上させます。

これらの変更により、ゲームのデザインがモダンなものになります。

結果

実装後
実装前

所感

ある程度はデザインが改善したという印象があります。以下変更点について見ていきます。

  1. Google FontsからRobotoフォントをインポートして、モダンなフォントを使用します。
    -> フォントのRobotはこちらの記事によると人気ランキング1位のGoogleフォントであり、モダンなフォントといえると思います。

  2. bodyタグにスタイルを追加して、ゲーム画面を中央に配置します。
    -> 従来は水平方向に対してのみ中央寄せされていましたが、垂直方向にも中央寄せされることで、デザインが多少向上したと思います。

  3. canvasのスタイルを更新し、ボーダーと角丸を追加します。
    -> 角丸ボーダーの付与で、デザインが多少向上したと思います。

  4. プレイヤー、障害物、テキストの色を変更して、見た目を向上させます。
    -> 上記に加えて、背景色の白も変更されています。従来はゲーム内で利用される色は原色や真っ黒、純白でしたが、これらを避ける色に変更されました。具体的には、赤は#ff0000から#FF4500に、黒は#000から#333に、白は#fffから#f0f0f0に変更されています。一概には言えませんが、原色や真っ黒、純白は避けるべきという意見もあるため、モダンな色に変更されたといえるかもしれません。

質問2(再度依頼)

質問

Q. 以下は簡単なジャンプ回避アクションゲームです。
モダンなデザインを実装してください。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Side Scroller Game with Score</title>
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
    <style>
        body {
            font-family: 'Roboto', sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
        }
        canvas {
            display: block;
            border: 5px solid #333;
            border-radius: 10px;
            background-color: #87CEEB;
        }
    </style>
</head>
<body>
    <canvas id="gameCanvas" width="480" height="320"></canvas>
    <script>
        const canvas = document.getElementById("gameCanvas");
        const ctx = canvas.getContext("2d");

        let player = {
            x: 50,
            y: canvas.height / 2,
            size: 20,
            dy: 0,
            gravity: 0.5
        };

        let obstacles = [];
        let obstacleSpeed = 3;
        let gameOver = false;
        let gameStartTime = new Date().getTime();
        let score = 0;

        function drawPlayer() {
            ctx.beginPath();
            ctx.rect(player.x, player.y, player.size, player.size);
            ctx.fillStyle = "#333";
            ctx.fill();
            ctx.closePath();
        }

        function createObstacle() {
            const obstacle = {
                x: canvas.width,
                y: canvas.height - 20,
                width: 20,
                height: 20,
                passed: false
            };
            obstacles.push(obstacle);
        }

        function drawObstacles() {
            for (let i = 0; i < obstacles.length; i++) {
                ctx.beginPath();
                ctx.rect(obstacles[i].x, obstacles[i].y, obstacles[i].width, obstacles[i].height);
                ctx.fillStyle = "#FF4500";
                ctx.fill();
                ctx.closePath();
            }
        }

        function updateObstacles() {
            for (let i = 0; i < obstacles.length; i++) {
                obstacles[i].x -= obstacleSpeed;

                if (checkCollision(player, obstacles[i])) {
                    gameOver = true;
                    break;
                }

                if (!obstacles[i].passed && obstacles[i].x + obstacles[i].width < player.x) {
                    obstacles[i].passed = true;
                    // スコアに障害物の速度を加算
                    score += obstacleSpeed;
                }
            }
            obstacles = obstacles.filter(o => o.x + o.width > 0);
        }

        function increaseObstacleSpeed() {
            let elapsedTime = new Date().getTime() - gameStartTime;
            obstacleSpeed = 3 + Math.floor(elapsedTime / 10000);
        }

        function drawScore() {
            ctx.font = "20px Roboto";
            ctx.fillStyle = "#333";
            ctx.fillText("Score: " + score, 10, 30);
        }

        function checkCollision(player, obstacle) {
            return player.x < obstacle.x + obstacle.width &&
                   player.x + player.size > obstacle.x &&
                   player.y < obstacle.y + obstacle.height &&
                   player.y + player.size > obstacle.y;
        }

        function updatePlayer() {
            player.dy += player.gravity;
            player.y += player.dy;

            if (player.y + player.size > canvas.height) {
                player.dy = 0;
                player.y = canvas.height - player.size;
            }
        }

        function jump() {
            if (!gameOver && player.y + player.size >= canvas.height) {
                player.dy = -10;
            }
        }

        function gameLoop() {
            if (!gameOver) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                increaseObstacleSpeed();

                drawPlayer();
                drawObstacles();
                drawScore();
                updatePlayer();
                updateObstacles();

                requestAnimationFrame(gameLoop);
            } else {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.font = "30px Roboto";
                ctx.fillStyle = "#333";
                ctx.fillText("Game Over", canvas.width / 2 - 70, canvas.height / 2);
                ctx.fillText("Final Score: " + score, canvas.width / 2 - 90, canvas.height / 2 + 40);
            }
        }

        // クリックでジャンプ
        canvas.addEventListener("click", jump);

        // 2秒ごとに新しい障害物を作成
        setInterval(createObstacle, 2000);

        gameLoop();

    </script>
</body>
</html>

回答

A.以下は、モダンなデザインを実装したジャンプ回避アクションゲームのコードです。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Side Scroller Game with Score</title>
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
    <style>
        body {
            font-family: 'Roboto', sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
            background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
        }
        canvas {
            display: block;
            border: 5px solid #333;
            border-radius: 10px;
            box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
            background-color: #87CEEB;
        }
    </style>
</head>
<body>
    <canvas id="gameCanvas" width="480" height="320"></canvas>
    <script>
        const canvas = document.getElementById("gameCanvas");
        const ctx = canvas.getContext("2d");

        let player = {
            x: 50,
            y: canvas.height / 2,
            size: 20,
            dy: 0,
            gravity: 0.5
        };

        let obstacles = [];
        let obstacleSpeed = 3;
        let gameOver = false;
        let gameStartTime = new Date().getTime();
        let score = 0;

        function drawPlayer() {
            ctx.beginPath();
            ctx.rect(player.x, player.y, player.size, player.size);
            ctx.fillStyle = "#333";
            ctx.fill();
            ctx.closePath();
        }

        function createObstacle() {
            const obstacle = {
                x: canvas.width,
                y: canvas.height - 20,
                width: 20,
                height: 20,
                passed: false
            };
            obstacles.push(obstacle);
        }

        function drawObstacles() {
            for (let i = 0; i < obstacles.length; i++) {
                ctx.beginPath();
                ctx.rect(obstacles[i].x, obstacles[i].y, obstacles[i].width, obstacles[i].height);
                ctx.fillStyle = "#FF4500";
                ctx.fill();
                ctx.closePath();
            }
        }

        function updateObstacles() {
            for (let i = 0; i < obstacles.length; i++) {
                obstacles[i].x -= obstacleSpeed;

                if (checkCollision(player, obstacles[i])) {
                    gameOver = true;
                    break;
                }

                if (!obstacles[i].passed && obstacles[i].x + obstacles[i].width < player.x) {
                    obstacles[i].passed = true;
                    // スコアに障害物の速度を加算
                    score += obstacleSpeed;
                }
            }
            obstacles = obstacles.filter(o => o.x + o.width > 0);
        }

        function increaseObstacleSpeed() {
            let elapsedTime = new Date().getTime() - gameStartTime;
            obstacleSpeed = 3 + Math.floor(elapsedTime / 10000);
        }

        function drawScore() {
            ctx.font = "20px Roboto";
            ctx.fillStyle = "#333";
            ctx.fillText("Score: " + score, 10, 30);
        }

        function checkCollision(player, obstacle) {
            return player.x < obstacle.x + obstacle.width &&
                   player.x + player.size > obstacle.x &&
                   player.y < obstacle.y + obstacle.height &&
                   player.y + player.size > obstacle.y;
        }

        function updatePlayer() {
            player.dy += player.gravity;
            player.y += player.dy;

            if (player.y + player.size > canvas.height) {
                player.dy = 0;
                player.y = canvas.height - player.size;
            }
        }

        function jump() {
            if (!gameOver && player.y + player.size >= canvas.height) {
                player.dy = -10;
            }
        }

        function gameLoop() {
            if (!gameOver) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                increaseObstacleSpeed();

                drawPlayer();
                drawObstacles();
                drawScore();
                updatePlayer();
                updateObstacles();

                requestAnimationFrame(gameLoop);
            } else {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.font = "30px Roboto";
                ctx.fillStyle = "#333";
                ctx.fillText("Game Over", canvas.width / 2 - 70, canvas.height / 2);
                ctx.fillText("Final Score: " + score, canvas.width / 2 - 90, canvas.height / 2 + 40);
            }
        }

        // クリックでジャンプ
        canvas.addEventListener("click", jump);

        // 2秒ごとに新しい障害物を作成
        setInterval(createObstacle, 2000);

        gameLoop();

    </script>
</body>
</html>

このコードでは、背景にグラデーションを追加し、キャンバスに影を追加することでモダンなデザインを実現しています。

結果

実装後
実装前

所感

背景にグラデーションが付与されましたが、これの評価は人によって分かれるかもしれません。

2の回答コードに対して再度、モダンなデザインの実装依頼を出しましたが、変化はありませんでした。

質問3(別プロンプトを試す)

質問

Q. 以下は簡単なジャンプ回避アクションゲームです。
美しい洗練されたデザインを実装してください。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Side Scroller Game with Score</title>
    <style>
        canvas {
            display: block;
            margin: 0 auto;
            background-color: #87CEEB;
        }
    </style>
</head>
<body>
    <canvas id="gameCanvas" width="480" height="320"></canvas>
    <script>
        const canvas = document.getElementById("gameCanvas");
        const ctx = canvas.getContext("2d");

        let player = {
            x: 50,
            y: canvas.height / 2,
            size: 20,
            dy: 0,
            gravity: 0.5
        };

        let obstacles = [];
        let obstacleSpeed = 3;
        let gameOver = false;
        let gameStartTime = new Date().getTime();
        let score = 0;

        function drawPlayer() {
            ctx.beginPath();
            ctx.rect(player.x, player.y, player.size, player.size);
            ctx.fillStyle = "black";
            ctx.fill();
            ctx.closePath();
        }

        function createObstacle() {
            const obstacle = {
                x: canvas.width,
                y: canvas.height - 20,
                width: 20,
                height: 20,
                passed: false
            };
            obstacles.push(obstacle);
        }

        function drawObstacles() {
            for (let i = 0; i < obstacles.length; i++) {
                ctx.beginPath();
                ctx.rect(obstacles[i].x, obstacles[i].y, obstacles[i].width, obstacles[i].height);
                ctx.fillStyle = "red";
                ctx.fill();
                ctx.closePath();
            }
        }

        function updateObstacles() {
            for (let i = 0; i < obstacles.length; i++) {
                obstacles[i].x -= obstacleSpeed;

                if (checkCollision(player, obstacles[i])) {
                    gameOver = true;
                    break;
                }

                if (!obstacles[i].passed && obstacles[i].x + obstacles[i].width < player.x) {
                    obstacles[i].passed = true;
                    // スコアに障害物の速度を加算
                    score += obstacleSpeed;
                }
            }
            obstacles = obstacles.filter(o => o.x + o.width > 0);
        }

        function increaseObstacleSpeed() {
            let elapsedTime = new Date().getTime() - gameStartTime;
            obstacleSpeed = 3 + Math.floor(elapsedTime / 10000);
        }

        function drawScore() {
            ctx.font = "20px Arial";
            ctx.fillStyle = "black";
            ctx.fillText("Score: " + score, 10, 30);
        }

        function checkCollision(player, obstacle) {
            return player.x < obstacle.x + obstacle.width &&
                   player.x + player.size > obstacle.x &&
                   player.y < obstacle.y + obstacle.height &&
                   player.y + player.size > obstacle.y;
        }

        function updatePlayer() {
            player.dy += player.gravity;
            player.y += player.dy;

            if (player.y + player.size > canvas.height) {
                player.dy = 0;
                player.y = canvas.height - player.size;
            }
        }

        function jump() {
            if (!gameOver && player.y + player.size >= canvas.height) {
                player.dy = -10;
            }
        }

        function gameLoop() {
            if (!gameOver) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                increaseObstacleSpeed();

                drawPlayer();
                drawObstacles();
                drawScore();
                updatePlayer();
                updateObstacles();

                requestAnimationFrame(gameLoop);
            } else {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.font = "30px Arial";
                ctx.fillStyle = "black";
                ctx.fillText("Game Over", canvas.width / 2 - 70, canvas.height / 2);
                ctx.fillText("Final Score: " + score, canvas.width / 2 - 90, canvas.height / 2 + 40);
            }
        }

        // クリックでジャンプ
        canvas.addEventListener("click", jump);

        // 2秒ごとに新しい障害物を作成
        setInterval(createObstacle, 2000);

        gameLoop();

    </script>
</body>
</html>

回答

A.以下は、美しい洗練されたデザインを実装したジャンプ回避アクションゲームのコードです。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Side Scroller Game with Score</title>
    <style>
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background-color: #87CEEB;
            margin: 0;
            font-family: Arial, sans-serif;
        }

        canvas {
            border: 5px solid #000;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
        }

        @keyframes playerAnimation {
            0% { transform: translateY(0); }
            50% { transform: translateY(-5px); }
            100% { transform: translateY(0); }
        }

        @keyframes obstacleAnimation {
            0% { transform: translateY(0); }
            50% { transform: translateY(-3px); }
            100% { transform: translateY(0); }
        }
    </style>
</head>
<body>
    <canvas id="gameCanvas" width="480" height="320"></canvas>
    <script>
        const canvas = document.getElementById("gameCanvas");
        const ctx = canvas.getContext("2d");

        let player = {
            x: 50,
            y: canvas.height / 2,
            size: 20,
            dy: 0,
            gravity: 0.5
        };

        let obstacles = [];
        let obstacleSpeed = 3;
        let gameOver = false;
        let gameStartTime = new Date().getTime();
        let score = 0;

        function drawPlayer() {
            ctx.beginPath();
            ctx.rect(player.x, player.y, player.size, player.size);
            ctx.fillStyle = "#000";
            ctx.fill();
            ctx.closePath();
            ctx.save();
            ctx.translate(player.x + player.size / 2, player.y + player.size / 2);
            ctx.scale(1, -1);
            ctx.drawImage(canvas, -player.size / 2, -player.size / 2, player.size, player.size);
            ctx.restore();
        }

        function createObstacle() {
            const obstacle = {
                x: canvas.width,
                y: canvas.height - 20,
                width: 20,
                height: 20,
                passed: false
            };
            obstacles.push(obstacle);
        }

        function drawObstacles() {
            for (let i = 0; i < obstacles.length; i++) {
                ctx.beginPath();
                ctx.rect(obstacles[i].x, obstacles[i].y, obstacles[i].width, obstacles[i].height);
                ctx.fillStyle = "#f00";
                ctx.fill();
                ctx.closePath();
                ctx.save();
                ctx.translate(obstacles[i].x + obstacles[i].width / 2, obstacles[i].y + obstacles[i].height / 2);
                ctx.scale(1, -1);
                ctx.drawImage(canvas, -obstacles[i].width / 2, -obstacles[i].height / 2, obstacles[i].width, obstacles[i].height);
                ctx.restore();
            }
        }

        function updateObstacles() {
            for (let i = 0; i < obstacles.length; i++) {
                obstacles[i].x -= obstacleSpeed;

                if (checkCollision(player, obstacles[i])) {
                    gameOver = true;
                    break;
                }

                if (!obstacles[i].passed && obstacles[i].x + obstacles[i].width < player.x) {
                    obstacles[i].passed = true;
                    // スコアに障害物の速度を加算
                    score += obstacleSpeed;
                }
            }
            obstacles = obstacles.filter(o => o.x + o.width > 0);
        }

        function increaseObstacleSpeed() {
            let elapsedTime = new Date().getTime() - gameStartTime;
            obstacleSpeed = 3 + Math.floor(elapsedTime / 10000);
        }

        function drawScore() {
            ctx.font = "20px Arial";
            ctx.fillStyle = "#000";
            ctx.fillText("Score: " + score, 10, 30);
        }

        function checkCollision(player, obstacle) {
            return player.x < obstacle.x + obstacle.width &&
                   player.x + player.size > obstacle.x &&
                   player.y < obstacle.y + obstacle.height &&
                   player.y + player.size > obstacle.y;
        }

        function updatePlayer() {
            player.dy += player.gravity;
            player.y += player.dy;

            if (player.y + player.size > canvas.height) {
                player.dy = 0;
                player.y = canvas.height - player.size;
            }
        }

        function jump() {
            if (!gameOver && player.y + player.size >= canvas.height) {
                player.dy = -10;
            }
        }

        function gameLoop() {
            if (!gameOver) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                increaseObstacleSpeed();

                drawPlayer();
                drawObstacles();
                drawScore();
                updatePlayer();
                updateObstacles();

                requestAnimationFrame(gameLoop);
            } else {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.font = "30px Arial";
                ctx.fillStyle = "#000";
                ctx.fillText("Game Over", canvas.width / 2 - 70, canvas.height / 2);
                ctx.fillText("Final Score: " + score, canvas.width / 2 - 90, canvas.height / 2 + 40);
            }
        }

        // クリックでジャンプ
        canvas.addEventListener("click", jump);

        // 2秒ごとに新しい障害物を作成
        setInterval(createObstacle, 2000);

        gameLoop();

    </script>
</body>
</html>

このコードでは、以下の変更を行っています。

  1. `body`要素にスタイルを追加して、ゲーム画面を中央に配置し、背景色を設定します。

  2. `canvas`要素に枠線と影を追加します。

  3. プレイヤーと障害物のアニメーションを追加するために、`@keyframes`を使用してアニメーションを定義します。ただし、このアニメーションは現在のコードでは適用されていません。必要に応じて、アニメーションを適用する方法を検討してください。

これらの変更により、ゲームのデザインが美しく洗練されたものになります。

結果

実装後
質問1の実装結果

所感

背景の色が青色に変化したり、canvasに影が付与されましたが質問1の結果と本質的には変化していないようです。

質問4(モダンな技術の実装依頼)

質問

Q.以下は簡単なジャンプ回避アクションゲームです。
モダンな技術を実装してください。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Side Scroller Game with Score</title>
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
    <style>
        body {
            font-family: 'Roboto', sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
            background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
        }
        canvas {
            display: block;
            border: 5px solid #333;
            border-radius: 10px;
            box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
            background-color: #87CEEB;
        }
    </style>
</head>
<body>
    <canvas id="gameCanvas" width="480" height="320"></canvas>
    <script>
        const canvas = document.getElementById("gameCanvas");
        const ctx = canvas.getContext("2d");

        let player = {
            x: 50,
            y: canvas.height / 2,
            size: 20,
            dy: 0,
            gravity: 0.5
        };

        let obstacles = [];
        let obstacleSpeed = 3;
        let gameOver = false;
        let gameStartTime = new Date().getTime();
        let score = 0;

        function drawPlayer() {
            ctx.beginPath();
            ctx.rect(player.x, player.y, player.size, player.size);
            ctx.fillStyle = "#333";
            ctx.fill();
            ctx.closePath();
        }

        function createObstacle() {
            const obstacle = {
                x: canvas.width,
                y: canvas.height - 20,
                width: 20,
                height: 20,
                passed: false
            };
            obstacles.push(obstacle);
        }

        function drawObstacles() {
            for (let i = 0; i < obstacles.length; i++) {
                ctx.beginPath();
                ctx.rect(obstacles[i].x, obstacles[i].y, obstacles[i].width, obstacles[i].height);
                ctx.fillStyle = "#FF4500";
                ctx.fill();
                ctx.closePath();
            }
        }

        function updateObstacles() {
            for (let i = 0; i < obstacles.length; i++) {
                obstacles[i].x -= obstacleSpeed;

                if (checkCollision(player, obstacles[i])) {
                    gameOver = true;
                    break;
                }

                if (!obstacles[i].passed && obstacles[i].x + obstacles[i].width < player.x) {
                    obstacles[i].passed = true;
                    // スコアに障害物の速度を加算
                    score += obstacleSpeed;
                }
            }
            obstacles = obstacles.filter(o => o.x + o.width > 0);
        }

        function increaseObstacleSpeed() {
            let elapsedTime = new Date().getTime() - gameStartTime;
            obstacleSpeed = 3 + Math.floor(elapsedTime / 10000);
        }

        function drawScore() {
            ctx.font = "20px Roboto";
            ctx.fillStyle = "#333";
            ctx.fillText("Score: " + score, 10, 30);
        }

        function checkCollision(player, obstacle) {
            return player.x < obstacle.x + obstacle.width &&
                   player.x + player.size > obstacle.x &&
                   player.y < obstacle.y + obstacle.height &&
                   player.y + player.size > obstacle.y;
        }

        function updatePlayer() {
            player.dy += player.gravity;
            player.y += player.dy;

            if (player.y + player.size > canvas.height) {
                player.dy = 0;
                player.y = canvas.height - player.size;
            }
        }

        function jump() {
            if (!gameOver && player.y + player.size >= canvas.height) {
                player.dy = -10;
            }
        }

        function gameLoop() {
            if (!gameOver) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                increaseObstacleSpeed();

                drawPlayer();
                drawObstacles();
                drawScore();
                updatePlayer();
                updateObstacles();

                requestAnimationFrame(gameLoop);
            } else {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.font = "30px Roboto";
                ctx.fillStyle = "#333";
                ctx.fillText("Game Over", canvas.width / 2 - 70, canvas.height / 2);
                ctx.fillText("Final Score: " + score, canvas.width / 2 - 90, canvas.height / 2 + 40);
            }
        }

        // クリックでジャンプ
        canvas.addEventListener("click", jump);

        // 2秒ごとに新しい障害物を作成
        setInterval(createObstacle, 2000);

        gameLoop();

    </script>
</body>
</html>

回答

A.以下は、簡単なジャンプ回避アクションゲームにモダンな技術を実装したバージョンです。このコードでは、ES6の構文や機能が使用されています。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Side Scroller Game with Score</title>
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
    <style>
        body {
            font-family: 'Roboto', sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
            background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
        }
        canvas {
            display: block;
            border: 5px solid #333;
            border-radius: 10px;
            box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
            background-color: #87CEEB;
        }
    </style>
</head>
<body>
    <canvas id="gameCanvas" width="480" height="320"></canvas>
    <script>
        const canvas = document.getElementById("gameCanvas");
        const ctx = canvas.getContext("2d");

        const player = {
            x: 50,
            y: canvas.height / 2,
            size: 20,
            dy: 0,
            gravity: 0.5
        };

        let obstacles = [];
        let obstacleSpeed = 3;
        let gameOver = false;
        let gameStartTime = new Date().getTime();
        let score = 0;

        const drawPlayer = () => {
            ctx.beginPath();
            ctx.rect(player.x, player.y, player.size, player.size);
            ctx.fillStyle = "#333";
            ctx.fill();
            ctx.closePath();
        };

        const createObstacle = () => {
            const obstacle = {
                x: canvas.width,
                y: canvas.height - 20,
                width: 20,
                height: 20,
                passed: false
            };
            obstacles.push(obstacle);
        };

        const drawObstacles = () => {
            for (const obstacle of obstacles) {
                ctx.beginPath();
                ctx.rect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
                ctx.fillStyle = "#FF4500";
                ctx.fill();
                ctx.closePath();
            }
        };

        const updateObstacles = () => {
            for (const obstacle of obstacles) {
                obstacle.x -= obstacleSpeed;

                if (checkCollision(player, obstacle)) {
                    gameOver = true;
                    break;
                }

                if (!obstacle.passed && obstacle.x + obstacle.width < player.x) {
                    obstacle.passed = true;
                    score += obstacleSpeed;
                }
            }
            obstacles = obstacles.filter(o => o.x + o.width > 0);
        };

        const increaseObstacleSpeed = () => {
            const elapsedTime = new Date().getTime() - gameStartTime;
            obstacleSpeed = 3 + Math.floor(elapsedTime / 10000);
        };

        const drawScore = () => {
            ctx.font = "20px Roboto";
            ctx.fillStyle = "#333";
            ctx.fillText(`Score: ${score}`, 10, 30);
        };

        const checkCollision = (player, obstacle) => {
            return player.x < obstacle.x + obstacle.width &&
                   player.x + player.size > obstacle.x &&
                   player.y < obstacle.y + obstacle.height &&
                   player.y + player.size > obstacle.y;
        };

        const updatePlayer = () => {
            player.dy += player.gravity;
            player.y += player.dy;

            if (player.y + player.size > canvas.height) {
                player.dy = 0;
                player.y = canvas.height - player.size;
            }
        };

        const jump = () => {
            if (!gameOver && player.y + player.size >= canvas.height) {
                player.dy = -10;
            }
        };

        const gameLoop = () => {
            if (!gameOver) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                increaseObstacleSpeed();

                drawPlayer();
                drawObstacles();
                drawScore();
                updatePlayer();
                updateObstacles();

                requestAnimationFrame(gameLoop);
            } else {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.font = "30px Roboto";
                ctx.fillStyle = "#333";
                ctx.fillText("Game Over", canvas.width / 2 - 70, canvas.height / 2);
                ctx.fillText(`Final Score: ${score}`, canvas.width / 2 - 90, canvas.height / 2 + 40);
            }
        };

        canvas.addEventListener("click", jump);

        setInterval(createObstacle, 2000);

        gameLoop();

    </script>
</body>
</html>

このコードでは、以下のモダンな技術が使用されています。

  1. ES6のアロー関数

  2. `const` と `let` を使った変数宣言

  3. テンプレートリテラルを使った文字列結合

これらの変更により、コードはより簡潔で読みやすくなっています。

結果

実装後
実装前

所感

ES6を用いたプログラミングはモダンといえますが、実装後もデザインには変化がないようです。

質問5(モダンなゲームデザインにするためのアイデア)

質問

Q.以下は簡単なジャンプ回避アクションゲームです。
よりモダンなデザインにするためのアイデアを教えてください。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Side Scroller Game with Score</title>
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
    <style>
        body {
            font-family: 'Roboto', sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #87CEEB;
            background-image: linear-gradient(120deg, #a1c4fd 0%, #c2e9fb 100%);
        }
        canvas {
            display: block;
            border: 5px solid #333;
            border-radius: 10px;
            box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
            background-color: #87CEEB;
        }
    </style>
</head>
<body>
    <canvas id="gameCanvas" width="480" height="320"></canvas>
    <script>
        const canvas = document.getElementById("gameCanvas");
        const ctx = canvas.getContext("2d");

        let player = {
            x: 50,
            y: canvas.height / 2,
            size: 20,
            dy: 0,
            gravity: 0.5
        };

        let obstacles = [];
        let obstacleSpeed = 3;
        let gameOver = false;
        let gameStartTime = new Date().getTime();
        let score = 0;

        function drawPlayer() {
            ctx.beginPath();
            ctx.rect(player.x, player.y, player.size, player.size);
            ctx.fillStyle = "#333";
            ctx.fill();
            ctx.closePath();
        }

        function createObstacle() {
            const obstacle = {
                x: canvas.width,
                y: canvas.height - 20,
                width: 20,
                height: 20,
                passed: false
            };
            obstacles.push(obstacle);
        }

        function drawObstacles() {
            for (let i = 0; i < obstacles.length; i++) {
                ctx.beginPath();
                ctx.rect(obstacles[i].x, obstacles[i].y, obstacles[i].width, obstacles[i].height);
                ctx.fillStyle = "#FF4500";
                ctx.fill();
                ctx.closePath();
            }
        }

        function updateObstacles() {
            for (let i = 0; i < obstacles.length; i++) {
                obstacles[i].x -= obstacleSpeed;

                if (checkCollision(player, obstacles[i])) {
                    gameOver = true;
                    break;
                }

                if (!obstacles[i].passed && obstacles[i].x + obstacles[i].width < player.x) {
                    obstacles[i].passed = true;
                    // スコアに障害物の速度を加算
                    score += obstacleSpeed;
                }
            }
            obstacles = obstacles.filter(o => o.x + o.width > 0);
        }

        function increaseObstacleSpeed() {
            let elapsedTime = new Date().getTime() - gameStartTime;
            obstacleSpeed = 3 + Math.floor(elapsedTime / 10000);
        }

        function drawScore() {
            ctx.font = "20px Roboto";
            ctx.fillStyle = "#333";
            ctx.fillText("Score: " + score, 10, 30);
        }

        function checkCollision(player, obstacle) {
            return player.x < obstacle.x + obstacle.width &&
                   player.x + player.size > obstacle.x &&
                   player.y < obstacle.y + obstacle.height &&
                   player.y + player.size > obstacle.y;
        }

        function updatePlayer() {
            player.dy += player.gravity;
            player.y += player.dy;

            if (player.y + player.size > canvas.height) {
                player.dy = 0;
                player.y = canvas.height - player.size;
            }
        }

        function jump() {
            if (!gameOver && player.y + player.size >= canvas.height) {
                player.dy = -10;
            }
        }

        function gameLoop() {
            if (!gameOver) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                increaseObstacleSpeed();

                drawPlayer();
                drawObstacles();
                drawScore();
                updatePlayer();
                updateObstacles();

                requestAnimationFrame(gameLoop);
            } else {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.font = "30px Roboto";
                ctx.fillStyle = "#333";
                ctx.fillText("Game Over", canvas.width / 2 - 70, canvas.height / 2);
                ctx.fillText("Final Score: " + score, canvas.width / 2 - 90, canvas.height / 2 + 40);
            }
        }

        // クリックでジャンプ
        canvas.addEventListener("click", jump);

        // 2秒ごとに新しい障害物を作成
        setInterval(createObstacle, 2000);

        gameLoop();

    </script>
</body>
</html>

回答

A.以下に、あなたのゲームをよりモダンなデザインにするためのいくつかの提案をします。

  1. アニメーションとトランジション: ゲーム内の要素にアニメーションやトランジションを追加することで、視覚的な魅力を高めることができます。例えば、プレイヤーがジャンプしたときや障害物が通過したときにアニメーションを追加することができます。

  2. カラースキームの改善: 現在のカラースキームはシンプルですが、もっと鮮やかな色を使用して視覚的な魅力を高めることができます。また、異なる種類の障害物に対して異なる色を使用することで、ゲームプレイに変化を加えることができます。

  3. 背景画像の追加: 背景に画像を追加することで、ゲームに深みを与えることができます。これは、CSSの`background-image`プロパティを使用して行うことができます。

  4. スコアボードの改善: スコアボードをより目立つようにし、プレイヤーの進行状況を明確に示すことが重要です。これは、フォントサイズを大きくするか、スコアボードの背景色を変更することで達成できます。

  5. レスポンシブデザイン: デバイスのサイズに関係なく、ゲームが正しく表示されるようにすることが重要です。これは、ビューポートの幅に基づいてキャンバスのサイズを調整することで達成できます。

  6. サウンドエフェクト: ジャンプやスコアの増加、ゲームオーバー時などにサウンドエフェクトを追加することで、ゲーム体験を向上させることができます。

以上の提案は、JavaScript、HTML、CSSの知識を用いて実装することができます。ただし、これらの変更を行う前に、現在のコードをバックアップしておくことをお勧めします。

結果

(現在、この結果について調査中であり、本記事では触れません。結果が出次第、別の記事で取り上げる予定です。GPT-4に上記のアイデアを意識したデザインをどこまで実装させられるか調査した結果を公開予定です。)

所感

上記のアイデアはどれも良いゲームデザインにするための重要なアイデアだと思います。これらがGPT-4にどの程度実装できるのかは非常に興味深いことだと思いまし、今後明らかにしたいと考えています。

質問6(別ゲームにモダンなデザインの実装依頼)

質問

Q.(別ゲーム5点に対して質問1と同様のモダンなデザインを実装してくださいという質問をしました。質問は省略します。)

回答

A.(どれも類似の回答のためこちらも省略します。)

結果(オセロゲーム)

オセロゲームの実装後
オセロゲームの実装前

結果(弾幕シューティングゲーム)

シューティングゲームの実装後
シューティングゲームの実装前

結果(モグラ叩きゲーム)

モグラ叩きゲームの実装後
モグラ叩きゲームの実装前

結果(歴史クイズゲーム)

歴史クイズゲームの実装後
歴史クイズゲームの実装前

結果(マルバツゲーム)

マルバツゲームの実装後
マルバツゲームの実装前

所感

どのゲームに対しても、中央寄せ、canvasにボーダーをつける、フォントとカラーをモダンなものに変えるといった質問1と同等の実装が行われました。
基本的には、どのゲームデザインもある程度は良くなったという印象があります。

まとめ

本記事では、ゲーム開発者やChatGPTに興味を持つ方々を対象に、以前こちらで公開したJavascriptでGPT-4にサクッと作らせたゲームに対して「モダンなデザインを実装してください」という依頼をGPT-4に行った結果を公開しました。

ある程度モダンなゲームデザインが実装されたものの、この依頼一つで、人間が思いつかないような独創的で洗練されたデザインにGPT-4が仕上げてくれるということはないようです。

より良いデザインにするためには、アニメーションやトランジション、サウンド、レスポンシブデザイン、画像などを更に実装する必要があると思います。現在、GPT-4にこれらの実装を依頼する作業を行なっている最中であり、結果が出次第、別の記事で取り上げる予定です。

関連記事



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