【Python/tkinter】マウスカーソルを追いかけるキャラクターを実現

マウスカーソルを追いかけるキャラクターの実現方法解説ページアイキャッチ

このページにはプロモーションが含まれています

このページでは、Python で tkinter による「マウスカーソルを追いかけるキャラクター」の実現方法について解説していきます。

出来上がりは単純に下のアニメのように画像がマウスカーソルの方向に向かって移動するだけです。

キャラクターがマウスカーソルを追いかける様子

ただ、これを実現するためには、tkinter での定期的な処理やイベント処理だけでなく「三角関数」をうまく利用する必要もあります。三角関数を利用して実現できることについても学べますので、是非読んでみてください!

マウスカーソルを追いかけるキャラクターの実現方法

それでは、マウスカーソルを追いかけるキャラクターをどうやって実現すれば良いかについて解説していきます。

まず前提として、今回作成するキャラクターはマウスカーソルの位置に直接移動するのではなく、マウスカーソルの方向に向かって一定距離ずつ進むようにしていきたいと思います。

キャラクターを一定距離ずつ移動させる様子

マウスカーソルの位置に直接移動するのであれば、マウスカーソルの位置の座標を取得し、キャラクターの画像をその座標に移動してやれば良いだけです。なので、マウスカーソルの位置の座標さえ取得できれば、簡単に実現することが出来ます。

その一方で、マウスカーソルの方向に向かって一定距離ずつ進ませる場合は、マウスカーソルがキャラクターから見てどの方向にあるかを求め、その方向に対して一定距離のみキャラクターの画像を移動させる必要があります。

一定距離移動させる際に、移動させる方向が必要であることを示す図

キャラクターから見たマウスカーソルの方向(角度)を求めるためにはどうすれば良いでしょうか?また、その方向に対して一定距離のみキャラクターを移動させるためにはどうすれば良いでしょうか?

マウスカーソルを追いかけるキャラクターを実現するためには、この辺りを考えながらプログラミングしていく必要があります。ただ、この辺りの処理は三角関数および逆三角関数を利用すれば簡単に実現することが出来ます。

マウスカーソルの方向を算出する

まず、キャラクターから見たマウスカーソルの方向(角度)の求め方について考えていきましょう!

これは、math モジュールの math.atan2 関数を利用することで簡単に実現することが出来ます。

math.atan2 は、引数で指定した座標 (x, y) より、原点から見てその座標がどの方向に存在するかを求める関数になります。方向というと曖昧かもしれませんが、要は原点とその座標を結ぶ直線が x 軸と正方向になす角の角度を求めることが出来ます(math.atan2 では第1引数には y 座標、第2引数には x 座標を指定します)。

atan2関数で求められる角度を示す図

したがって、キャラクターが原点の位置に移動するようにキャラクターの位置とマウスカーソルの位置を平行移動し、さらに平行移動後のマウスカーソルの位置の座標に対して math.atan2 を実行することで、キャラクターから見たマウスカーソルの方向を算出することが出来ます。

キャラクターから見たマウスカーソルの方向をatan2関数で求める様子

あとは、tkinter の bind メソッドを利用してマウスが移動した時に関数やメソッドが実行されるように設定し、その関数 or メソッドの中で math.atan2 を実行するようにすれば、マウスカーソルの位置が変わるたびに方向 θ を求めることができるようになります。

こんな感じで、座標から角度・方向を算出したい場合は math.atan2 が便利なので是非覚えておいてください!

ちなみに、多くのプログラミング言語では atan2 だけでなく atan 関数も用意されています。これらの違いについては、下記ページで解説しておりますので、詳しく知りたい方はぜひ読んでみてください。C言語向けの記事ではありますが atan2atan の違いは理解していただけると思います!

【C言語】atan関数とatan2関数について解説(傾きor座標から角度を求める関数)

スポンサーリンク

マウスカーソルの方向に一定距離移動する

キャラクターから見たマウスカーソルの方向が求められるようになったので、次はその方向に向かってキャラクターを一定距離移動させることを実現していきたいと思います。

キャラクターの現在位置の座標を (x1, y1)、方向(角度)を θ、移動させる距離を r、さらに移動後のキャラクターの位置の座標を (x2, y2) とすれば、これらは下の図のような関係になります。

キャラクターの移動に必要なパラメータの関係図

つまり、キャラクターの位置を示す x 座標を x2 - x1y 座標を y2 - y1 だけ増加させれば、キャラクターが方向 θ に距離 r だけ移動することになります。

x座標とy座標の増加によりキャラクターが移動する様子

また、最初にも紹介した下の図における青で色をつけた部分は、底辺の長さ x2 - x1、高さ y2 - y1、斜辺の長さ r の三角形になっていることが確認できますね!

キャラクターの移動に必要なパラメータの関係図

さらに斜辺と x 軸とが正方向になす角度が θ ですので、cosθsinθ は下記の式によって求めることができることになります。

  • cosθ = r / (x2 - x1)
  • sinθ = r / (y2 - y1)

さらに、上式を x2 - x1y2 - y1 について解くようにすれば次の式に変形することが出来ます。

  • x2 - x1 = r * cosθ
  • y2 - y1 = r * sinθ

つまり、キャラクターの方向 θ への距離 r の移動は、キャラクターが存在する位置の x 座標に r * cosθ を加算し、y 座標に r * sinθ を加算することで実現することが出来ます。

で、この方向 θマウスカーソルの方向を算出する で紹介した方法で求めるようにすれば、マウスカーソルの方向にキャラクターを移動させることが出来ます。

あとは、tkinter の after メソッドを利用して関数やメソッドを定期的に実行するように設定し、その関数 or メソッドの中で座標の加算を行うようにすれば、キャラクターの位置がマウスカーソルを追いかける形で変化していくようになります。

さらに同じ関数 or メソッドの中で、キャラクターの位置に応じてキャラクターを表示するための画像を移動させてやれば、キャラクターがマウスカーソルを追いかけてどんどん移動する動きを実現することが出来ます。

こんな感じで、特定の方向 θ に移動させる際には r * cosθr * sinθ の計算を利用することが多いので、この計算式も覚えておくと良いと思います!

マウスカーソルを追いかけるキャラクターのスクリプト

以上を踏まえてマウスカーソルを追いかけるキャラクターを tkinter で実現するスクリプトは下記のようになります。

マウスカーソルを追いかけるキャラクター
import tkinter
import math
from PIL import Image, ImageTk

WIDTH = 400  # キャンバスの幅
HEIGHT = 400  # キャンバスの高さ
MOUSE_OVER_RANGE = 10  # キャラクターの中心付近の範囲
SPEED = 3  # キャラクターが一度に移動する距離(px)
IMAGE_SIZE = 100 # キャラクターの画像のサイズ

PATH = "画像のファイルパス"  # キャラクターの画像のパス


class MouseChaser:

	def __init__(self, master):
		'''コンストラクタ'''

		self.master = master  # 親ウィジェット
		self.speed = SPEED  # キャラの動作スピード(px)
		self.mouse_x = None  # マウスのx座標
		self.mouse_y = None  # マウスのy座標

		# キャンバスを作成
		self.canvas = tkinter.Canvas(
			master,
			width=WIDTH, height=HEIGHT
		)
		self.canvas.pack()

		# キャラクターの位置の初期化
		self.chara_x = WIDTH / 2
		self.chara_y = HEIGHT / 2

		# キャラクターの画像オブジェクト生成
		self.image = self.createImageObj(PATH)

		# キャラクタをキャンバスへ描画
		self.canvas.create_image(
			self.chara_x, self.chara_y,
			anchor=tkinter.CENTER,
			image=self.image,
			tag="character"
		)

		# 10ms後にmoveを実行する
		self.master.after(10, self.move)

		# マウスの移動時にdirectionを実行する
		self.canvas.bind("<Motion>", self.direction)

	def createImageObj(self, path):
		'''pathで指定されたパスの画像のオブジェクトを生成'''

		# PIL画像オブジェクトを生成
		image = Image.open(path)

		# IMAGE_SIZExIMAGE_SIZEの矩形に合わせて&縦横比を保ってリサイズ
		ratio = min(IMAGE_SIZE / image.width, IMAGE_SIZE / image.height)
		resize_size = (round(ratio * image.width), round(ratio * image.height))
		resized_image = image.resize(resize_size)

		# tkinter画像オブジェクトに変換したものを返却
		return ImageTk.PhotoImage(resized_image)

	def direction(self, event):
		'''マウスカーソルの方向を算出'''

		# マウスの座標を覚えておく
		self.mouse_x = event.x
		self.mouse_y = event.y

		# キャラクターから見たマウスカーソルの方向を算出
		self.angle = math.atan2(
			self.mouse_y - self.chara_y,
			self.mouse_x - self.chara_x
		)

	def isMousedOver(self):
		'''キャラクターの中心付近とマウスが重なったかどうかを判断'''

		if self.mouse_x < self.chara_x - MOUSE_OVER_RANGE / 2:
			return False
		if self.mouse_x > self.chara_x + MOUSE_OVER_RANGE / 2:
			return False
		if self.mouse_y < self.chara_y - MOUSE_OVER_RANGE / 2:
			return False
		if self.mouse_y > self.chara_y + MOUSE_OVER_RANGE / 2:
			return False

		return True

	def move(self):
		'''キャラクターの移動'''

		# moveを10ms毎に定期的に実行
		self.master.after(10, self.move)

		# まだマウスカーソルが動いていないなら何もしない
		if self.mouse_x is None or self.mouse_y is None:
			return

		# キャラクターの中心がマウスカーソルの位置と一致したら止める
		if self.isMousedOver():
			self.speed = 0
		else:
			self.speed = SPEED

		# キャラクターを距離speedだけangle方向に移動させる
		self.chara_x += self.speed * math.cos(self.angle)
		self.chara_y += self.speed * math.sin(self.angle)

		# 移動後の位置にキャラクターの画像を移動
		self.canvas.coords(
			"character",
			self.chara_x, self.chara_y
		)


app = tkinter.Tk()
chaser = MouseChaser(app)
app.mainloop()

実行するためには、予め下記の 画像のファイルパス 部分を変更してキャラクターとして表示する画像のファイルパスを設定する必要があるので注意してください。

画像の設定
PATH = "画像のファイルパス" # キャラクターの画像のパス

上記設定後にスクリプトを実行してアプリ上でマウスカーソルを移動させれば、下のアニメのようにキャラクターがマウスカーソルを追いかける形で移動します。

キャラクターがマウスカーソルを追いかける様子

以降では、簡単に各メソッドの処理内容について解説を行なっていきます。

__init__

__init__ では主に下記のことを行なっています。

  1. 各種データ属性の初期化
  2. キャンバスの作成(tkinter.Canvas の実行)
  3. キャンバスの配置(pack メソッドの実行)
  4. 画像オブジェクトの生成(createImageObj メソッドの実行)
  5. キャンバスへの画像の描画(create_image メソッドの実行)
  6. 定期処理の開始(after メソッドの実行)
  7. マウス移動時のイベント処理の設定(bind メソッドの実行)

詳細はスクリプトにコメントで記していますので、詳しくはコメントをご参照しただければと思います。

また、4. を除く 2. から 7. の処理に関しては、それぞれ個別のページで詳しく解説しておりますので、詳しく知りたい内容がございましたら別途該当のページをご参照しただければと思います。

2. の「キャンバスの作成」の解説ページ

tkinterのキャンバスの作り方解説ページアイキャッチ Tkinterの使い方:キャンバスウィジェットの作り方

3. の「ウィジェットの配置」の解説ページ

ウィジェット配置方法解説ページのアイキャッチ Tkinterの使い方:ウィジェットの配置(pack・grid・place)

5. の「キャンバスへの図形の描画」の解説ページ

tkinterキャンバスに図形を描画する方法解説ページのアイキャッチ Tkinterの使い方:Canvasクラスで図形を描画する

6. の「after メソッド」の解説ページ

Tkinterの使い方:after で処理を「遅らせて」or 処理を「定期的」に実行する

7. の「イベント処理」の解説ページ

イベント処理解説ページのアイキャッチ Tkinterの使い方:イベント処理を行う

スポンサーリンク

createImageObj メソッド

前述の __init__ で行なっている処理の 4. で実行しているのが、この  createImageObj メソッドになります。

このメソッドでは下記の処理を行なっています。

  1. PATH の画像ファイルを読み込んで PIL 画像オブジェクトを生成
  2. IMAGE_SIZE x IMAGE_SIZE の矩形に合わせて画像をリサイズ
  3. リサイズ後の PIL 画像オブジェクトを tkinter 画像オブジェクトに変換して返却

PATHIMAGE_SIZE はスクリプトの先頭付近で設定されるパラメータとなります。

2. で行なっている矩形に合わせて画像をリサイズする方法については下記ページで詳しく解説しておりますので、詳細を知りたい方は下記ページをご参照いただければと思います。

画像の縦横比を保ったまま矩形に合わせてリサイズする方法の解説ページアイキャッチ 【Python/PIL】縦横比を保ったまま矩形に合わせて画像をリサイズ(resize・thumnail)

3. での画像オブジェクトの変換に関しましても下記ページで解説しています。PIL 画像オブジェクトから tkinter 画像オブジェクトへの変換は簡単に行えますので、GUI アプリ作成時に画像を扱う際には PIL が便利です。

画像オブジェクト変換方法の解説ページアイキャッチ 【Python】PIL ⇔ OpenCV2 ⇔ Tkinter 画像オブジェクトの相互変換

direction メソッド

__init__createImageObj メソッドを実行することで、キャラクターの画像がキャンバスの中心部分に描画されるようになります。ただしまだキャラクターは移動しません。

ここからは、「マウスカーソルを追いかけるキャラクター」を実現するためのメソッドの解説を行なっていきます。

そのメソッドの1つが direction メソッドであり、このメソッドでは マウスカーソルの方向を算出する で説明した「キャラクターから見たマウスカーソルの方向」を求める処理を行なっています。

この direction メソッドは、__init__ の中の下記の処理によって "<Motion>" イベントが発生した際、すなわちマウスカーソルの移動が行われた際に実行されるようになります。

マウス移動時にdirectionを実行するための設定
# マウスの移動時にdirectionを実行する
self.canvas.bind("", self.direction)

さらに実行された際には、引数 event における event.xevent.y に移動後のマウスカーソルの座標が渡されます。

このマウスカーソルの座標と、キャラクターの位置の座標であるデータ属性 chara_xchara_y を利用して math.atan2 を実行すれば、キャラクターから見たマウスカーソルの方向を求めることが出来ます。

キャラクターから見たマウスカーソルの方向を求める様子

前述の通り、direction メソッドはマウスカーソルの移動が行われた際に実行されますので、マウスカーソルが移動するたびにこの方向が変化していくことになります。

また、求めた方向はクラスのデータ属性 angle に設定していますので、あとはこの angle の方向にキャラクターを移動させるようにすれば、「マウスカーソルを追いかけるキャラクター」を実現することが出来ます。

move メソッド

その angle の方向へのキャラクターの移動を行なっているのが move メソッドになります。

この move メソッドでは マウスカーソルの方向に一定距離移動する で説明したキャラクターを一定距離移動させる処理を行なっています。

特にこのキャラクターの移動を行なっているのが move メソッドの後半部分で、キャラクターの位置の x 座標である chara_x と キャラクターの位置の y 座標である chara_y それぞれを マウスカーソルの方向に一定距離移動する で紹介した r * cosθr * sinθ 分だけ増加させています。

ただ、スクリプトにおいては r がデータ属性の speedθ がデータ属性の angle にそれぞれ置き換わっているので注意してください。

さらに、キャラクターの移動後の座標 (chara_x, chara_y) にキャンバスの coords メソッドを利用してキャラクターの画像を移動させています。

このキャラクターの座標の計算とその座標への画像の移動の2つにより、キャラクターの移動を実現しています。

ちなみに coords メソッドをはじめとするキャンバスに描画した図形や画像に対する操作を行うメソッドについては下記ページでまとめていますので、こちらも興味があれば読んでみてください。

tkinterキャンバスの図形の操作方法解説ページのアイキャッチ Tkinterの使い方:Canvasクラスで描画した図形を操作する

また、move メソッドの最初に下記を実行しており、これにより move メソッドは一度実行されれば以降は 10 ms ごとに定期的に実行されるようになります。ですので、時間の経過と共にどんどんキャラクターがマウスカーソルに近づいていくことになります。

moveメソッドの定期実行
# moveを10ms毎に定期的に実行
self.master.after(10, self.move)

下記に関しては、まだデータ属性 mouse_xmouse_yNone の場合に move メソッドで何もしないようにするための処理です。これらは前述の direction メソッドでのみ None以外に設定されるため、マウスカーソルがまだ動かされていない状態ではキャラクターが移動しないことになります。

キャラクターの移動のスキップ
# まだマウスカーソルが動いていないなら何もしない
if self.mouse_x is None or self.mouse_y is None:
	return

さらに下記では、マウスカーソルにキャラクターが到達した際に、キャラクターの移動を止めるための処理になります。マウスカーソルにキャラクターが到達したかどうかを isMousedOver で判断しており、isMousedOverTrue を返却した際にはデータ属性 speed0 になることになります。

キャラクターがカーソルに到達したかの判断
# キャラクターの中心がマウスカーソルの位置と一致したら止める
if self.isMousedOver():
	self.speed = 0
else:
	self.speed = SPEED

speed0 の場合は下記の右辺が 0 になり、キャラクターの移動が停止することになります。

キャラクターの停止
# キャラクターを距離speedだけangle方向に移動させる
self.chara_x += self.speed * math.cos(self.angle)
self.chara_y += self.speed * math.sin(self.angle)

また、isMousedOverFalse を返却した場合はデータ属性 speed をデフォルトの値 SPEED に設定するようにしています(SPEED はスクリプトの先頭付近で設定している値です)。

したがって、一度キャラクターがマウスカーソルに到達して停止した後でも、マウスカーソルを他の位置に移動させればキャラクターが再度移動し始めることになります。

スポンサーリンク

isMousedOver メソッド

で、前述の通り、isMousedOver メソッドではマウスカーソルにキャラクターが到達したかどうかの判断を行なっています。

要は、下の図のようにキャラクターの中心付近にマウスカーソルが存在するかどうかを判断し、存在する場合は True を、それ以外を False を返却しているだけです。

キャラクターがマウスカーソルに到達したかどうかを判断する様子

以上が本スクリプトの解説となります。

まとめ

このページでは、Python で tkinter による「マウスカーソルを追いかけるキャラクター」の実現方法について解説しました!

なんだか簡単に実現できそうにも思えますが、実は三角関数を駆使する必要があって、特に三角関数が苦手な方にとっては難易度はそれなりに高かったのではないかと思います。

このページで解説した内容はほんの一例で、三角関数を利用することでキャラクターの様々な動作を実現することが可能です!

特にゲームなどを作ろうと思うと三角関数を利用することも多いです。特に三角関数が苦手な方であれば、逆に三角関数を理解するチャンスでもありますので、いろんなゲーム作りに挑戦してみると良いと思います!

同じカテゴリのページ一覧を表示