1. Home
  2. /
  3. プログラミング
  4. /
  5. pythonで簡単プログラミング
  6. /
  7. 【第7回】pythonでウィンドウを使ったゲームを作成する方法

【第7回】pythonでウィンドウを使ったゲームを作成する方法

モンスターロード サムネイル pythonで簡単プログラミング

#python #ゲーム #ウィンドウ #メニュー #ボタン #ラベル #画像 #tkinter #Pillow #クラス #オブジェクト

本記事では、前回の記事に書いた『テキストだけのアドベンチャーゲーム』を、よりゲームらしくウィンドウ上で動作するゲームに発展させ、その作り方について解説します。

さらに、 クラス と オブジェクト を導入してプログラムを洗練させており、これらについても解説します。

今回はゲームの骨格と、プログラムの概要について説明し、詳細は次回にて解説を行います。

本記事では、主としてWindows、および、python3を前提にしています。
スポンサーリンク

プログラムについて

解説に入る前に、今回のプログラムを以下に示します。
早速実行してみたくなった方は、別の記事に記載の『プログラムを実行するまでの準備』以降を参照して実行してみてください。

後述の画像ファイルも必要になります。
下記URLからプログラムを含めて一式がダウンロードできます。

https://drive.google.com/drive/folders/1FOStfjSQf0k-5XZ_fVdqkR9MY_KCzXbP?usp=sharing

[monster_road.py]

import tkinter as tk        # GUIモジュール
import tkinter.scrolledtext as tkscr  # GUI:スクロールテキストクラスモジュール
import PIL.Image as im      # Pillowの画像クラスモジュール
import PIL.ImageTk as imtk  # Pillowの写真画像クラスモジュール
import sys                  # システム用モジュール
import random as rd         # 乱数を扱うモジュール
import time

#
# 登場人物・キャラのクラス定義
#

class me :                  # 自分自身のクラスを定義する
    def __init__(self):     #
        self.HP = 100       # 生命点(HP)
        self.score = 0      # 得点(スコア)

class monster :             # モンスターのクラスを定義する

    def __init__(self):     # モンスタークラスの初期化関数
        self.name = ""      # 名前
        self.attack_pt = 0  # 攻撃力
        self.exp_pt = 0     # 経験値(スコアになる)

    def set(self, img, name, attack_pt, exp):   # モンスターの情報を設定する関数
        self.image = img            # 画像を設定する
        self.name = name            # 名前を設定する
        self.attack_pt = attack_pt  # 攻撃力を設定する
        self.exp = exp              # 相手に与える経験値(=スコア)を設定する

    def attack(self, anyone):       # モンスターが攻撃する関数
        anyone.HP -= self.attack_pt # だれかの生命点(HP)を減らす

#
# ウィンドウアプリケーションの定義
#

class tkApp(tk.Frame):          # GUIモジュール tkinter を使ったアプリを定義する
                                # tk.Frameを拡張したクラスにする
    def __init__(self, master=None):
        super().__init__(master)    # 基底クラスtk.Frameの初期化を実行する
        self.pack()                 # レイアウト

        # スクロールバー付きテキストボックスを作成・配置する
        self.textbox = tkscr.ScrolledText(width=63, height=12)
        self.textbox.place(x=25, y=250)

        self.titleimg = im.open("title.png")# タイトルの画像を読み込む
        self.backimg = im.open("back.png")  # 分かれ道の画像を読み込む
        slimeimg = im.open("slime.png")     # スライムの画像を読み込む
        goblinimg = im.open("goblin.png")   # ゴブリンの画像を読み込む
        dragonimg = im.open("dragon.png")   # ドラゴンの画像を読み込む

        self.slime = monster()
        self.slime.set(slimeimg, name="スライム", attack_pt=5, exp=1)

        self.goblin = monster()
        self.goblin.set(goblinimg, name="ゴブリン", attack_pt=20, exp=5)

        self.dragon = monster()
        self.dragon.set(dragonimg, name="ドラゴン", attack_pt=40, exp=20)

        self.text_r = 1      # テキストの行位置

        #
        #
        # 初期画面表示
        #
        #

        self.menu_bar = tk.Menu(wnd)            # メニューバー

        # プレイメニュー
        self.play_menu = tk.Menu(self.menu_bar, tearoff=False)
        self.play_menu.add_command(label="スタート", command=self.game_start)
        self.play_menu.add_separator()
        self.play_menu.add_command(label="終了", command=self.game_exit)

        # ヘルプメニュー
        self.help_menu = tk.Menu(self.menu_bar, tearoff=False)
        self.help_menu.add_command(label="ヘルプ", command=self.help)

        self.menu_bar.add_cascade(label="プレイ", menu=self.play_menu)
        self.menu_bar.add_cascade(label="ヘルプ", menu=self.help_menu)

        wnd.config(menu=self.menu_bar)

        self.change_image(self.titleimg) # 画像をタイトルの画像にする(初回の変更)

        # スタートボタンを作成して配置する
        self.start_btn = tk.Button(wnd, text="スタート",
                        command=self.start_btn_click,
                        width=7, height=1, bg="green", fg="white")
        self.start_btn.place(x=20, y=20)

        # 左ボタンを作成して配置する
        self.left_btn = tk.Button(wnd, text="左",
                        command=self.left_btn_click,
                        width=7, height=1, bg="steelblue", fg="white",
                        state=tk.DISABLED)
        self.left_btn.place(x=50, y=440)

        # 真ん中ボタンを作成して配置する
        self.mid_btn = tk.Button(wnd, text="真ん中",
                        command=self.mid_btn_click,
                        width=7, height=1, bg="steelblue", fg="white",
                        state=tk.DISABLED)
        self.mid_btn.place(x=230, y=440)

        # 右ボタンを作成して配置する
        self.right_btn = tk.Button(wnd, text="右",
                        command=self.right_btn_click,
                        width=7, height=1, bg="steelblue", fg="white",
                        state=tk.DISABLED)
        self.right_btn.place(x=400, y=440)

    def change_image(self,image):       # 画像を変更する関数
        png = imtk.PhotoImage(image)    # 指定された画像を写真画像として保存する
        lab = tk.Label(wnd, image=png)  # 画像をラベルに貼り付ける
        lab.image = png                 # これをやらないと画像が消されてしまう
        lab.pack()                      # レイアウトを実行する
        lab.place(x=100, y=20)          # 配置する

    def change_score(self, score):      # 得点(スコア)の表示を更新する関数
        # 得点(スコア)のタイトルだけを表示する
        lbl = tk.Label(wnd, text="スコア: ", fg="black", width=5 )
        lbl.place(x=410, y=20)

        # 得点(スコア)の数値だけを表示する
        lbl = tk.Label(wnd, text=str(score), bg="white", fg="black",
                        width=3, relief="solid", anchor="w" )
        lbl.place(x=460, y=20)

    def change_hp(self, HP):            # 生命点(HP)の表示を更新する関数
        # 生命点(HP)のタイトルだけを表示する
        lbl = tk.Label(wnd, text="生命点: ", fg="black", width=5 )
        lbl.place(x=410, y=50)

        # 生命点(HP)の数値だけを表示する
        lbl = tk.Label(wnd, text=str(HP), bg="white", fg="black",
                        width=3, relief="solid", anchor="w" )
        lbl.place(x=460, y=50)

    def enable_dir_button(self):                # 方向ボタンを全て有効にする関数
        self.left_btn["state"] = tk.NORMAL
        self.mid_btn["state"] = tk.NORMAL
        self.right_btn["state"] = tk.NORMAL

    def disable_dir_button(self):               # 方向ボタンを全て無効にする関数
        self.left_btn["state"] = tk.DISABLED
        self.mid_btn["state"] = tk.DISABLED
        self.right_btn["state"] = tk.DISABLED

    def putText(self,text):
        # 次の行にテキストを表示する
        self.textbox.insert(str(self.text_r)+'.0', text + '\n')
        self.textbox.see('end')             # 最後の行にスクロールさせる
        self.text_r += 1                    # 次に表示させる行を進める

    def game_start(self):               # ゲーム開始時の設定をする
        self.change_image(self.backimg) # 画像を分かれ道の画像にする(初回の変更)

        self.textbox.delete("1.0", "end")

        self.text_r = 1      # テキストの行位置

        self.putText("3つの分かれ道があります。")
        self.putText("どれに進みますか?")

        self.me = me()                      # 自分のクラスオブジェクトを作成する

        self.change_score(self.me.score)    # 得点(スコア)を表示する
        self.change_hp(self.me.HP)          # 生命点(HP)を表示する

        self.enable_dir_button()            # 方向ボタンを全て有効にする
    
    def game_exit(self):        # ゲームを終了する関数
        sys.exit(0)

    def help(self):             # ヘルプを表示する関数
        self.putText("**************************************")
        self.putText("**************************************")
        self.putText("")
        self.putText("使い方は説明しなくても分かりますよね😌")
        self.putText("")
        self.putText("**************************************")
        self.putText("**************************************")

    def start_btn_click(self):  # スタートボタンがクリックされた時に呼ばれる関数
        self.game_start()

    def left_btn_click(self):   # 左ボタンがクリックされた時に呼ばれる関数
        self.choose_monster(1)  # 左を選ぶ

    def mid_btn_click(self):    # 真ん中ボタンがクリックされた時に呼ばれる関数
        self.choose_monster(2)  # 真ん中を選ぶ

    def right_btn_click(self):  # 右ボタンがクリックされた時に呼ばれる関数
        self.choose_monster(3)  # 右を選ぶ

    def choose_monster(self,dir):
    
        if self.me.HP <= 0 :        # すでに生命点(HP)が無くなっていた
            return

        self.disable_dir_button()   # 方向ボタンを全て無効にする

        mon_no = rd.randint(1,3)    # 1~3の乱数を発生させモンスター番号にする
        mon_no = mon_no + dir - 1   # 進んだ方向(dir)でモンスターを変更する

        if mon_no > 3 :             # 変更した結果が3を超えたら
            mon_no = mon_no - 3     # 1に修正する(モンスターは1~3)のため

        if mon_no == 1 :            # 1はスライム
            monster = self.slime
        elif mon_no == 2 :          # 2はゴブリン
            monster = self.goblin
        else :                      # 3はドラゴン
            monster = self.dragon

        self.change_image(monster.image)    # モンスターの画像を表示する
        self.putText(monster.name + "が現れた!")
        monster.attack(self.me)             # モンスターが出現する
        self.putText("攻撃されてHPが" + str(self.me.HP) + "になった")

        if self.me.HP <= 0 :            # 生命点(HP)が無くなった
            self.putText("やられた")
            self.putText("")
            self.putText("--- GAME OVER ---")
            return

        self.me.score += monster.exp        # 経験値を得点(スコア)に加算する
        self.change_score(self.me.score)
        self.change_hp(self.me.HP)

        app.update_idletasks()              # 画面を強制的に更新
        time.sleep(1.0)                     # チョット待つ
        self.change_image(self.backimg)     # 画像を分かれ道の画像にする
        self.enable_dir_button()            # 方向ボタンを全て有効にする


#
# 入り口の処理
#

# 画面作成

# ウィンドウを作成する

wnd = tk.Tk()                   # ウィンドウを作成する
wnd.geometry('500x500')         # ウィンドウサイズを500 ✕ 500ドットにする
wnd.title('モンスターロード')   # ウィンドウタイトルを設定する

# ウィンドウのフレームを作成する

app = tkApp(wnd)

# ウィンドウを表示してメニュー・ボタン等の待受け処理に入る
# ウィンドウを終了するまで戻ってこない無限ループ

wnd.mainloop()

画像ファイルについて

プログラム内で使用している画像ファイルを以下に記載します。
拙い絵ですが、そこはご容赦ください😅
後日、洗練したものに更新する可能性があります。

title.png
back.png
dragon.png
goblin.png
slime.png

ゲームの画面構成について

”python monster_road.py” として、ゲームを起動すると、下記の画面になります。

起動直後の画面

丸付き数字はそれぞれ以下のとおりです。
① … ゲームタイトル
② … メニュー
③ … スタートボタン
④ … テキストボックス
⑤ … 画像表示領域
⑥ … 進路ボタン

スタートボタンを押すと以下の画面になります。

スタート直後の分かれ道の画面

①は、スコアと生命点を表示するラベルです。

以下は、モンスターに遭遇したときの画面です。

モンスター遭遇の画面

以下は、メニューをクリックしたときの画面です。

メニューの操作画面

プログラムの概要について

今回のプログラムの概要は以下になります。

1.オブジェクト と クラス
2.処理の流れ

以下にそれぞれ解説します。

オブジェクト と クラス

 pythonは オブジェクト指向言語 です。
 オブジェクト指向言語 とは、プログラムによる操作の対象を自然界に存在する物体( オブジェクト )になぞらえて扱う言語です。
そしてオブジェクトの中身を”型”として定義したものがクラスです。

たとえば、丸顔、小麦肌、脚長の人は、オブジェクトとすることができます。
これに対し、クラスは、顔の形、肌の色、足の長さを入れられる人型になります。
人によってクラスの中のそれぞれの値は変化し、その人個人がオブジェクトとして識別されます。

コンピュータ上では、クラスは、プログラムの中に書かれた定義内容でしかありません。
そして、クラスに書かれた情報領域をメモリ上に獲得したことでオブジェクトとなります。

本プログラムでは、自分(プレイヤー)とモンスターをオブジェクトとしました。
これらのオブジェクトは、生命点(HP)や経験値・攻撃力という情報を メンバ変数 に持ち、『攻撃する』という自発的動作を行ないます。

以下にクラス定義の内容を図示します。これはクラス図と呼ぶ図です。
ボックスの一番上の段にクラス名、中段に情報( メンバ変数 )、下段に関数を書きます。

クラス図

自分(プレイヤー)は生命点(HP)と得点(スコア)の情報を持ちますが、本ゲームでは、自分は攻撃をすることも逃げることもできないため、下段に関数はありません。
モンスターは、名前と攻撃力と、経験値を情報として持ち、モンスターの種類によって情報を変えることができるよう、set関数を持たせました。
また、攻撃することができるよう、attack関数を持たせました。

以下に実際の クラス 定義を示します。

#
# 登場人物・キャラのクラス定義
#

class me :                  # 自分自身のクラスを定義する
    def __init__(self):     #
        self.HP = 100       # 生命点(HP)
        self.score = 0      # 得点(スコア)

class monster :             # モンスターのクラスを定義する

    def __init__(self):     # モンスタークラスの初期化関数
        self.name = ""      # 名前
        self.attack_pt = 0  # 攻撃力
        self.exp_pt = 0     # 経験値(スコアになる)

    def set(self, img, name, attack_pt, exp):   # モンスターの情報を設定する関数
        self.image = img            # 画像を設定する
        self.name = name            # 名前を設定する
        self.attack_pt = attack_pt  # 攻撃力を設定する
        self.exp = exp              # 相手に与える経験値(=スコア)を設定する

    def attack(self, anyone):       # モンスターが攻撃する関数
        anyone.HP -= self.attack_pt # だれかの生命点(HP)を減らす

クラスをオブジェクトとしてメモリ上に獲得して実体化するには、クラス名の後ろに括弧をつけます。
自分やモンスターのオブジェクト化(実体化)は、以下のように__init__関数、game_start関数で行っています。

    def __init__(self, master=None):
        super().__init__(master)    # 基底クラスtk.Frameの初期化を実行する
        self.pack()                 # レイアウト

    (中略)

        self.slime = monster()
        self.slime.set(slimeimg, name="スライム", attack_pt=5, exp=1)

        self.goblin = monster()
        self.goblin.set(goblinimg, name="ゴブリン", attack_pt=20, exp=5)

        self.dragon = monster()
        self.dragon.set(dragonimg, name="ドラゴン", attack_pt=40, exp=20)

        self.text_r = 1      # テキストの行位置
    def game_start(self):               # ゲーム開始時の設定をする
        self.change_image(self.backimg) # 画像を分かれ道の画像にする(初回の変更)

        self.textbox.delete("1.0", "end")

        self.text_r = 1      # テキストの行位置

        self.putText("3つの分かれ道があります。")
        self.putText("どれに進みますか?")

        self.me = me()                      # 自分のクラスオブジェクトを作成する

        self.change_score(self.me.score)    # 得点(スコア)を表示する
        self.change_hp(self.me.HP)          # 生命点(HP)を表示する

        self.enable_dir_button()            # 方向ボタンを全て有効にする

処理の流れ

大きく分けて、処理をする部分は”メイン”と”クラス tkApp のウィンドウオブジェクト”の2つに分かれます。
“メイン”は、プログラムを実行したときにまず動き出す入り口となる処理部分です。
下図の左側の”メイン”から、右側の”クラス tkApp のウィンドウオブジェクト”を作り、呼び出してウィンドウを表示します。

処理の流れ その1

ウィンドウのメニューやボタンを押すと、それに対応した関数が呼び出されます。
メニューやボタンが押されない間は、図の◇のところでぐるぐると回り続けてループします。
メニューの終了ボタンか、ウィンドウの✕ボタンを押すと、◇のところのループが終了し、左の”メイン”に戻ります。

以下は、メニューやボタンを押した後に行なう細かい処理を記載しています。

処理の流れ その2

”モンスターを選択して戦う”処理が中心となって、テキストボックスにテキストを表示する処理や、画像、得点などを変更する処理など、他の処理を呼び出します。

最後に

いかがでしたでしょうか?
今回はプログラムの概要について解説しました。
以下が重要ポイントです。

– オブジェクトは処理の対象を元に自然界の物体になぞらえて決める
– オブジェクトに持たせる情報(メンバ変数)と処理(関数)を決める
– オブジェクトの型をクラスとして定義する

– ウィンドウオブジェクトを作成・表示してウィンドウの処理を行なう
– ウィンドウオブジェクトはメニューやボタンの入力をループして待受ける

次回は、今回説明しなかったウィンドウの処理や、その他の部分の詳細について解説します。

この記事へのお問い合わせや、一歩踏み込んだサポートが必要な場合は、
ホームページからメールで受け付けています。お気軽にご連絡ください。

コメント

タイトルとURLをコピーしました