このページでは、Python で tkinter を用いた「モグラたたきゲーム」の作り方について解説していきます。
出来上がりは下の動画ようなゲームとなります。
この動画で紹介したようなゲームを作ってみたいという方は、是非このページを読み進めていっていただければと思います。
tkinter の使い方についてもたくさん学べますし、ゲームですので楽しくプログラミングしていけると思います!
Contents
作成するモグラたたきゲーム
まず、このページで作成する「モグラたたきゲーム」がどのようなものであるのかについて説明しておきます。
このページで作る「モグラたたき」ゲームの動画
まず、このページで作成するモグラたたきゲームは下の動画のようなものになります。
スポンサーリンク
モグラたたきゲームの説明
今回作成する「モグラたたきゲーム」は、まず起動すると下の図のようにモグラの穴が表示されます。
さらに、時間経過と共にランダムな位置の穴からモグラが出現し、穴から出たモグラは上方向に少し移動した後に下方向に移動し、穴まで戻ったら再び穴に隠れてモグラが見えなくなります。
モグラが穴から出てきているときにマウスでクリックすればモグラを叩くことができ、これによりポイントが加算されます。
逆に、モグラを叩くことができずにモグラが穴に隠れてしまった場合、ポイントが減算されることになります。
モグラたたきゲームを作成するのに必要なモジュール
今回、キャンバスにモグラの画像を描画することでモグラの表示を実現しています。この画像を扱う際に、PIL を利用しているので注意してください。
PIL は汎用性の高い画像処理モジュールであり tkinter とも相性の良いモジュールですので、まだインストールしていない方はこの機会にインストールしておくことをオススメします!
モグラたたきゲームのスクリプト
今回作成するモグラたたきゲームのスクリプトの最終形は下記のようになります。
次の モグラたたきゲームの作り方 においては、このスクリプトを作っていく上での過程を説明していきたいと思います。
import tkinter
import random
from PIL import Image, ImageTk
MOLE_PATH = "animal_chara_mogura_hakase.png" # モグラの画像のファイルパス
NUM_H_HOLE = 4 # 横方向の穴の数
NUM_V_HOLE = 3 # 縦方向の穴の数
WIDTH_HOLE = 100 # 穴の幅(長軸の長さ)
HEIGHT_HOLE = 50 # 穴の高さ(短軸の長さ)
WIDTH_SPACE = 20 # 穴と穴のスペースの幅
HEIGHT_SPACE = 80 # 穴と穴のスペースの高さ
POINT_DRAW_TIME = 500 # 加算or減算されたポイントが表示される時間
MOLE_UPDATE_INTERVAL = 100 # モグラの状態や位置を更新する時間間隔
FIGURE_UPDATE_INTERVAL = 100 # モグラの画像を更新する時間間隔
MOLE_CHOICE_INTERVAL = 1000 # 穴から出すモグラを決める時間間隔
class Mole:
def __init__(self, x, y, width, height, speed, figure):
'''コンストラクタ'''
self.x = x # 中央下の位置(横方向)
self.y = y # 中央下の位置(縦方向)
self.width = width # 画像の幅
self.height = height # 画像の高さ
self.figure = figure # 描画画像の図形ID
self.hole_y = y # モグラが穴に潜る高さ
self.top_y = self.hole_y - HEIGHT_HOLE / 3 # 穴から出る高さ
self.speed = speed # 移動スピード
self.point = int(speed * 10) # 叩いた時にゲットできるポイント
self.is_up = False # 上方向に移動中かどうかを管理するフラグ
self.is_draw = True # 描画するかどうかを管理するフラグ
self.is_hitted = False # 既に叩かれているかどうかを管理するフラグ
self.is_appearing = False # 穴から出ているかどうかを管理するフラグ
def appear(self):
'''モグラを穴から出す'''
# 穴から出ているフラグをON
self.is_appearing = True
# 上方向に移動を開始
self.is_up = True
def update(self):
'''定期的な状態更新を行う'''
if self.is_up:
# 上方向への移動中
# 上方向に移動
self.y = max(self.top_y, self.y - self.speed)
if self.y == self.top_y:
# 上方向の上限まで移動したので下方向への移動を開始
self.is_up = False
else:
# 下方向への移動中
# 下方向に移動
self.y = min(self.hole_y, self.y + self.speed)
if self.y == self.hole_y:
# モグラがまた穴に潜ってしまった場合
# モグラの状態をリセットする
self.is_appearing = False
self.is_hitted = False
self.is_draw = True
if self.is_hitted:
# 叩かれたフラグが立っている場合
# 描画実行フラグのON/OFFを切り替える
self.is_draw = not self.is_draw
def hit(self):
'''叩かれた時の処理を実行'''
# 下方向への移動を開始
self.is_up = False
# 叩かれたフラグをセット
self.is_hitted = True
def isHit(self, mouse_x, mouse_y):
'''叩かれたかどうかを判断する'''
if not self.is_appearing:
# 穴から出ていない
return False
if self.is_hitted:
# すでに叩かれている
return False
# モグラの画像が表示されている領域の座標を計算
x1 = self.x - self.width / 2
y1 = self.y - self.height
x2 = self.x + self.width / 2
y2 = self.y
# (mouse_x,mouse_y)が上記領域内であるかどうかを判定
if x1 <= mouse_x and x2 >= mouse_x:
if y1 <= mouse_y and y2 >= mouse_y:
# 領域内をクリックされたので叩かれた
return True
# 叩かれなかった
return False
class WhackaMole:
def __init__(self, master):
'''コンストラクタ'''
# 親ウィジェット
self.master = master
# 幅と高さを穴のサイズ・数、スペースのサイズから計算
self.width = NUM_H_HOLE * (WIDTH_HOLE + WIDTH_SPACE) + WIDTH_SPACE
self.height = NUM_V_HOLE * (HEIGHT_HOLE + HEIGHT_SPACE) + HEIGHT_SPACE
self.createWidgets()
self.drawBackground()
self.drawHoles()
self.createMoles()
self.updateFigures()
self.choiceMole()
self.updateMoles()
def createWidgets(self):
'''ウィジェットの作成と配置を行う'''
# キャンバスの作成と配置
self.canvas = tkinter.Canvas(
self.master,
width=self.width,
height=self.height,
highlightthickness=0
)
self.canvas.pack()
# ラベルの作成と配置
self.label = tkinter.Label(
self.master,
font=("", 40),
text="0"
)
self.label.pack()
def drawBackground(self):
'''背景を描画する'''
# キャンバス全面に緑色の長方形を描画
self.bg = self.canvas.create_rectangle(
0, 0, self.width, self.height,
fill="green"
)
def drawHoles(self):
'''穴を描画する'''
# 空の管理リストを作成
self.hole_coords = []
for v in range(NUM_V_HOLE):
for h in range(NUM_H_HOLE):
# 穴の中心座標を計算
x = h * (WIDTH_HOLE + WIDTH_SPACE) + WIDTH_SPACE + WIDTH_HOLE / 2
y = v * (HEIGHT_HOLE + HEIGHT_SPACE) + HEIGHT_SPACE + HEIGHT_HOLE / 2
# 穴の左上と右下座標を計算
x1 = x - WIDTH_HOLE / 2
y1 = y - HEIGHT_HOLE / 2
x2 = x + WIDTH_HOLE / 2
y2 = y + HEIGHT_HOLE / 2
# 穴を楕円として描画
self.canvas.create_oval(
x1, y1, x2, y2,
fill="black"
)
# 管理リストに追加
self.hole_coords.append((x, y))
def createMoles(self):
'''モグラを作成して表示する'''
# モグラの画像を読み込む
image = Image.open(MOLE_PATH)
# 不要な画像の外側の透明画素を除去
cropped_image = image.crop(image.getbbox())
# 穴の幅に合わせて画像を拡大縮小
ratio = (WIDTH_HOLE - 20) / cropped_image.width
resize_size = (
round(ratio * cropped_image.width),
round(ratio * cropped_image.height)
)
resized_image = cropped_image.resize(resize_size)
# tkinter用の画像オブジェクトに変換
self.mole_image = ImageTk.PhotoImage(resized_image)
self.moles = []
for coord in self.hole_coords:
# 穴の座標を取得
x, y = coord
# 穴の中心にモグラの画像の下が接するように画像を描画
figure = self.canvas.create_image(
x, y,
anchor=tkinter.S,
image=self.mole_image
)
# 描画した画像を背景画像の下側に隠す
self.canvas.lower(figure, "all")
# モグラオブジェクトを作成
width = self.mole_image.width()
height = self.mole_image.height()
mole = Mole(x, y, width, height, 1, figure)
# モグラオブジェクトを管理リストに追加
self.moles.append(mole)
# 描画画像がクリックされた時にonClickが実行されるように設定
self.canvas.tag_bind(figure, "<ButtonPress>", self.onClick)
def updateFigures(self):
'''モグラの画像を更新する'''
for mole in self.moles:
if mole.is_appearing and mole.is_draw:
# 出現中&描画フラグONの画像の場合
# モグラの画像を最前面に移動
self.canvas.lift(mole.figure, "all")
# モグラの位置を更新
self.canvas.coords(mole.figure, mole.x, mole.y)
else:
# モグラの画像を最背面に移動
self.canvas.lower(mole.figure, "all")
# FIGURE_UPDATE_INTERVAL後に再度モグラの状態を更新
self.master.after(FIGURE_UPDATE_INTERVAL, self.updateFigures)
def choiceMole(self):
'''選んだモグラを穴から出させる'''
# 穴に隠れているモグラだけのリストを作成
hide_moles = []
for mole in self.moles:
if not mole.is_appearing:
hide_moles.append(mole)
if len(hide_moles) != 0:
# 穴に隠れているモグラがいる場合
# ランダムにモグラを1つ選んで穴から出させる
mole = random.choice(hide_moles)
mole.appear()
# MOLE_CHOICE_INTERVAL後に再度別のモグラを穴から出させる
self.master.after(MOLE_CHOICE_INTERVAL, self.choiceMole)
def updateMoles(self):
'''モグラの状態や位置を更新する'''
for mole in self.moles:
# 更新前のモグラの状態を退避
is_hitted = mole.is_hitted
before_appearing = mole.is_appearing
# モグラの状態や位置を更新する
mole.update()
# 更新後にモグラが隠れたかどうかのフラグを取得
after_appearing = mole.is_appearing
if not is_hitted and before_appearing and not after_appearing:
# moleが叩かれていない&上記のupdateメソッドによりmoleが穴に潜り込んだ場合
# ポイントの加算と加算ポイントの描画
self.pointUp(-mole.point)
self.drawPoint(-mole.point, mole.x, mole.y)
# MOLE_UPDATE_INTERVAL後に再度モグラの状態を更新
self.master.after(MOLE_UPDATE_INTERVAL, self.updateMoles)
def onClick(self, event):
'''クリックされた時の処理を実行する'''
for mole in self.moles:
# moleの画像がクリックされたかどうかを判断
if mole.isHit(event.x, event.y):
# クリックされた場合
# moleを叩く
mole.hit()
# ポイントの加算と加算ポイントの描画
self.pointUp(mole.point)
self.drawPoint(mole.point, event.x, event.y)
def pointUp(self, point):
'''ポイントを加算する'''
# ラベルに表示されているポイントを取得
now_point = int(self.label.cget("text"))
# 取得したポイントに加算して表示文字列に設定
now_point += point
self.label.config(text=str(now_point))
def drawPoint(self, point, x, y):
'''加算されたポイントを表示'''
if point >= 0:
sign = "+"
color = "yellow"
else:
sign = "-"
color = "red"
point_figure = self.canvas.create_text(
x, y,
text=sign + str(abs(point)),
fill=color,
font=("", 40)
)
# DRAW_TIME_POINT後に描画した文字列を削除
self.master.after(
POINT_DRAW_TIME, lambda: self.canvas.delete(point_figure))
app = tkinter.Tk()
game = WhackaMole(app)
app.mainloop()
スポンサーリンク
モグラたたきゲームの作り方
では、ここから本題である「モグラたたきゲームの作り方」について解説していきます。
大きく分けると、モグラゲームを作るために下記の9個のことを行なっていきます。結構解説内容の分量も多いですので、休みを入れながらゆっくり読んでいっていただければと思います。
- 各種パラメータを定義する
- ウィジェットを作成する
- 背景と穴を描画する
- モグラを描画する
- モグラのクラスとモグラのオブジェクトを作成する
- モグラの画像を定期的に更新する
- モグラを穴から出す
- モグラを叩けるようにする
- ポイントの加減算を行う
最終的に出来上がるスクリプトは モグラたたきゲームのスクリプト と全く同じになりますので、モグラたたきゲームのスクリプト のスクリプトで何をやっているか分からないところや気になるところだけ読んでも良いですし、もちろん最初から読んでいただいても問題ないです!
解説については、スクリプトを変更しながら、その変更内容について補足していく形で行なっていきたいと思います。スクリプトの変更部分は太字で示していますが、違いが分かりにくい可能性もありますのでご注意ください。
各種パラメータを定義する
では、モグラたたきゲームの作り方の解説を行なっていきます。
まずは、モグラたたきゲームの画面の作成等に必要なパラメータをグローバル変数として設定していきたいと思います。
具体的には、スクリプトを下記のように作成します(必要なモジュールのインポートも行っています)。
import tkinter
import random
from PIL import Image, ImageTk
MOLE_PATH = "animal_chara_mogura_hakase.png" # モグラの画像のファイルパス
NUM_H_HOLE = 4 # 横方向の穴の数
NUM_V_HOLE = 3 # 縦方向の穴の数
WIDTH_HOLE = 100 # 穴の幅(長軸の長さ)
HEIGHT_HOLE = 50 # 穴の高さ(短軸の長さ)
WIDTH_SPACE = 20 # 穴と穴のスペースの幅
HEIGHT_SPACE = 80 # 穴と穴のスペースの高さ
POINT_DRAW_TIME = 500 # 加算or減算されたポイントが表示される時間
MOLE_UPDATE_INTERVAL = 100 # モグラの状態や位置を更新する時間間隔
FIGURE_UPDATE_INTERVAL = 100 # モグラの画像を更新する時間間隔
MOLE_CHOICE_INTERVAL = 1000 # 穴から出すモグラを決める時間間隔
配置に関するパラメータ
各種パラメータの意味合いはコメントにも書いていますが、特に穴の配置に関するパラメータの意味合いを図で示すと、下の図のようになります。
時間に関するパラメータ
図で表せなかった時間に関するパラメータについて捕捉しておくと、まず POINT_DRAW_TIME
はモグラを叩いて加算されたポイント or モグラを叩けなくて減算されたポイントを画面に描画しておく時間になります。
MOLE_UPDATE_INTERVAL
はモグラが穴から出てきた時にモグラを上方向 or 下方向に移動させる時間間隔になります。これが大きいほどモグラが早く移動します。また、モグラの状態の更新もこの時間間隔で行っていきます。
FIGURE_UPDATE_INTERVAL
はモグラの位置等に応じてモグラの画像を更新する時間間隔になります。この値は MOLE_UPDATE_INTERVAL
以下の値に設定することをオススメします。MOLE_UPDATE_INTERVAL
よりも大きな値に設定すると、モグラを叩けるようにする で導入するモグラの点滅がうまく動作しない場合があるので注意してください。
MOLE_CHOICE_INTERVAL
は新しく穴から出すモグラを選択する時間の間隔になります。1匹モグラを穴から出した後、この MOLE_CHOICE_INTERVAL
ms 経過した後に新しいモグラを選択して穴から出すことになります。
これらの時間に関するパラメータの単位は全て “ミリ秒” になります。
画像に関するパラメータ
また、MOLE_PATH
はモグラを表示する際に使用する画像のファイルパスとなります。ご自身で用意したモグラの画像のファイルパスに応じて MOLE_PATH
を設定してください。
ちなみに、このページで紹介するゲーム画面におけるモグラは、全て いらすとや の下記 URL のモグラの画像を使用しています。
https://www.irasutoya.com/2020/11/blog-post_69.html
上記 URL から転載させていただいたものが下の画像となります。
転載元:いらすとや
特に使用したいモグラの画像がないのであれば、この画像がオススメです。
ただし、前述の通り、ご自身が保存した画像のファイルパスに応じて MOLE_PATH
は変更する必要があるのでご注意ください。
ウィジェットを作成する
続いて、モグラたたきゲームに必要になる「ウィジェット」を作成していきます。
今回作成するモグラたたきゲームでは、メインウィンドウに加え、穴やモグラを描画するためのキャンバス及び、ポイントを表示するためのラベルウィジェットを利用します。
これらのウィジェットの作成は、スクリプトを下記のように変更することで実現できます。
import tkinter
import random
from PIL import Image, ImageTk
MOLE_PATH = "animal_chara_mogura_hakase.png" # モグラの画像のファイルパス
NUM_H_HOLE = 4 # 横方向の穴の数
NUM_V_HOLE = 3 # 縦方向の穴の数
WIDTH_HOLE = 100 # 穴の幅(長軸の長さ)
HEIGHT_HOLE = 50 # 穴の高さ(短軸の長さ)
WIDTH_SPACE = 20 # 穴と穴のスペースの幅
HEIGHT_SPACE = 80 # 穴と穴のスペースの高さ
POINT_DRAW_TIME = 500 # 加算or減算されたポイントが表示される時間
MOLE_UPDATE_INTERVAL = 100 # モグラの状態や位置を更新する時間間隔
FIGURE_UPDATE_INTERVAL = 100 # モグラの画像を更新する時間間隔
MOLE_CHOICE_INTERVAL = 1000 # 穴から出すモグラを決める時間間隔
class WhackaMole:
def __init__(self, master):
'''コンストラクタ'''
# 親ウィジェット
self.master = master
# 幅と高さを穴のサイズ・数、スペースのサイズから計算
self.width = NUM_H_HOLE * (WIDTH_HOLE + WIDTH_SPACE) + WIDTH_SPACE
self.height = NUM_V_HOLE * (HEIGHT_HOLE + HEIGHT_SPACE) + HEIGHT_SPACE
self.createWidgets()
def createWidgets(self):
'''ウィジェットの作成と配置を行う'''
# キャンバスの作成と配置
self.canvas = tkinter.Canvas(
self.master,
width=self.width,
height=self.height,
highlightthickness=0
)
self.canvas.pack()
# ラベルの作成と配置
self.label = tkinter.Label(
self.master,
font=("", 40),
text="0"
)
self.label.pack()
app = tkinter.Tk()
game = WhackaMole(app)
app.mainloop()
今回はモグラたたきゲーム全体を制御するクラスとして WhackaMole
を用意しています(モグラたたきは英語で「Whack a Mole」と言うそうです)。
メインウィンドウを tkinter.Tk()
により作成し、そのメインウィンドウオブジェクトを WhackaMole
クラスに渡し、さらに WhackaMole
クラスの createWidgets
メソッドで tkinter.Canvas()
と tkinter.Label()
を実行してメインウィンドウ上へのキャンバス及びラベルの作成を行なっています。
また、上記では WhackaMole
クラスのデータ属性を3つ用意しており、それぞれの意味合いは下記のようになります。
master
:モグラたたきゲームの作成先となる親ウィジェットwidth
:キャンバスの幅height
:キャンバスの高さ
master
は今回の場合はメインウィンドウとなります。これを tkinter.Canvas()
及び tkinter.Label()
の第1引数に指定することで、キャンバスとラベルがメインウィンドウ上に作成されることになります。
また、width
と height
に関しては、穴の個数や穴のサイズ、スペースのサイズから計算を行なっており、これらの値を tkinter.Canvas()
実行時にキャンバスのサイズとして指定するようにしています。
今回は、穴の両脇に空きスペースを入れるような配置にしたかったので、下の図のように穴と空きスペースがキャンバス内に配置できるよう、上記のような計算式で幅と高さを計算しています(下の図は幅の例になりますが、高さも同様の考え方で配置を行なっています)。
計算時に用いている各変数の意味合いについては 各種パラメータを定義する を参照していただければと思います。
上記の変更により、スクリプトを実行すればアプリのメインウィンドウが表示され、その中にキャンバスとラベルが表示されるようになり、さらにメインループが実行されアプリが待機状態となります(キャンバスは分かりにくかも…)。
ウィジェット作成に関する解説は以上となりますが、メインウィンドウに関しては下記ページで、
Tkinterの使い方:メインウィンドウを作成するキャンバスに関しては下記ページで、
Tkinterの使い方:キャンバスウィジェットの作り方ラベルに関しては下記ページで、
Tkinterの使い方:ラベルウィジェット(Label)の使い方メインループに関しては下記ページでそれぞれ解説しておりますので、必要に応じてご参照いただければと思います。
【Python】tkinterのmainloopについて解説スポンサーリンク
背景と穴を描画する
続いて「背景」と「穴」を描画していきます。
これらの描画は、WhackaMole
クラスに下記の drawBackground
メソッドと drawHoles
メソッドを追加し、さらに __init__
からこれらのメソッドを実行するように変更することで実現できます(ここからは変更を行うメソッドのみを抜粋して変更箇所を示していきます。変更している箇所はこれまで通り、太字で示しています)。
class WhackaMole:
def __init__(self, master):
'''コンストラクタ'''
# 親ウィジェット
self.master = master
# 幅と高さを穴のサイズ・数、スペースのサイズから計算
self.width = NUM_H_HOLE * (WIDTH_HOLE + WIDTH_SPACE) + WIDTH_SPACE
self.height = NUM_V_HOLE * (HEIGHT_HOLE + HEIGHT_SPACE) + HEIGHT_SPACE
self.createWidgets()
self.drawBackground()
self.drawHoles()
def drawBackground(self):
'''背景を描画する'''
# キャンバス全面に緑色の長方形を描画
self.bg = self.canvas.create_rectangle(
0, 0, self.width, self.height,
fill="green"
)
def drawHoles(self):
'''穴を描画する'''
# 空の管理リストを作成
self.hole_coords = []
for v in range(NUM_V_HOLE):
for h in range(NUM_H_HOLE):
# 穴の中心座標を計算
x = h * (WIDTH_HOLE + WIDTH_SPACE) + WIDTH_SPACE + WIDTH_HOLE / 2
y = v * (HEIGHT_HOLE + HEIGHT_SPACE) + HEIGHT_SPACE + HEIGHT_HOLE / 2
# 穴の左上と右下座標を計算
x1 = x - WIDTH_HOLE / 2
y1 = y - HEIGHT_HOLE / 2
x2 = x + WIDTH_HOLE / 2
y2 = y + HEIGHT_HOLE / 2
# 穴を楕円として描画
self.canvas.create_oval(
x1, y1, x2, y2,
fill="black"
)
# 管理リストに追加
self.hole_coords.append((x, y))
上記のように変更を行なったスクリプトを実行すれば、下の図のような緑色背景の中に黒色の穴が表示されるようになります。
背景の描画
背景の描画を行なっているのが drawBackground
メソッドになります。
といっても、drawBackground
では単にキャンバスの create_rectangle
で色が "green"
の長方形をキャンバス全面に描画しているだけです。
実は、キャンバスの背景の色を "green"
にするだけであれば、長方形をわざわざ描画しなくても、tkinter.Canvas()
実行時に bg="green"
オプションを指定すれば良いだけです。
ただ、今回のモグラたたきゲームにおいては、この長方形がモグラを隠すために非常に重要な役割を果たしますので、今回は長方形を描画することでキャンバスの背景の色の変更を行なっています。
穴の描画
穴の描画を行なっているのが drawHoles
メソッドになります。
NUM_V_HOLE
x NUM_H_HOLE
個の穴を楕円としてキャンバスの create_oval
メソッドで描画しています。
楕円などの図形をキャンバスで描画する際には、その楕円の “左上座標” と “右下座標” をメソッドの引数に指定する必要があります。
そのため drawHoles
メソッドでは、左上座標 (x1
, y1
) と右下座標 (x2
, y2
) を求めてから、これらの座標を引数に指定して create_oval
メソッドを実行するようにしています。
さらに、これらの左上座標と右下座標を求めるにあたって事前に楕円の中心座標 (x
, y
) を求めています。そして、この中心座標から穴の幅と高さを考慮して左上座標と右下座標を求めています。
難しいのが、この中心座標の求め方かなぁと思います。穴の周りに空きスペースが出来るように中心座標の x
と y
を算出する必要があります。
例えば h = 2
、v = 1
の場合、drawHoles
メソッドでは下の図のような長さを求めながら中心座標を算出する計算を行っています。
drawHoles
にはもう一つポイントがあって、それは各穴の中心座標を hole_coords
リストに格納している点です。
以降で作成していくモグラは、穴の中心座標の上側に表示させるようにしていくため、わざわざ計算し直さなくてもいいように、各穴の中心座標をリストで管理できるようにしています。
モグラを描画する
背景と穴が描画できましたので、次はモグラを描画していきたいと思います。
モグラは各穴に1匹ずつ描画していきます。つまり、穴の数だけモグラを描画していきます。
モグラの画像オブジェクトを生成する
前述の通り、モグラは画像を描画することで表現していきます。
そのために、まずはキャンバスに描画可能なモグラの画像オブジェクトを作成していきたいと思います。
ただ、モグラは穴から出てきたり、穴に隠れたりするわけですから、穴のサイズに合わせて画像サイズを拡大縮小してからオブジェクトの生成を行なっていきたいと思います。
また、各種パラメータを定義する で紹介した画像もそうなのですが、モグラの絵の周りに余分な透明な画素が付いている場合があるので、それも除去してから画像オブジェクトを生成したいと思います。
これらの処理は、WhackaMole
クラスに下記の createMoles
メソッドを追加し、さらに __init__
から追加した createMoles
メソッドを実行するように変更することで実現することができます。
class WhackaMole:
def __init__(self, master):
'''コンストラクタ'''
# 親ウィジェット
self.master = master
# 幅と高さを穴のサイズ・数、スペースのサイズから計算
self.width = NUM_H_HOLE * (WIDTH_HOLE + WIDTH_SPACE) + WIDTH_SPACE
self.height = NUM_V_HOLE * (HEIGHT_HOLE + HEIGHT_SPACE) + HEIGHT_SPACE
self.createWidgets()
self.drawBackground()
self.drawHoles()
self.createMoles()
def createMoles(self):
'''モグラを作成して表示する'''
# モグラの画像を読み込む
image = Image.open(MOLE_PATH)
# 不要な画像の外側の透明画素を除去
cropped_image = image.crop(image.getbbox())
# 穴の幅に合わせて画像を拡大縮小
ratio = (WIDTH_HOLE - 20) / cropped_image.width
resize_size = (
round(ratio * cropped_image.width),
round(ratio * cropped_image.height)
)
resized_image = cropped_image.resize(resize_size)
# tkinter用の画像オブジェクトに変換
self.mole_image = ImageTk.PhotoImage(resized_image)
createMoles
メソッドでは、まず PIL の Image.open
関数を実行して MOLE_PATH
のモグラの画像ファイルを読み込み、読み込んだ画像の PIL オブジェクト image
の生成を行なっています。
各種パラメータを定義する でも解説したように、モグラの画像ファイルは自身で用意していただき、さらに用意したファイルのパスに応じて MOLE_PATH
も設定する必要があるので注意してください。
続いて createMoles
メソッドでは、image
に crop
メソッドを実行させることで、モグラの絵の周りにある不要な透明画素を除去した画像オブジェクト cropped_image
を生成しています。
さらに、cropped_image
に resize
メソッド実行させることで、穴の横幅に基づいて拡大縮小を行なった画像オブジェクト resized_image
を生成しています。
resize
メソッドに指定する引数は拡大縮小後の幅と高さのタプルです。
この拡大縮小後の幅と高さを求めるために、画像の横幅を穴の幅よりも若干小さい WIDTH_HOLE - 20
に拡大縮小するための拡大率 ratio
を計算し、さらに求めた拡大率 ratio
を cropped_image
の幅と高さそれぞれに掛けることで、拡大縮小後の幅と高さを求めています。
このように resize
メソッドの引数を指定することで、画像の幅を穴の幅よりも若干小さくし、さらに画像の縦横比を保ったまま拡大縮小することを実現しています。
ちなみに拡大率を計算するときに WIDTH_HOLE - 20
を用いていますが、この 20
はてきとうです。画像の幅を穴に対してちょっと小さくしたかったので、とりあえず 20
を選んでみました。
で、生成された resized_image
はまさに描画したい画像のオブジェクトとなったのですが、tkinter のキャンバスに描画するためには、tkinter の画像オブジェクトに変換する必要があります。
この変換を、PIL の ImageTk.PhotoImage
クラスのコンストラクタ実行により行なっています。そして、このコンストラクタの返却オブジェクトが、キャンバスに描画可能な画像オブジェクトとなります。
この画像オブジェクトの変換については下記ページで解説していますので、詳しく知りたい方は下記ページをご参照ください。
【Python】PIL ⇔ OpenCV2 ⇔ Tkinter 画像オブジェクトの相互変換モグラを穴の数だけ描画する
キャンバスに描画可能なオブジェクトが準備できましたので、あとはこの画像を穴の数だけ穴の位置に合わせて描画していけばよいだけです。
これは createMoles
メソッドを下記のように変更することで実現することができます。
class WhackaMole:
def createMoles(self):
'''モグラを作成して表示する'''
# モグラの画像を読み込む
image = Image.open(MOLE_PATH)
# 不要な画像の外側の透明画素を除去
cropped_image = image.crop(image.getbbox())
# 穴の幅に合わせて画像を拡大縮小
ratio = (WIDTH_HOLE - 20) / cropped_image.width
resize_size = (
round(ratio * cropped_image.width),
round(ratio * cropped_image.height)
)
resized_image = cropped_image.resize(resize_size)
# tkinter用の画像オブジェクトに変換
self.mole_image = ImageTk.PhotoImage(resized_image)
for coord in self.hole_coords:
# 穴の座標を取得
x, y = coord
# 穴の中心にモグラの画像の下が接するように画像を描画
figure = self.canvas.create_image(
x, y,
anchor=tkinter.S,
image=self.mole_image
)
上記のように変更を加えたスクリプトを実行すれば、全ての穴の上にモグラが表示されるようになったことが確認できると思います。
WhackaMole
クラスの hole_coords
は 背景と穴を描画する で作成した穴を描画するメソッド drawHoles
で作成したリストで、各穴の中心座標が管理されています。
createMoles
を上記のように変更することで、ループの中でその座標を取得し、取得した座標に先ほど生成した画像オブジェクトの描画を行なっています。画像はキャンバスの create_image
メソッドにより描画することができ、さらに描画する位置は第1引数と第2引数で指定することができます。
ポイントは create_image
メソッドで指定しているオプション anchor=tkinter.S
を指定している点です。この anchor=tkinter.S
を指定することで、第1引数と第2引数で指定した座標が画像の中央下に来るように画像を描画することができます。
ですので、上記のように create_image
メソッドを指定することで、穴の中心 (x
, y
) 座標にモグラの画像の中央下が来るように画像が描画され、モグラが穴の上にいる感じで描画することができます。
で、今後はこの位置のモグラを上方向に移動させることで、モグラが穴から出た感じを表現していくことになります。
ちなみにこのオプションを指定しない場合、第1引数と第2引数で指定した座標に画像の中央が来るように画像が描画されることになります。
こういった、図形を描画する際に使用するメソッドに指定可能なオプションは下記ページで解説していますので、詳しく知りたい方は下記ページをご参照ください。
Tkinterの使い方:Canvasクラスで図形を描画するモグラを隠れさせる
モグラは描画できるようになりましたが、モグラたたきゲーム開始時はモグラが穴に隠れている状態にしたいので、次はモグラを隠れさせるようにしていきたいと思います。
隠れさせるためにモグラを非表示にしてもよいのですが、今回はキャンバス全体に背景として長方形が描画されていますので、その長方形の背面にモグラを移動させることで、モグラを隠れさせるようにしたいと思います。
これは、WhackaMole
クラスの createMoles
を下記のように変更するだけで実現することができます。
class WhackaMole:
def createMoles(self):
# 略
for coord in self.hole_coords:
# 穴の座標を取得
x, y = coord
# 穴の中心にモグラの画像の下が接するように画像を描画
figure = self.canvas.create_image(
x, y,
anchor=tkinter.S,
image=self.mole_image
)
# 描画した画像を背景画像の下側に隠す
self.canvas.lower(figure, "all")
上記のように変更を加えれば、モグラが全て隠れた状態になることが確認できると思います。
キャンバスの lower
メソッドは、第1引数で指定した ID の図形を第2引数で指定した ID の図形の背面に移動させるメソッドになります。
第2引数に "all"
を指定した場合、第1引数で指定した ID の図形が最背面に移動することになります(引数にはタグ名を指定することも可能です)。
この図形の ID は、create_image
等の図形を描画するメソッドの返却値として取得することが可能です。上記の createMoles
メソッドでは create_image
の全ての返却値に対して lower
メソッドを実行していますので、全てのモグラの画像が背景の長方形の裏側に隠れることになります。
また、キャンバスには、逆に図形を前面に移動するためのメソッドとして lift
が存在します。従って、この lift
メソッドを実行すれば、今は隠れて見えないモグラの画像を前面に移動させて見えるようにすることができます。
後述での解説においては、この lift
メソッドを利用してモグラの画像を前面に移動させることで、モグラが穴から出たことを表現していきます。
これらの描画した図形を後から操作するメソッドについては下記ページでまとめていますので、詳しく知りたい方は下記ページをご参照いただければと思います。
Tkinterの使い方:Canvasクラスで描画した図形を操作するモグラのクラスとモグラのオブジェクトを作成する
ここまでで、モグラたたきゲームの土台が大体できてきたかなぁという感じです。
ここからはモグラに対する処理が多くなってきますので、それらの処理を実装しやすいよう、次はモグラを表現するクラスを作成していきたいと思います。
Mole
クラスの作成
今回は、このモグラを表現するクラスを Mole
とし、下記のように作成したいと思います。ひとまずコンストラクタの __init__
だけを用意しています。
# 略
MOLE_CHOICE_INTERVAL = 1000 # 穴から出すモグラを決める時間間隔
class Mole:
def __init__(self, x, y, width, height, speed, figure):
'''コンストラクタ'''
self.x = x # 中央下の位置(横方向)
self.y = y # 中央下の位置(縦方向)
self.width = width # 画像の幅
self.height = height # 画像の高さ
self.figure = figure # 描画画像の図形ID
self.hole_y = y # モグラが穴に潜る高さ
self.top_y = self.hole_y - HEIGHT_HOLE / 3 # 穴から出る高さ
self.speed = speed # 移動スピード
self.point = int(speed * 10) # 叩いた時にゲットできるポイント
self.is_up = False # 上方向に移動中かどうかを管理するフラグ
self.is_draw = True # 描画するかどうかを管理するフラグ
self.is_hitted = False # 既に叩かれているかどうかを管理するフラグ
self.is_appearing = False # 穴から出ているかどうかを管理するフラグ
class WhackaMole:
#略
データ属性が多いですが、それぞれの意味合いはコメントに記述しているので大体わかるかなぁと思います。
一応説明しておくと、まず x
と y
はモグラの現在位置を示すデータ属性です。モグラの画像が描画されている位置と考えてもよいです。
x
と y
は、モグラの中心ではなくモグラの「中央下」の位置を示すデータ属性である点に注意してください。モグラを描画する でモグラの「中央下」の位置を基準に画像の描画を行なったため、それに合わせて「中央下」の位置を示すようにしています。
より具体的に言えば、モグラを描画する で各モグラを穴の中央の位置に合わせて描画を行なったので、この x
と y
にはその穴の中央の位置が指定されることを想定しています。
また、width
と height
は描画されているモグラの画像の幅と高さになります。さらに、figure
はモグラの画像の図形 ID となります。
今後モグラを表示していく際には、この x
と y
の値に従ってモグラの画像の描画位置を移動していくことになります。
また、モグラを描画する でも使用した lower
メソッドのように、キャンバスに描画した図形を操作するためには図形 ID が必要ですので、Mole
クラスのオブジェクトからすぐに図形 ID が取得できるよう、データ属性 figure
を用意しています。
さらに、モグラの画像がクリックされたかどうかの判定(モグラが叩かれたかどうかの判定)は、x
と y
及び width
と height
から画像が表示されている領域を求め、その領域内がクリックされたかどうかで判定を行なっていきます。
ここまで紹介してきたデータ属性は主に描画されているモグラの画像に関するものでしたが、それに対して hole_y
と top_y
と speed
はモグラの縦方向の移動に関するデータ属性になります。
モグラが穴から出てきた際には、モグラを上方向に移動し、さらに頂点まで達した際にはモグラを穴の中央の位置まで下方向に移動させていくことになります。
この頂点の位置を示すデータ属性が top_y
であり、穴の中央の位置を示すデータ属性が hole_y
となります。
さらに、一度の移動で移動する量が、データ属性 speed
となります。
また、point
はモグラを叩いた時に加算されるポイント、モグラを叩けなかった時に減算されるポイントで、speed
が大きい方が叩くのが難しくなるので、その分大きなポイントが加算されるように設定しています。
is
が付いているデータ属性は全てモグラの状態を示すフラグです。これらに関しては、後から実際に使用するようになった際に説明していきたいと思います。
Mole
オブジェクトの作成
現状はまだ __init__
しかありませんが、一応 Mole
クラスの枠組みはできましたので、次は実際に Mole
クラスのオブジェクトを作成していきたいと思います。
モグラは穴の数の分だけ穴から出てくる可能性があるため、穴の数の分、Mole
クラスのオブジェクトを作成していきます。
このようなオブジェクトの生成は、WhackaMole
クラスの createMoles
を下記のように変更することで実現することができます。
class WhackaMole:
def createMoles(self):
# 略
# tkinter用の画像オブジェクトに変換
self.mole_image = ImageTk.PhotoImage(resized_image)
# モグラの管理リストを作成
self.moles = []
for coord in self.hole_coords:
# 穴の座標を取得
x, y = coord
# 穴の中心にモグラの画像の下が接するように画像を描画
figure = self.canvas.create_image(
x, y,
anchor=tkinter.S,
image=self.mole_image
)
# 描画した画像を背景画像の下側に隠す
self.canvas.lower(figure, "all")
# モグラオブジェクトを作成
width = self.mole_image.width()
height = self.mole_image.height()
mole = Mole(x, y, width, height, 1, figure)
# モグラオブジェクトを管理リストに追加
self.moles.append(mole)
hole_coords
では全ての穴の座標が管理されていますので、hole_coords
に対するループの中で Mole()
を実行することで、穴の数の分のオブジェクトを生成することができます。
また、Mole()
実行時に下記の引数を指定しています。
x
:モグラの現在位置(中央下)の横方向の座標y
:モグラの現在位置(中央下)の縦方向の座標width
:モグラの画像の幅height
:モグラの画像の高さspeed
:モグラの移動スピードfigure
:モグラの画像の図形 ID(create_image
の返却値)
x
と y
に関しては、create_image
の第1引数と第2引数に指定した座標を指定すれば良いですし(create_image
の第1引数と第2引数にも画像の中央下の座標を指定している)、figure
に関しては create_image
の返却値をそのまま指定してやれば良いです。
また、width
と height
に関しては、tkinter の画像オブジェクトに width
メソッドおよび height
メソッドを実行させることで取得することができます。
speed
は 1
に設定していますが、各種パラメータを定義する での設定値の場合はこれくらいが丁度いいかなぁと思います。お好みに合わせてこの値は変更しても良いですし、モグラ毎に値を変更したり、時間の経過に伴って後からこの値を変更して徐々にモグラのスピードを上げていくのも面白いかと思います。
上記のように作成した Mole
のオブジェクトは全て、データ属性のリスト moles
に格納していますので、以降はこの moles
からオブジェクトを取得して Mole
のオブジェクトに処理を依頼する事ができます。
スポンサーリンク
モグラの画像を定期的に更新する
以降ではモグラを穴から出して上方向に移動するような処理を実現していくのですが、その前に、ここでモグラの画像を定期的に更新するようにしていきたいと思います。
そのために、まず、WhackaMole
クラスに下記の updateFigures
メソッドを追加し、さらに、この updateFigures
を実行するように __init__
を変更します。
class WhackaMole:
def __init__(self, master):
# 略
self.createWidgets()
self.drawBackground()
self.drawHoles()
self.createMoles()
self.updateFigures()
def updateFigures(self):
'''モグラの画像を更新する'''
for mole in self.moles:
if mole.is_appearing and mole.is_draw:
# 出現中&描画フラグONの画像の場合
# モグラの画像を最前面に移動
self.canvas.lift(mole.figure, "all")
# モグラの位置を更新
self.canvas.coords(mole.figure, mole.x, mole.y)
else:
# モグラの画像を最背面に移動
self.canvas.lower(mole.figure, "all")
# FIGURE_UPDATE_INTERVAL後に再度モグラの状態を更新
self.master.after(FIGURE_UPDATE_INTERVAL, self.updateFigures)
updateFigures
メソッドは、モグラのオブジェクト mole
の状態や位置に応じて描画している画像を更新するメソッドになります。
mole
のデータ属性 is_appearing
は、mole
が穴から出ている状態であるかを示すフラグとなります。また、mole
のデータ属性 is_draw
はモグラを描画するかどうかを示すフラグとなります。
より具体的には、データ属性 is_draw
は、モグラが叩かれた時にモグラを点滅させるためのフラグになります。これについては後述の モグラを叩けるようにする で詳しく解説しますので、まずは気にせず読み進めていただければと思います。ちなみにデータ属性 is_draw
の初期値は True
となります。
updateFigures
メソッドでは、この2つのデータ属性が True
の時に、まず mole
の画像をキャンバスの lift
メソッドにより最前面に移動します。
モグラを描画する ではモグラが穴に隠れていることを表現するために画像を最背面に移動しましたが、最前面に移動することによりモグラが画面に表示され、モグラが穴から出てきたことを表現する事ができます。
続いて updateFigures
メソッドでは、キャンバスの coords
メソッドにより mole
の画像をモグラの位置(x
と y
)に移動させます。これにより、モグラの位置が移動されていれば、モグラの画像の表示位置が変化することになります。
逆に is_appearing
と is_draw
のどちらか一方でも False
の場合は、lower
メソッドによりまた画像を最背面に移動させるような処理を行なっています。
さらに、updateFigures
メソッドの最後で after
メソッドを実行し、FIGURE_UPDATE_INTERVAL
ms 後に再度 updateFigures
メソッドが実行されるように設定しています。
そのため、この updateFigures
メソッドは FIGURE_UPDATE_INTERVAL
ms 間隔で定期的に実行されることになります。
ですので、他のメソッドでモグラの状態を変更したり(is_appearing
や is_draw
を変更)、モグラの位置を変更したりした場合(x
や y
を変更)、自動的にそれらに応じてモグラの画像が更新されるようになることになります。
と言っても、まだモグラの状態や位置を変更するメソッドを作成していないため、上記のような変更を行なったスクリプトを実行しても今までも見た目に変化はありません。
ただ、お試しで下記のように updateFigures
メソッドを変更すれば、モグラの画像が表示され、上側に移動していく様子が確認できると思います(確認が終わったら追加した行は消しておいてください)。
要は、他のメソッドでこのような位置や状態の変更を行なってやることで、モグラを穴から出したり穴に隠したり、穴から出たモグラを上方向に移動したりする様子が、キャンバス上の図形に反映されるようになったことになります。
class WhackaMole:
def updateFigures(self):
'''モグラの画像を更新する'''
for mole in self.moles:
# お試しで変更
mole.y -= 1
mole.is_appearing = True
if mole.is_appearing and mole.is_draw:
# 出現中&描画フラグONの画像の場合
# モグラの画像を最前面に移動
self.canvas.lift(mole.figure, "all")
# モグラの位置を更新
self.canvas.coords(mole.figure, mole.x, mole.y)
else:
# モグラの画像を最背面に移動
self.canvas.lower(mole.figure, "all")
# FIGURE_UPDATE_INTERVAL後に再度モグラの状態を更新
self.master.after(FIGURE_UPDATE_INTERVAL, self.updateFigures)
ちなみに、ここで利用した after
メソッドについては下記ページで解説していますので、詳しく知りたい方はぜひ下記ページをご参照ください。tkinter を利用するのであれば知っておいて損はないメソッドだと思います。
モグラを穴から出す
モグラの状態を変更することでモグラの画像を表示し、さらにモグラの位置を変更することでモグラの画像が移動するようになりましたので、次は「モグラを穴から出す」処理を実現していきたいと思います。
モグラを穴から出す手順
おさらいしておくと、モグラの画像を定期的に更新する で追加した updateFigures
メソッドにより、Mole
クラスのオブジェクトのデータ属性 is_appearing
を True
に設定してやれば、そのモグラの画像が最前面に移動して画面に表示されるようになりました(is_draw
に関してはまだ使用しません)。
また、Mole
クラスのオブジェクトのデータ属性 x
や y
を変更すれば、モグラの画像の位置が移動するようになりました。
さらに、updateFigures
メソッドは定期的に実行されるようにしていますので、上記のデータ属性の変更を行えば、自動的にその変更が画面に反映されることになります。
したがって、モグラを穴から出す処理は、次のようなことを行うことで実現することができます。
まず、穴から出したいモグラのオブジェクトの is_appearing
を True
に設定して画面に表示します(穴から出すモグラはランダムに選択するようにしていきます)。
その後に y
を減少させ、モグラを上側に移動させていきます(キャンバスの縦軸の正方向は下方向のため、y
減少させればキャンバスの上側に移動することになります)。
モグラのクラスとモグラのオブジェクトを作成する で解説したように、モグラが上側に移動する頂点の縦軸の座標はデータ属性 top_y
で設定されています。ですので、y
が top_y
以下になれば上側への移動をやめ、今度は逆に y
を増加させることで、モグラを下側に移動させていきます。
さらに、y
が穴の位置を示すデータ属性 hole_y
以上になった際に is_appearing
を False
に設定してやれば、モグラが最背面に移動してモグラが非表示になり、モグラが穴に隠れたように見せる事ができます。
上記のような一連の処理を実装してやれば、穴からモグラを出すことを実現する事ができます。
ただし、y
をループ処理などで一気に増減させてしまうとモグラが一瞬で移動して隠れることになってしまいます。
そのため、MOLE_UPDATE_INTERVAL
ms 毎に定期的に y
を増減させるようにすることで、モグラがゆっくり上下方向に移動するようにしていきたいと思います(定期的な処理を実現するので、updateFigures
メソッドの時同様に after
メソッドを利用します)。
モグラを穴から出させて表示する
では、モグラを穴から出す処理を実装していきたいと思います。
まずは、穴から出た瞬間にモグラを表示するためのメソッドを Mole
クラスに追加していきます。
といっても、ここで行うのはモグラを表示させるだけですので、Mole
クラスに下記の appear
メソッドを追加すれば良いだけです(この appear
メソッドは上方向の移動を行うために後から少し変更します)。
class Mole:
def appear(self):
'''モグラを穴から出す'''
# 穴から出ているフラグをON
self.is_appearing = True
この appear
メソッドが実行されれば、実行したモグラの画像が updateFigures
メソッドにより最前面に移動し、画面に表示されるようになります。
穴から出すモグラをランダムに選ぶ
次は、穴から出したいモグラのオブジェクトに先ほど追加した appear
メソッドを実行させるようにしていきたいと思います。
今回は、穴から出すモグラを「まだ穴から出ていない全モグラ」の中からランダムに選択するようにしていきたいと思います。
より具体的には、WhackaMole
クラスのデータ属性 moles
には全モグラのオブジェクトが格納されていますので、このオブジェクトの中から is_appearing
が False
のオブジェクトのみを抽出し、さらにその抽出したオブジェクトの中から穴から出すモグラをランダムに1匹選択するようにします。
このようなランダムな選択は、WhackaMole
クラスに下記の choiceMole
メソッドを追加し、さらに __init__
からこのchoiceMole
メソッドを実行するようにすることで実現することができます。
class WhackaMole:
def __init__(self, master):
# 略
self.createWidgets()
self.drawBackground()
self.drawHoles()
self.createMoles()
self.updateFigures()
self.choiceMole()
def choiceMole(self):
'''選んだモグラを穴から出させる'''
# 穴に隠れているモグラだけのリストを作成
hide_moles = []
for mole in self.moles:
if not mole.is_appearing:
hide_moles.append(mole)
if len(hide_moles) != 0:
# 穴に隠れているモグラがいる場合
# ランダムにモグラを1つ選んで穴から出させる
mole = random.choice(hide_moles)
mole.appear()
# MOLE_CHOICE_INTERVAL後に再度別のモグラを穴から出させる
self.master.after(MOLE_CHOICE_INTERVAL, self.choiceMole)
hide_moles
には、moles
で管理されている Mole
クラスの全オブジェクトの中で、is_appearing
が False
のオブジェクトのみが格納されます。
さらに、random.choice
は引数に指定したリストの中からランダムに1つのオブジェクトを選択して返却する関数ですので、random.choice(hide_moles)
を実行すれば、 is_appearing
が False
のオブジェクトの中から1つのオブジェクトをランダムに選択することになります。
このような処理により、「まだ穴から出ていない全モグラ」の中からランダムに穴から出すモグラを選択することを実現しています。
さらに、選択したオブジェクトに appear
メソッドを実行させることで is_appearing
が True
に設定され、そのオブジェクトのモグラの画像が画面に表示されることになります。
モグラを1匹のみ穴から出させるのであれば上記のような処理を1回実行すれば良いだけなのですが、モグラたたきゲームでは定期的にモグラが穴から出てくるようになっているため、今回作成するゲームにおいても定期的に choiceMole
メソッドを実行して定期的にモグラを穴から出すようにしています。
そのために、最後の行で afterr
メソッドを実行しています。
上記の変更を加えたスクリプトを実行すれば、MOLE_CHOICE_INTERVAL
ms 毎にランダムな穴の位置にモグラの画像が表示されていく様子が確認できると思います(MOLE_CHOICE_INTERVAL
は 各種パラメータを定義する で設定したグローバル変数で、デフォルトは 1000
ms に設定しています)。
ただし、現状ではランダムに選んだモグラを表示しているだけなので、モグラが移動したり消えたりすることはありません。
次は、モグラを上方向に移動させることで、よりモグラが穴から出てきた感を出せるようにしていきたいと思います。
穴から出たモグラを上方向に移動する
前述の通り、モグラを上方向に移動するためには、モグラのオブジェクトのデータ属性 y
を減少させることで実現できます。
これを実現するために、まず Mole
クラスに下記の update
メソッドを追加したいと思います。
class Mole:
def update(self):
'''定期的な状態更新を行う'''
if self.is_up:
# 上方向への移動中
# 上方向に移動
self.y = max(self.top_y, self.y - self.speed)
update
メソッドは、実行したオブジェクトのデータ属性 is_up
が True
の際に、データ属性 y
を speed
だけ減らすことで上方向に移動させるメソッドになります。
ただし、モグラを上方向に移動する頂点の座標は top_y
を下限としていますので、max
関数を利用して top_y
よりも y
が小さくならないようにしています。
また、データ属性 is_up
は、そのオブジェクトが上方向に移動中かどうかを示すフラグです。初期値は モグラのクラスとモグラのオブジェクトを作成する で追加した Mole
クラスの __init__
を見ていただければわかるとおり False
になっています。
ですので、update
メソッドの中でモグラを上方向に移動させるためには、is_up
を予め True
に設定しておく必要があります。
で、モグラを上方向に移動したいのは、モグラが穴から出てきた時です。そして、モグラを穴から出して表示するためのメソッドは Mole
クラスの appear
です。
したがって、この appear
メソッドの中で is_up
を True
に設定するようにすれば、穴から出たモグラが update
メソッド実行時に上方向に移動していくようになります。
ということで、Mole
クラスの appear
メソッドを下記のように変更します。
class Mole:
def appear(self):
'''モグラを穴から出す'''
# 穴から出ているフラグをON
self.is_appearing = True
# 上方向に移動を開始
self.is_up = True
あとは、Mole
クラスの update
メソッドを実行するようにすれば、モグラのオブジェクトの y
を減少してモグラの画像が上方向に移動することになります。
ただし、これも前述の通り、ループ処理で y
を減少させてしまえばモグラが一気に上方向に移動してしまうことになるため、update
メソッドは間隔を空けて定期的に実行する必要があります。
そのため、after
メソッドを利用して update
メソッドを定期的に実行するようにしていきます。
具体的には、WhackaMole
クラスに下記の updateMoles
メソッドを追加し、さらに updateMoles
メソッドを __init__
から実行するように変更を行います。
class WhackaMole:
def __init__(self, master):
# 略
self.createWidgets()
self.drawBackground()
self.drawHoles()
self.createMoles()
self.updateFigures()
self.choiceMole()
self.updateMoles()
def updateMoles(self):
'''モグラの状態や位置を更新する'''
for mole in self.moles:
# モグラの状態や位置を更新する
mole.update()
# MOLE_UPDATE_INTERVAL後に再度モグラの状態を更新
self.master.after(MOLE_UPDATE_INTERVAL, self.updateMoles)
updateMoles
メソッドでは after
を実行して MOLE_UPDATE_INTERVAL
ms 後に再度 updateMoles
メソッドを実行するように設定します。
そのため、updateMoles
メソッドは MOLE_UPDATE_INTERVAL
ms 間隔で定期的に実行される関数となります。
また、この updateMoles
メソッドの中では、moles
で管理されている全ての Mole
クラスのオブジェクトに update
メソッドを実行させているため、穴から出ているモグラ全てが上方向に移動することになります(ただし、y
が top_y
になったモグラに関しては、それ以上は上方向に移動しません)。
ここまでの変更を加えたスクリプトを実行すれば、ランダムに選ばれたモグラが表示され、その表示されたモグラが上方向に移動していくことが確認できると思います。
ここで、このモグラに対する処理をおさらいしておくと、まず choiceMole
メソッドでランダムに穴から出す Mole
クラスのオブジェクトが選ばれ、そのオブジェクトが appear
メソッドを実行することでデータ属性 is_appearing
と is_up
がともに True
に設定されます。
また、updateMoles
メソッドの中で全 Mole
クラスのオブジェクトが update
メソッドを実行し、is_up
が True
のオブジェクトに対しては y
が減少されます。
さらに updateFigures
メソッドの中で is_appearing
が True
のオブジェクトの画像が表示され、さらに y
の値に従って表示座標が変更されます。
これらの choiceMole
メソッド・updateMoles
メソッド・updateFigures
メソッドはそれぞれ独立して定期的に実行されているように見えますが、実際には上記のように、それぞれモグラのデータ属性の値に応じて連動して動作していることになります。
頂点まで移動したモグラを下方向に移動する
モグラが上方向に移動するようになったので、次はモグラが頂点まで移動した際に、下方向に移動するようにしていきたいと思います。
これは、先ほど追加した Mole
クラスの update
メソッドを下記のように変更するだけで実現することができます。
class Mole:
def update(self):
'''定期的な状態更新を行う'''
if self.is_up:
# 上方向への移動中
# 上方向に移動
self.y = max(self.top_y, self.y - self.speed)
if self.y == self.top_y:
# 上方向の上限まで移動したので下方向への移動を開始
self.is_up = False
else:
# 下方向への移動中
# 下方向に移動
self.y = min(self.hole_y, self.y + self.speed)
上記の変更により、is_up
が False
の時に update
メソッドが実行されると y
に speed
が加算され、モグラが下方向に移動するようになります。ただし、穴の位置までモグラが移動した場合はそれ以上は下方向に移動しないよう、min
関数を使用して制御しています。
さらに is_up
が True
の場合は、y
が top_y
になった際に is_up
を False
に設定しているように変更してます。
そのため、それ以降に update
が実行された際にはモグラが下方向に移動していくようになります。
この変更を加えたスクリプトを実行すれば、モグラが上方向にある程度移動した後、次は下方向に移動し始めることが確認できると思います。また、穴の中心位置まで移動したらモグラが止まることも確認できると思います。
穴まで戻ったモグラを穴に隠れさせる
長い解説でしたが、次がモグラを穴から出させる処理の最後の実装となります。
次は、穴の中心の位置まで降りてきたモグラを画面から見えなくすることで、モグラを穴に隠れさせるようにしていきます。
ただ、これは簡単で、モグラが穴の中心まで降りてきた時、すなわち y
が hole_y
になった時に、モグラを画面から見えなくするように is_appearing
を False
に設定してやれば良いだけです。
つまり、Mole
クラスの update
メソッドを下記のように変更すれば良いです。
class Mole:
def update(self):
'''定期的な状態更新を行う'''
if self.is_up:
# 上方向への移動中
# 上方向に移動
self.y = max(self.top_y, self.y - self.speed)
if self.y == self.top_y:
# 上方向の上限まで移動したので下方向への移動を開始
self.is_up = False
else:
# 下方向への移動中
# 下方向に移動
self.y = min(self.hole_y, self.y + self.speed)
if self.y == self.hole_y:
# モグラがまた穴に潜ってしまった場合
# モグラの状態をリセットする
self.is_appearing = False
変更後のスクリプトを実行すれば、穴から出てきたモグラが上方向および下方向に移動した後に自動的に見えなくなり、モグラが隠れた様子が表現できていることが確認できると思います。
モグラを叩けるようにする
モグラが穴から出るようになりましたので、次はモグラを叩けるようにしていきたいと思います。
今回は、モグラの画像がマウスでクリックされた時、そのモグラが叩かれたと判断するようにしていきたいと思います。
そして、叩かれたモグラに関しては、上方向移動中であっても強制的に下方向に移動するようにしていきます。さらに、一度叩かれたモグラは、穴に隠れるまではもう叩けないようにしたいと思います。
ただ、単に下方向に移動させるだけだと、モグラが叩かれたのかどうかが分かりにくいため、叩かれたモグラを点滅させるようにすることで、どのモグラが叩かれたのかが分かりやすいようにもしていきたいと思います。
モグラが叩かれた時の処理
まずはモグラが叩かれた時の処理を Mole
クラスのメソッドとして追加したいと思います。
具体的には、Mole
クラスに下記の hit
メソッドを追加します。
class Mole:
def hit(self):
'''叩かれた時の処理を実行'''
# 下方向への移動を開始
self.is_up = False
# 叩かれたフラグをセット
self.is_hitted = True
hit
メソッドは “叩かれたと判断されたモグラ” のオブジェクトに実行させることを想定したメソッドであり、前述の通り、叩かれたモグラを強制的に下方向に移動させるため、is_up
を False
に設定するようにしています。
また、叩かれたモグラが再び叩かれることを防ぐため、モグラが既に叩かれたかどうかが判断できるようにデータ属性 is_hitted
を True
にするようにしています。
モグラが叩かれかどうかの判断
次は、モグラが叩かれたかどうかを判断するためのメソッドを Mole
クラスに追加します。
モグラを描画する と モグラのクラスとモグラのオブジェクトを作成する で解説したように、モグラの画像は Mole
クラスのデータ属性 x
と y
の位置が画像の中央下の位置となるように描画されます。
つまり、モグラの画像は下記の左上座標と右下座標で表される領域内に描画されていることになります。
- 左上座標
x
座標:x - width / 2
y
座標:y - height
- 右下座標
x
座標:x + width / 2
y
座標:y
従って、マウスでクリックされた位置の座標が上記の領域内であるとすれば、この Mole
クラスのオブジェクトは叩かれたと判断することができます。
すなわち、座標 (mouse_x
, mouse_y
) がクリックされた時に、メソッドを実行した Mole
クラスのオブジェクトの画像がクリックされたかどうかは、下記の isHit
メソッドにより判断することができます。
class Mole:
def isHit(self, mouse_x, mouse_y):
'''叩かれたかどうかを判断する'''
if not self.is_appearing:
# 穴から出ていない
return False
if self.is_hitted:
# すでに叩かれている
return False
# モグラの画像が表示されている領域の座標を計算
x1 = self.x - self.width / 2
y1 = self.y - self.height
x2 = self.x + self.width / 2
y2 = self.y
# (mouse_x,mouse_y)が上記領域内であるかどうかを判定
if x1 <= mouse_x and x2 >= mouse_x:
if y1 <= mouse_y and y2 >= mouse_y:
# 領域内をクリックされたので叩かれた
return True
# 叩かれなかった
return False
isHit
メソッドは、叩かれたと判断した場合に True
を、叩かれなかったと判断した場合に False
を返却するメソッドになります。
引数 mouse_x
と mouse_y
はマウスでクリックされた座標を受け取ることを想定しています。
基本的に画像が描画されている領域内に座標 (mouse_x
, mouse_y
) が存在するかどうかで叩かれたかどうかを判断していますが、モグラが穴から出ていない場合(is_appearing
が False
の場合)とモグラが既に叩かれている場合(is_hitted
が True
の場合)は必ず False
を返却するようにしています。
モグラが既に叩かれている場合に False
を返却するようにすることで、何回もモグラが叩かれることを防ぐことができます。
ただし、この is_hitted
をずっと True
にしておくと、一度叩かれたモグラは再度穴から出てきた時にも叩けなくなってしまいます。
そのため、モグラが穴に隠れる際に is_hitted
を False
に設定するようにしたいと思います。これは、Mole
クラスの update
メソッドを下記のように変更することで実現することができます。
class Mole:
def update(self):
'''定期的な状態更新を行う'''
if self.is_up:
# 上方向への移動中
# 上方向に移動
self.y = max(self.top_y, self.y - self.speed)
if self.y == self.top_y:
# 上方向の上限まで移動したので下方向への移動を開始
self.is_up = False
else:
# 下方向への移動中
# 下方向に移動
self.y = min(self.hole_y, self.y + self.speed)
if self.y == self.hole_y:
# モグラがまた穴に潜ってしまった場合
# モグラの状態をリセットする
self.is_appearing = False
self.is_hitted = False
画像クリック時のイベントの受付設定
Mole
クラスの isHit
メソッドでマウスのクリック座標からモグラが叩かれたかどうかが判断できるようになり、さらに Mole
クラスの hit
メソッドでモグラが叩かれた時の処理を実行できるようになりました。
次は、実際にマウスでクリックされた時に、これらのメソッドを実行するようにしていきたいと思います。
まずは、画像がマウスでクリックされた時のイベント処理の設定を行います。
これは、WhackaMole
クラスの createMoles
メソッドにおける hole_coords
に対するループの最後に、下記のように tag_bind
メソッドの実行を追加することで実現できます。
class WhackaMole:
def createMoles(self):
'''モグラを作成して表示する'''
# 略
for coord in self.hole_coords:
# 穴の座標を取得
x, y = coord
# 穴の中心にモグラの画像の下が接するように画像を描画
figure = self.canvas.create_image(
x, y,
anchor=tkinter.S,
image=self.mole_image
)
# 描画した画像を背景画像の下側に隠す
self.canvas.lower(figure, "all")
# モグラオブジェクトを作成
width = self.mole_image.width()
height = self.mole_image.height()
mole = Mole(x, y, width, height, 1, figure)
# モグラオブジェクトを管理リストに追加
self.moles.append(mole)
# 描画画像がクリックされた時にonClickが実行されるように設定
self.canvas.tag_bind(figure, "<ButtonPress>", self.onClick)
キャンバスの tag_bind
メソッドは、描画した図形に対してイベント受付の設定を行うメソッドになります。第1引数には、イベントを受け付ける図形の ID を指定します。
また、第2引数と第3引数には、通常の bind
メソッドの第1引数と第2引数同様に、受け付けたいイベントのイベントシーケンスとイベント発生時に実行したい関数 or メソッドを指定します。
上記のように tag_bind
メソッドを実行すれば、描画したモグラの画像のいずれかがマウスでクリックされた際に、WhackaMole
クラスの onClick
メソッドが実行されるようになります。
さらに、その実行されるメソッド onClick
を WhackaMole
クラスに下記のように追加します。
class WhackaMole:
def onClick(self, event):
'''クリックされた時の処理を実行する'''
for mole in self.moles:
# moleの画像がクリックされたかどうかを判断
if mole.isHit(event.x, event.y):
# クリックされた場合
# moleを叩く
mole.hit()
tag_bind
メソッドで設定したような、イベント発生時に実行されるメソッドや関数には、引数としてイベントの詳細情報が渡されます(引数 event
)。
さらにマウスでクリックされた時に実行されるメソッドにおいては、この引数のデータ属性 x
と y
にはクリックされた座標が設定されています。
従って、Mole
クラスのオブジェクトに isHit(event.x, event.y)
を実行させれば、そのオブジェクトの画像がクリックされたかどうかを判断することができます。
そして、クリックされた場合に Mole
クラスのオブジェクトに hit
メソッドを実行させれば、そのオブジェクトに叩かれた時の処理を実行させることができます。
ここまでの変更を加えたスクリプトを実行してモグラをクリックすれば、モグラが頂点まで移動する前に即座に下方向に移動することが確認できると思います。
ただ、下方向に移動させるだけでは叩かれたかどうかが分かりにくいので、次は叩かれたモグラを点滅して表示させることで、もうちょっと叩かれたモグラが視覚的に分かりやすくなるようにしていきたいと思います。
叩かれたモグラの点滅
モグラの点滅は、叩かれたモグラのオブジェクトのデータ属性 is_draw
を定期的に True
と False
とを交互に設定するようにすることで実現できます。
かなり前に触れた内容なのでここで復習しておくと、モグラの画像を定期的に更新する で作成したWhackaMole
クラスの updateFigures
メソッドでは、データ属性 is_appearing
とデータ属性 is_draw
が共に True
である Mole
クラスのオブジェクトの画像のみが最前面に移動されて画面に表示されるようになっています。
逆に、どちらか一方でも False
であれば、そのオブジェクトの画像は最背面に移動されて背景の後ろに隠れて表示されないことになります。
で、現状は is_draw
は常に True
なので(__init__
で True
に初期化される)、データ属性 is_appearing
さえ True
にしてやれば画面に表示されるようになっています。そしてそれを利用して、モグラを穴から出すことを表現しています。
上記のような仕組みでモグラの画像の表示 or 非表示が切り替わるようになっていますので、叩かれたモグラを点滅させるには、叩かれた後に定期的に is_draw
の値を反転(True
と False
を反転)させてやれば、updateFigures
メソッドで画像の表示 or 非表示も定期的に反転していくことになります。
さらに、Mole
クラスの update
は定期的に実行されるメソッドですので、このメソッドの中で叩かれた状態のモグラの is_draw
の値を反転させるようにしてやれば、叩かれたモグラの表示 or 非表示が定期的に切り替わり、点滅表示しているように見せることができます。
上記のような処理は、Mole
クラスの update
メソッドを下記のように変更することで実現できます。
class Mole:
def update(self):
'''定期的な状態更新を行う'''
if self.is_up:
# 上方向への移動中
# 略
else:
# 下方向への移動中
# 下方向に移動
self.y = min(self.hole_y, self.y + self.speed)
if self.y == self.hole_y:
# モグラがまた穴に潜ってしまった場合
# モグラの状態をリセットする
self.is_appearing = False
self.is_hitted = False
self.is_draw = True
if self.is_hitted:
# 叩かれたフラグが立っている場合
# 描画実行フラグのON/OFFを切り替える
self.is_draw = not self.is_draw
叩かれたモグラのオブジェクトであるかどうかはデータ属性 is_hitted
で判断できますので、is_hitted
が True
の場合のみ is_draw
の値を反転させるようにしています。
また、モグラが隠れた時、すなわち is_appearing
を False
に設定するタイミングで、is_draw
を True
に戻すようにしています。これは、穴に隠れたモグラが再び穴から出てきた時に is_draw
が False
の状態のままになってしまっていることを防ぐためです。
上記の変更を加えてスクリプトを実行してモグラを叩けば、モグラが点滅するようになったことが確認できると思います。
スポンサーリンク
ポイントの加減算を行う
次に、モグラたたきゲームの作り方の最後として、モグラを叩いた時にポイントを加算し、さらにモグラを叩けなかった時にポイントを減算するようにしていきたいと思います。
ポイント表示用のウィジェットとしてラベルウィジェットを用意していますので、このウィジェットで表示されている値を増減させていくことになります。
また、モグラを叩いてポイントが加算される際には、その加算されるポイントをマウスでクリックされた位置に描画し、
モグラを叩くことができずにポイントが減算される際には、その叩けなかったモグラが潜り込んだ穴の位置に減算されるポイントを描画するようにしていきます。
ただし、描画したポイントがずっと画面上に残ってしまうと邪魔なので、描画したポイントは一定時間経過後に削除するようにしたいと思います。
ポイントの加減算
まずは、メインウィンドウ下部のラベルウィジェットに表示されているポイントを、モグラを叩いた or モグラを叩けなかった場合に加減算できるよう、下記の pointUp
メソッドを WhackaMole
クラスに追加します。
class WhackaMole:
def pointUp(self, point):
'''ポイントを加算する'''
# ラベルに表示されているポイントを取得
now_point = int(self.label.cget("text"))
# 取得したポイントに加算して表示文字列に設定
now_point += point
self.label.config(text=str(now_point))
上記 pointUp
は、引数 point
の値を現在のラベルウィジェットに表示されている値に加算するメソッドになります。point
が負の値の場合は、ラベルウィジェットに表示されている値が減算されることになります。
メインウィンドウ下部のラベルウィジェットには現在のポイントが表示されており、その表示されている文字列は cget("text")
をラベルウィジェットに実行させることで取得することができます。
また、ラベルウィジェットに表示されている文字列は、ラベルウィジェットに config
メソッドを実行させることで変更可能です(メソッド実行時に text
オプションを指定する)。
上記 pointUp
メソッドでは、ラベルウィジェットから取得したポイントに point
を加算し、ラベルウィジェットの表示文字列を加算後のポイントに変更することで、ポイントの加減算を実現しています。
加減算されたポイントの描画
次は、モグラを叩いた or モグラを叩けなかった場合に加減算されるポイントをキャンバスに描画するようにしていきます。
このために、下記の drawPoint
メソッドを WhackaMole
クラスに追加します。
class WhackaMole:
def drawPoint(self, point, x, y):
'''加算されたポイントを表示'''
if point >= 0:
sign = "+"
color = "yellow"
else:
sign = "-"
color = "red"
point_figure = self.canvas.create_text(
x, y,
text=sign + str(abs(point)),
fill=color,
font=("", 40)
)
# DRAW_TIME_POINT後に描画した文字列を削除
self.master.after(
POINT_DRAW_TIME, lambda: self.canvas.delete(point_figure))
drawPoint
は、引数 point
で指定されるポイントをキャンバス上の座標 (x
, y
) の位置に文字列として描画するメソッドになります。文字列の描画はキャンバスの create_text
により行うことができます。
point
が 0
を含む正の値の場合は、ポイントの前に +
を付加して黄色の文字で描画を行い、point
が負の値の場合は、ポイントの前に -
を付加して赤色の文字で描画を行います。
ただし、キャンバスに描画した図形は放っておくと永遠にキャンバス上に残り続けてしまいます。
加減算されたポイントがずっと残ってしまうと邪魔なので、after
メソッドを利用して POINT_DRAW_TIME
ms 後に self.canvas.delete(point_figure)
が実行されるようにすることで、時間が経ったら自然に描画した文字列が消えるようにしています(delete
メソッドは引数で指定した ID の図形をキャンバスから削除するメソッドになります)。
after
メソッドは定期的な処理を実現するだけでなく、処理を遅らせて実行する際にも活躍するメソッドです。
モグラが叩れた時のポイントの加算
ラベルウィジェットのポイントを加減算するメソッド pointUp
と加減算されたポイントを描画するメソッド drawPoint
が用意できましたので、次は実際にポイントが加算されるタイミング or ポイントが減算されるタイミングでこれらのメソッドを実行するようにしていきたいと思います。
まず、ポイントを加算するタイミングはモグラが叩かれた時です。
モグラが叩かれたかどうかは、WhackaMole
クラスの onClick
から Mole
クラスの isHit
メソッドを実行することで判断していますので、isHit
メソッドが True
を返却したときに pointUp
メソッドと drawPoint
メソッドを実行すれば、適切なタイミングでポイントの加算と加算されたポイントの描画を行うことができます。
このため、WhackaMole
クラスの onClick
メソッドを下記のように変更します。
class WhackaMole:
def onClick(self, event):
'''クリックされた時の処理を実行する'''
for mole in self.moles:
# moleの画像がクリックされたかどうかを判断
if mole.isHit(event.x, event.y):
# クリックされた場合
# moleを叩く
mole.hit()
# ポイントの加算と加算ポイントの描画
self.pointUp(mole.point)
self.drawPoint(mole.point, event.x, event.y)
モグラを叩いた時に加算されるポイントは Mole
クラスのデータ属性 point
で設定されていますので、このデータ属性を pointUp
メソッドと drawPoint
メソッドの引数に指定するようにしています(データ属性 point
は モグラのクラスとモグラのオブジェクトを作成する で追加した Mole
クラスの __init__
で設定しています)。
drawPoint
メソッドではポイントを描画する位置の x
座標と y
座標を指定する必要があるため、ここにマウスでクリックされた座標である event.x
と event.y
を指定しています。
ここまでの変更を加えたスクリプトを実行してモグラをクリックすれば、クリックした位置に加算されたポイントが描画され、さらにラベルウィジェットに表示されているポイントも増加していくようになったことが確認できると思います。
モグラが叩れなかった時のポイントの減算
次はモグラが叩かれなかった時のポイントの減算と減算ポイントの描画を行なっていきたいと思います。
「モグラが叩かれなかった」とは、要はモグラが穴から出たにも関わらず「叩かれないまま再びモグラが穴に潜って隠れてしまった」ことを指します。
従って、ポイントの減算と減算ポイントの描画を行うタイミングは、穴から出ている&叩かれていないモグラが穴に潜って隠れてしまった時になります。
また、モグラが穴から出ているかどうかは Mole
クラスのデータ属性 is_appearing
で管理され、さらにモグラが叩かれたかどうかは Mole
クラスのデータ属性 is_hitted
で管理されています。
さらに、モグラを穴に潜って隠れさせる処理は、Mole
クラスの update
メソッドの中でのみ行われます(y
を増加させて hole_y
以上になった時に is_appearing
を False
にセットする)。
ですので、モグラが隠れたタイミングは、update
の実行前後で is_appearing
が True
から False
に変化した時であると考えることができます。
さらに、is_hitted
が False
のモグラは叩かれていないモグラですので、この辺りを考慮すれば、下記のように WhackaMole
クラスの updateMoles
メソッドを変更することで、適切なタイミングでのポイントの減算と減算ポイントの描画を実現することができることになります。
class WhackaMole:
def updateMoles(self):
'''モグラの状態や位置を更新する'''
for mole in self.moles:
# 更新前のモグラの状態を退避
is_hitted = mole.is_hitted
before_appearing = mole.is_appearing
# モグラの状態や位置を更新する
mole.update()
# 更新後にモグラが隠れたかどうかのフラグを取得
after_appearing = mole.is_appearing
if not is_hitted and before_appearing and not after_appearing:
# moleが叩かれていない&上記のupdateメソッドによりmoleが穴に潜り込んだ場合
# ポイントの加算と加算ポイントの描画
self.pointUp(-mole.point)
self.drawPoint(-mole.point, mole.x, mole.y)
# MOLE_UPDATE_INTERVAL後に再度モグラの状態を更新
self.master.after(MOLE_UPDATE_INTERVAL, self.updateMoles)
今回はポイントの減算を行いますので、Mole
クラスのデータ属性 point
の符号を -
にしたものを pointUp
メソッドと drawPoint
メソッドに指定するようにしています。
また、ポイントを描画する位置は穴の中心座標になりますが、モグラが穴に隠れたタイミングでは、その Mole
クラスのオブジェクトのデータ属性 x
と y
は穴の中心座標と必ず一致するため、この x
と y
を drawPoint
の引数に指定するようにしています。
このように変更を加えたスクリプトを実行してモグラを叩かずに放置しておけば、モグラが穴に隠れたタイミングで減算ポイントが表示され、さらにラベルウィジェットに表示されているポイントも減っていくことが確認できると思います。
以上で、モグラたたきゲームが完了したことなります!
まとめ
このページでは、Python で tkinter を用いた「モグラたたきゲームの作り方」について解説しました!
単純なゲームではありますが、実際に作ってみると結構大変ですね…。ただ、その分学べることも多かったのではないかと思います!
本当はもうちょっとモグラが穴から出ている感を出したかったのですが、結局今回紹介したような動作とさせていただきました。
今回作成したモグラたたきゲームを動かしてみれば、おそらく皆さんも「ここが気に入らない!」「もっとこうしてみたい!」と感じる点があるのではないかと思います。
もしあるのであれば、是非スクリプトを変更し、その点の改善に挑戦してみてください!プログラミングの力をつける一番の近道は、自身で実現してみたいことをプログラミングしてみることだと思います。
題材がゲームなので、割と楽しくプログラミングもできると思いますので、是非いろんなスクリプトの変更を試し、”より良い” モグラたたきゲームへの発展に挑戦してみてください!
オススメ参考書(PR)
簡単なアプリやゲームを作りながら Python について学びたいという方には、下記の Pythonでつくる ゲーム開発 入門講座 がオススメです!ちなみに私が Python を始めるときに最初に買った書籍です!
下記ようなゲームを作成しながら Python の基本が楽しく学べます!素材もダウンロードして利用できるため、作成したゲームの見た目にも満足できると思います。
- すごろく
- おみくじ
- 迷路ゲーム
- 落ち物パズル
- RPG
また本書籍は下記のような構成になっているため、Python 初心者でも内容を理解しやすいです。
- プログラミング・Python の基礎から解説
- 絵を用いた解説が豊富
- ライブラリの使い方から解説(tkitner と Pygame)
- ソースコードの1行1行に注釈
ゲーム開発は楽しくプログラミングを学べるだけでなく、ゲームで学んだことは他の分野のプログラミングにも活かせるものが多いですし(キーボードの入力受付のイベントや定期的な処理・画像や座標を扱い方等)、逆に他の分野のプログラミングで学んだ知識を活かしやすいことも特徴だと思います(例えばコンピュータの動作に機械学習を取り入れるなど)。
プログラミングを学ぶのにゲーム開発は相性抜群だと思います。
Python の基礎や tkinter・Pygame の使い方をご存知なのであれば、下記の 実践編 をいきなり読むのもアリです。
実践編 では「シューティングゲーム」や「アクションゲーム」「3D カーレース」等のより難易度の高いゲームを作りながらプログラミングの力をつけていくことができます!
また、単にゲームを作るのではなく、対戦相手となるコンピュータの動作のアルゴリズムにも興味のある方は下記の「Pythonで作って学べるゲームのアルゴリズム入門」がオススメです。
この本はゲームのコンピュータ(AI)の動作アルゴリズム(思考ルーチン)に対する入門解説本になります。例えばオセロゲームにおけるコンピュータが、どのような思考によって石を置く場所を決めているか等の基本的な知識を得ることが出来ます。
プログラミングを挫折せずに続けていくためには楽しさを味わいながら学習することが大事ですので、特にゲームに興味のある方は、この辺りの参考書と一緒に Python を学んでいくのがオススメです!