0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Pyxel】自作イベントで処理を制御したい

Posted at

はじめに

 Pyxelでのゲーム開発は、pyxel.btnpなどのシンプルなイベント管理用のメソッドを組み合わせ、ゲームの状態を更新していきます。その際、細かい条件分岐を経て、処理を行うか判定していきますが、この条件分岐まで含めて自作イベントとして定義することで、より快適なゲーム開発を目指します。

最終目標

 前述のとおり、pyxelにはpyxel.btnpなどのシンプルなイベント管理用のメソッドがあります。これらを使うと、以下のように任意のキーが押されたら何かをするという実装が可能です。

if pyxel.btnp(pyxel.KEY_SPACE):    # 任意のキーが押されたら
   # 何らかの処理

 最終目標は、以下のように自作イベントと標準のイベントを違和感なく併用し、イベント駆動に近い開発体験を実現することとします。

# ゲームループの中
if event.my_event(BUTTON_1):
    print('ボタン1が押されました')
elif event.my_event(BUTTON_2):
    print('ボタン2が押されました')
elif event.my_event(BUTTON_3):
    print('ボタン3が押されました')
elif pyxel.btnp(pyxel.KEY_SPACE):
    print('スペースキーが押されました')

まず普通に作ってみる

 ボタンが押されたり離されたりすると、それを出力するというプログラムを書いてみました。ちゃんと検討すればもっと上手く書きようあるだろという話は本題ではないので、ちょっと凝った挙動をさせると分岐が複雑になるという点をご確認ください。

import pyxel

class App:
    def __init__(self):
        pyxel.init(128, 128)
        pyxel.mouse(True)
        self.btn_rect = [16, 96, 16, 16]
        self.btn_pressed = False
        
    def update(self):
        if pyxel.btn(pyxel.MOUSE_BUTTON_LEFT):
            if self.btn_rect[0] <= pyxel.mouse_x <= self.btn_rect[0] + self.btn_rect[2] and self.btn_rect[1] <= pyxel.mouse_y <= self.btn_rect[1] + self.btn_rect[3]:
                if self.btn_pressed:
                    print('ボタンが押されています')
                else:
                    self.btn_pressed = True
                    print('ボタンが押されました')
            else:
                if self.btn_pressed:
                    self.btn_pressed = False
                    print('ボタンが離されました')
        else:
            if self.btn_pressed:
                self.btn_pressed = False
                print('ボタンが離されました')

    def draw(self):
        pyxel.cls(0)
        pyxel.rect(*self.btn_rect, 14)
        pyxel.rectb(*self.btn_rect, 7)


if __name__ == '__main__':
    app = App()
    pyxel.run(app.update, app.draw)

要するに、この分岐部分が

if pyxel.btn(pyxel.MOUSE_BUTTON_LEFT):
    if self.btn_rect[0] <= pyxel.mouse_x <= self.btn_rect[0] + self.btn_rect[2] and self.btn_rect[1] <= pyxel.mouse_y <= self.btn_rect[1] + self.btn_rect[3]:
        if self.btn_pressed:
            print('ボタンが押されています')
        else:
            self.btn_pressed = True
            print('ボタンが押されました')
    else:
        if self.btn_pressed:
            self.btn_pressed = False
            print('ボタンが離されました')
else:
    if self.btn_pressed:
        self.btn_pressed = False
        print('ボタンが離されました')

こう書き換えられれば目標達成です

if event.my_event(BUTTON_PRESS):
    print('ボタンが押されました')
elif event.my_event(BUTTON_HOLD):
    print('ボタンが押されています')
elif event.my_event(BUTTON_RELEASE):
    print('ボタンが離されました')

自作イベントを使って作ってみる

イベントを定義

 今回やりたい範囲のことは、簡易的なイベントキューで実装できるので、イベントを一つ取り出したり追加するだけのシンプルなものを使います。

 ボタンイベントのupdateは先ほどの分岐部分を使いますが、printではなくイベントを発生させるように変更しています。update内で使うフラグなどの変数は、インスタンス生成時に定義します。

event.py
import pyxel
event_queue = []
def my_event(e):
    if e in event_queue:
        event_queue.remove(e)
        return True
    return False

def add(e):
    if e not in event_queue:
        event_queue.append(e)

def clear():
    global event_queue
    event_queue = []

class Button:
    def __init__(self, press_num, hold_num, release_num, x, y, w, h):
        self.press_num = press_num
        self.hold_num = hold_num
        self.release_num = release_num
        self.x, self.y = x, y
        self.w, self.h = w, h
        self.pressed = False

    def update(self):
        if pyxel.btnp(pyxel.MOUSE_BUTTON_LEFT):
            if self.x <= pyxel.mouse_x <= self.x + self.w and self.y <= pyxel.mouse_y <= self.y + self.h:
                if self.pressed:
                    add(self.hold_num)
                else:
                    self.pressed = True
                    add(self.press_num)
            else:
                if self.pressed:
                    self.btn_pressed = False
                    add(self.release_num)
        else:
            if self.pressed:
                self.pressed = False
                add(self.release_num)
    
    def draw(self):
        pyxel.rect(self.x, self.y, self.w, self.h, 14)
        pyxel.rectb(self.x, self.y, self.w, self.h, 7)

定義したイベントを使う

 ボタンのインスタンスを作成時にイベント用の定数を割り振っています。こうすることで複数のボタンを使う際にも、別の定数を渡せば独立したイベントとして判定できます。pyxelのキー入力もpyxel.KEY_0などのように割り振っているので、それに合わせるため、このようにしています。

それぞれのイベントが一個ずつしか同時発生しない設計なら、最初からイベントごとに定数を割り振ってもOKです(レトロなRPGとか)

 ゲームループの中で監視したいイベントのupdateを呼び出す必要がありますが、一度イベントさえ書いてしまえば他のゲームにも使いまわせますし、使い勝手は悪くないと思います。

main.py
import pyxel
import event

BUTTON_PRESS = 0
BUTTON_HOLD = 1
BUTTON_RELEASE = 2
class App:
    def __init__(self):
        pyxel.init(128, 128)
        pyxel.mouse(True)
        self.btn = event.Button(BUTTON_PRESS, BUTTON_HOLD, BUTTON_RELEASE, 16, 96, 16, 16)
        
    def update(self):
        self.btn.update()
        if event.my_event(BUTTON_PRESS):
            print('ボタンが押されました')
        elif event.my_event(BUTTON_HOLD):
            print('ボタンが押されています')
        elif event.my_event(BUTTON_RELEASE):
            print('ボタンが離されました')
        event.clear()

    def draw(self):
        pyxel.cls(0)
        self.btn.draw()

if __name__ == '__main__':
    app = App()
    pyxel.run(app.update, app.draw)

まとめ

 自作のイベントを用いることで、ゲームの処理と処理を実行する条件を分離できるので、処理の流れが分かりやすくなる。

最後に

 ここまで読んで下さりありがとうございました。自作イベントが発生するまで待機し、イベントの種類次第で画面遷移するなど、様々な場面に応用が利きそうだなといった感想です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?