はじめに
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
内で使うフラグなどの変数は、インスタンス生成時に定義します。
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
を呼び出す必要がありますが、一度イベントさえ書いてしまえば他のゲームにも使いまわせますし、使い勝手は悪くないと思います。
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)
まとめ
自作のイベントを用いることで、ゲームの処理と処理を実行する条件を分離できるので、処理の流れが分かりやすくなる。
最後に
ここまで読んで下さりありがとうございました。自作イベントが発生するまで待機し、イベントの種類次第で画面遷移するなど、様々な場面に応用が利きそうだなといった感想です。