【Python/tkinter】横スクロールアクションゲームを作る(キャラクターの表示)

横スクロールアクションゲームを作る(キャラクターの表示)の解説ページアイキャッチ

このページでは、Python で tkinter を利用した簡単な「横スクロールアクションゲーム」の作り方を解説していきます。

このページは「横スクロールアクションゲームの作り方の解説」の3ページ目となります。

2ページ目は下記ページとなり、2ページ目では必要なウィジェットの作成や背景の表示の解説を行なっています。

横スクロールアクションゲームを作る(ウィジェットの作成と背景表示)の解説ページアイキャッチ【Python/tkinter】横スクロールアクションゲームを作る(ウィジェットの作成および背景の表示)

また、このページでは、上記ページの このページで作成したスクリプト で紹介しているスクリプトを変更していきながらゲームを作成していきます。

ですので、事前に上記のページを読んでおくことをオススメします。

この「横スクロールアクションゲームの作り方の解説」の3ページ目では、主に操作キャラクターの表示および、次のページで解説する操作キャラクターの移動を行うための前準備を行なっていきたいと思います。

Character クラスのオブジェクトの生成

今回、操作キャラクターの表示を行うための機能の一部は Character クラスに実装していきたいと思います。

操作キャラクター用のクラスとして Character クラスのサブクラスである Player を用意しているのですが、ゲーム内に登場するキャラクターはまだ操作キャラクターだけですので、他のキャラクターが登場するまでは Character クラスのみを実装していきます。

そして、他のキャラクターが登場したタイミングで、Player クラスを利用するようにしていきたいと思います。

ということで、まずは、その Character クラスのオブジェクトを生成するようにしていきます。

Character クラスの枠組みだけは既に用意していますので、あとは Game クラスから Character クラスのコンストラクタを実行させるようにすれば、Character クラスのオブジェクトの生成は完了します。

具体的には、Game クラスの __init__ を下記のように変更します。

Characterオブジェクトの生成
class Game:

	def __init__(self, master):
		self.master = master
		self.screen = Screen(self.master)

		# ↓これを追加
		self.characters = []
		self.player = Character()
		self.characters.append(self.player)
		# ↑これを追加

リスト characters をデータ属性に追加し、Character クラスのオブジェクトをそのリストに追加するようにしています。

わざわざリストを用意しているのは、今後登場するキャラクターが増えることを見越してのことです。

今後扱うキャラクターが増えた際にも、ループ処理により一括で処理の依頼等を行えるよう、全てのオブジェクトをリストで管理するようにしています。

ただし、操作キャラクターは他のキャラクターに比べて特別で、キーボードのキー入力により移動を行ったり、このキャラクターの位置に応じて画面のスクロールを行ったりする必要があります。

そのため、リストからだけではなく直接オブジェクトが参照できるようにもした方が便利なので、別途データ属性 player を持たせるようにしています。

操作キャラクターに対してのみ処理を行いたいような場合は、この player を利用するようにしていきます(といってもこのページで利用するのは characters の方のみとなります)。

操作キャラクターの画像の準備

今回作成するゲームでは、キャラクターの画像を準備し、その画像をキャンバスに描画することでキャラクターを画面に表示するようにしていきたいと思います。

そのため、事前に操作キャラクターの画像を準備しておく必要があります。

ご自身が操作したいキャラクターの画像であればなんでも良いのですが、背景が透明になっている画像が望ましいです。背景が透明でないと、下の図のようにキャラクターの周りが白色になってしまいます。

背景が透明で無い場合のキャラクター表示

また、この背景が透明であることを利用すれば、敵キャラクター等との詳細な当たり判定も行うことができるようになります。この背景が透明であることを利用した当たり判定については下記ページで解説しています。

横スクロールアクションゲームを作る(より詳細な当たり判定)の解説ページアイキャッチ【Python/tkinter】横スクロールアクションゲームを作る(より詳細な当たり判定)

ひとまず、このページでは いらすとや の下記 URL の画像を操作キャラクター画像として利用していきたいと思います。

http://www.irasutoya.com/2014/04/blog-post_15.html

下の図は上記 URL の画像を転載させていただいたものになります。

スクリプトに入力する画像の例

転載元:いらすとや

特に操作キャラクターとして使用したい画像にこだわりがないのであれば、上の画像を適当なフォルダに保存しておいてください。画像を読み込む際には、この画像を保存した先のファイルパスを指定する必要がありますのでメモしておくと良いと思います。

スポンサーリンク

キャラクターの画像オブジェクト生成

続いて、準備したキャラクターの画像からキャンバスに描画可能な画像オブジェクトを生成していきます。

この画像オブジェクトの生成は、Character クラスに prepareImage メソッドを作成することで実現していきたいと思います。

画像の拡大縮小

単にキャンバスに描画可能な画像オブジェクトを作成するだけであれば、tkinter.PhotoImage クラスのコンストラクタを実行するだけで実現することができます。

ですが、単にこれを行うだけだと、下の図のように操作キャラクターが大きすぎたり小さすぎたりする可能性があります。そのため、PIL を利用して画像の拡大縮小を行なってから、PIL 用の画像オブジェクトを tkinter 用の画像オブジェクトに変換するという手順を踏んでいきたいと思います。

操作キャラクターの画像が大きすぎる場合

基本的に行うことは、下記の2ページ目で行なった 背景画像の拡大縮小 とほぼ同じです。

横スクロールアクションゲームを作る(ウィジェットの作成と背景表示)の解説ページアイキャッチ【Python/tkinter】横スクロールアクションゲームを作る(ウィジェットの作成および背景の表示)

ただし、背景は画像の縦横比を無視し、キャンバスのサイズに合わせて拡大縮小を行いましたが、今回の操作キャラクターの画像の拡大縮小は、画像の縦横比を保ったまま、矩形のサイズに合わせて拡大縮小を行うようにしたいと思います。

キャラクターの画像を縦横比を保ったまま矩形に合わせて拡大縮小する様子

この矩形のサイズに合わせて拡大縮小を行う方法は下記ページで解説していますので、詳しく知りたい方はこちらをご参照いただければと思います。

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

具体的には、Character クラスに下記の prepareImage メソッドを追加することで、上記のような操作キャラクターの画像の拡大縮小を実現することができます。

画像の拡大縮小
class Character:

	# ↓これを追加
	def prepareImage(self, path, size):
		image = Image.open(path)

		width, height = size
		ratio = min(width / image.width, height/ image.height)
		resize_size = (round(ratio * image.width), round(ratio * image.height))
		resized_image = image.resize(resize_size)
	# ↑これを追加

引数の path には操作キャラクター表示用に使用したい画像のファイルパスを指定し、size(width, height) 形式のタプルを指定します。この size の矩形に合わせて縦横比を保ったまま画像の拡大縮小が行われます。

描画用画像オブジェクトの生成

ただし、上記で作成される画像オブジェクトは PIL 用のものであり、このままだとキャンバスに描画できません。そのため、背景画像の拡大縮小 でも行なったように、PIL 用の画像オブジェクトを tkinter 用の画像オブジェクトに変換を行う必要があります。

具体的には、先程作成した prepareImage の一番最後を下記のように変更します。

画像オブジェクトの生成
class Character:

	def prepareImage(self, path, size):
		image = Image.open(path)

		width, height = size
		ratio = min(width / image.width, height/ image.height)
		resize_size = (round(ratio * image.width), round(ratio * image.height))
		resized_image = image.resize(resize_size)

		# ↓これを追加
		self.right_image = ImageTk.PhotoImage(resized_image)
		# ↑これを追加

これにより、Character クラスのデータ属性 right_image にキャンバスに描画可能な画像オブジェクトを参照させることができます。

ちなみに、right_imageright は右方向を向いている画像であることを示すために付けています。

もしかしたら、現状では用意した操作キャラクターの画像によっては左方向を向いてしまうかもしれませんが、下記ページで行う変更により、その画像の向きを考慮して right_image に右方向を向いた画像を参照させることができるようになります(特別なことを行うわけではなく、単に引数を追加するだけです)。

横スクロールアクションゲームを作る(キャラクターの移動)の解説ページアイキャッチ【Python/tkinter】横スクロールアクションゲームを作る(キャラクターの移動)

スポンサーリンク

画像の生成の実行

続いて、prepareImage の実行部分を実装していきます。今回は Character クラスの __init__ から prepareImage メソッドを実行するようにしたいと思います。

具体的には、Character クラスの __init__ を下記のように変更します。

画像オブジェクト生成の実行
class Character:

	def __init__(self):
		#↓これを削除
		#pass
		#↑これを削除
		
		# ↓これを追加
		self.prepareImage("hashiru_boy.png", (100, 100))
		# ↑これを追加

前述の通り、prepareImage メソッドの第1引数には、用意した操作キャラクターの画像のファイルパスを指定する必要があります。上記では "hashiru_boy.png" を指定していますが、ご自身が保存されたキャラクターの画像のフォルダやファイルパスに応じて、この引数は適切に変更していただければと思います。

また prepareImage メソッドの第2引数には、画像の拡大縮小時に用いる矩形の幅と高さをタプル形式で指定する必要があります。この矩形に合わせて、画像の縦横比を保ったまま第1引数で指定した画像の拡大縮小が行われます。

この第2引数も、お好みに合わせて適切に変更していただければと思います。

上記の変更により、Character クラスのオブジェクト生成時にキャンバスに描画可能な画像オブジェクトが生成され、その画像オブジェクトがデータ属性 right_image から参照されるようになります。

といっても、まだここで生成した画像オブジェクトをキャンバスに描画していませんので、スクリプトを実行しても操作キャラクターは表示されません。

次は、この right_image を他のクラスから取得を行えるようにし、その次にその画像をキャンバスに描画を行って操作キャラクターを画面に表示できるようにしていきたいと思います。

キャラクターの画像オブジェクトの取得

では、まずは他のクラスから画像オブジェクトを取得できるようにするためのメソッドを作成していきたいと思います。

具体的には、Character クラスに下記の getImage メソッドを追加します。

画像オブジェクトの取得
class Character:

	# ↓これを追加
	def getImage(self):
		return self.right_image
	# ↑これを追加

単に画像オブジェクトを取得するだけですので、getImage メソッドは現状、上記のように self.right_image メソッドを返却するだけのメソッドになります。

ただし、キャラクターの移動を実現する際に、操作キャラクターの移動方向を考慮して画像オブジェクトを返却するように変更します。

キャラクターの描画

続いて操作キャラクターの描画を行なうためのメソッドを Screen クラスに用意していきたいと思います。

スポンサーリンク

キャンバスへの描画

この操作キャラクターの描画に関しても、下記ページで行なった ゲームの背景の表示 と同様に、tkinter.Canvas クラスの create_image メソッドにより実現することができます。

横スクロールアクションゲームを作る(ウィジェットの作成と背景表示)の解説ページアイキャッチ【Python/tkinter】横スクロールアクションゲームを作る(ウィジェットの作成および背景の表示)

現状では、描画するキャラクターは操作キャラクター1つのみですが、今後登場するキャラクターが増えていくことを見据えて、画像のオブジェクト単体ではなく画像のオブジェクトのリストを受け取り、そのリストに格納されたオブジェクトを1つ1つ create_image メソッドで描画していくようにしておきたいと思います。

リストで管理することで複数の画像オブジェクトを一括で描画するようにする様子

具体的には、Screen クラスに下記の update メソッドを追加します。

操作キャラクターの描画
class Screen:

	#↓これを追加
	def update(self, image_infos):
		for image in image_infos:
			self.canvas.create_image(
				0, 0,
				anchor=tkinter.NW,
				image=image
			)
	#↑これを追加

リスト image_infos に対するループの中で create_image メソッドを実行することで、image_infos 内の画像オブジェクト全てを描画するようにしています。

また create_image メソッドのオプションについては、image オプションを除いて ゲームの背景の表示 の時と全く同じになります(ただし後から変更を行います)。

あとは、上記の update メソッドが実行されるようにすれば、操作キャラクターの画像が描画されることになります。

キャンバスへの描画の実行

Screen クラスに指示を出すのは Game クラスの役割ですので、先程作成した update メソッドは Game クラスから実行させるようにしたいと思います。

update メソッドの引数には画像オブジェクトのリストを指定する必要がありますので、まずは キャラクターの画像オブジェクトの取得 で用意した getImage メソッドを利用して各キャラクター(といっても現状は操作キャラクター一人のみ)の画像オブジェクトを取得し、それをリストに格納していきます。

続いて、その画像オブジェクトのリストを引数に指定して update メソッドを実行するようにしていきたいと思います。

具体的には、下記のように、Game クラスの __init__ を変更します。

操作キャラクターの描画
class Game:

	def __init__(self, master):

		#略

		self.characters.append(player)

		#↓これを追加
		image_infos = []
		for character in self.characters:
			image = character.getImage()
			image_infos.append(image)
		
		self.screen.update(image_infos)
		#↑これを追加

以上のように変更を行なった後にスクリプトを起動すれば、操作キャラクターが画面上に表示されるようになるはずです。

画面上に操作キャラクターが表示されるようになったことを示す図

キャラクターの位置に応じた画像の描画

一応操作キャラクターが画面上に表示されるようになったものの、上の図を見れば分かるように、操作キャラクターはキャンバスの左上に描画されています。

これは、Screen クラスの update メソッドで create_image を実行する際に、第1引数と第2引数に 0 を指定しているからです(この第1引数と第2引数の位置に画像が描画される)。

逆に言えば、第1引数と第2引数に指定する値を変更すれば、キャラクターが描画される位置を変化させることができます。今後キャラクターの移動などを実現していくことを考えると、この描画位置も制御できるようにしたほうが便利ですので、次はこの描画位置の制御を行うようにしていきたいと思います。

スポンサーリンク

キャラクターの位置の管理

この描画位置の制御を行うために、まずは Character クラスで、キャラクターの位置を管理できるようにしたいと思います。

具体的には、Character クラスの __init__ を下記のように変更し、データ属性 xy でキャラクターの位置を管理できるようにします。

位置を管理するデータ属性
class Character:

	def __init__(self):
		self.prepareImage("hashiru_boy.png", (100, 100))

		#↓これを追加
		self.x = 0
		self.y = 0
		#↑これを追加

この xy が、操作キャラクターの現在位置を示すデータ属性となります。x が横方向の位置で、y が縦方向の位置となります。

データ属性xとyの意味合いを説明する図

一つここで注意点を挙げておくと、tkinter におけるキャンバスの縦方向における正方向は「下方向」なので注意してください。つまり、y が小さいほどキャラクターが上側に、大きいほどキャラクターが下側に存在することになります。

数学でよく使っていた座標のまま考えると上下が逆になってしまうので注意が必要です。

キャラクターの位置の受け渡し

次は、Screen クラスの update メソッドで実行する create_image の第1引数と第2引数に、このデータ属性 xy を指定できるようにしていきたいと思います。

Screen クラスの update メソッドは Game クラスの __ init__ から実行していますが、現状では実行時に単に画像オブジェクトのリストを渡しているだけなので、Screen クラスの update メソッドは Character クラスのデータ属性 xy が参照できません。

なので、create_image の第1引数と第2引数に指定すべき値がわからない状態です。

そのため、Screen クラスの update メソッドを実行する際に、画像オブジェクトだけではなく、Character クラスのデータ属性 xy の値も渡すように変更します。

具体的には、Game クラスの __ init__ を下記のように変更します。

キャラクター位置の受け渡し
class Game:

	def __init__(self, master):

		#略

		image_infos = []
		for character in self.characters:
			image = character.getImage()

			#↓これを追加
			image_info = (image, character.x, character.y)
			image_infos.append(image_info)
			#↑これを追加

			#↓これを削除
			#image_infos.append(image)
			#↑これを削除

		self.screen.update(image_infos)

これにより、Screen クラスの update メソッドは、単なる画像オブジェクトのリストではなく、画像オブジェクトと描画すべき位置のリストを引数で受け取ることになり、どこに描画すべきかが分かるようになります。

キャラクターの位置を考慮した描画

続いて Screen クラスの update メソッドを変更し、create_image の第1引数と第2引数にキャラクターを描画する位置を指定するようにしていきます。

具体的には、Screen クラスの update メソッドを下記のように変更します。

位置を考慮したキャラクターの描画
class Screen:

	def update(self, image_infos):
		
		for image, x, y in image_infos: # ←ここを変更

			self.canvas.create_image(
				x, y, # ←ここを変更
				anchor=tkinter.NW,
				image=image
			)

以上の変更により、キャラクターの位置、すなわち Characte クラスのデータ属性 xy に応じて操作キャラクターの画像の描画位置が自動的に変化するようになります。

つまり、キャラクターの移動は、Character クラスのデータ属性 xy を変更した後に Screen クラスの update メソッドを実行することで実現できるようになったことになります。

スポンサーリンク

キャラクターの初期位置の調整

といっても、まだ xy の値は 00 のままなので、スクリプト実行時にキャラクターが描画される位置は先ほどと変わらず左上のままです。

画面上に操作キャラクターが表示されるようになったことを示す図

キャラクターの位置に応じて画像が描画できるようになったので、次はキャラクターの初期位置の調整を行なっていきたいと思います。

現状操作キャラクターは画面の左上に描画されていることになります。左側にいるのは別にいいのですが、上側にいるので宙に浮いている・空を飛んでいる感じになってしまっています。

そのため、キャラクターを地面に立たせるイメージで、キャラクターの初期位置を画面の最下部に移動したいと思います。

キャラクターの縦方向の位置を移動させるので Character クラスのデータ属性 y を変更すれば良いのですが、この y はいくつにすれば良いでしょうか?これについて考えていきたいと思います。

先ほど変更したように、Screen クラスの update メソッドでは下記のように操作キャラクターの描画を行います。

位置を考慮したキャラクターの描画
class Screen:

	def update(self, image_infos):
		
		for image, x, y in image_infos:

			self.canvas.create_image(
				x, y,
				anchor=tkinter.NW,
				image=image
			)

上記のように引数を指定して create_image メソッドを実行した場合、anchor=tkinter.NW を指定しているので、画像の左上が座標 (x, y) の位置にくるように画像の描画が行われます。

画像が描画される位置とcreate_imageメソッドの第1引数第2引数の関係図

したがって、画像の一番下の位置に操作キャラクターを描画したいのであれば、y の値が ゲーム画面の高さ - 画像の高さ であれば良いことになります。

画像を最下部に描画するためのy座標の計算の仕方の説明図

MEMO

前述の通り、キャンバスでは左上が原点であり、y の正方向は下方向であることに注意してください

数学等で学ぶ座標系で考えると上下が逆になってしまいます

画像の高さ に関しては、tkinter 用の画像オブジェクトに height メソッドを実行させることで取得することができます。現状 Character のデータ属性 right_image が tkinter 用の画像オブジェクトを参照するようになっていますので、right_image.height() を実行すれば良いことになります。

したがって、Character クラスの __init__ にてデータ属性 y を下記のように設定してやれば、操作キャラクターを画面の一番下に立たせるようにすることができます。

キャラクターの初期位置の調整
class Character:

	def __init__(self):
		self.prepareImage("hashiru_boy.png", (100, 100))

		#↓これを追加
		self.base_y = VIEW_HEIGHT - self.right_image.height()
		#↑これを追加

		self.x = 0
		self.y = self.base_y #←ここを変更

VIEW_HEIGHT は表示領域(すなわちゲーム画面)の高さを示すグローバル変数になります。

データ属性として base_y の追加も行っていますが、このデータ属性はキャラクターの基準位置(ジャンプ中以外に存在する位置)の縦方向の位置を示すデータ属性としたいと思います。操作キャラクターは基本的にゲーム画面の一番下にいるようにしたいので、前述で説明した方法で座標の計算を行なっています。そして、この値を y にも設定しています。

この base_y があれば、例えばジャンプしたキャラクターが最終的に着地する座標などとして使用できますので、今後参照しやすいようにデータ属性として追加しています(このデータ属性は次のページのキャラクターの移動で活躍します)。

上記のように変更してからスクリプトを実行すれば、表示される操作キャラクターの位置が左下に変化したことが確認できると思います。また、これにより、キャラクターの位置に応じた画像の描画 も行えるようになっていることも実感できると思います。

操作キャラクターが画面の左下に表示されるようになったことを示す図

定期的なキャラクターの描画

このページの最後として、キャラクターの描画を定期的に行う方法について解説していきたいと思います。

定期的な描画の実現

キャラクターの位置に応じた画像の描画 の最後でも説明しましたが、キャラクターの位置を考慮して画像を描画できるようにしたことで、キャラクターの移動は、Character クラスのデータ属性 xy を変更した後に Screen クラスの update メソッドを実行することで実現できるようになっています。

ただし、現状では Screen クラスの update メソッドは Game クラスの __init__ から一回しか実行されていないので、それ以降一度も実行されないことになります。したがって、xy が変更されたとしても、その後 Screen クラスの update メソッドが実行されることはありません…。

これだと Character クラスのデータ属性 xy を変化させても意味がないので、ここで Screen クラスの update メソッドを定期的に実行するようにしていきたいと思います。

tkinter を用いた場合、この定期的な処理の実行は after メソッドを利用することで簡単に実現することができます。この after メソッドに関しては下記ページで詳細を解説していますので、詳しく知りたい方は下記ページをご参照いただければと思います。

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

今回は Game クラスに update メソッドを用意し、このメソッドの中から Screen クラスの update メソッドの実行および、定期的に処理を実行するための after メソッドの実行を行うようにしたいと思います。

具体的には、まず Game クラスに下記の update メソッドを追加します。

キャラクターの定期的な描画
class Game:

	#↓これを追加
	def update(self):

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

		image_infos = []
		for character in self.characters:

			image = character.getImage()

			image_info = (image, character.x, character.y)
			image_infos.append(image_info)

		self.screen.update(image_infos)
	#↑これを追加

この  update メソッドでは上記のように after メソッドを実行していますので、最初に1回実行すれば 100 ms 単位で定期的に繰り返し実行されることになります。

after メソッドは、tkinter のウィジェットに実行させる必要があるので注意してください。上記では self.master、すなわち今回作成するゲームのメインウィンドウウィジェットに実行させています。

で、今回定期的に行いたいのはキャラクターの描画、すなわち Screen クラスの update メソッドの実行ですので、元々 Game クラスの __init__ で実行していた Screen クラスの update メソッドの実行処理を、上記の Game クラスの update メソッドの中で実行するようにしています。

これにより、Screen クラスの update メソッドが 100 ms 毎に実行され、定期的なキャラクターの描画を実現することができます。

スポンサーリンク

定期的処理の開始

ただし、この定期的な処理が行われるのは、Game クラスの update メソッドが1度実行されてからになります。

そのため、Game クラスの update メソッドの最初の一回を Game クラスの __init__ から実行するよう、下記のように変更します。

定期処理の開始
class Game:

	def __init__(self, master):
		self.master = master
		self.screen = Screen(self.master)

		self.characters = []
		self.player = Character()
		self.characters.append(self.player)

		#↓これを追加
		self.update()
		#↑これを追加

		#↓これを削除
		#image_infos = []
		#for character in self.characters:
		#	image = character.getImage()
		#	image_info = (image, character.x, character.y)
		#	image_infos.append(image_info)
		#
		#self.screen.update(image_infos)
		#↑これを削除

Screen クラスの update メソッドの実行は Game クラスの update から実行するようになったので、__init__ からはその処理は削除しています。

上記の変更により、Game クラスのオブジェクト生成時に Game クラスの update メソッドが実行され、以降は after メソッドにより 100 ms ごとに定期的に Game クラスの update メソッドが実行されるようになります。

描画前の画像の削除

上記の変更により定期的に画像がキャンバスに描画されていくことになります。

ただし、キャンバスに描画した画像は削除しない限りはキャンバス上に残り続けてしまうので、このままだと操作キャラクターが移動しても移動前の画像がキャンバスに残ってしまうことになります。

キャンバスに描画した画像が延々と残り続ける様子

これを防ぐためには、キャンバスに描画したキャラクターの画像を一旦削除してから、描画を実行するようにする必要があります。

画像を削除してから再度描画する様子

このキャンバスに描画した画像の削除は、tkinter.Canvasdelete メソッドにより実現することができます。この delete メソッドの引数には、画像の描画時に実行する create_image メソッドの返却値を指定する必要があります(タグ名を指定するのでも良い)。

現状、キャンバスに描画するキャラクターは操作キャラクターのみですが、今後敵キャラクターやゴールも登場することになります。

そのため、各キャラクター描画時の create_image メソッドの返却値はリストに保持するようにしたいと思います。

そして、次回キャラクターを描画する前にそのリストの持つ値全てに対して delete メソッドを実行するようにします。

ということで、まずは、create_image メソッドの返却値を格納するためのリストを Screen クラスのデータ属性 draw_images として追加したいと思います。

具体的には、Screen クラスの __init__ を下記のように変更します。

描画済み画像の管理リスト
class Screen:

	def __init__(self, master):
		self.master = master

		self.view_width = VIEW_WIDTH
		self.view_height = VIEW_HEIGHT
		self.game_width = GAME_WIDTH
		self.game_height = self.view_height

		#↓これを追加
		self.draw_images = []
		#↑これを追加

		#略

続いて Screen クラスの update メソッドで、最初にリスト draw_images の値全てに対して delete メソッドを実行するようにします。

さらに、create_image メソッドの返却値をリスト draw_images を格納するようにします。

具体的には、Screen クラスの update メソッドを下記のように変更します。

削除してから画像を描画
class Screen:

	def update(self, image_infos):

		#↓これを追加
		for draw_image in self.draw_images:
			self.canvas.delete(draw_image)

		self.draw_images.clear()
		#↑これを追加
		
		for image, x, y in image_infos:

			draw_image = self.canvas.create_image( # ←ここを変更
				x, y,
				anchor=tkinter.NW,
				image=image
			)

			#↓これを追加
			self.draw_images.append(draw_image)
			#↑これを追加

これにより、前回の Screen クラスの update メソッドで描画されたキャラクターの画像全てをキャンバスから削除した後、移動後のキャラクターの画像が描画されるようになります。

といっても、まだ操作キャラクターが移動していないのでここまで変更してきた内容に効果があるのかが実感しにくいと思います。

早く効果を実感したいという方は、お試しで Game クラスの update メソッドを下記のように変更してみてください(効果が確認できたら変更は戻しておいてください)。

キャラクターの自動移動
class Game:

	def update(self):

		image_infos = []
		for character in self.characters:

			#↓これを追加
			character.x += 10
			#↑これを追加

			image = character.getImage()

			#略

この変更で、定期的にキャラクターの位置が右側に移動するようになります。この状態でスクリプトを実行すれば、以前に描画した画像が残るようなこともなく、キャラクターが移動していくように見えると思います。

上記の変更だと自動的にキャラクターが移動してしまいますが、この移動をキーボード入力時に行うようにすれば、プレイヤーがキャラクターを操作できるようになることになります。

このための方法については、下記の次のページで解説していきたいと思います。

横スクロールアクションゲームを作る(キャラクターの移動)の解説ページアイキャッチ【Python/tkinter】横スクロールアクションゲームを作る(キャラクターの移動)

また、ここで使用した delete メソッドを始めとする、tkinter.Canvas クラスの各メソッドについては下記ページでも詳しく解説していますので、いろんなメソッドを使いこなしたい方はぜひ読んでみてください。

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

このページで作成したスクリプト

以上で、このページの解説は終了です。

最後に、ここまでの解説を踏まえて作成したスクリプトの全体を下記に掲載しておきます。次のページではこのスクリプトをベースに解説を進めていきたいと思います。

このページで作成したスクリプト
import tkinter
from PIL import Image, ImageTk, ImageOps
import random

# アプリの設定
VIEW_WIDTH = 600
VIEW_HEIGHT = 400
GAME_WIDTH = 1500

UPDATE_TIME = 100

BG_IMAGE_PATH = "bg_natural_sougen.jpeg"
PLAYER_IMAGE_PATH = "hashiru_boy.png"

class Character:

	def __init__(self):
		self.prepareImage(PLAYER_IMAGE_PATH, (100, 100))

		self.base_y = VIEW_HEIGHT - self.right_image.height()
		self.x = 0
		self.y = self.base_y
		
	def getImage(self):
		return self.right_image
	
	def prepareImage(self, path, size):
		image = Image.open(path)

		width, height = size
		ratio = min(width / image.width, height/ image.height)
		resize_size = (round(ratio * image.width), round(ratio * image.height))
		resized_image = image.resize(resize_size)

		self.right_image = ImageTk.PhotoImage(resized_image)


class Player(Character):

	def __init__(self):
		pass

class Enemy(Character):

	def __init__(self):
		pass

class CatEnemy(Enemy):

	def __init__(self):
		pass

class DogEnemy(Enemy):

	def __init__(self):
		pass

class Goal(Character):

	def __init__(self):
		pass

class Screen:

	def __init__(self, master):
		self.master = master

		self.view_width = VIEW_WIDTH
		self.view_height = VIEW_HEIGHT
		self.game_width = GAME_WIDTH
		self.game_height = self.view_height
		self.draw_images = []

		self.createWidgets()
		self.drawBackground()

	def createWidgets(self):
		self.canvas = tkinter.Canvas(
			self.master,
			width=self.view_width,
			height=self.view_height,
			scrollregion= (
				0,0,self.game_width,self.game_height
			),
			highlightthickness=0
		)
		self.canvas.grid(column=0, row=0)

		xbar = tkinter.Scrollbar(
			self.master,
			orient=tkinter.HORIZONTAL,
		)

		xbar.grid(
			row=1, column=0,
			sticky=tkinter.W + tkinter.E
		)

		xbar.config(
			command=self.canvas.xview
		)

		self.canvas.config(
			xscrollcommand=xbar.set
		)

	def drawBackground(self):
		image = Image.open(BG_IMAGE_PATH)

		size = (self.game_width, self.game_height)
		resized_image = image.resize(size)

		self.bg_image = ImageTk.PhotoImage(resized_image)

		self.canvas.create_image(
			0, 0,
			anchor=tkinter.NW,
			image=self.bg_image
		)

	def update(self, image_infos):
		for draw_image in self.draw_images:
			self.canvas.delete(draw_image)

		self.draw_images.clear()
		
		for image, x, y in image_infos:

			draw_image = self.canvas.create_image(
				x, y,
				anchor=tkinter.NW,
				image=image
			)
			self.draw_images.append(draw_image)
	

class Game:

	def __init__(self, master):
		self.master = master
		self.screen = Screen(self.master)

		self.characters = []
		self.player = Character()
		self.characters.append(self.player)

		self.update()

	def update(self):
		self.master.after(UPDATE_TIME, self.update)

		image_infos = []
		for character in self.characters:

			image = character.getImage()

			image_info = (image, character.x, character.y)
			image_infos.append(image_info)

		self.screen.update(image_infos)

def main():
	app = tkinter.Tk()
	game = Game(app)
	app.mainloop()

if __name__ == "__main__":
	main()

基本的には、ここまで紹介してきたスクリプトを寄せ集めてきたものですが、操作キャラクターの画像のファイルパスと画面を更新する間隔をグローバル変数として定義するようにしています。

今回定義したグローバル変数の意味合いは下記のようになります。

  • PLAYER_IMAGE_PATH:操作キャラクターの画像のファイルパス
  • UPDATE_TIME:画面やキャラクターの状態を更新する間隔(単位は ms)

もし、ここまでの解説の中でこれらを自身の環境に合わせて変更された方は、こちらの定義値も変更しておく必要があるので注意してください。

スポンサーリンク

まとめ

このページでは、横スクロールアクションゲームにおける「キャラクターの表示」について解説しました。

まだキャラクターが動いていないのでゲームっぽさはないかも知れませんが、キャラクターの位置に応じて画像の描画ができるようになったので、キャラクターの移動の実現まではもうすぐです!

特に今回使用した after メソッドに関しては、tkinter でアプリ開発を行う上で必須級のメソッドになりますので、ぜひこの機会に使い方を覚えておいていただければと思います。

次のページでは「キャラクターの移動」について解説していきます!おそらく次のページあたりからグッとゲーム開発っぽくなってくると思います!

横スクロールアクションゲームを作る(キャラクターの移動)の解説ページアイキャッチ【Python/tkinter】横スクロールアクションゲームを作る(キャラクターの移動)