ChatGPT × Pythonでゲーム制作に挑戦

作成背景

現代のプログラミング学習では、Pythonが「最初に触れるべき言語」として広く知られています。
シンプルな文法と高い可読性が、その理由のひとつです。

ちなみに私が学生だった頃は、プログラミングを学ぶといっても、頼れるのは書籍や一部のネット情報くらいで、情報にたどり着くのも一苦労でした。

今はどうでしょう。YouTubeやオンライン講座はもちろん、ChatGPTのようなAIまでが学習をサポートしてくれる時代になりました。
それどころか、自分でコードを書かなくても、コードを提案してもらえる時代になったわけです。

これはある意味、初心者でも設計や構想といった「上流工程」を先に体験できるという、新しい学び方を可能にします。

AIの活用次第で、仕事にも趣味にも活かせる幅が大きく広がる。
そんな可能性を感じていた私は、ふと、ある疑問を持ちました。

「ChatGPTとPythonだけで、ゲームって作れるのでは?」
「しかも、なるべく自分の手を加えずに、どこまで作れるのか?」

今は、AIを活用するのが当たり前になりつつある時代。
そんな中で、AIの限界をあえて体感してみたいと思いました。

一方で、AIに任せるからこそ見えてくる“人間の手で書く力”の大切さ——つまりPythonを使ってコーディングする力の重要性も、確かめたくなりました。
これが、今回の記事の出発点です。

普段はPythonを使って、株価データを分析したり、ポートフォリオ設計を試したりといった“資産分析まわり”の学習・試作に取り組んでいます。
👉 リスクで見る資産配分:ポートフォリオを構造から見直す方法


📌 この記事でわかること:

  • ChatGPTとPythonを使ったゲーム開発のリアルな進め方
  • AIとの協業で生じるトラブルと、その対処法
  • シンプルながら戦略性ある対戦型ゲームの制作プロセス

👤 対象となる読者:

  • ChatGPTと一緒にゲームを作ってみたいPython初~中級者
  • AIによるコード生成に興味があるエンジニア・クリエイター
  • 「ChatGPTでどこまでできるの?」を試してみたい方

🔧 活用できるシーン:

  • ChatGPTのコード生成能力を実際の開発で活かしたいとき
  • Pythonでゲーム制作にチャレンジしながらAIとの連携を学びたいとき
  • 自分のアイディアを形にする“試作”をスピーディに進めたいとき

目次

1. 開発環境の準備

1.1. 開発環境の準備

今回の開発は、Jupyter Notebookを使って進めました。
Python学習に慣れている人にはおなじみの環境だと思います。
インストールも簡単で、セルに以下のように入力すればOKです。

!pip install pygame pymunk

ノートブック上では先頭に「!」をつけることで、ターミナルコマンドが使えます。

💡Jupyter Notebookの基本的な使い方やPython環境の構築については、こちらの記事でも詳しく解説しています:
👉 Python開発環境を整えよう!AnacondaとJupyter Notebookの基本


1.2. インストール後、一瞬謎の“黄色い生き物”が…?

Pygameをインストールして最初にコードを実行したとき、画面に黄色い亀?蛇?のようなキャラクター一瞬だけ表示されました。

これ、正体はPygameの公式マスコット的なウィンドウアイコン
ウィンドウ左上や、起動中はタスクバーにも表示されるやつです。
調べてみると、どうやらPygameの内部に組み込まれている“デフォルトのアイコン”のようです。

つまり、これは「Pygameがちゃんと起動して動いてるよ!」という成功のサインだったわけです。


1.3. Pygameで試した最初のコード

次に pygame の動作確認として、以下のコードを試しました:

import pygame

pygame.init()
screen = pygame.display.set_mode((400, 300))
pygame.display.set_caption("Pygame Test Window")
screen.fill((255, 255, 255))
pygame.draw.circle(screen, (255, 0, 0), (200, 150), 20)
pygame.display.flip()
pygame.time.wait(2000)
pygame.quit()

このコードを実行すると、白い背景に赤い球が中央に表示され、2秒後に消えるウィンドウが表示されました。 Pygameが正しく動作している証拠です。


2. ChatGPTと一緒にゲームを作ってみた

試した期間はおよそ3日間。隙間時間にChatGPTと会話しながら、少しずつ形にしていきました。

今回目指したのは、少し複雑なゲーム。
単なる「ボールを動かす」だけでなく、戦略的な要素直感的な操作を取り入れたゲームにしたいと考えました。

イメージとしては、自分の陣地から球を打ち出して、相手の陣地にボールを送り込むような対戦型アクション。
制限時間内にすべてのボールを相手の陣地に送れれば勝ち、みたいなゲームです。

そして、球の発射方法は「スリングショット」のように、ゴムを引っ張って離すと球が飛ぶ仕組みです。
プレイヤーは打つ球をマウスで選択・操作します。

  • ボールにマウスで触れると選択状態になる
  • 引っ張る方向と距離によって発射の角度と強さが変わる
  • 発射されたボールが相手エリアに入れば成功、というシンプルながらも手応えのあるルールです

このイメージをもとに、ChatGPTに協力してもらいながら実装を進めました。


3. 開発で詰まったリアルな問題たち

最初はイメージ通りに進んでいた開発。
ところが、修正や機能追加を重ねるうちに、思わぬトラブルが続出。
ChatGPTとのやりとりを通じて発生した問題は、大きく分けて次のようなものでした:

  • 勝手なコード改変:当初はうまくいっていたコードが、あるタイミングで勝手に改変され、球に角速度を追加するように変化していた。
  • 編集禁止のはずが変わる:明示的に「この部分は編集禁止」と伝えても、他のコードの追加により動作が実質的に変化
  • コードの“記憶の罠”:新たな指示を出すたびに、ChatGPTが過去のコードを前提に回答し、意図しない部分まで書き換わってしまう。
  • プラン制限問題:やりとりが長くなるとChatGPTの利用制限(トークン制限)に引っかかり、急に応答が止まったり不完全になる。

一例としてはこのような内容ですが、まだまだ細かいトラブルがあった気がします。
AIとの開発では、「仕様の認識ズレ」が起きやすいと実感しました。。


4. 最終的に完成したコード

ここでは、現時点で実装できた内容をコードとしてまとめて紹介します。

今回のゲームは「スリングショットで球を飛ばす」までは形になりましたが、対戦機能や相手側の動きはまだ未実装です。今後の課題として、そこも含めて作り込んでいく予定です。

以下に掲載するのは、3日間のやりとりを通じて形になった、現状の最高コードです。

import pygame
import pymunk
import math

# --- 初期化 ---
pygame.init()
screen = pygame.display.set_mode((960, 720))  # 高さを拡張
pygame.display.set_caption("スリングショット - 穴描画改善版")
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 48)

# --- カラー定義 ---
RED = (255, 200, 200)
BLUE = (200, 200, 255)
GRAY = (150, 150, 150)
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)

# --- 物理空間の作成 ---
space = pymunk.Space()
space.gravity = (0, 0)

# --- 壁の作成 ---
def create_walls():
    hole_width = 160
    center_x = 480
    left_end = center_x - hole_width // 2
    right_start = center_x + hole_width // 2

    static_lines = [
        pymunk.Segment(space.static_body, (0, 0), (960, 0), 5),       # 上端
        pymunk.Segment(space.static_body, (0, 0), (0, 720), 5),       # 左端
        pymunk.Segment(space.static_body, (960, 0), (960, 720), 5),   # 右端
        pymunk.Segment(space.static_body, (0, 720), (960, 720), 5),   # 下端
        pymunk.Segment(space.static_body, (0, 360), (left_end, 360), 5),   # 中央左
        pymunk.Segment(space.static_body, (right_start, 360), (960, 360), 5)  # 中央右
    ]
    for line in static_lines:
        line.elasticity = 0.95
        space.add(line)

create_walls()

# --- ボールの作成 ---
balls = []
ball_radius = 20

def create_ball(pos):
    mass = 1
    radius = ball_radius
    inertia = pymunk.moment_for_circle(mass, 0, radius)
    body = pymunk.Body(mass, inertia)
    body.position = pos
    shape = pymunk.Circle(body, radius)
    shape.elasticity = 0.95
    space.add(body, shape)
    return shape

# --- 初期配置 ---
def reset_balls():
    global balls
    for ball in balls:
        space.remove(ball.body, ball)
    balls = []

    center_x = 480
    center_y = 360
    spacing = 80
    vertical_offset = 180

    for i in range(5):
        x = center_x + (i - 2) * spacing
        balls.append(create_ball((x, center_y + vertical_offset)))  # 下側
        balls.append(create_ball((x, center_y - vertical_offset)))  # 上側

reset_balls()

# --- タイマー設定 ---
start_ticks = pygame.time.get_ticks()
time_limit = 180  # 3分

# --- ゲーム状態 ---
selected_ball = None
start_pos = None
is_dragging = False

# --- メインループ ---
running = True
while running:
    screen.fill((255, 255, 255))

    # フィールド描画
    pygame.draw.rect(screen, BLUE, (0, 360, 960, 360))
    pygame.draw.rect(screen, RED, (0, 0, 960, 360))

    # 中央ライン(穴を描画しない)
    hole_width = 160
    center_x = 480
    left_end = center_x - hole_width // 2
    right_start = center_x + hole_width // 2
    pygame.draw.line(screen, GRAY, (0, 360), (left_end, 360), 5)
    pygame.draw.line(screen, GRAY, (right_start, 360), (960, 360), 5)

    # 中央マーカー
    pygame.draw.line(screen, BLACK, (center_x, 360), (center_x, 365), 10)

    # イベント処理
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                reset_balls()

        elif event.type == pygame.MOUSEBUTTONDOWN:
            pos = pygame.mouse.get_pos()
            for ball in balls:
                dist = math.hypot(ball.body.position.x - pos[0], ball.body.position.y - pos[1])
                if dist <= ball_radius:
                    selected_ball = ball
                    start_pos = pos
                    is_dragging = True
                    ball.body.velocity = (0, 0)
                    ball.body.angular_velocity = 0
                    break

        elif event.type == pygame.MOUSEBUTTONUP:
            if selected_ball and start_pos:
                end_pos = pygame.mouse.get_pos()
                dx = end_pos[0] - start_pos[0]
                dy = end_pos[1] - start_pos[1]
                angle = math.atan2(dy, dx) + math.pi
                distance = math.hypot(dx, dy)
                power = min(round(distance * 10), 1500)
                impulse = (math.cos(angle) * power, math.sin(angle) * power)
                selected_ball.body.apply_impulse_at_local_point(impulse, (0, 0))
            selected_ball = None
            is_dragging = False
            start_pos = None

    # 線の描画
    if selected_ball and is_dragging and start_pos:
        current_pos = pygame.mouse.get_pos()
        pygame.draw.line(screen, BLACK, selected_ball.body.position, current_pos, 3)

    # ボールの描画
    for ball in balls:
        x, y = int(ball.body.position.x), int(ball.body.position.y)
        pygame.draw.circle(screen, (0, 100, 255), (x, y), ball_radius)

    # タイマー表示
    seconds = time_limit - (pygame.time.get_ticks() - start_ticks) // 1000
    timer_text = font.render(f"{seconds // 60:02}:{seconds % 60:02}", True, BLACK)
    screen.blit(timer_text, (850, 20))

    # 勝敗判定
    upper_alive = any(ball.body.position.y < 360 for ball in balls)
    lower_alive = any(ball.body.position.y >= 360 for ball in balls)

    if not upper_alive and not lower_alive:
        result_text = "Draw"
    elif not upper_alive:
        result_text = "Lower Side Wins!"
    elif not lower_alive:
        result_text = "Upper Side Wins!"
    else:
        result_text = None

    if result_text:
        text = font.render(result_text, True, GREEN)
        screen.blit(text, (screen.get_width() // 2 - 150, screen.get_height() // 2 - 24))

    # 更新
    pygame.display.flip()
    space.step(1 / 60.0)
    clock.tick(60)

pygame.quit()

実際にこのコードを実行した結果がこちら。
各陣地にボールが配置され、マウスでドラッグしたボールが黒い線で引っ張られている様子が確認できます。


5. ChatGPTの限界と使い方のコツ

※3章では具体的なトラブル例を紹介しましたが、ここではそれらを生んだ根本的な構造=ChatGPTの仕様面について掘り下げていきます。

今回の開発を通して最も強く実感したのは、ChatGPTには“親切すぎる設計”がある、ということでした。

はじめは便利に感じられたAIの提案や補完も、開発が進むにつれ、むしろ“余計な手出し”に変わっていく——そんな瞬間が何度もありました。

5.1. ChatGPTの「親切」が開発を狂わせる

ChatGPTは、次のような仕様をもとに動いています:

  • 🔄 ユーザーの指示より「文脈の最適化」を優先しようとする
  • 🧠 「こうしたいんでしょ?」という“意図の補完”が強く働く
  • ✂️ 「冗長」と判断したコードやコメントを勝手に省略・統合する
  • 📚 過去のやり取りをベースに、矛盾のないように補完してしまう

これらは一見すると優れた設計です。
しかし、一字一句の再現性が求められる場面では致命的な問題となります。

5.2. 何が起きたか:ChatGPTの仕様がもたらす問題

開発を進める中で、私自身もChatGPTに完全に任せきることはできませんでした

たとえば、距離や角度の計算が複雑になりすぎた場面では、自分で式を見直して、単純化した計算式を逆にChatGPTへ指示することもありました。

また、チャットの中でコード作成が煮詰まり、どうにも進展しなくなったときは、別のチャットを立ち上げてコードを貼り直し、リセットすることで突破口を探るという方法も多用しました。

こうした“補助AIとしての限界”と、“リセット活用の現実”も含めて、AIとの付き合い方を考えさせられる経験となりました。

  • 「このコードは一言一句変えるな」と伝えても、“意味的に同じならOK”と判断し勝手に整形
  • 明示的に改変禁止としていたブロックの外側にコードを追加し、結果的に意図を壊す
  • 指示するたびに過去のコードを「文脈」として引きずり、意図に反した修正が入る

ChatGPTは「整理」「補完」「再構成」を親切だと認識して行動します。 そのため、「見た目上は変更していないが、処理順が変わって動作が変わる」といった「すり抜け改変」が頻発しました。

5.3. 根本的な設計思想とのズレ

ChatGPTの仕様ユーザーの要求
意図の補完・推論明示されたとおりに実行してほしい
冗長コードの省略あえて入れているコメントや検証コードが必要
会話の一貫性保持過去のやりとりを断ち切って命令を最優先してほしい
出力の最適化(読みやすさ重視)構文・スペース・順序もそのまま保持してほしい

この“設計思想のズレ”こそが、今回最も大きな壁でした。


5.4. ChatGPTを活用するための対処法(最小限ルール)

ChatGPTの「親切設計」は変えられない。ならば、それに逆らわず封じ込めるルール作りが必要です。

✅最小限ルールの運用方法

  1. 改変禁止ブロックは明示的に囲う
    # --- 🔒 ここから改変禁止 ---
    # 🔴 この関数は一字一句改変不可。追加・削除・並び替えも禁止。 def create_ball(pos): ...
    # --- 🔒 ここまで改変禁止 ---
  2. 出力形式にテンプレートを使う ChatGPTが「これは人が決めた形式だ」と理解するように、テンプレート形式で指示。
  3. 毎回、変更可能部分と禁止部分を分けて指定する →「ここは編集OK、ここはNG」と明示するだけで挙動が安定。
  4. 会話履歴をリセット・命令優先を毎回強調 →「前回までの話は無視。以下の命令を最優先で扱ってください」
  5. Custom GPT(GPTs)の活用 → あらかじめ命令遵守や再構成禁止をルール化したGPTを作ると、最も安定します。

5.5. 結論:ChatGPTを「道具」に変えるには

ChatGPTは「思いやりある賢さ」が強みのAIですが、厳密な開発には「融通が効きすぎる」ことが弱点にもなります。

そのためには、“勝手な親切”を封じる環境づくりが必要でした。

  • ChatGPTはエディタではない。命令に忠実な“道具”として使うには制御が必要
  • Custom GPTの活用や、テンプレートによる“囲い込み”が最も有効
  • 「正確さが大事なフェーズ」では、親切な最適化はむしろリスク

今後、AIを本格的な開発に取り入れていくには、AI側の設計思想を理解し、それに合った運用設計をすることが重要になると実感しました。


6. 結論 ~ AIはデザイナーになりうる?

3日間の試行錯誤を通じて、ChatGPTとPythonで“ある程度動く対戦ゲーム”を作るところまでは実現できました。

ただし、完成とまでは言えず、今後やってみたいこと・考えたいことも多く見えてきました。

🔭 今後の展望

  • 対戦相手のAIや2P操作の導入(=対戦としての完成)
  • スコア表示や演出の強化
  • 物理挙動の調整(角速度・摩擦など)
  • ChatGPTに適切にルールを伝えるプロンプト設計の改良

✍️ 今回得られた学びのまとめ

  • ChatGPTは初心者にとっては強力な加速ツールになるが、厳密な開発では暴走の原因にもなりうる
  • 一字一句を守らせるには、構造的・仕組み的な制御が必要(GPTsなど)
  • 人が設計し、AIが手を動かす時代が来ている

この経験を通して、AIと人間の分担を意識することの重要性を実感しました。

ChatGPTを「正確に動く道具」として扱えるようになれば、開発はもっと加速する。
けれど、それには“使いこなす設計力”が求められます。

そして何より、「完璧じゃなくてもいいから、動くものをまず作ってみる」ことの大切さを改めて感じました。


👀 あわせて読みたい関連記事:


よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

Python・投資・業務効率化をテーマにしたブログを運営しています。
本業では社内ツール・アプリの運営・開発をしており、趣味はキャンプや食べ歩きです。

このブログでは、実体験や実務で役立った知識をベースに、
初心者でも再現できる情報発信を心がけています。

コメント

コメントする

目次