質問: pygame.time.Clock() myclock.tick(30) pygame.display.flip() x =320 #circleの初期位置指定 while True: time.sleep(0.1) screen.fill(BLACK) pygame.draw.circle(screen, RED, (x, 400), 5) #円 pygame.display.update() for event in pygame.event.get(): if event.type==pygame.QUIT: pygame.quit() sys.exit() pressed_key=pygame.key.get_pressed() if (pressed_key[K_LEFT] and x>0): x -= 5 if (pressed_key[K_RIGHT] and x<640): x += 5 if pressed_key[K_ESCAPE]: pygame.quit() sys.exit() if time.time()-start>120: pygame.quit() sys.exit() ``` ### 試したこと ここに問題に対して試したことを記載してください。 ### 補足情報(FW/ツールのバージョンなど) ここにより詳細な情報を記載してください。
回答: 遅延を発生させるだけなら `pygame.time.delay` がありますが、 その間の他の処理(アニメーション描画や入力イベント)が止まってしまう為、 実質、目的の用途には使えません。この場合入力だけが遅延してほしいはずです。 解決策: **優先順位付きキュー (heapq モジュール)**を使いスケジューラーを実装ます。 * 入力イベントを貯めるキュー(Pythonのリスト)を用意する * 入力されたキーと、入力された時の時間 + 遅延時間をキューへ記録 (heappush) ※ pygame.time.get_ticks の戻り値の時間で判断する * キューにアイテムが有る時(リストが空でない時) キューの先頭のアイテムの時間と現在の時間を比較して、 時間が経過していれば、キューからアイテムを取り除き、(heappop) そのタスクを実行します。 因みに、汎用性を持たせるために優先度付きキューに言及しましたが、 遅延時間が一定(最初に入れられたものが必ず最初に処理される)なら、 優先度~の部分は省けるので、通常のリストを普通に使うだけでも良いです。 (効率的に処理するならリストの代わりに `deque` についても調べてみて下さい) `queue.PriorityQueue` というそのものの実装もあるのですが、 まずは、heapq を使いましょう。heapq モジュールは、データ自体を持たず list の append, pop の代わりに heappush, heappop を使います。 heapq の使い方を把握した上で、queue.PriorityQueue や 他のスケジューラ関連のソースコードを読むと理解が捗ります。 ``` # 1) while ループの外側で delay_inputs = [] # 2) 遅延イベントの処理 while ループ内 now = pygame.time.get_ticks() if delay_inputs and delay_inputs[0][0] <= now: _, key_input = heapq.heappop(delay_inputs) if key_input == "left": x -= 5 elif key_input == "right": x += 5 # 3) キー入力時 pressed_key=pygame.key.get_pressed() if (pressed_key[K_LEFT] and x>0): heapq.heappush(delay_inputs, (now+500, "left")) if (pressed_key[K_RIGHT] and x<640): heapq.heappush(delay_inputs, (now+500, "right")) ``` 他の手段: 幾つかありますが、中の実装はどれも同じ(heapq)です 自分で実装するかライブラリに任せるかの違い。 * pygame.time.set_timer を使う。 → 繰り返し実行されるので、タイマー解除を忘れずに。 但し、キー入力毎にタイマーを割り当てるのは無駄が多い。 * ~~pygame.key.key_repeat を使う。~~ → 最初のキーを読み飛ばす等の対策が必要。少しトリッキーな方法。 * asyncio モジュールを使う。`call_later` → pygame/asyncio のループを同時に扱う必要あり。 (他にasyncioのライブラリを使うのでなければ利点なし) * threading モジュールを使う。 → **別スレッド内で `sched` モジュールを使う。** 移動時の変数はスレッド内で書き換えず pygame.event.post でイベントを送り、必ずメインスレッド側で行う。 そうでない場合はロック等の排他制御が必要です。(コードが複雑になります) → キー入力の度に threading.Timer は毎回スレッド生成で無駄が多い * **pygame zero を導入する** → `pgzero.clock.schedule(func, delay)` で delay ms 秒後に関数実行 ※ pygameのみでと言う拘りがなければ、pygame zero 導入が一番簡単です。 が、遅延時間を判定して実行するループが別途必要なので、 この関数だけ借りてくるような事は出来ません。 追記: Pygame Zero 入門 チュートリアルの最後の方に、丁度、0.5秒後に実行するサンプルがあります。 参考資料 (ドキュメントとソースコードへのリンク) これらの実装は参考になると思います。 * heapq ヒープキューアルゴリズム ※優先度キュー実装についての注釈 * 同期キュークラス 優先順位付きキュー [source] * sched イベントスケジューラ [source] * asyncio call_later/call_at [source] * pygame zero - clock.schedule [source]