【Python】Tkinterによる70行で作るGUIアプリ「追いかけっこゲーム」

# はじめに
こんにちは。
今回は**「追いかけっこゲーム」**を作っていきたいと思います。
完成するとこんな感じに動きます！

![追いかけっこゲーム.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309633/0f05f34c-f49f-3dbb-29f8-b4bd582d9d66.gif)

赤い円が自分(player)で青い円が敵(enemy)です。
マウスカーソルを操作して、赤い円を動かすことができます。青い円に触れたら止まります。つまりゲームオーバーといった感じです！



# 環境

- Windows 10 home
- Python 3.7.1

numpyを使うので以下のコマンドでインストールしておきましょう。

```
pip install numpy
```


# 「追いかけっこゲーム」の制作

## インポート
使うライブラリは以下の2つです。

```py
import tkinter as tk
import numpy as np
```


## ウィンドウの作成

```py
import tkinter as tk
import numpy as np

class Application(tk.Frame):
    def __init__(self,master):
        super().__init__(master)
        self.pack()

        self.width=self.height=500
        master.geometry(str(self.width)+"x"+str(self.height))
        master.title("追いかけっこゲーム")


def main():
    win = tk.Tk()
    app = Application(master = win)
    app.mainloop()


if __name__ == "__main__":

    main()
```

このプログラムでウィンドウを作っていきます。
サイズは**500×500**です。タイトル名は**追いかけっこゲーム**です。
詳しくは[PythonによるTkinterを使った雛形（クラス化手法）](https://qiita.com/MichihiroTsukano/items/4d8721aaa59e1c913d9a)の記事をご参照ください。



## 敵の動きのベクトル取得に関して

敵の動きのベクトルは以下のenemyVec関数で取得しています。少しややこしい部分なので、解説を入れようと思います。

```py
def enemyVec(self,player,enemy,speed):#敵の動き(x,y)のベクトルを返す
	rad=np.arctan((player.y-enemy.y)/(player.x-enemy.x))#向き（角度の計算）
	if player.x-enemy.x >= 0:
		vx=np.cos(rad)*speed
		vy=np.sin(rad)*speed
	else:
		vx=np.cos(rad)*(-1*speed)
		vy=np.sin(rad)*(-1*speed)
	return [vx,vy]
```

引数として、playerオブジェクトとenemyオブジェクトを渡しています。それぞれの中心座標を計算で使うためです。ここで、この2つのオブジェクトの向きの計算が少しややこしくなると思います。つまり、2点間の角度の計算です。
2点の座標が分かっているので、ベクトルのx成分とy成分を求めることができます。


(例) A(x1,y1),B(x2,y2)の場合

```math

\vec{AB} =
\begin{matrix}
x2 - x1 \\
y2 - y1 
\end{matrix}
```



そして、x軸とベクトルがなす角度θを以下のtanの逆関数を使って表すことができます。


```math
θ = \tan^-1(\frac{y}{x})
```


tanθの範囲が-π/2～π/2なので、playerとenemyのx座標の差の正負で場合分けをし、speedに-1をかけて、方向を変えています。



## 完成したプログラム

こちらが完成したプログラムになります。

```追いかけっこゲーム.py
import tkinter as tk
import numpy as np

class Circle():
	def __init__(self,canvas,x,y,r,color,tag=None):
		self.canvas = canvas
		self.x = x #円の中心のx座標
		self.y = y #円の中心のy座標
		self.r = r #円の半径
		self.color = color
		self.tag = tag

	def createCircle(self):
		self.canvas.create_oval(self.x-self.r,self.y-self.r,self.x+self.r,self.y+self.r,fill=self.color,tag=self.tag)

class Application(tk.Frame):
	def __init__(self,master):
		super().__init__(master)
		self.pack()

		self.width=self.height=500
		master.geometry(str(self.width)+"x"+str(self.height))
		master.title("追いかけっこゲーム")

		self.canvas = tk.Canvas(master,width=self.width,height=self.height,bg="black")
		self.canvas.pack()

		self.player = Circle(self.canvas,250,250,30,"red","player") #インスタンスplayerの生成
		self.enemy = Circle(self.canvas,0,0,30,"blue","enemy") #インスタンスenemyの生成

		self.canvas.bind("<Motion>",self.mouseEvent)
		self.master.after(50,self.update)

	def update(self):
		if self.judgeflag(self.player,self.enemy):
			eV = self.enemyVec(self.player,self.enemy,10)
			self.enemy.x += eV[0]
			self.enemy.y += eV[1]
			self.canvas.delete("player")
			self.canvas.delete("enemy")
			self.player.createCircle()
			self.enemy.createCircle()

		self.master.after(50,self.update)

	def enemyVec(self,player,enemy,speed): #敵の動き(x,y)のベクトルを返す
		rad=np.arctan((player.y-enemy.y)/(player.x-enemy.x)) #向き（角度の計算）
		if player.x-enemy.x >= 0:
			vx=np.cos(rad)*speed
			vy=np.sin(rad)*speed
		else:
			vx=np.cos(rad)*(-1*speed)
			vy=np.sin(rad)*(-1*speed)
		return [vx,vy]

	def judgeflag(self,player,enemy): #当たり判定
		if np.sqrt((player.x-enemy.x)**2+(player.y-enemy.y)**2) > player.r+enemy.r: return True
		else: return False

	def mouseEvent(self,event):
		self.player.x = event.x
		self.player.y = event.y

def main():
	win = tk.Tk()
	app = Application(master=win)
	app.mainloop()

if __name__ == "__main__":
	main()
```



以上で追いかけっこゲームは完成になります。
ここまで読んでいただき、ありがとうございました。