このページでは、Python で tkinter による「マウスカーソルを追いかけるキャラクター」の実現方法について解説していきます。
出来上がりは単純に下のアニメのように画像がマウスカーソルの方向に向かって移動するだけです。

ただ、これを実現するためには、tkinter での定期的な処理やイベント処理だけでなく「三角関数」をうまく利用する必要もあります。三角関数を利用して実現できることについても学べますので、是非読んでみてください!
Contents
マウスカーソルを追いかけるキャラクターの実現方法
それでは、マウスカーソルを追いかけるキャラクターをどうやって実現すれば良いかについて解説していきます。
まず前提として、今回作成するキャラクターはマウスカーソルの位置に直接移動するのではなく、マウスカーソルの方向に向かって一定距離ずつ進むようにしていきたいと思います。

マウスカーソルの位置に直接移動するのであれば、マウスカーソルの位置の座標を取得し、キャラクターの画像をその座標に移動してやれば良いだけです。なので、マウスカーソルの位置の座標さえ取得できれば、簡単に実現することが出来ます。
その一方で、マウスカーソルの方向に向かって一定距離ずつ進ませる場合は、マウスカーソルがキャラクターから見てどの方向にあるかを求め、その方向に対して一定距離のみキャラクターの画像を移動させる必要があります。

キャラクターから見たマウスカーソルの方向(角度)を求めるためにはどうすれば良いでしょうか?また、その方向に対して一定距離のみキャラクターを移動させるためにはどうすれば良いでしょうか?
マウスカーソルを追いかけるキャラクターを実現するためには、この辺りを考えながらプログラミングしていく必要があります。ただ、この辺りの処理は三角関数および逆三角関数を利用すれば簡単に実現することが出来ます。
マウスカーソルの方向を算出する
まず、キャラクターから見たマウスカーソルの方向(角度)の求め方について考えていきましょう!
これは、math モジュールの math.atan2 関数を利用することで簡単に実現することが出来ます。
math.atan2 は、引数で指定した座標 (x, y) より、原点から見てその座標がどの方向に存在するかを求める関数になります。方向というと曖昧かもしれませんが、要は原点とその座標を結ぶ直線が x 軸と正方向になす角の角度を求めることが出来ます(math.atan2 では第1引数には y 座標、第2引数には x 座標を指定します)。

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

あとは、tkinter の bind メソッドを利用してマウスが移動した時に関数やメソッドが実行されるように設定し、その関数 or メソッドの中で math.atan2 を実行するようにすれば、マウスカーソルの位置が変わるたびに方向 θ を求めることができるようになります。
こんな感じで、座標から角度・方向を算出したい場合は math.atan2 が便利なので是非覚えておいてください!
ちなみに、多くのプログラミング言語では atan2 だけでなく atan 関数も用意されています。これらの違いについては、下記ページで解説しておりますので、詳しく知りたい方はぜひ読んでみてください。C言語向けの記事ではありますが atan2 と atan の違いは理解していただけると思います!
スポンサーリンク
マウスカーソルの方向に一定距離移動する
キャラクターから見たマウスカーソルの方向が求められるようになったので、次はその方向に向かってキャラクターを一定距離移動させることを実現していきたいと思います。
キャラクターの現在位置の座標を (x1, y1)、方向(角度)を θ、移動させる距離を r、さらに移動後のキャラクターの位置の座標を (x2, y2) とすれば、これらは下の図のような関係になります。

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

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

さらに斜辺と x 軸とが正方向になす角度が θ ですので、cosθ と sinθ は下記の式によって求めることができることになります。
cosθ = r / (x2 - x1)sinθ = r / (y2 - y1)
さらに、上式を x2 - x1 と y2 - 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__ では主に下記のことを行なっています。
- 各種データ属性の初期化
- キャンバスの作成(
tkinter.Canvasの実行) - キャンバスの配置(
packメソッドの実行) - 画像オブジェクトの生成(
createImageObjメソッドの実行) - キャンバスへの画像の描画(
create_imageメソッドの実行) - 定期処理の開始(
afterメソッドの実行) - マウス移動時のイベント処理の設定(
bindメソッドの実行)
詳細はスクリプトにコメントで記していますので、詳しくはコメントをご参照しただければと思います。
また、4. を除く 2. から 7. の処理に関しては、それぞれ個別のページで詳しく解説しておりますので、詳しく知りたい内容がございましたら別途該当のページをご参照しただければと思います。
2. の「キャンバスの作成」の解説ページ
3. の「ウィジェットの配置」の解説ページ
5. の「キャンバスへの図形の描画」の解説ページ
6. の「after メソッド」の解説ページ
7. の「イベント処理」の解説ページ
スポンサーリンク
createImageObj メソッド
前述の __init__ で行なっている処理の 4. で実行しているのが、この createImageObj メソッドになります。
このメソッドでは下記の処理を行なっています。
PATHの画像ファイルを読み込んで PIL 画像オブジェクトを生成IMAGE_SIZExIMAGE_SIZEの矩形に合わせて画像をリサイズ- リサイズ後の PIL 画像オブジェクトを tkinter 画像オブジェクトに変換して返却
PATH と IMAGE_SIZE はスクリプトの先頭付近で設定されるパラメータとなります。
2. で行なっている矩形に合わせて画像をリサイズする方法については下記ページで詳しく解説しておりますので、詳細を知りたい方は下記ページをご参照いただければと思います。
3. での画像オブジェクトの変換に関しましても下記ページで解説しています。PIL 画像オブジェクトから tkinter 画像オブジェクトへの変換は簡単に行えますので、GUI アプリ作成時に画像を扱う際には PIL が便利です。
direction メソッド
__init__ と createImageObj メソッドを実行することで、キャラクターの画像がキャンバスの中心部分に描画されるようになります。ただしまだキャラクターは移動しません。
ここからは、「マウスカーソルを追いかけるキャラクター」を実現するためのメソッドの解説を行なっていきます。
そのメソッドの1つが direction メソッドであり、このメソッドでは マウスカーソルの方向を算出する で説明した「キャラクターから見たマウスカーソルの方向」を求める処理を行なっています。
この direction メソッドは、__init__ の中の下記の処理によって "<Motion>" イベントが発生した際、すなわちマウスカーソルの移動が行われた際に実行されるようになります。
# マウスの移動時にdirectionを実行する
self.canvas.bind("", self.direction)
さらに実行された際には、引数 event における event.x と event.y に移動後のマウスカーソルの座標が渡されます。
このマウスカーソルの座標と、キャラクターの位置の座標であるデータ属性 chara_x と chara_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 メソッドをはじめとするキャンバスに描画した図形や画像に対する操作を行うメソッドについては下記ページでまとめていますので、こちらも興味があれば読んでみてください。
また、move メソッドの最初に下記を実行しており、これにより move メソッドは一度実行されれば以降は 10 ms ごとに定期的に実行されるようになります。ですので、時間の経過と共にどんどんキャラクターがマウスカーソルに近づいていくことになります。
# moveを10ms毎に定期的に実行
self.master.after(10, self.move)
下記に関しては、まだデータ属性 mouse_x と mouse_y が None の場合に move メソッドで何もしないようにするための処理です。これらは前述の direction メソッドでのみ None以外に設定されるため、マウスカーソルがまだ動かされていない状態ではキャラクターが移動しないことになります。
# まだマウスカーソルが動いていないなら何もしない
if self.mouse_x is None or self.mouse_y is None:
return
さらに下記では、マウスカーソルにキャラクターが到達した際に、キャラクターの移動を止めるための処理になります。マウスカーソルにキャラクターが到達したかどうかを isMousedOver で判断しており、isMousedOver が True を返却した際にはデータ属性 speed が 0 になることになります。
# キャラクターの中心がマウスカーソルの位置と一致したら止める
if self.isMousedOver():
self.speed = 0
else:
self.speed = SPEED
speed が 0 の場合は下記の右辺が 0 になり、キャラクターの移動が停止することになります。
# キャラクターを距離speedだけangle方向に移動させる
self.chara_x += self.speed * math.cos(self.angle)
self.chara_y += self.speed * math.sin(self.angle)
また、isMousedOver が False を返却した場合はデータ属性 speed をデフォルトの値 SPEED に設定するようにしています(SPEED はスクリプトの先頭付近で設定している値です)。
したがって、一度キャラクターがマウスカーソルに到達して停止した後でも、マウスカーソルを他の位置に移動させればキャラクターが再度移動し始めることになります。
スポンサーリンク
isMousedOver メソッド
で、前述の通り、isMousedOver メソッドではマウスカーソルにキャラクターが到達したかどうかの判断を行なっています。
要は、下の図のようにキャラクターの中心付近にマウスカーソルが存在するかどうかを判断し、存在する場合は True を、それ以外を False を返却しているだけです。

以上が本スクリプトの解説となります。
まとめ
このページでは、Python で tkinter による「マウスカーソルを追いかけるキャラクター」の実現方法について解説しました!
なんだか簡単に実現できそうにも思えますが、実は三角関数を駆使する必要があって、特に三角関数が苦手な方にとっては難易度はそれなりに高かったのではないかと思います。
このページで解説した内容はほんの一例で、三角関数を利用することでキャラクターの様々な動作を実現することが可能です!
特にゲームなどを作ろうと思うと三角関数を利用することも多いです。特に三角関数が苦手な方であれば、逆に三角関数を理解するチャンスでもありますので、いろんなゲーム作りに挑戦してみると良いと思います!

