はじめに
tkinterのネタが尽きたかもしれない…
tkinterの入門のつもりで始めたもののtkinterで重要そうな関数はほどんど説明してしまいました。 キーボード操作やさまざまな図形の描写がありますがどれも過去に紹介した関数と類似した関数なのでチュートリアルで説明するよりはドキュメント風にまとめるほうが良いと考えました。
- プログラミング初学者(Python)のためのtkinter入門 ボタンとメッセージボックスの表示
- Pythonでシューティングゲームを作る1(tkinterチュートリアル) マウスイベントについて解説
ひとまず、シューティングゲームの完成を目指す
tkinterのネタはこれから探すとして、ひとまずシューティングゲームの完成を目指そうと思います。
このアプリケーションのコード
import tkinter
def motion(mouse):
global player
player.x = mouse.x - int(0.5 * player.w)
player.y = mouse.y - int(0.5 * player.h)
def click1(mouse):
global player
player.shoot()
def is_in_window(flyer):
flyer.move()
return flyer.exist
def collision_flyers(flyers):
f_1 = flyers[0]
for f_2 in flyers[1:]:
f_1.collision(f_2)
return f_1.exist
def game_loop():
Flyer.flyers = {k:v for k, v in Flyer.flyers.items() if is_in_window(v)}
#Flyerの衝突判定を行う。ループ内で削除を行うのでforを使わない。
i = 0
while i < len(Flyer.flyers):
k = list(Flyer.flyers.keys())[i]
flyer_exist = collision_flyers(list(Flyer.flyers.values())[i:])
if not flyer_exist:
Flyer.flyers.pop(k)
else:
i += 1
#60(ms)ごとにgame_loop()を呼びだす。
window.after(60, game_loop)
class Flyer:
flyers = {}
next_id = 0
def __init__(self, x, y, w, h, tag, vx=0, vy=0, atk=0, dfn=0, hp=1, color="white"):
self.x = x
self.y = y
self.w = w
self.h = h
self.tag = tag
self.vx = vx
self.vy = vy
self.atk = atk
self.dfn = dfn
self.hp = hp
self.color = color
self.exist = True
self.id = Flyer.next_id
Flyer.next_id += 1
Flyer.flyers[self.id] = self
def move(self):
canvas.delete(self.tag)
self.x += self.vx
self.y += self.vy
if self.x + self.w < 0 or self.y + self.h < 0:
self.exist = False
canvas.create_rectangle(self.x, self.y, self.x + self.w, self.y + self.h, fill=self.color, tag=self.tag)
def __is_collision(self, flyer):
return (flyer.y < self.y + self.h and self.y < flyer.y + flyer.h and flyer.x < self.x + self.w and self.x < flyer.x + flyer.w)
def collision(self, flyer):
if self.__is_collision(flyer):
self.hp += min(0, self.dfn - flyer.atk)
flyer.hp += min(0, flyer.dfn - self.atk)
if self.hp < 0:
self.exist = False
canvas.delete(self.tag)
if flyer.hp < 0:
flyer.exist = False
canvas.delete(flyer.tag)
class Bullet(Flyer):
def __init__(self, x, y, w, h, tag, vx, vy, atk=1, dfn=0, hp=1, color="blue"):
super().__init__(x, y, w, h, tag, vx, vy, atk, dfn, hp, color)
class Player(Flyer):
game_over = False
bullet_c = 0
def __init__(self, x, y, w, h, tag="player", vx=0, vy=0, atk=1, dfn=0, hp=1, color="white"):
super().__init__(x, y, w, h, tag, vx, vy, atk, dfn, hp, color)
def shoot(self):
bullet_w = 10
bullet_h = 10
bullet_v_x = 0
bullet_v_y = -10
bullet_atk = 10
bullet_tag = "player_bullet" + str(Player.bullet_c)
Bullet(self.x + int(0.5 * self.w - 0.5 * bullet_w), self.y - bullet_h, bullet_w, bullet_h, bullet_tag, bullet_v_x, bullet_v_y)
Player.bullet_c += 1
class Enemy(Flyer):
def __init__(self, x, y, w, h, tag, vx=0, vy=1, atk=1, dfn=0, hp=1, color="red"):
super().__init__(x, y, w, h, tag, vx, vy, atk, dfn, hp, color)
#画面の設定
window = tkinter.Tk()
window.resizable(width=False, height=False)
#背景の設定
canvas = tkinter.Canvas(window, width = 400, height = 600)
canvas.create_rectangle(0, 0, 400, 600, fill="black")
canvas.pack()
#自機と敵機の設定
player = Player(180, 560, 40, 40)
enemy_1 = Enemy(180, 50, 40, 40, "enemy_1")
enemy_2 = Enemy(0, 50, 40, 40, "enemy_2")
enemy_3 = Enemy(360, 50, 40, 40, "enemy_3")
#マウスイベントを受け付ける
window.bind("<Motion>", motion)
window.bind("<Button-1>", click1)
game_loop()
#ウィンドウの応答
window.mainloop()
コードを見ながらクラス継承の説明
継承をどこで使うのか?
今回、書いたクラスはFlyer, Bullet, Player, Enemyの4つです。Bulletは発射される弾で、Playerは操作する機体、Enemyは敵です。これら3つは共通する要素(飛行する、ぶつかると壊れる)があります。できれば一箇所にまとめたい。 そこで継承が登場します。Flyerという3つのクラスの共通する要素を抜き出したクラスを書きます。そして、それを継承することで共通部分をまとめることができます。
Flyerオブジェクトのグローバル変数
class Flyer:
flyers = {}
next_id = 0
グローバル変数はすべてのFlyerを管理するための変数です。グローバル変数は__init__
の前に記述します。
- flyers: すべてのFlyerの辞書。idとFlyerを格納する。
- next_id: Flyerに割り振るidを被らないように決める。
Flyerオブジェクトのメンバ変数
def __init__(self, x, y, w, h, tag, vx=0, vy=0, atk=0, dfn=0, hp=1, color="white"):
self.x = x
self.y = y
self.w = w
self.h = h
self.tag = tag
self.vx = vx
self.vy = vy
self.atk = atk
self.dfn = dfn
self.hp = hp
self.color = color
self.exist = True
self.id = Flyer.next_id
Flyer.next_id += 1
Flyer.flyers[self.id] = self
すべてのFlyerは四角とします。
説明
- self.x(x座標)、self.y(y座標)でFlyerの位置の位置を表します。
- self.w(幅)、self.h(高さ)でFlyerの大きさを表します。
- self.vx(x軸方向の移動速度)、self.vy (y軸方向の移動速度)
- self.atk(衝突したとき相手に与えるダメージ量)、self.dfn(衝突したとき受けるダメージの軽減量)、self.hp(ヒットポイント0になると消える)
- self.colorは画面に表示されるFlyer色
- self.existはFlyerが存在しているかどうかを示す変数
- self.id は識別子
Flyerオブジェクトのメンバ関数
def move(self):
canvas.delete(self.tag)
self.x += self.vx
self.y += self.vy
if self.x + self.w < 0 or self.y + self.h < 0:
self.exist = False
canvas.create_rectangle(self.x, self.y, self.x + self.w, self.y + self.h, fill=self.color, tag=self.tag)
def __is_collision(self, flyer):
return (flyer.y < self.y + self.h and self.y < flyer.y + flyer.h and flyer.x < self.x + self.w and self.x < flyer.x + flyer.w)
def collision(self, flyer):
if self.__is_collision(flyer):
self.hp += min(0, self.dfn - flyer.atk)
flyer.hp += min(0, flyer.dfn - self.atk)
if self.hp < 0:
self.exist = False
canvas.delete(self.tag)
if flyer.hp < 0:
flyer.exist = False
canvas.delete(flyer.tag)
説明
- self.__is_collisionは衝突判定を行います。
- self.moveはFlyerの移動を表してます。
- self.collisionは衝突したときの処理を行ってます。
Flyerオブジェクトを継承
class Player(Flyer):
game_over = False
bullet_c = 0
def __init__(self, x, y, w, h, tag="player", vx=0, vy=0, atk=1, dfn=0, hp=1, color="white"):
super().__init__(x, y, w, h, tag, vx, vy, atk, dfn, hp, color)
def shoot(self):
bullet_w = 10
bullet_h = 10
bullet_v_x = 0
bullet_v_y = -10
bullet_atk = 10
bullet_tag = "player_bullet" + str(Player.bullet_c)
Bullet(self.x + int(0.5 * self.w - 0.5 * bullet_w), self.y - bullet_h, bullet_w, bullet_h, bullet_tag, bullet_v_x, bullet_v_y)
Player.bullet_c += 1
継承はclass クラス名(親クラス名)
という書き方で行います。PlayerがFlyerを継承する場合はclass Player(Flyer)
と書きます。Playerは特殊なクラスなので新たな機能を追加してます。
Tkinterでの簡単な並列処理
今回、サブプロセスで実行される関数
def game_loop():
...Flyerの移動とFlyerの衝突判定
(ここら辺は汚くなってるので書き直すと思います)...
#60(ms)ごとにgame_loop()を呼びだす。
window.after(60, game_loop)
game_loopがメインプロセスと別に実行される処理です。 サブプロセスといってもゲームに関する処理はほとんどgame_loopで行われます。 メインプロセスではマウスやキーボードの応答を行ってます。 並列処理を簡単に行う方法としてwindow.afterを使います。
window.afterの使い方。
window.after(delay_time, sub_process)
sub_processはサブプロセスで実行する関数です。 delay_timeはwindow.afterが実行されてから遅延してサブプロセスが開始されるまでの時間です。
おわり
まだ、シューティングゲームには足りないところがあるので追加して解説していこうと思います。今回の記事もコードが長くてまだまだ足りないところがあるので気が向いたらリライトします。
コメント