見出し画像

素人が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 っていうライブラリ(画像描画とかサウンド再生をやってくれるやつ)を使ってたりして、そこへの初期化とかもまとめてやってくれてるんだ。

だから

  1. 音声を再生するためのサウンドモジュール

  2. 画面(ディスプレイ)を扱うためのモジュール

  3. 入力デバイス(キーボード、ゲームパッド)を扱うモジュール

…など、それぞれを動ける状態にするために 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()」でキレイにお片付けしてあげるのがマストってわけ~!




いいなと思ったら応援しよう!