1. Home
  2. /
  3. プログラミング
  4. /
  5. pythonで簡単プログラミング
  6. /
  7. 【第8回】pythonでウィンドウを使ったゲームを作成する方法(解説編:画面処理)

【第8回】pythonでウィンドウを使ったゲームを作成する方法(解説編:画面処理)

monster_road pythonで簡単プログラミング

#python #ゲーム #ウィンドウ #メニュー #ボタン #ラベル #テキストボックス #画像 #tkinter #Pillow

本記事では、前回の記事で解説しなかった内容のうち、ウィンドウとその中の部品の画面処理について解説します。
ゲームとしての処理を通した解説に関しては、次回の記事で行います。

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

プログラムについて

解説に入る前に、プログラムを以下に示します。
実行するための前準備や、プログラムが必要とする画像ファイルなどは、前回をご参照ください。
[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 :             # モンスターのクラスを定義する
    #image : im.Image        # 画像を格納するメンバ変数の定義

    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()

今回の解説範囲について

前回は以下の2つの大きな観点で、特に1.に関して解説しました。

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

本記事では、2.の部分の以下の分類の中で、a.について解説します。

a.画面処理
b.画面以外のゲームに関する処理

ウィンドウアプリケーション

今回最もプログラム量が多く、様々な種類の関数や情報を持つウィンドウアプリケーションという GUI について解説します。
ウィンドウアプリケーションを作成するために使用できる モジュール はいくつかありますが、本記事では最も始めやすく扱いやすいtkinterティーケーインターモジュールを使用します。
このモジュールはpython3に標準で付いており、python3とともにインストールされるため、import文で インポート するだけで使用できます。

import tkinter as tk        # GUIモジュール
import tkinter.scrolledtext as tkscr  # GUI:スクロールテキストクラスモジュール

ウィンドウアプリケーションでは、まず、フレームという枠を作成します。
その後、フレーム内にボタンなどのユーザの操作を受け付ける部品を配置していきます。
以下に関係している行を示します。

class tkApp(tk.Frame):          # GUIモジュール tkinter を使ったアプリを定義する
 
    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)

    ::: (中略)


#
# 入り口の処理
#

# 画面作成

# ウィンドウを作成する

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

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

app = tkApp(wnd)

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

wnd.mainloop()

まず、”class tkApp(tk.Frame):”と記載することで、tkinterモジュール内で定義されているFrameというクラスを機能拡張して、tkAppクラスを定義しています。
(“tk”はimport文で、”import tkinter as tk”として、tkinterと書かなくていいように省略したものです。)
例えますと、素材不明でサイズや中身も不明ですが枠を作る時に使われる木型や金型がtk.Frameで、そこから作り出した具体的なサイズの木の枠がtkAppになります。

今回のゲームはウィンドウ内のボタンやメニューの操作だけで、ゲームのすべてが動作しますので、tkAppの中に、ほぼすべての動作と関係する情報を定義しています。
tkAppはプログラムの9割以上を占めていますが、単なるクラス定義ですので、そのままでは動作しません。
実際にプログラムが動作し始めるところは、プログラムのずっと下の方20行程度のところにある下記の”wnd = tk.Tk()”以降です。

#
# 入り口の処理
#

# 画面作成

# ウィンドウを作成する

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

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

app = tkApp(wnd)

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

wnd.mainloop()

“wnd = tk.Tk()”で、ウィンドウを作成します。
“wnd.geometry(‘500×500’)”で500×500ドットのサイズにし、
”wnd.title(‘モンスターロード’)”でウィンドウのタイトルを設定しています。
このあと、”app = tkApp(wnd)”を実行します。
これにより、tkAppという クラス を オブジェクト として実体化し、動作します。
つまり、ウィンドウ内のメニューやボタンなどがここで準備されます。ただし、画面にはまだ表示されません。
そして最後に、”wnd.mainloop()”を実行すると、ウィンドウやメニュー、ボタンなどが表示された後、受付け待ちをし続ける無限ループに入ります。
ウォンドウの終了ボタン(×ボタン)や、メニューから終了を選ぶとこのループから帰ってきて、”wnd.mainloop()”の後ろの処理に移ります。
今回のプログラムでは、後ろにはもう処理がないため、何もしないままプログラムが終了します。

なお、このゲームは途中でモンスターと遭遇した後に、一秒間だけモンスターを表示させた後、分かれ道をまた表示させています。
これをするには、”wnd.mainloop()”で画面表示して受付待ちしているだけではダメであり、以下のような処理を入れて強制的に画面表示をしてから1秒待つようにしています。

app.update_idletasks()              # 画面を強制的に更新
time.sleep(1.0)                     # チョット待つ

”app.update_idletasks()”は画面を強制的に更新して表示します。
この処理は”wnd.mainloop()”関数の中でも行われているようです。
”time.sleep(1.0)”は、timeモジュール内のsleep関数で、1.0秒間 CPU を眠らせる(スリープさせる)処理です。

フレーム

次に、具体的なウィンドウの中身をまとめるフレーム(tkApp)について解説します。
まず、tkAppの先頭では、下記の行が出てきます。

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

”super().__init__(master)”は、Frameクラスとして必要な初期化を実行しています。
“super()”は、tkAppが拡張する元としているFrameクラスのオブジェクトを指します。
“__init__()”はオブジェクトの初期化関数で、入力には”wnd”(=ウィンドウ)が入力されます。
これは、”app = tkApp(wnd)”の行で”wnd”を入力しているためです。

次に、”self.pack()”という行がありますが、まず、”selfセルフ“は、英訳すると自分自身のことであり、自分自身のオブジェクトを指します。
“self.pack()”は自分自身のオブジェクトであるフレームを、単にウィンドウに配置することになります。
本来はpack関数は、いくつかある配置の仕方の1つで、オプションなどもあって説明が必要ですが、ここでは、単にフレームを置くだけと考えてください。

テキストボックス

このゲームでは、何か行動を起こした時や戦闘の結果などをテキストで表示します。
長文のテキストを表示できる部品には、テキストボックスが適切なため、このゲームでも使います。

テキストボックスの配置

テキストボックスを配置しているのは、以下の行です。

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

”tkscr.ScrolledText”の頭についているtkscrは、”import tkinter.scrolledtext as tkscr”でインポートしたモジュールの省略形です。
ScrolledText関数は、スクロールバー付きのテキストボックスで、
”tkscr.ScrolledText(width=63, height=12)”として、半角文字で幅を63文字、高さを12文字の面積を持つテキストボックスにします。
そして、”self.textbox.place(x=25, y=250)”でウィンドウ上のx=25,y=250の位置にテキストボックスを配置します。

テキストの出力

テキストボックスにテキストを出力している箇所は、以下のputText関数内の着色部分です。

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

”self.textbox.insert(str(self.text_r)+’.0′, text + ‘\n’)”により、テキストをテキストボックスに挿入しています。
1つめの入力には、テキストボックス内のテキストの挿入位置を、R行目のC文字目として、”R.C”の形で指定します。
上述の例では、”str(self.text_r)+’.0’”となっています。
これは、self.text_rに入っている行位置をstr関数で文字列化して’.0′(0文字目)と結合して設定しており、例えば、self.text_rが5だった場合、”5.0″ を指定したことになります。
2つ目の入力は”text + ‘\n’”となっており、putText関数の入力である変数textに、’\n’を結合して指定しています。
これによって、textを表示した後、改行することを指定しています。

‘\n’の’\’は、このブログの表示ではバックスラッシュ(反対向きの”/”)になっていますが、この文字をメモ帳に貼り付けて見ると、円記号(¥)になっているのが分かります。
つまり、’¥n’を指定していることになり、これが改行することを表現しています。
pythonだけでなく他のプログラミング言語でも改行はこの表現を使用します。

スクロール

self.textbox.insert関数で、テキストボックスにテキストを挿入しただけでは、テキストボックスの枠を超えた場合に、文字が隠れてしまいます。
そこで、以下のように、最終行までスクロールさせて見えるようにします。

self.textbox.see('end')             # 最後の行にスクロールさせる

self.textbox.see関数はスクロールさせる関数ですが、入力に’end’を指定することで、最終行までスクロールさせます。

テキストの消去

このゲームでは、スタートボタンを押すことでゲームを再開することができますが、この時に、以前表示していたテキストをテキストボックス内から消す必要があります。
これは以下のようにして行います。

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

self.textbox.delete関数は指定した位置から指定した分だけテキストを削除する関数です。
入力の”1,0″の位置は先頭行の先頭文字を、”end”は最終行を表すため、テキストボックス内の最初から最後までテキストを消します。

画像ラベル(Pillow利用)

ゲームに登場するモンスターや背景画像は、ファイルから読みこんで、ラベルに貼り付けて表示しています。

ファイルからの読み込み

まず、ファイルから読み込んでいる部分です。
以下の行で実行しています。

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)

“im.open(“pngファイル”)”で、”pngファイル”を読み込んで、左辺のメンバ変数に記憶させています。
次に、モンスターの場合には、monsterクラスをオブジェクト化し、
”set(画像, name=”モンスター名”, attack_pt=攻撃力, exp=経験値)”で、それぞれのモンスターの設定を行い、上記で記憶した画像を設定しています。

以上をタイトル、背景や、各モンスターに対して実行しているだけです。

ラベルに貼り付ける

次に、読み込んだ画像をラベルに貼り付ける部分です。

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)          # 配置する

画像をラベルに貼り付ける処理は、上記のchange_image関数の中で一挙に引き受けています。
関数の呼び出し元で、どの画像に変更するのかを入力に指定して呼び出すようにしています。

”png = imtk.PhotoImage(image)”では、指定された画像を写真画像と呼ばれる種類の画像として取り扱って、変数pngに保存しています。
写真画像とは、RGB形式(Red Green Blue)に加えて、透明を表現する情報(Alphaアルファ)を持った画像です。
PNG、JPEG、GIFなどは写真画像にあたります。

次に、”lab = tk.Label(wnd, image=png)”で、変数pngに保存した画像をラベルに貼り付けています。
次の行の”lab.image = png”ですが、さきほどLabel関数に指定したのと同じ代入をここで繰り返しています。この2度書き記述がどうしても必要となった理由は小生もよくわかりません(*1)が、ここでは、おまじない程度に考えてください。

”lab.pack()”では、ラベル内での配置を行います。

最後に、”lab.place(x=100, y=20)”でウィンドウの指定の位置に配置しています。

画像のファイル読み込みや変数への保存処理では、Pillowモジュールの関数を使っています。
tkinterにも同じ名前の関数があるのですが、扱える画像ファイルの種類が少ないなど、制限が多いため、Pillowを使用するのがお薦めです。

*1 この行がないと画像がガベージ処理というゴミ掃除処理で削除されてしまうとのことです。
ガベージ処理とは、使われていないオブジェクトを掃除する処理ですので、変数pngをtk.Label関数に指定したのに消されてしまったわけで、そうすると、消されてしまうような指定は不要にして、後ろの”lab.image = png”だけで良いはずです。
使わせ方に違和感を覚えます。

メニューバーとメニュー

このゲームは、ウィンドウアプリケーションとして、メニューバーとそこからぶら下がるメニューを備えています。
以下のプログラムでプレイメニューとヘルプメニューを表示する準備をしています。

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)

“tk.Menu(wnd)”はウィンドウ上にあるメニューを並べたメニューバー(下図の②)のオブジェクトを作成します。
そして、変数self.menu_barに覚えさせています。

モンスターロード:起動直後の画面
画面配置

次に、”tk.Menu(self.menu_bar, tearoff=False)”で、メニューバーにぶら下がるメニューを作成します。
下図の左上のメニューバーの”プレイ”メニューからぶら下がるメニューです。

モンスターロード:メニュー選択

”tearoff=False”は、メニューをメニューバーから切り離しできないようにする指定のため、切り離しができない通常のメニューになっています。
切り離しができる場合は、切り取り線が表示されます。
そして、”self.play_menu.add_command(label=”スタート”, command=self.game_start)”の行で
プレイメニューに”スタート”というラベルで、”self.game_start”というコマンドを追加しています。
コマンドとは関数のことです。関数の中身は次回解説します。
次に”self.play_menu.add_separator()”ですが、これは、上図の”プレイ”メニューの”スタート”と”終了”の間の区切り(セパレーター)線です。
そして、”self.play_menu.add_command(label=”終了”, command=self.game_exit)”で、”終了”メニューを追加して”プレイ”メニューとしています。

ヘルプメニューは同様のことを行っているだけのため、説明を割愛します。

その後、”プレイ”メニューは、”self.menu_bar.add_cascade(label=”プレイ”, menu=self.play_menu)”で、メニューバーに接続し、メニューバーは”wnd.config(menu=self.menu_bar)”でウィンドウに接続してメニューの完成となります。

ボタン

このゲームではボタンが4つ配置されていますが、そのうちの”スタート”ボタンだけについて解説します。
”スタートボタン”の配置は、以下のように、tk.Button関数でボタンの オブジェクト を作成して”self.start_btn”に記憶しています。

# スタートボタンを作成して配置する
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.start_btn.place(x=20, y=20)”は、前出のテキストボックスの場合と同様で、ウィンドウ上のx=20,y=20の位置にテキストボックスを配置しています。
なお、tk.Button関数の入力には、以下を指定しています。

・ 配置先のウィンドウ(上記プログラム中のwnd変数)
・ ボタンに表示させるテキスト(textパラメタ)
・ コマンド(=ボタン押下時に呼び出す関数)(commandパラメタ)
・ ボタンの幅(widthパラメタ)
・ ボタンの高さ(heightパラメタ)
・ ボタンの背景色(bgパラメタ)
・ ボタンの前景色(fgパラメタ)

なお、ボタンは、下記の関数で、無効化・有効化ができるようにしています。

    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

”self.left_btn[“state”] = tk.NORMAL”のように、”state”の値にtk.NORMALを指定すると有効化、”tk.DISABLED”を指定すると無効化できます。
ちなみに、”self.left_btn[“state”]”は、pythonの”辞書(dictディクト)”という機能を使用しています。
これについては、後日、別の記事で解説します。

テキストラベル

ここでは画像ではなくテキストを貼りつけたラベルを解説します。
このゲームでは、下図の①のところでテキストラベルを使用しています。

モンスターロード:分かれ道選択画面

テキストラベルは、テキストボックスほど多くのテキストを表示する必要がなく、また、編集できなくて良い場合に使用します。
以下は、得点(スコア)の表示部分です。

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)

ほぼ画像ラベルと同じ処理手順を必要とします。
”lbl = tk.Label(wnd, text=”スコア: “, fg=”black”, width=5 )”では、”スコア”というテキストを前景色=黒で5文字の幅で表示するラベルを作成しています。
画像ラベルとは違い、後は、”lbl.place(x=410, y=20)”を実行して、ウィンドウの指定位置に表示させるだけとなっています。

次の行の”# 得点(スコア)の数値だけを表示する”のところでは、以下の点でさきほどと違っています。

・ テキストにstr(score)が指定されている
・ 背景色(bg)が指定されている
・ reliefオプションが指定されている
・ anchorオプションが指定されている

まず、”str(score)”ですが、これは、変数scoreの内容が数値であるものとして、それをstr関数で文字列にしています。数値と文字列の違いやstr関数については、後日、別の記事で解説します。

背景色(bg)は白にして、値が変わるラベルであることを分かるようにしています。

reliefオプションは、枠の種類を指定します。
ここでは、黒で太めの線である ”solid”=硬い枠 を指定しています。

anchorオプションは、文字を寄せる方向を東西南北(“e”,”w”,”s”,”n”)で指定します。
得点は0から999まで表示可能で、0から99のときは左に寄せて表示されるように、”w”=west(西)としています。(意味なく西にしてしまいました。東のほうが見栄えが良かったかも😅)

生命点(HP)の方も、文字以外は同じですので、説明は割愛します。

最後に

いかがでしたでしょうか?
今回はプログラムの中身で、主にウィンドウの表示に関する画面処理について解説しました。

・ ウィンドウアプリケーション
・ フレーム
・ テキストボックス
・ 画像ラベル(Pillow利用)
・ メニューバーとメニュー
・ ボタン
・ テキストラベル

ウィンドウ内にフレームを作成し、フレーム内にボタンなどの部品を配置していく
という作り方で、ウィンドウアプリケーションを作成していきました。
また、ウィンドウアプリケーションは、ボタンなどの入力を受付待ちするという独特な処理が必要でした。
各部品に閉じた、さらなる詳細については、後日、別記事で解説していきます。

次回は、ゲームとしての流れの観点でプログラム内を解説しますので、ご期待ください!!!

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

コメント

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