作成背景
現代のプログラミング学習では、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の「親切設計」は変えられない。ならば、それに逆らわず封じ込めるルール作りが必要です。
✅最小限ルールの運用方法
- 改変禁止ブロックは明示的に囲う
# --- 🔒 ここから改変禁止 ---
# 🔴 この関数は一字一句改変不可。追加・削除・並び替えも禁止。 def create_ball(pos): ...
# --- 🔒 ここまで改変禁止 ---
- 出力形式にテンプレートを使う ChatGPTが「これは人が決めた形式だ」と理解するように、テンプレート形式で指示。
- 毎回、変更可能部分と禁止部分を分けて指定する →「ここは編集OK、ここはNG」と明示するだけで挙動が安定。
- 会話履歴をリセット・命令優先を毎回強調 →「前回までの話は無視。以下の命令を最優先で扱ってください」
- 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を「正確に動く道具」として扱えるようになれば、開発はもっと加速する。
けれど、それには“使いこなす設計力”が求められます。
そして何より、「完璧じゃなくてもいいから、動くものをまず作ってみる」ことの大切さを改めて感じました。
👀 あわせて読みたい関連記事:
コメント