工学じじいの縁側日記

工学じじいの縁側日記

引退間際の工学じじいがきままに、プログラミングやデバイス、工学について呟きます。

【簡単?】初心者がpython3とpygameでヒット&ブローゲーム作ってみる #02【習作?】

初心者がpython3とpygameでヒット&ブローゲーム作ってみる  #02

f:id:gomta777:20191115111505p:plain

前回から

ko-gaku-jiji.hatenablog.jp

前回は、テキストベースでコンソール画面(コマンドプロンプト)で動作するヒット&ブローゲームを作ってみました。
折角pygameがあるので、ガワを作っていきます。
解説では、画像の貼り付けの基本やpygameの初期化などは省略しています。
その辺は、過去記事で確認してください。
ko-gaku-jiji.hatenablog.jp
ko-gaku-jiji.hatenablog.jp




画面遷移

普段ゲームをしていると、あまり気にしませんが、一般的にゲームはいくつかのシーンからできています。
昔ながらのビデオゲームは大体

  1. タイトル画面
  2. ゲーム画面
  3. ハイスコア・ゲームオーバー画面

をぐるぐる繰り返して、ゲームとして動作します。
それぞれ、タイトル画面からは、ゲーム画面にのみ、ゲーム画面からは、ハイスコア・ゲームオーバー画面にのみ、ハイスコア・ゲームオーバー画面からはタイトル画面へのみ、画面が移動します。

タイトル→ゲーム画面→ゲームオーバー画面→タイトル→・・・

それぞれを、ゲームの状態とすると、3つの状態が考えられるということになります。

状態番号 状態
0 タイトル
1 ゲーム中
2 ゲームオーバー

ゲームライブラリの中では、シーン遷移を標準で持っているものも結構あります。
何個かのシーンを定義しておいて、シーンをスワップしたり、ポップ、プッシュしたりいろいろ制御できたりします。
pygameではそのような機構は備えられていないので、自前で何とかします。

シーン遷移(もどき)

pygameでシーン遷移もどきを実装する方法を考えます。
pygameの主な処理は、簡単には

  • データの初期設定
  • ループ
    • データの更新処理
    • イベント処理
    • 描画処理

という流れになるかと思います。
疑似的にシーン遷移を表すために、以下の様に処理をします。
シーン0、シーン1、シーン2があったとして

  • データの初期設定
  • ループ
    • データの更新処理
    • 現在のシーンの遷移処理
    • イベント処理
    • 描画処理
      • if scene == 0 then シーン0の描画
      • elif scene == 1 then シーン1の描画
      • ellif scene == 2 then シーン2の描画

途中で、条件を満たしたら現在のシーン(カレントシーン)が次のシーンへと遷移する、状態遷移の処理を行います。
そして、描画処理で現在のシーンに従った画面を描画する。
という流れで処理をすると、状態遷移の処理と、描画処理が完全に切り分
けて行えます。
つまり、データ更新の部分では、状態遷移や、各種データを更新するだけ。
描画部分では、シーンと各種データに従って描画処理のみを行う。てな風に、完全に仕事を分けて処理を行います。

実装(設計)

シーン遷移の処理を実装していきます。
シーンを表すものとして、

gamescene という変数を準備します。
そして、数値(状態番号)でシーンを表します。

状態番号 状態
0 タイトル
1 ゲーム中
2 ゲームオーバー

if タイトル画面中にスタートボタンが押された:
gamescene = 1
elif ゲーム画面中にゲームオーバーになった:
gamescene = 2
elif ゲームオーバー中にリトライボタンを押した:
gamescene = 0

としておき、
描画部分では、
if gamescene == 0:
タイトル画面描画
elif gamescene == 1:
ゲーム中画面描画
elif gamescne == 2:
ゲームオーバー画面表示

と、表示のみにとどめてデータの更新はしないものとします。
さっき書いた、処理の切り分けですね。

超簡単にシーン切り替えの実装

簡単にシーン遷移もどきを作ってみます。
画像を表示して、画像をクリックするたびに状態が

f:id:gomta777:20191117122626p:plain
図 状態遷移


タイトル画面(Push Start) → ゲーム画面(Judgement) → ゲームオーバー画面(game over) → タイトル画面。。。

と、状態が切り替わります。

画像は適当にフリー素材などを使います。
(私は、power pointの作図で作りました。ほしい方は、githubからどうぞ、
https://github.com/gomta777/hitandblow/tree/master/gamescnene/

実装

シーンを表す変数を用意します。初期状態は0(タイトル画面)としておきます。 

    gamescene = 0  # 0 タイトル、1 ゲーム中、2 ゲームオーバー、-1 エラー

画像を3枚(push start, judgement, game overのもの)を読み込みます。
./images/ の部分は自分のフォルダ構成に合わせて変更してください。

    gamebutton = []
    gamebuttonrect = Rect(200, 250, 200, 50) # 画像の表示位置を表す矩形
    gamebutton.append(pygame.image.load("./images/pushstart.png"))
    gamebutton.append(pygame.image.load("./images/judge.png"))
    gamebutton.append(pygame.image.load("./images/gameover.png"))

gamebuttonrectは、表示領域を表す矩形で、後でクリック判定にも使います。pygameには画像自体にイベントを発生させる機能がないため、矩形を設定しておいて、クリック検出します。

描画処理を書きます。

    running = True # メインループ

    while running:
        screen.fill((100, 100, 100))  # 背景色で塗る
        # この時点では、 screen.blit(gamebutton[gamescene], gamebuttonrect) で済むけど、
        # とりあえずは、if文でわけておく
        if gamescene == 0:
            screen.blit(gamebutton[0], gamebuttonrect)
        elif gamescene == 1:
            screen.blit(gamebutton[1], gamebuttonrect)
        elif gamescene ==2:
            screen.blit(gamebutton[2], gamebuttonrect)
        else:
            print("error")

        for event in pygame.event.get():
            if event.type == QUIT:  # 終了イベント
                running = False
                pygame.quit()  # pygameのウィンドウを閉じる
                sys.exit()  # システム終了
           
        pygame.display.update()  # 描画処理を実行

これで、とりあえず、gamesceneに従って別のものを表示する処理ができます。
gamesceneの初期値が0の時は、push startの画像が表示されますが、初期氏を変更すると別の画像が表示されることを確認しましょう。

状態遷移の処理

状態遷移といっても、今回はgamesceneの値を、あるトリガー(画像領域のクリック)に従って変更するだけです。
pygameでは、左クリックの検出は以下のように書けます。

    for event in pygame.event.get():
            if event.type == QUIT:  # 終了イベント
                running = False
                pygame.quit()  # pygameのウィンドウを閉じる
                sys.exit()  # システム終了
            if (event.type == pygame.MOUSEBUTTONUP) and (event.button == 1):
             # ここに、クリックされたときの処理を書く

画像を、ゲーム内のオブジェクトとして読み込めて、それにイベントリスナーを…みたいな処理ができれば楽なんですけどね(笑)
そのようなことはできませんので、ゲーム画面内の領域を指定してその領域内がクリックされたかどうかで判断します。

前提その1:左クリックが検出されると(検出したevent.typ がpygame.MOUSEBUTTONUPの時)、event.posには、自動的にクリックした画面内の座標が収まっています。
前提その2:Rect型には、いくつかの当たり判定のためのメソッド(関数)が用意されていて、Rectとposの判定は、Rect.collidepointメソッドで取得できる。

Rect.collidepoint
点座標がRectオブジェクトの描写範囲内にあるか調べます。
Rect.collidepoint(x, y): return bool
Rect.collidepoint((x,y)): return bool
引数として設定した座標がRectオブジェクトの描写範囲内にある場合はtrueが戻り値として返されます。
指定した座標がRectオブジェクトの右辺上や下辺上にある場合、その座標はRectオブジェクト内部にあるとは見なされません。

pygameドキュメントより引用

あらかじめ、画像を表示する領域として

gamebuttonrect = Rect(200, 250, 200, 50) # 画像の表示位置を表す矩形

を用意しておいたので、この矩形の中にクリックした点(event.pos)があった時に、状態を変更する処理を書きます。

  for event in pygame.event.get():
    # 終了処理の部分は省略
    if (event.type == pygame.MOUSEBUTTONUP) and (event.button == 1):
        if gamebuttonrect.collidepoint(event.pos):
        # 画像領域がクリックされたとき
            if gamescene == 0:  # ゲーム画面へ
                gamescene = 1
            elif gamescene == 1:  # ゲームオーバー画面へ
                gamescene = 2
            elif gamescene == 2:  # タイトル画面へ戻る
                gamescene = 0
            else:
                gamescene = -1 # エラー

何のことはない、if文を使って、ボタンが押されるたびに次の状態に変更しているだけですね。

動作確認

動画にしようと思いましたが、そこまでするまでもないかなという結論に至りました(笑)
ボタンを押すたびに、画像が切り替わるのが確認できると思います。

f:id:gomta777:20191117132356p:plainf:id:gomta777:20191117132402p:plainf:id:gomta777:20191117132407p:plain
図 画像の変化

完成したソースコード一覧

今回書いたもののまとめです。
初めにも書きましたが、解説では画像の貼り付けの基本やpygameの初期化などは省略しています。その辺は過去記事を参照してみてください。

from pygame.locals import *
import pygame
import sys
import random

def main():
    gamescene = 0  # 0 タイトル、1 ゲーム中、2 ゲームオーバー、-1 エラー
    pygame.init()  # Pygameを初期化
    screen = pygame.display.set_mode((900, 600))  # 画面を作成
    pygame.display.set_caption("Hit & Blow game")

    gamebutton = []
    gamebuttonrect = Rect(200, 250, 200, 50) # 画像の表示位置を表す矩形
    gamebutton.append(pygame.image.load("./images/pushstart.png"))
    gamebutton.append(pygame.image.load("./images/judge.png"))
    gamebutton.append(pygame.image.load("./images/gameover.png"))

    running = True
    # メインループ

    while running:
        screen.fill((100, 100, 100))  # 背景色で塗る
        # この時点では、 screen.blit(gamebutton[gamescene], gamebuttonrect) で済むけど、
        # とりあえずは、if文でわけておく
        if gamescene == 0:
            screen.blit(gamebutton[0], gamebuttonrect)
        elif gamescene == 1:
            screen.blit(gamebutton[1], gamebuttonrect)
        elif gamescene ==2:
            screen.blit(gamebutton[2], gamebuttonrect)
        else:
            print("error")

        for event in pygame.event.get():
            if event.type == QUIT:  # 終了イベント
                running = False
                pygame.quit()  # pygameのウィンドウを閉じる
                sys.exit()  # システム終了
            if (event.type == pygame.MOUSEBUTTONUP) and (event.button == 1):
                if gamebuttonrect.collidepoint(event.pos):
                    if gamescene == 0:
                        gamescene = 1
                    elif gamescene == 1:
                        gamescene = 2
                    elif gamescene == 2:
                        gamescene = 0
                    else:
                        gamescene = -1

        pygame.display.update()  # 描画処理を実行


if __name__ == "__main__":
    main()

次回

今回のソースコード一式はgithubにもあります。
https://github.com/gomta777/hitandblow/
のgamesceneが今回のものです。

次回以降は、だんだんゲームっぽい雰囲気を出していきたいと思います。
よくあるカジュアルゲームみたいに仕上がるといいですね。



よかったら、ブログ村↓のクリックお願いします。