🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

PyCharm

エディター・開発ツール

Q&A

解決済

3回答

2573閲覧

pythonでの処理を遅らせる方法(after, time)

tom_honmono

総合スコア22

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

PyCharm

エディター・開発ツール

0グッド

0クリップ

投稿2021/03/11 10:45

pythonでRPGのような自作ゲームを作っています。その中で、自動戦闘の場面があります。
「Aの攻撃! ⇨ Bに100ダメージ! ⇨Bの攻撃!⇨ ・・・・・・」
と表示させるのを、tkinter.StringVar()を使ってlabelで表示しているのですが、
「Aの攻撃!」と表示した後、1秒くらい待ってから「Bに100ダメージ!」を出さないとプレイヤーが文字を読めないじゃないですか。

そこで、

python

1def game(): 2 Text1.set(NR[Left] + "の攻撃!") 3 time.sleep(1) 4 Text1.set(NR[Right] + "に100のダメージ!")

としました。ところがこれだと、一つ目のTextがsetされる前にスリープ処理が入ってしまい、速攻で二つ目のTextがsetされて表示されてしまいました。
time.sleepの場所を、cv.after(1000)と変えてみても、結果は同じでした。
一応解決策は見つけたのですが、

python

1def game(): 2 Text1.set(NR[Left] + "の攻撃!") 3 cv.after(1000,game2) 4def game2(): 5 Text1.set(NR[Right]+"に"+ str(atkAll[Leftdata]) +"のダメージ")

このようにしたら目的の動作はしてくれました。が、これはちょっとめんどくさいので何かいい方法はありませんか?
抽象的な聞き方をしてしまって申し訳ありません、よろしくお願いします

PCはMacOS BigSur 11.2.1
Pycharmでコードを書いています。

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

この問題、単にメッセージを適当なタイミングで表示するだけではありませんよね。ゲームなのですから、

敵のターン -> 行動の選択 -> 敵の攻撃 -> 当たり判定 -> ダメージ算出 -> 状態変更 -> 自分のターン ・・・

のような、そのゲームの流れをコントロール/支配している部分(コア)というのがある(これから作る)と思います。
そして、ゲームの進行のタイミングもそのコアが適切に調整すべきですし、メッセージの表示はコアが担当するのが適当でしょう。

投稿2021/03/11 11:14

TakaiY

総合スコア13748

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tom_honmono

2021/03/11 23:57

なるほど、確かにそうですね。今後機能をつけていく中でそうしたいと思います、ありがとうございます
guest

0

ベストアンサー

タイマー&コールバックを使ったコードを、
ジェネレーターで書き換える方法があります。

python

1 2import tkinter as tk 3 4import gentimer 5 6root = tk.Tk() 7label = tk.Label(root) 8label.pack() 9 10def count_up(count): 11 for num in range(count): 12 label.config(text=str(num)) 13 yield 1 # sleep 1 sec without blocking 14 15gentimer.tk(root, count_up(10), root.quit) 16root.mainloop()

今、PyPI に登録しようとしてるのですが、
数行で出来るので、実装方法も紹介。
(pypi に登録したパッケージでは、処理のキャンセル迄を実装してます。)

python

1def gen_timer(root, gen): 2 def _next(): 3 interval = next(gen, None) 4 if interval is not None: 5 root.after(int(interval*1000), _next) 6 root.after_idle(_next) 7 8# gen_timer(root, count_up(10))

解説: ジェネレーターを使うと yield の部分で処理を中断、
再度 next() で呼ぶと、続きから再開できます。

この仕組をイベントループのタイマーと組み合わせて、yield で 待機したい秒数を返し、
イベントループ側では、指定秒数後に続きの処理を再開~次のyield 迄実行、という風にしてます。

追記: ドキュメント書きかけのままですが、質問と同じような流れで
time.sleep での待機 → GUIのイベントループ内では一瞬フリーズする
→ タイマーを使って解消 → コールバック多用することになりコードが追い難くなる
→ ジェネレーターを使って解消


Demo スペースキーの入力待等も同様に、同じ関数内で処理できます。

投稿2021/03/11 11:30

編集2021/03/12 06:37
teamikl

総合スコア8715

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tom_honmono

2021/03/11 23:57

ありがとうございます。難しいですが、おそらく僕が求めていたものがそれです。
teamikl

2021/03/12 03:00

丁度昨日、パッケージを作る練習の題材としていたので、 自分にとってタイムリーな話題でした。 登録したので pip install gentimer でインストール可能です。
guest

0

呼び出し部分が少し説明が必要かなと思ったので、
ジェネレーターを使った実装(サンプル)

python

1 2import tkinter as tk 3 4root = tk.Tk() 5text = tk.StringVar() 6label = tk.Label(root, textvar=text) 7label.pack() 8 9 10### [Step1] この関数を導入します 11def gen_timer(root, gen): 12 def _next(): 13 interval = next(gen, None) 14 if interval is not None: 15 root.after(int(interval*1000), _next) 16 root.after_idle(_next) 17 18def game(): 19 text.set("味方の攻撃!") 20 yield 1 ### [Step2] time.sleep(1) の部分を置き換え 21 text.set("敵に100のダメージ") 22 23### [Step3] 呼び出し方法を変更。 24gen_timer(root, game()) 25 26### ボタンのイベントcommand 等に登録する際は、 27# イベント実行時に、gen_timer 関数が呼ばれるように構成する。 28# ジェネレータを作成するタイミングが異なる点が注意 29 30# 従来: 関数を登録していたところを 31# command=game 32# OK: 33# command=lambda: gen_timer(root, game()) 34# NG: 2回目以降の呼び出しで、ジェネレーターが再利用されてしまう → 2回目以降反応しない 35# command=functools.partial(gen_timer, root, game()) 36 37button = tk.Button(root, text="start") 38button.config(command=lambda:gen_timer(root, game())) 39button.pack() 40 41root.mainloop()

変更が必要な点は3箇所

  • Step1: ユーティリティ関数の導入
  • Step2: time.sleep -> yield へ変更
  • Step3: 呼び出し部分の変更

※ functools.partial を使いたい場合は、
ジェネレータ生成を遅らせるように、少し工夫が必要です。

functools.partial -> lambda は殆どの場合、書き換えが可能です。
引数の順序をどうこうする、パズル的な難しさがありますが、
うまく使えると、記述を簡潔に出来たり実行効率を良くしたり出来ます。

それぞれ状況に応じて長所短所があるので、
確実な方法は、新たな関数を作る方法です。

python

1 2from functools import partial 3 4def gen_timer_func(root, func): 5 gen_timer(root, func()) 6 7button.config(command=partial(gen_timer_func, root, game)) 8 9 10## 引数を渡したい場合は、少し複雑になります。 11 12## functools.partial を使う場合 13make_game = partial(game, arg1, arg2) 14button.config(command=partial(gen_timer_func, root, make_game) 15 16## lambda を使う場合。記述はシンプルになりますが 17# 注意点: for文内で使うと想定した挙動にならない落とし穴があります。 18button.config( 19 command=lambda: gen_timer(root, game(arg1, arg2)))) 20 21# arg1, arg2 がループ内で変化する変数の場合、このような対策が必要になる 22button.config( 23 command=lambda arg1=arg1, arg2=arg2: gen_timer(root, game(arg1, arg2)))) 24 25## 関数 26# 無難な方法は(少し手間でも)関数を定義する方法です 27def start_game(): 28 gen_timer(root, game(arg1, arg2)) 29 30button.config(command=start_game)

投稿2021/03/12 02:12

teamikl

総合スコア8715

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問