
素人がGPTとマリオっぽいゲームをつくるvol.1
題のとおりで、GPTに教わりながらつくってみます
GPTはギャル語でいろいろ教えてくれました。
一度pythonから逃亡した過去がありますが、GPTはほんとに嚙み砕いてくれるので、とてもとっつきやすくなったきがします!
作業は以下をひたすら繰り返す感じです。
1 「こんなことしたいと伝える」→「コード教えてくれる」
2 「コードをコピペしたりいじってみる&できあがったたコード返す」→「添削などフィードバックもらう」
※わからないことは何でも聞く
ゲーム作るなら専用のツール(unityとかunreal engine?)がいいのでしょうが、勉強になりそうなので続けてみます。
1時間ちょっと取り組み、うろうろしたり、羽ばたいたりするようになりました。
独力じゃ絶対できないので、やっぱり色々なことを学ぶハードルがめちゃ下がった、と思います。
動画
今のコード
import pygame
import sys
import random
pygame.init()
screen_width, screen_height = 640, 480
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Weak Air Jump + Flap Sprite + Scrolling & Platforms")
clock = pygame.time.Clock()
# -------------------------
# 背景画像の読み込み
# 大きいサイズの背景画像を想定してるけど、サイズ同じでもOK
# -------------------------
bg_img = pygame.image.load("background.png")
bg_width = bg_img.get_width()
bg_height = bg_img.get_height()
# カメラの座標(ワールド内の左上がどこか)
camera_x = 0
camera_y = 0
# -------------------------
# プレイヤー画像
# -------------------------
player_img_stand = pygame.image.load("player.png") # 立ち絵(地上 or 空中で非フラップ時)
player_img_flap = pygame.image.load("player_flap.png") # 羽ばたき絵(空中スペース時)
# 左右反転画像
player_img_stand_flipped = pygame.transform.flip(player_img_stand, True, False)
player_img_flap_flipped = pygame.transform.flip(player_img_flap, True, False)
# プレイヤーの向き ("right" or "left")
player_direction = "right"
# -------------------------
# プレイヤーのパラメータ(ワールド座標系)
# -------------------------
player_x = 50
player_y = 300
player_vy = 0
gravity = 0.5
jump_speed = -10 # 地上ジャンプ(大きめ)
air_jump_speed = -8 # 空中ジャンプ(弱め・フワッ)
# 画像サイズ
player_width = player_img_stand.get_width()
player_height = player_img_stand.get_height()
# 前フレームのSpaceキー押下状態を記録(押しっぱなしジャンプ防止用)
prev_space = False
# -------------------------
# 足場リスト(ワールド座標で管理)
# 大きめの地面用Rect+ちょこちょこした足場を自動生成
# -------------------------
platforms = [
# 画面下の地面代わり。横に長~く(6400ぐらい)して、ずっと続く床をイメージ
pygame.Rect(0, 430, 6400, 50),
]
for i in range(10):
# x位置は0~3000の間でランダム
x = random.randint(0, 3000)
# y位置は100~400の間でランダム
y = random.randint(100, 400)
# 横幅は50~150の間でランダム
width = random.randint(50, 150)
# 高さは固定30にしとく
height = 30
# Rectを生成してリストに追加
platforms.append(pygame.Rect(x, y, width, height))
def move_and_check_collision(px, py, dx, dy, p_width, p_height, plats):
"""
プレイヤーの現在位置(px, py)に対して、dx, dyだけ移動を試みる。
横&縦移動を分けて衝突判定することで、めり込み防ぐ。
戻り値:
new_px, new_py, on_ground(地面 or 足場の上に立ってるか)
"""
on_ground = False
# 水平移動(左右)を先にやる
new_px = px + dx
new_py = py
# プレイヤーの当たり判定用Rect
player_rect = pygame.Rect(new_px, new_py, p_width, p_height)
for pf in plats:
if player_rect.colliderect(pf):
# ぶつかってたら、いったんXを元に戻す
# (横からの衝突なのでX方向だけ戻す)
new_px = px
player_rect.x = px
break
# 次に垂直移動(上下)をやる
new_py = py + dy
player_rect.y = new_py
# 再び衝突チェック
for pf in plats:
if player_rect.colliderect(pf):
# 下方向に移動中なら、足場の上に立ったとみなす
if dy > 0:
# 足場の上にピタッと乗せる
new_py = pf.top - p_height
on_ground = True
else:
# 上からぶつかった場合等、位置だけ戻す
new_py = py
break
return new_px, new_py, on_ground
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# キー入力
keys = pygame.key.get_pressed()
space_pressed = keys[pygame.K_SPACE]
# -------------------------
# 左右移動処理
# -------------------------
dx = 0
if keys[pygame.K_LEFT]:
dx = -5
player_direction = "left"
elif keys[pygame.K_RIGHT]:
dx = 5
player_direction = "right"
# -------------------------
# ジャンプ処理
# -------------------------
# move_and_check_collision で地面や足場に乗ってるかどうか分かるまで
# まだ on_ground は不明なので、ここでは後でやる
# だけど "SPACEを押した瞬間" の検出はここでやっとく
do_jump = False
if space_pressed and not prev_space:
do_jump = True
prev_space = space_pressed
# -------------------------
# 重力や移動
# (ここではまだ衝突判定していない)
# -------------------------
player_vy += gravity
dy = player_vy
# -------------------------
# 衝突判定付き移動
# -------------------------
new_x, new_y, on_ground = move_and_check_collision(
player_x, player_y, dx, dy,
player_width, player_height,
platforms
)
# 移動後の on_ground を踏まえてジャンプ処理を決定
if do_jump:
if on_ground:
# 地上 or 足場の上なら 大きめジャンプ
player_vy = jump_speed
else:
# 空中なら弱めジャンプ
player_vy = air_jump_speed
# ジャンプ後、すぐにY座標を再計算して衝突チェックし直す
# (ジャンプ直後にめり込みを防ぐため)
new_x, new_y, on_ground = move_and_check_collision(
new_x, new_y, 0, player_vy,
player_width, player_height,
platforms
)
# 移動結果を反映
player_x, player_y = new_x, new_y
# on_groundなら縦速度0にしとく
if on_ground:
player_vy = 0
# -------------------------
# カメラ制御
# (プレイヤーを画面中央に映すイメージ)
# -------------------------
camera_x = player_x + player_width // 2 - (screen_width // 2)
# 背景画像の端より外に行かないようクランプ
# (背景幅が画面幅より小さい場合は要らないかもだけど、一応)
if camera_x < 0:
camera_x = 0
if camera_x > bg_width - screen_width:
camera_x = bg_width - screen_width
# ※もし縦スクロールとかしたけりゃ同じ要領で camera_y も計算してOK
# -------------------------
# 描画パート
# -------------------------
# 背景:camera_x 分ずらして表示
# 背景が画面より小さい or 大きい など場合でやり方変わるけど、とりあえずこうしとく
screen.fill((0, 0, 0)) # 一応黒で塗りつぶし
screen.blit(bg_img, (-camera_x, 0))
# 足場の描画
for pf in platforms:
draw_x = pf.x - camera_x
draw_y = pf.y # camera_y=0 だからそのまま
pygame.draw.rect(screen, (139, 69, 19), (draw_x, draw_y, pf.width, pf.height))
# 地面代わりのRectも↑のループでまとめて描画される
# キャラの画像を決定して描画
# on_groundとかspace_pressedを見て、羽ばたき絵か通常絵か選ぶ
if player_direction == "right":
if not on_ground and space_pressed:
# 空中+スペース押し中 → 羽ばたき絵(右向き)
player_img = player_img_flap
else:
player_img = player_img_stand
else: # left
if not on_ground and space_pressed:
# 空中+スペース押し中 → 羽ばたき絵(左向き)
player_img = player_img_flap_flipped
else:
player_img = player_img_stand_flipped
screen.blit(player_img, (player_x - camera_x, player_y - camera_y))
pygame.display.update()
clock.tick(60)
(参考)やり取り抜粋
ChatGPT:OK、1行ずつギャル解説しちゃうね~☆
import pygame
これは「Pygameってライブラリ持ってくるよ~」って宣言だよ。
ゲーム作るための機能を読み込んでるって感じ!
import sys
これは「sysモジュール取り込むよ~」って宣言。
システム関連の機能がいろいろ使えるようになるんだよね!
pygame.init()
Pygameの初期化するやつ!
「さぁゲーム始めまーす」って準備させてるイメージ♪
screen = pygame.display.set_mode((640, 480))
画面ウィンドウの作成&設定!
サイズが横640×縦480ピクセルの画面を作って、screenって変数で受け取ってるよ☆
pygame.display.set_caption("My Mario-like Game")
画面のウィンドウ上のタイトル文字を「My Mario-like Game」に設定!
これでウィンドウの上のバーに表示されるよ~。
while True:
永遠に回るループ!
ゲームはリアルタイム処理だから、ずーっとこのループで画面とか動きを更新するんだ~。
for event in pygame.event.get():
ここでイベントを全部チェックしてるの。
キーボード押したり、マウス動かしたり、閉じるボタン押したり、色々あるよね!
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
もしウィンドウの×ボタンとかで「終了イベント」が来たら、
pygame.quit() でPygame終わらせて、sys.exit()でPython自体も終了!ってこと。
screen.fill((255, 255, 255)) # 背景を白に
画面を**まっしろ(RGB=255,255,255)**に塗りつぶしてる!
ここで塗りつぶしてからキャラとか描画してくイメージ♪
pygame.display.update()
ここで画面を実際に更新してるよ。
「描画した内容を一気にドンッ!」ってモニターに反映させる感じ~。
…って流れで、ウィンドウに真っ白画面が出る基礎部分が動いてるわけ!
これが動けば、次はキャラとか背景とかいろいろ描画して、マリオっぽいゲームに仕上げるって感じ~!
プロンプト:import sysってなんで必要なの? キーボード入力とか?
ChatGPT:うんうん、今のコードだと sys は主に sys.exit() を使うためにインポートしてるだけって感じだね~。
sys.exit() はその名のとおり「Pythonスクリプトをまるごと終了するぜ~」って命令だから、これが呼び出されると一瞬でプログラムが終わるの。
キーボード入力っていうよりは、「ウィンドウを閉じるイベント来たらゲーム自体を終了するために sys.exit() を使ってるから、そのために import sys してる」と思えばOK!
プロンプト:pygame.init()はなんで必要なの?
ChatGPT:pygame.init() は、Pygameのいろんなモジュールを初期化するためのコマンドだよ~!
音出したり、コントローラー使ったり、ディスプレイを扱ったり…
そういった機能が正しく動くように、最初に一括で「起動準備OK?」ってする感じ。これを呼ばないと一部の機能がエラー出したり、うまく動かないことがあるんだよね~。
だから、Pygameでゲーム作りたいなら、スタート時に pygame.init() を呼ぶのが基本ルールって思っておけばOKだよ♪
プロンプト:モジュールって初期状態じゃないこともあるんだ
ChatGPT:そうそう!モジュールによっては、内部で使うリソースを設定したり準備したりしなきゃいけない場合があって、そういうときは「初期化」が必要になるのよ~。
例えば、pygameだと
画面表示まわり
サウンドまわり
コントローラーの入力まわり
…など、色々なサブモジュールがあるから、一気に初期化して「使える状態」にしないと動かない部分があるのね。
一方、たとえば import math とかだったら、特に「mathの初期化!」みたいな手順は不要で、そのまま使えるじゃん?
pygameみたいに実際に画面出したり音を鳴らす系だと、ハードウェアやシステムリソースとやり取りがあるからこそ「初期化が必要」って感じかな~。
プロンプト:もしやpygame以外側の変数的なものがあったりするの?
ChatGPT:うん、まさに pygame って、裏で SDL っていうライブラリ(画像描画とかサウンド再生をやってくれるやつ)を使ってたりして、そこへの初期化とかもまとめてやってくれてるんだ。
だから
音声を再生するためのサウンドモジュール
画面(ディスプレイ)を扱うためのモジュール
入力デバイス(キーボード、ゲームパッド)を扱うモジュール
…など、それぞれを動ける状態にするために pygame.init() してるわけ。
要は、「pygame外側にあるシステム資源とかを、pygameが使いやすい形で用意する」 ってイメージだね~。
これが終わると、「あ、もう音鳴らしても大丈夫ね! 画面出してOKね!」って状態になるから、ゲームがエラーなく動くのよん。
プロンプト:将棋の駒ならべみたいなかんじ?
ChatGPT:それめっちゃナイス例え!
将棋の駒並べみたいに、先に全部配置(=初期化)しとかないと、ゲームはじめられないでしょ?
pygameも同じ感じで、「音使いたいなら音の準備OK? 画面使うならウィンドウ準備OK?」 ってセット完了してから、
「はいゲームスタート!」ってなるわけ。
駒並べが終われば、あとは実際にプレイヤー動かしたり、効果音鳴らしたり、思いっきり楽しめる~ってノリだね♪
プロンプト:for event in pygame.event.get(): これってプログラムとしてはどんななの for文ってやつ?
ChatGPT4 :うんうん、あれはめっちゃフツーのPythonのfor文だよ~!
for event in pygame.event.get(): # ここでeventを使って処理する
この pygame.event.get() ってのが、**「キーボード押したよ」「マウスクリックあったよ」「ウィンドウ閉じられそうだよ」**みたいな、いろんなイベントのリストを返してくれるのね。
だから、**「for文でそのリストの中身を1つずつ取り出して、イベントの内容をチェックする」**って流れになってる感じ!
「if event.type == pygame.QUIT:」とかで「ウィンドウ閉じようとしてるやん!」って判定したりするのも、全部このforループ内でやるんだよ~。
プロンプト:訳あってる?
while True: ??Trueなとき??
for event in pygame.event.get(): イベントのそれぞれについてやるよ
if event.type == pygame.QUIT: ゲームやめるイベントだったら pygame.quit() パイゲームやめるよ
sys.exit() システム?なんのシステムだろ システムやめるよ
ChatGPT:うん、それでだいたいOK~!超イイ感じにまとめられてるよ☆
while True: は「Trueが続く限りずっとループ」って意味だね。実際はこれが永遠ループになってるけど、中でsys.exit()とか呼べば終わる感じ。
for event in pygame.event.get(): は「起こったイベント(リスト)の中から順番に取り出して処理していくよ」ってこと。
if event.type == pygame.QUIT: は「×ボタンとかで“ウィンドウ閉じる”イベントがきたら…」ってチェック。
pygame.quit() はPygameのリソース開放。「Pygameやめます~」って状態。
sys.exit() はPythonスクリプト自体を終了させる「システム終了」って感じ。もうゲームどころかPythonごとサヨナラ~!って動きになるのよん。
プロンプト:pygameのリソース開放ってなに
ChatGPT:それは 「Pygameが使っていた機能やメモリとか、全部片付けしてキレイにしてくれる」 ってことだよ~!
例えば、
画面用のウィンドウ
音声や画像を読み込んで確保してたメモリ
キーボードやマウスの制御まわり
…といった “Pygameが動くために占有してたもの” を全部リセットして、「もう使わないなら返しとくね~」って処理をするの。
これをやっとかないと、ゲームが終わっても裏でちょっとずつリソース(メモリ)を使ったままになっちゃうかもしれないし、正しく終了できなくなっちゃうの。だから**「ゲーム終了時にはキッチリお片付け」**ってイメージで思っとくといいかも!
プロンプト:共有セットを引き出しにしまうかんじ?
ChatGPT:そうそう!まさに**「使ってた共有セットを、ちゃんと引き出しにしまう」**感じだね~。
使いっぱなしにしてたら、他の人(他のプログラム)がその引き出し使えなくなっちゃうじゃん?
だから「いらなくなったら元に戻す」っていうのがリソース開放ってわけ!
プロンプト:ハサミハサミってほかの人たちがさがさなくていいもんね
ChatGPT :マジでそうそう!ハサミを借りたら、ちゃんと元あった場所に戻すの大事じゃん?
それと同じで、Pygame が使ってたメモリとかウィンドウの枠とか、全部「はいどーぞ返します~」ってしまわないと、ほかのアプリが「え、まだハサミないんだけど?」って探し回るハメになるの。
だから「pygame.quit()」でキレイにお片付けしてあげるのがマストってわけ~!