Pygame の Event はメインスレッド以外では取得できない?

あああ、スレッド使って取得すれば長い描画や音声処理の間でも 60fps で入力を取得できると期待してたのに! してたのにぃ!!

というわけでテスト用に作った残骸を晒しておきます。
(Python 2.5 + Pygame 1.8)

from __future__ import division
import threading
import Queue
import pygame
from pygame.locals import *

FULLSCREEN = False  # True にするとフルスクリーン表示
MAIN_FPS = 1  # 秒間描画枚数
EVENT_FPS = 60  # イベント取得間隔
WIDTH, HEIGHT = 640, 480  # 表示する画面のサイズ
LINEWIDTH = 3  # 線の太さ
BG_COLOR = 127, 127, 127  # 背景色


class PygameEvent(threading.Thread):

    def __init__(self, **kwds):
        threading.Thread.__init__(self, **kwds)
        self.queue = Queue.Queue()
        self.starttime = pygame.time.get_ticks()
        self.running = True
        print 'start'
        self.start()

    def run(self):
        clock = pygame.time.Clock()
        count = 0
        while self.running:
            count += 1
            print count,
            if pygame.event.get_grab():
                now = pygame.time.get_ticks()
                for event in pygame.event.get():
                    pygame.display.set_caption(
                        '%d %s' % (now, event))
                    self.queue.put_nowait((now, event))
            clock.tick(EVENT_FPS)

    def get(self):
        eventlist = []
        try:
            while True:
                eventlist.append(self.queue.get_nowait())
        except Queue.Empty:
            pass
        return eventlist
            
        
def main():
    screen = pygame.display.set_mode(
        (WIDTH, HEIGHT),
        (pygame.FULLSCREEN |
         pygame.HWSURFACE |
         pygame.DOUBLEBUF) if FULLSCREEN else 0)
    font = pygame.font.Font(None, 16)
    clock = pygame.time.Clock()
##    pygameevent = PygameEvent()
    while True:
        screen.fill(BG_COLOR)

        for event in pygame.event.get():
            time = pygame.time.get_ticks()
##        for time, event in pygameevent.get():
            if event.type == QUIT:
                return
            elif event.type == KEYDOWN and event.key == K_ESCAPE:
                return
            elif event.type == MOUSEMOTION:
                color = [x * 255 for x in event.buttons]
                x, y = event.pos
                xr, yr = event.rel
                pygame.draw.line(screen, color,
                                 (x - xr, y - yr), (x, y), LINEWIDTH)
                screen.blit(
                    font.render('%.3f' % (time / 1000), True, color),
                    event.pos)
            else:
                print event
        pygame.display.flip()
        clock.tick(MAIN_FPS)


if __name__=='__main__':
    pygame.init()
    try:
        main()
    finally:
        pygame.quit()
# 好きに流用してください。

1 fps 間隔で描画するマウスの動線に取得した時間が付記される、というスクリプト。マウスボタンをカチカチさせながら動かしてみてください。(現状 PygameEvent クラス使わない動作になっていますので取得する時間も 1 fps 間隔)

このスクリプト(というか MOUSEMOTION イベント)フルスクリーンで動かした場合とウィンドウで動かした場合とで動作が違うのでそこらへんも興味深いです。

2009-02-18 Pygame ドキュメントにありました

Pygame handles all it's event messaging through an event queue. The routines in this module help you manage that event queue. The input queue is heavily dependent on the pygame display module. If the display has not been initialized and a video mode not set, the event queue will not really work.

pygame.event — Pygame v1.9.5.dev0 documentation

ディスプレイを管轄しているスレッドでないとイベントは受けられないっぽい。

pygame.mouse 使った実装も試してみたけどやはりダメ*1。「pygame.mouse — Pygame v1.9.5.dev0 documentation」の方の記述でも When the display mode is set, the event queue will start receiving mouse events. でイベントに依存してるみたいだし。

あきらめてなるたけ処理を細切れにするようにするしかないか。

まあ元々古いタイプの2Dゲームをメインターゲットにしたモジュールだから垂直帰線区間割込み単位で処理が完結するのが前提なんだろうし。無いものねだりかも。

*1:メインスレッドで最後に pygame.event.get なりした時に値がまとめて変わる。これはこれで(pygame.event での状態とずれがなくなるわけだから)便利ではあるけど