お役立ち

Pythonでシューティングゲームを作る2(Tkinterの並列処理)

  • このエントリーをはてなブックマークに追加

Pythonでシューティングゲームを作る2回目です。

はじめに

tkinterのネタが尽きたかもしれない…

tkinterの入門のつもりで始めたもののtkinterで重要そうな関数はほどんど説明してしまいました。
キーボード操作やさまざまな図形の描写がありますがどれも過去に紹介した関数と類似した関数なのでチュートリアルで説明するよりはドキュメント風にまとめるほうが良いと考えました。

ひとまず、シューティングゲームの完成を目指す

tkinterのネタはこれから探すとして、ひとまずシューティングゲームの完成を目指そうと思います。
game3

このアプリケーションのコード

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)
window.bind("<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が実行されてから遅延してサブプロセスが開始されるまでの時間です。

おわり

まだ、シューティングゲームには足りないところがあるので追加して解説していこうと思います。今回の記事もコードが長くてまだまだ足りないところがあるので気が向いたらリライトします。

  • このエントリーをはてなブックマークに追加