目次
はじめに
普段pygameでゲーム制作を行っている筆者ですが、学習を始めてからずっと悩んでいた問題を、本格的に解決しようと思ったので、その過程を書いていきます。同じような悩みで時間を浪費する人が、一人でも少なくなるように願っています。ちなみに筆者が使っているPCはSurface Pro9です。
何にそんなに悩んでいたのか
そもそも何にそんなに悩んでいたのかですが、タイトルの通りイベントキューに関することです。この話をするにあたって、キー入力を検知したら、キーの名前を出力し2秒待機するコードを置いておきます。
import pygame, sys
from pygame.locals import *
pygame.init()
screen = pygame.display.set_mode((512, 512))
clock = pygame.time.Clock()
def main():
loop = True
while loop:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
print(pygame.key.name(event.key))
pygame.time.wait(2000)
clock.tick(30)
pygame.display.update()
if __name__ == '__main__':
main()
筆者の場合はキーの名前の出力に当たる部分が、「画面にメッセージを表示する」だったのですが、そこはこの問題の本質ではないので置いておきます。じゃあ問題は何か、それは
「待機中にイベントキューが蓄積してしまう」
です
これの何が嫌だったかというと、実際に上のコードを動かしてもらえばわかるのですが、連打したら、押した回数だけキーの名前を出して2秒待機してを繰り返すんですよね。筆者の場合は、メッセージウィンドウの表示中に十字キーを連打すると、メッセージ終了後に勝手に移動し始めるという、わけ分からない仕様が実装されてしまったわけです。これでは非常に困ります。
解決策!(失敗)
解決しようとしてダメだった方法を紹介します。興味がない人は次の成功パターンまで飛ばしてください。
case1
待機中にイベントキューが蓄積するのが問題なのだから、待機後にイベントキューを空にすればいいんでしょ?
pygame.time.wait(2000)
pygame.event.clear()
→失敗
case2
じゃ、じゃあ待機中にイベントキューが蓄積しないようにすればいいんでしょ?
pygame.event.set_blocked(KEYDOWN)
pygame.time.wait(2000)
pygame.event.set_allowed(KEYDOWN)
→失敗
case3
こうなったら、そもそもイベントキューの先頭だけとってきて、残りはすべて消去するようにしてやる!
-events = pygame.event.get()
-for event in events:
+event = pygame.event.poll()
+pygame.event.clear()
+if event:
→失敗
ここらへんで気づいたのですが、待機直後のイベントキューにはイベントがまだ溜まっていなくて、そのあとループを回してるときに、時間差でイベントキューが溜まってるっぽいんですよね。
import pygame, sys
from pygame.locals import *
pygame.init()
screen = pygame.display.set_mode((512, 512))
clock = pygame.time.Clock()
def main():
loop = True
while loop:
print('次のループ開始')
pygame.event.set_blocked(None)
pygame.event.set_allowed(KEYDOWN)
pygame.event.set_allowed(QUIT)
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
print(pygame.key.name(event.key))
print('wait開始')
pygame.time.wait(2000)
print(pygame.event.get())
print('wait終了')
clock.tick(30)
pygame.display.update()
if __name__ == '__main__':
main()
気になる人はこのコードをコピペして、待機中にキーを連打してみてください
余談ですが、pygame.time.wait
のほかにも処理を止める方法はいくつかありますよね。以下にダメだったものを挙げておきます。
pygame.event.delay
time.sleep
解決策!(成功)
いろいろ失敗を経て、イベントキューを何とかするよりも、イベントを受け取る側を何とかする方がよいと気づきました。
import pygame, sys
from pygame.locals import *
pygame.init()
screen = pygame.display.set_mode((512, 512))
clock = pygame.time.Clock()
def main():
loop = True
flag = False
start_time = None
wait_time = 2000
while loop:
events = pygame.event.get()
if start_time is not None and pygame.time.get_ticks() - start_time > wait_time:
flag = False
start_time = None
print('wait終了')
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN and not flag:
print(pygame.key.name(event.key))
print('wait開始')
start_time = pygame.time.get_ticks()
flag = True
clock.tick(30)
pygame.display.update()
if __name__ == '__main__':
main()
このように、前回イベントを処理してから、指定時間経つまでは無視するようにすれば、イベントを検知してしまっても、条件分岐ではじかれてくれるので、期待通りの動作が得られました。もしもpygame.time.wait
のような感じで、処理を止めたい場所に1行挿入する形で使いたい!という人がいれば、以下のように自作の待機関数を作るのがいいでしょう。
def my_wait(wait_time):
loop = True
start_time = pygame.time.get_ticks()
while loop:
if pygame.time.get_ticks() - start_time > wait_time:
loop = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
最後に
ここまで読んで下さりありがとうございました。筆者は、自作の待機関数をつかうのが快適に感じたので、そこに落ち着きました。待機中にイベントを検知したい時と、したくない時で、pygame.time.wait
と使い分けようと思います。今回扱った現象の詳しい原因や、よりスマートな解決策をご存じの方はコメントで教えていただけるとありがたいです。