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

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

新規登録して質問してみよう
ただいま回答率
85.35%
Tkinter

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

Python

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

Q&A

解決済

3回答

831閲覧

クリックされてから動き始めるプログラムの作り方がわかりません

tom_honmono

総合スコア22

Tkinter

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

Python

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

0グッド

0クリップ

投稿2020/04/10 17:30

ミニゲームを製作していますが、このプログラムだと始めた瞬間に動いてしまいます。マウスのクリックで始まるようにしたいのですが、どうしたらいいでしょうか。バインドを使うことがわかりますがDEFの中の処理の書き方がわかりません。
一応自分でコードは書いてみて、わからないところはPASSで書いています。
よろしくお願いします

python

1 2 3from tkinter import* 4import random 5import time 6 7class Ball: 8 def __init__ (self,canvas,paddle,color): 9 self.canvas=canvas 10 self.paddle=paddle 11 self.id=canvas.create_oval(10,10,25,25,fill=color) 12 self.canvas.move(self.id,245,100) 13 starts=[-3,-2,-1,1,2,3] 14 random.shuffle(starts) 15 self.x=starts[0] 16 self.y=-3 17 self.canvas_height=self.canvas.winfo_height() 18 self.canvas_width=self.canvas.winfo_width() 19 self.hit_bottom=False 20 21 def hit_paddle(self,pos): 22 paddle_pos=self.canvas.coords(self.paddle.id) 23 if pos[2]>=paddle_pos[0] and pos[0]<=paddle_pos[2]: 24 if pos[3]>=paddle_pos[1] and pos[3]<=paddle_pos[3]: 25 return True 26 return False 27 28 def draw(self): 29 self.canvas.move(self.id,self.x,self.y) 30 pos=self.canvas.coords(self.id) 31 if pos[1]<=0: 32 self.y=1 33 if pos[3]>=self.canvas_height: 34 self.hit_bottom=True 35 if self.hit_paddle(pos)==True: 36 self.y=-3 37 if pos[0]<=0: 38 self.x=3 39 if pos[2]>=self.canvas_width: 40 self.x=-3 41 42class Paddle: 43 def __init__(self,canvas,color): 44 self.canvas=canvas 45 self.id=canvas.create_rectangle(0,0,100,10,fill=color) 46 self.canvas.move(self.id,200,300) 47 self.x=-2 48 self.canvas_width=self.canvas.winfo_width() 49 self.canvas.bind_all('<KeyPress-Left>',self.turn_left) 50 self.canvas.bind_all('<KeyPress-Right>',self.turn_right) 51 self.canvas.bind_all('<Button-1>',self.start) 52 53 54 def draw(self): 55 self.canvas.move(self.id,self.x,0) 56 pos=self.canvas.coords(self.id) 57 if pos[0]<=0: 58 self.x=0 59 elif pos[2]>=self.canvas_width: 60 self.x=0 61 62 def turn_left(self,evt): 63 self.x=-2 64 65 def turn_right(self,evt): 66 self.x=2 67 68 def start(self,evt): 69 pass 70 71tk=Tk() 72tk.title("Game") 73tk.resizable(0,0) 74tk.wm_attributes("-topmost",1) 75canvas=Canvas(tk,width=400,height=400,bd=0,highlightthickness=0) 76canvas.create_rectangle(0,0,400,400,fill='black') 77canvas.pack() 78tk.update() 79 80 81 82paddle=Paddle(canvas,'blue') 83ball= Ball(canvas,paddle,'red') 84 85while True: 86 87 if ball.hit_bottom==False: 88 ball.draw() 89 paddle.draw() 90 if ball.hit_bottom==True: 91 canvas.create_text(200,150,text='GAME OVER', 92 fill='red', 93 font=('Times',35)) 94 time.sleep(1) 95 96 tk.update_idletasks() 97 tk.update() 98 time.sleep(0.01) 99 100 101 102 103```。

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

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

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

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

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

guest

回答3

0

ベストアンサー

クリック時の処理は、bind を使うところまでは動作してます。
そこから先、start()の中をどうするかですが、まずは

python

1if ball.hit_bottom==False: 2 # クリックされる迄は、ここが呼ばれないようにする方法を考える 3 ball.draw() 4 paddle.draw()

既に設定されている条件の
ball.hit_bottom==True は、ゲームオーバーの判定にも使われています。
ボールは複数作られる事も考えられるので、この部分の流用はせず、

ボタンクリック時には、baddle.start() が呼ばれる為、
paddleに状態(state)を管理する変数を導入します。

値は自分で自由に決められますが、
仮にスタート画面は 1、プレイ中は 2、ゲームオーバーは 3 としましょう。

python

1if ball.hit_bottom == False and paddle.state == 2: # 2:PLAYING 2 # クリックを押した時 state = 2 に変化させる方法を考える 3 ball.draw() 4 paddle.draw()

クリック時に呼ばれる start() メソッド中身で、「状態」を変化させます。

python

1def start(self, evt): 2 self.state = 2 # 2:PLAYING

まだ、これだけでは未設定の時に paddle.state を参照してもエラーになるので
初期化が必要になる為、Paddleクラスのコンストラクタにも追加します。

python

1# class Paddle def __init__ に追加(適切なインデントで記述) 2 3self.state = 1 # 1:START

今回は、元々のクリック処理のコードを流用する為に
Paddle クラスにゲームの状態を持たせましたが、
状態を管理する専用のクラスを設けても良いかもしれません。

以上で、「クリックされてから動き始める」は実装完了。
以下は余談です。


ゲームオーバー時の処理について

2つ程問題があります。

  • 現状のコードでは、1秒毎に毎回新しいテキストが生成されてます。
    tkinter の canvas では、一度生成すれば存在し続けるので、
    2回目以降は不要になります。

  • tk.update() がマウスイベント等の処理をしているので、
    sleep(1) でコードの実行を途中で止めると、イベント処理が滞ります。
    例えば、ウィンドウを動かしたりしようとすると、レスポンスが悪く、動作が重く感じます。


ウィンドウを閉じた時の処理に関して

TclError が出ますが、これはウィンドウが閉じられた後に
破棄されたリソースに対して update() が呼ばれる為起こるので、
エラーを回避するには、適切なタイミングでループを抜ける必要があります。

※ この辺りは tk.mainloop() を使うと、自前で処理する必要はありません。

python

1# 変更箇所1) class Paddle 内にメソッドを追加 2 3 def close(self): 4 self.state = 0 # 0:CLOSED 5 6 7# 変更箇所2) メインループの前で 8tk.protocol("WM_DELETE_WINDOW", paddle.close) 9 10 11# 変更箇所3) while True: を変更 12while paddle.state: 13 ...

状態を表す値として
0, 1, 2 という重複のない適当な数値を使いました。
ここは文字列でも何でも良いのですが、簡単な方法で、
変数にしておくとコードが保守しやすくなります。

python

1CLOSED, START, PLAYING, GAMEOVER = range(4)

これで、4つの変数が宣言されます。

変数名説明
CLOSED0ウィンドウが閉じられる時
START1スタート画面
PLAYING2プレイ中
GAMEOVER3ゲームオーバー

デメリット: デバッグで print(paddle.state) とした時に状態を確認しようとしても数値が表示されます。
値は文字列にしても良いのですが、ここに関しては良い方法があり、
列挙型 というものがよく使われます。

python

1from enum import Enum 2State = Enum('State', 'CLOSED START PLAYING GAMEOVER', start=0)

print(paddle.state) とした時に、数値と一緒に名前も見れるので便利。


最後に、ここまでの内容を反映したソースを添付します

python

1from tkinter import* 2import random 3import time 4from enum import Enum 5 6# ゲームの状態 7from enum import Enum 8State = Enum('State', 'CLOSED START PLAYING GAMEOVER', start=0) 9 10class Ball: 11 def __init__ (self,canvas,paddle,color): 12 self.canvas=canvas 13 self.paddle=paddle 14 self.id=canvas.create_oval(10,10,25,25,fill=color) 15 self.canvas.move(self.id,245,100) 16 starts=[-3,-2,-1,1,2,3] 17 random.shuffle(starts) 18 self.x=starts[0] 19 self.y=-3 20 self.canvas_height=self.canvas.winfo_height() 21 self.canvas_width=self.canvas.winfo_width() 22 23 def hit_paddle(self,pos): 24 paddle_pos=self.canvas.coords(self.paddle.id) 25 if pos[2]>=paddle_pos[0] and pos[0]<=paddle_pos[2]: 26 if pos[3]>=paddle_pos[1] and pos[3]<=paddle_pos[3]: 27 return True 28 return False 29 30 def draw(self): 31 self.canvas.move(self.id,self.x,self.y) 32 pos=self.canvas.coords(self.id) 33 if pos[1]<=0: 34 self.y=1 35 if pos[3]>=self.canvas_height: 36 self.paddle.gameover() # <- hit_bottom フラグを廃止して gameover() を呼び出し 37 if self.hit_paddle(pos)==True: 38 self.y=-3 39 if pos[0]<=0: 40 self.x=3 41 if pos[2]>=self.canvas_width: 42 self.x=-3 43 44class Paddle: 45 def __init__(self,canvas,color): 46 self.canvas=canvas 47 self.id=canvas.create_rectangle(0,0,100,10,fill=color) 48 self.canvas.move(self.id,200,300) 49 self.x=-2 50 self.canvas_width=self.canvas.winfo_width() 51 self.canvas.bind_all('<KeyPress-Left>',self.turn_left) 52 self.canvas.bind_all('<KeyPress-Right>',self.turn_right) 53 self.canvas.bind_all('<Button-1>',self.start) 54 55 self.state = State.START 56 self.gameover_text = canvas.create_text(200,150,text='GAME OVER', 57 fill='red', 58 font=('Times',35)) 59 self.canvas.itemconfigure(self.gameover_text, state="hidden") 60 61 62 def draw(self): 63 self.canvas.move(self.id,self.x,0) 64 pos=self.canvas.coords(self.id) 65 if pos[0]<=0: 66 self.x=0 67 elif pos[2]>=self.canvas_width: 68 self.x=0 69 70 def turn_left(self,evt): 71 self.x=-2 72 73 def turn_right(self,evt): 74 self.x=2 75 76 def start(self,evt): 77 if self.state == State.GAMEOVER: 78 self.canvas.itemconfigure(self.gameover_text, state="hidden") 79 # TODO: ゲームオーバー時にクリックで再挑戦する時 80 # ここで ball の位置(x,y)をリセット 81 pass 82 self.state = State.PLAYING 83 84 def gameover(self): 85 # ゲームオーバー時に表示 86 self.canvas.itemconfigure(self.gameover_text, state="normal") 87 self.state = State.GAMEOVER 88 89tk=Tk() 90tk.title("Game") 91tk.resizable(0,0) 92tk.wm_attributes("-topmost",1) 93canvas=Canvas(tk,width=400,height=400,bd=0,highlightthickness=0) 94canvas.create_rectangle(0,0,400,400,fill='black') 95canvas.pack() 96tk.update() 97 98paddle=Paddle(canvas,'blue') 99ball= Ball(canvas,paddle,'red') 100 101# ウィンドウを閉じた時に、ループを抜ける為に、`paddle.state = State.CLOSED` 102tk.protocol("WM_DELETE_WINDOW", lambda: setattr(paddle, "state", State.CLOSED)) 103 104while paddle.state != State.CLOSED: 105 if paddle.state == State.PLAYING: 106 ball.draw() 107 paddle.draw() 108 elif paddle.state == State.START: 109 pass 110 elif paddle.state == State.GAMEOVER: 111 pass 112 113 tk.update_idletasks() 114 tk.update() 115 time.sleep(0.01) 116

投稿2020/04/11 07:07

編集2020/04/11 12:22
teamikl

総合スコア8760

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

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

tom_honmono

2020/04/11 07:17

いつもありがとうございます。まだ自分では試していませんが、これから一つずつ試していき理解を深めようと思います。丁寧な回答、ありがとうございます。
guest

0

まず、tkinterを使うスレッドで time.sleep を使ってはいけません。
sleepしている間、イベント処理や描画処理が停止してしまうためです。
.after メソッドを使って、指定時間後に処理を呼び出してもらうように tkinter に依頼しましょう。
それと、PEP8に書いてありますが、== True== False を書かないようにしましょう。
参考: https://pep8-ja.readthedocs.io/ja/latest/

クリックした後にボールを表示してプレイ開始するようにしてみました。

python

1from tkinter import* 2import random 3import time 4 5 6class Ball: 7 8 def __init__(self, canvas, paddle, color): 9 self.canvas = canvas 10 self.paddle = paddle 11 self.id = canvas.create_oval(10, 10, 25, 25, fill=color) 12 self.canvas.move(self.id, 245, 100) 13 starts = [-3, -2, -1, 1, 2, 3] 14 random.shuffle(starts) 15 self.x = starts[0] 16 self.y = -3 17 self.canvas_height = self.canvas.winfo_height() 18 self.canvas_width = self.canvas.winfo_width() 19 self.hit_bottom = False 20 21 def hit_paddle(self, pos): 22 paddle_pos = self.canvas.coords(self.paddle.id) 23 if pos[2] >= paddle_pos[0] and pos[0] <= paddle_pos[2]: 24 if pos[3] >= paddle_pos[1] and pos[3] <= paddle_pos[3]: 25 return True 26 return False 27 28 def draw(self): 29 self.canvas.move(self.id, self.x, self.y) 30 pos = self.canvas.coords(self.id) 31 if pos[1] <= 0: 32 self.y = 1 33 if pos[3] >= self.canvas_height: 34 self.hit_bottom = True 35 if self.hit_paddle(pos): 36 self.y = -3 37 if pos[0] <= 0: 38 self.x = 3 39 if pos[2] >= self.canvas_width: 40 self.x = -3 41 42 43class Paddle: 44 45 def __init__(self, canvas, color): 46 self.canvas = canvas 47 self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color) 48 self.canvas.move(self.id, 200, 300) 49 self.x = -2 50 self.canvas_width = self.canvas.winfo_width() 51 self.canvas.bind_all('<KeyPress-Left>', self.turn_left) 52 self.canvas.bind_all('<KeyPress-Right>', self.turn_right) 53 self.canvas.bind_all('<Button-1>', self.start) 54 55 def draw(self): 56 self.canvas.move(self.id, self.x, 0) 57 pos = self.canvas.coords(self.id) 58 if pos[0] <= 0: 59 self.x = 0 60 elif pos[2] >= self.canvas_width: 61 self.x = 0 62 63 def turn_left(self, evt): 64 self.x = -2 65 66 def turn_right(self, evt): 67 self.x = 2 68 69 def start(self, evt): 70 pass 71 72 73class Game: 74 75 def __init__(self, tk): 76 canvas = Canvas(tk, width=400, height=400, bd=0, highlightthickness=0) 77 canvas.create_rectangle(0, 0, 400, 400, fill='black') 78 canvas.pack() 79 self.canvas = canvas 80 self.paddle = Paddle(canvas, 'blue') 81 self.clicked = False 82 83 def start(self): 84 85 def clicked(event): 86 self.clicked = True 87 88 self.canvas.bind("<Button-1>", clicked) 89 self.canvas.after(100, self.wait_clicked) 90 91 def wait_clicked(self): 92 if not self.clicked: 93 self.canvas.after(100, self.wait_clicked) 94 else: 95 self.ball = Ball(self.canvas, self.paddle, 'red') 96 self.canvas.after(100, self.play) 97 98 def play(self): 99 if self.ball.hit_bottom: 100 self.canvas.create_text(200, 150, text='GAME OVER', 101 fill='red', 102 font=('Times', 35)) 103 return 104 105 self.ball.draw() 106 self.paddle.draw() 107 self.canvas.after(100, self.play) 108 109 110def main(): 111 tk = Tk() 112 tk.title("Game") 113 tk.resizable(0, 0) 114 tk.wm_attributes("-topmost", 1) 115 game = Game(tk) 116 game.start() 117 tk.mainloop() 118 119if __name__ == "__main__": 120 main()

投稿2020/04/11 03:02

編集2020/04/11 03:05
shiracamus

総合スコア5406

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

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

tom_honmono

2020/04/11 07:17

ありがとうございます。このコードも試してみたいと思います。アドバイスありがとうございました。
guest

0

クリックするまで待つようにすればいいのでは

投稿2020/04/11 00:34

y_waiwai

総合スコア88042

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

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

tom_honmono

2020/04/11 07:18

ありがとうございます
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問