質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.06%

キーを押してから図形が動くまでの間に遅延をもたせたい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 139

kappar

score 11

前提・実現したいこと

pygameを使って、キー押しで図形が動くものを作っています。
キーを押してから図形が動くまでの間に遅延(0.5秒ほど)を設けたいと思っています。

発生している問題・エラーメッセージ

遅延を生じさせる方法がわかりません。
何を使うべきか、ご意見をいただけると幸いです。

エラーメッセージ

該当のソースコード

遅延なしで、キー押しで図形が動くところのコードが以下です。

import pygame
from pygame.locals import *
import sys
import time

start=time.time()

BLACK = (0, 0, 0)
RED = (255, 0, 0)
WHITE = (255, 255, 255)

pygame.init() #初期化
screen = pygame.display.set_mode((640, 500)) #画面サイズ
myclock = 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/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

遅延を発生させるだけなら 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秒後に実行するサンプルがあります。


参考資料 (ドキュメントとソースコードへのリンク)
これらの実装は参考になると思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/06/22 10:51

    とても丁寧に教えていただきありがとうございます!!
    特に、参考資料でソースコードへのリンクをつけてくださっていたのが非常に助かりました。

    先ほど無事にキー押しの遅延が実装できました。本当にありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.06%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る