このページでは、Python で tkinter を利用した簡単な「横スクロールアクションゲーム」の作り方を解説していきます。
このページは「横スクロールアクションゲームの作り方の解説」の6ページ目となります。
5ページ目は下記ページとなり、5ページ目では画面の自動スクロールの解説を行なっています。
【Python/tkinter】横スクロールアクションゲームを作る(画面の自動横スクロール)また、このページでは、上記ページの このページで作成したスクリプト で紹介しているスクリプトを変更していきながらゲームを作成していきます。
ですので、事前に上記のページを読んでおくことをオススメします。
この「横スクロールアクションゲームの作り方の解説」の6ページ目では、まず新たなキャラクターである「ゴールを作成」し、続いて「ゴール判定及びゴール画面の表示」を行なっていきたいと思います。
Contents
Character
クラスとサブクラスの整理
まずゴールを作成していく前に、Character
クラスと Character
クラスのサブクラスの整理をしていきたいと思います。
Player
クラスの作成
この辺りの話は下記ページで簡単に説明しているのですが、今回は Player
と Goal
、さらには以降で登場する Enemy
クラスのスーパークラスとして Character
クラスを用意しています。
ここまでの解説では主に、Player
で実現すべき操作キャラクターを、スーパークラスの Character
クラスで実現してきました。
今まで登場するキャラクターが操作キャラクターだけだったので不便はなかったですが、ここでいよいよ新たなキャラクターであるゴールが登場することになります。
ゴールと操作キャラクターを別のクラスとして扱えるよう、Character
クラスのサブクラスとして Player
クラスを作成し、操作プレイヤーを本来のクラスである Player
で実現するように変更を行なっていきたいと思います。そしてその後、Goal
クラスを作成し、ゴールをGoal
クラスで作成していきたいと思います。
まず、現状では操作キャラクターのオブジェクトを生成する際に、Game
クラスから Character
クラスのコンストラクタが実行されるようになっているため、Player
クラスのオブジェクトを生成するように変更します。
具体的には、Game
クラスの __init__
を下記のように変更します。
class Game:
def __init__(self, master):
self.master = master
self.screen = Screen(self.master)
self.characters = []
#↓これを追加
self.player = Player()
#↑これを追加
#↓これは削除
#self.player = Character()
#↑これは削除
self.characters.append(self.player)
#略
さらに、下記のように、Player
クラスの __init__
でスーパークラス(すなわち Character
クラス)の __init__
を実行するように変更します。
class Player(Character):
def __init__(self):
#↓これは削除
#pass
#↑これは削除
#↓これを追加
super().__init__()
#↑これを追加
変更後にスクリプトを実行すれば、今まで通りに操作キャラクターの表示や移動が行えることを確認できると思います。
ただし、現状では、上記のように Character
クラスのサブクラスから Character
クラスの __init__
を実行した際に、必ずキャラクターの画像として操作キャラクター表示用の画像のオブジェクトが生成されるようになってしまっています。
これは、Character
クラスの __init__
で prepareImage
メソッドを実行する際に、操作キャラクター表示用の画像のパスを指定するようになってしまっているからです。
他のクラスのオブジェクト生成時には異なる画像が指定できるよう、Character
クラスの __init__
を画像に関するパラメータは引数で受け取るようにし、さらに受け取った引数を用いて prepareImage
メソッドを実行するように変更したいと思います。
具体的には、下記のように、prepareImage
メソッドで受け取る引数を Character
クラスの __init__
でも受け取るように変更します。さらに __init__
が受け取った引数を prepareImage
メソッドに渡すように変更します。
class Character:
def __init__(self, path, size, is_right=True): #←ここを変更
self.prepareImage(path, size, is_right) #←ここを変更
# 略
続いて Player
クラスの __init__
を、スーパークラスの __init__
を実行する際に画像に関する引数を指定するように変更します。具体的には、今まで Character
クラスの __init__
で prepareImage
メソッドに渡していた引数をここで指定するように変更します。
class Player(Character):
def __init__(self):
super().__init__(PLAYER_IMAGE_PATH, (100, 100)) #←ここを変更
以上の変更により、各サブクラスの __init__
から、そのクラスに応じた画像を設定できるようになったことになります。
つまり、ゴールを作成する場合はゴール用の画像のファイルパスとサイズを、さらに敵キャラクターを作成する場合は敵キャラクター用の画像のファイルパスとサイズを指定してスーパークラスの __init__
を実行してやれば、キャラクターに応じた画像を画面上に表示させることができるようになったことになります(必要に応じて第3引数に is_right
の設定も行ってください)。
スポンサーリンク
サブクラスのカスタマイズ
以降では、キャラクターの各クラスに共通のデータ属性の設定は Character
クラスの __init__
で行い、サブクラス特有の設定は、そのサブクラスの __init__
の super().__init__()
実行後にデータ属性を上書きする形で行うようにしたいと思います。
例えば、現状 Character
クラスでは、ジャンプの高さを表すデータ属性 jump_height
を 200
に設定していますが、例えば操作キャラクターのジャンプの高さを高くしたいような場合、Player
クラスの __init__
で jump_height
をもっと大きな値で上書きするようにします。
class Player(Character):
def __init__(self):
super().__init__(PLAYER_IMAGE_PATH, (100, 100))
#以降でサブクラス固有のデータ属性の上書き
self.jump_height = 500
このデータ属性の上書きにより、操作キャラクターのみ他のキャラクターに比べて高くジャンプできるようになります。
こんな感じで、各キャラクターに特有のデータ属性を設定することで、キャラクターごとに特徴を持たせた動作などが可能になります。
また、上書きだけでなく、そのサブクラスにのみ必要なデータ属性は、そのサブクラスの __init__
でのみ設定するようにしていきたいと思います。逆に各サブクラス共通のデータ属性は Character
クラスの __init__
で設定を行うようにしていきます。
上記のようなサブクラス特有のデータ属性の設定を行うためには、データ属性の意味を理解しておいた方が良いため、ここで復習として Character
クラスの __init__
で設定しているデータ属性の意味をまとめておきます。
base_y
:キャラクターの基準位置x
:ゲーム開始時のキャラクターの位置(横方向)y
:ゲーム開始時のキャラクターの位置(縦方向)speed_x
:キャラクターの移動量(横方向)speed_y
:キャラクターの移動量(縦方向)jump_state
:ゲーム開始時のキャラクターのジャンプの状態jump_height
:キャラクターのジャンプの高さdirection
:ゲーム開始時のキャラクターの向き
これらの属性は下記の2つのページで追加したものですので、詳しく知りたい方は下記ページをご参照いただければと思います。
【Python/tkinter】横スクロールアクションゲームを作る(キャラクターの表示) 【Python/tkinter】横スクロールアクションゲームを作る(キャラクターの移動)ゴールの作成と表示
ここまでの変更により、キャラクターを実現するためのスーパークラスとして Character
クラスが準備できたことになります。
次は実際にゴールを作成するために Goal
クラスを作成していきたいと思います。
(復習)キャラクターが表示される仕組み
まずはゴールが画面に表示されるように Goal
クラスを作成してきたいと思います。
その前に、ここで一旦、操作キャラクターが画面に表示される仕組みについておさらいしておきます。
操作キャラクターはまず、Game
クラスの __init__
からの Player
クラスのコンストラクタの実行によりオブジェクトが生成されます。
このオブジェクト生成時には Player
クラスの __init__
が実行されます。さらにこの Player
クラスの __init__
からはスーパークラス Character
の __init__
が実行され、操作キャラクターの画像のオブジェクトの生成やサブクラス間で共通のデータ属性の設定が行われます。
特に画像のオブジェクトの生成を行うために、Character
の __init__
にはキャラクター用の画像のパスと矩形のサイズを指定する必要があります(その矩形に合わせて画像が拡大縮小される)。
また、生成された操作キャラクターのオブジェクトは Game
クラスのデータ属性のリスト characters
に格納されます。
さらに、Game
クラスの update
メソッドからリスト characters
に格納されている全オブジェクトのキャラクターの画像の描画を Screen
クラスに依頼することで、各キャラクターの画像がキャンバスに描画され、画面に表示されるようになっています。
つまり、すでにリスト characters
に格納されている全オブジェクトのキャラクターの画像が描画されるようになっていますので、ゴールにおいても、Game
クラスから Goal
クラスのコンストラクタを実行してオブジェクトを生成し、そのオブジェクトをリスト characters
に格納してやれば自動的に表示されるようになります。
したがって、ゴールを表示するだけであれば、下記の3つのみ実施してやれば良いことになります。
Goal
クラスのオブジェクト生成時に実行される__init__
の作成Game
クラスからのGoal
クラスのオブジェクト生成の実行(コンストラクタの実行)- 作成されたオブジェクトのリスト
characers
への格納
1点目の __init__
においても、Player
クラスと同様に作成すれば良いのですが、操作キャラクターと異なる画像を表示したいので、別途ゴール用の画像を準備し、ファイルパスを指定する必要があります。
スポンサーリンク
ゴール用の画像の準備
ということで、まずはゴール用の画像を準備しましょう!
ゴール用の画像はどんなものでも特に問題はないですが、これも操作キャラクターの時と同様に背景が透明に設定されているものが望ましいです。
このページでは いらすとや の下記 URL の画像をゴール用の画像として利用していきたいと思います。
http://www.irasutoya.com/2016/12/blog-post_99.html
下の図は上記 URL の画像を転載させていただいたものになります。
転載元:いらすとや
__init__
の作成
画像が準備できれば、Goal
クラスの __init__
でスーパークラス(Character
クラス)の __init__
を実行するように変更します。このスーパークラス(Character
クラス)の __init__
には、下記の3つの引数を指定する必要があるので注意してください。
- 第1引数:画像のファイルパス
- 第2引数:矩形のサイズ
- 第3引数:画像のキャラクターの向きが右向きかどうか(省略化)
第1引数と第2引数の意味は下記ページの キャラクターの画像オブジェクト生成 で、
【Python/tkinter】横スクロールアクションゲームを作る(キャラクターの表示)第3引数の意味は下記ページの 画像の向きに応じた画像オブジェクトの生成 で解説していますので、忘れてしまった方はこれらのページを参照していただければと思います。
【Python/tkinter】横スクロールアクションゲームを作る(キャラクターの移動)例えば、ゴール用の画像のファイルパスが "car_animals_flag.png"
で、200
x 200
ピクセルの矩形に合わせて画像の拡大縮小を行うのであれば、Goal
クラスの __init__
を下記のように変更します。
class Goal(Character):
def __init__(self):
#↓これを削除
#pass
#↑これを削除
#↓これを追加
super().__init__("car_animals_flag.png", (200, 200), False)
#↑これを追加
第3引数に False
を指定しているのは、"car_animals_flag.png"
の画像(前述で紹介したゴール用の画像)の向きが左向きだからです。
Goal
クラスのオブジェクト生成
現状では、Goal
クラスのオブジェクトの生成が行われていませんので、続いて Goal
クラスのオブジェクトを生成するように変更していきたいと思います。
各クラスのオブジェクトを生成するのは Game
クラスの役割ですので、次は Game
クラスを変更していきます。
具体的には、Game
クラスの __init__
を下記のように変更します。
class Game:
def __init__(self, master):
# 略
self.characters = []
self.player = Player()
self.characters.append(self.player)
#↓これを追加
goal = Goal()
self.characters.append(goal)
#↑これを追加
# 略
以上の変更により、Goal
クラスのオブジェクトが生成され、そのオブジェクトが Game
クラスのデータ属性のリスト characters
に追加されるようになります。
前述の通り、Game
クラスの update
メソッドは、リスト characters
の全要素のオブジェクトの画像を表示するように作成していますので、上記により Goal
クラスのオブジェクトが自動的に表示されるようになります。
ということで、ここまで変更したスクリプトを実行してみましょう!ゴールが画面に表示されるようになったことが確認できるはずです。
スポンサーリンク
ゴール用のカスタマイズ
ゴールが表示されるようになったことは確認できると思いますが、ゴールの位置が左端&ゴールの向きが右向きでちょっと違和感があるのではないかと思います。
このような表示になっているのは、スーパークラスの Character
クラスの __init__
において、データ属性 x
が 0
に、データ属性 direction
が Character.DIRECTION_RIGHT
に設定されているからです。
なので、これらはゴール用のカスタマイズとして Goal
クラスの __init__
の中で上書き変更しようと思います。
具体的には、Goal
クラスの __init__
を下記のように変更します。
class Goal(Character):
def __init__(self):
super().__init__("car_animals_flag.png", (200, 200), False)
#↓これを追加
self.direction = Character.DIRECTION_LEFT
self.x = GAME_WIDTH - self.width
#↑これを追加
x
に関しては、ゲーム画面の左端に表示するために上記のような計算を行なって位置の調整を行なっています(GAME_WIDTH
はゲーム画面の幅)。
上記のように変更してスクリプトを実行し、ゲーム画面の右端までキャラクターを移動すれば、左向きのゴールが表示されることが確認できると思います。
ゲームクリアの判定
ゴールが作成できましたので、続いては操作キャラクターがゴールと当たった際に “ゲームクリアした” と判定できるようにしていきたいと思います(次の章の ゲームクリア画面の表示 で、ゲームクリアしたと判定されている際にゲームクリア画面を表示するようにしていきます)。
まずは、操作キャラクターの状態管理を行うようにし、操作キャラクターがゲームクリアしたかどうかを判断できるようにしていきます。
続いて操作キャラクターがゴールと当たったかどうかを判断できるようにしていきます。いわゆる当たり判定というやつですね!
さらに、操作キャラクターとゴールが当たった時に、操作キャラクターの状態をゲームクリア状態に変更するようにしていきます。
次の章の ゲームクリア画面の表示 では、この操作キャラクターの状態がゲームクリア状態に変化した際にゲームクリア画面を表示していくことになります。
このページでは、ゲームクリア画面表示までの流れの実現を優先したいため、当たり判定に関しては簡単に&大雑把にのみ行うようにしたいと思います。より詳細な当たり判定に関しては、下記のページで解説させていただきます。
【Python/tkinter】横スクロールアクションゲームを作る(より詳細な当たり判定)解説の順番的には、次のページで敵キャラクターの作成を行い、さらにその次のページで詳細な当たり判定を実現していくことになります。
キャラクターの状態管理
ということで、まずは Character
クラスでキャラクターの状態管理を行うようにしていきたいと思います。この状態管理を行うことで、キャラクターが既にゲームクリアした状態であるどうかを判断できるようにしていきます。
状態の定義
今回作成するゲームでは、まずはキャラクターの状態として下記の3つを用意したいと思います。
- 通常状態
- 下記の2つ以外の状態
- ゲームクリア状態
- ゴールに到達した操作キャラクターの状態
- 倒された状態
- 操作キャラクターによって踏みつけられた敵キャラクターの状態
- 敵キャラクターにぶつかられた操作キャラクターの状態
最後の “倒された状態” に関しては、敵キャラクターを扱う下記のページから使用するようになりますが、ついでにこの状態もここで追加しておこうと思います。
【Python/tkinter】横スクロールアクションゲームを作る(敵キャラクターの作成)では、これらの状態をクラス変数として扱えるよう、まずは Character
クラスを下記のように変更します。
class Character:
#略
JUMP_NO = 0
JUMP_UP = 1
JUMP_DOWN = 2
#↓これを追加
STATE_NORMAL = 0
STATE_CLEAR = 1
STATE_DEFEATED = 2
#↑これを追加
これらは、それぞれ下記の状態を表すクラス変数となります。
Character.STATE_NORMAL
:通常状態Character.STATE_CLEAR
:ゲームクリア状態Character.STATE_DEFEATED
:倒された状態
さらに、Character
クラスにキャラクターの状態を管理できるようデータ属性を追加したいと思います。そのため、Character
クラスの __init__
を下記のように変更します。
class Character:
def __init__(self, path, size, is_right=True):
#略
self.direction = Character.DIRECTION_RIGHT
#↓これを追加
self.state = Character.STATE_NORMAL
#↑これを追加
上記で追加した state
がキャラクターの状態を管理するデータ属性になります。オブジェクト生成時は通常状態になるよう state
に Character.STATE_NORMAL
を設定しています。
現状 state
を設定するのは上記の __init__
のみですが、操作キャラクターとゴールが当たった時に操作キャラクターの state
をCharacter.STATE_CLEAR
に設定し、さらに操作キャラクターと敵キャラクターが当たった時に、踏みつけられた方のキャラクターの state
をCharacter.STATE_DEFEATED
に設定するようにしていけば、キャラクターがどのような状態であるかを state
によって判断することができるようになります。
Character
クラスはデータ属性 jump_state
も持っていますが、こちらはジャンプ中の状態を示すデータ属性になります
それに対してここで追加したデータ属性 state
は、キャラクターそのものの状態を示すデータ属性になります
ゲームクリア状態への遷移
続いて、操作キャラクターの状態をゲームクリア状態に遷移するメソッドを用意したいと思います。ゲームクリアするのは操作キャラクターだけですので、Player
クラスにこのメソッドを用意したいと思います。
具体的には、Player
クラスに下記の gameClear
メソッドを追加します。
class Player:
#↓これを追加
def gameClear(self):
self.state = Character.STATE_CLEAR
#↑これを追加
ゴールに当たった際に操作キャラクターに上記メソッドを実行させれば、操作キャラクターの状態がゲームクリアに遷移することになります。
スポンサーリンク
キャラクター同士の当たり判定
キャラクターの状態管理が行えるようになりましたので、次は当たり判定を行なっていきたいと思います。前述の通り、まずは大雑把に当たり判定を行います。
キャラクター同士が当たったかどうかの判定
まず、操作キャラクターに関しても、ゴールに関しても、表示されているのは画像です。
さらにこれらの画像は、キャンバスの create_image
メソッドにより、始点を (x
, y
)、終点を (x + width
, y + height
) とする矩形の中に描画されています(x
・y
・width
・height
はいずれもデータ属性で保持している値になります)。
ですので、このキャラクターが描画されている矩形とゴールが描画されている矩形の2つの矩形が重なっているかどうかが判断できれば、大雑把ではありますが当たり判定を行うことができることになります。
この2つの矩形が重なっているかどうかは、下記から求まる始点と終点の位置関係から判断することができます。
- 始点の
x
座標:下記の2つの座標の大きい方- 1つ目の矩形の始点の
x
座標 - 2つ目の矩形の始点の
x
座標
- 1つ目の矩形の始点の
- 始点の
y
座標:下記の2つの座標の大きい方- 1つ目の矩形の始点の
y
座標 - 2つ目の矩形の始点の
y
座標
- 1つ目の矩形の始点の
- 終点の
x
座標:下記の2つの座標の小さい方- 1つ目の矩形の終点の
x
座標 - 2つ目の矩形の終点の
x
座標
- 1つ目の矩形の終点の
- 終点の
y
座標:下記の2つの座標の小さい方- 1つ目の矩形の終点の
y
座標 - 2つ目の矩形の終点の
y
座標
- 1つ目の矩形の終点の
以降では、上記の座標を始点と終点とする矩形を「重なり矩形」と呼ばせていただきます。
このようにして求めた座標の始点と終点を持つ重なり矩形においては、1つ目の矩形と2つ目の矩形が重なっている時、始点が必ず終点に対して左上方向の位置に存在します。
逆に1つ目の矩形と2つ目の矩形が重なっていない時、重なり矩形において始点は終点に対して左上方向の位置には存在しません。
したがって、上記のように作成した重なり矩形の座標において、下記が成立するかどうかを判断すれば、1つ目の矩形と2つ目の矩形が重なっているかどうかを判定することができます。
また、下記の式が成立するとき、その始点と終点を持つ矩形、すなわち重なり矩形が2つの矩形の重なった領域となります。
始点のx座標 < 終点のx座標 and 始点のy座標 < 終点のy座標
今回は操作キャラクターとゴールとが当たったかどうかの判定を行うわけですので、上記が成立した場合、操作キャラクターがゲームクリアしたと判断することができます。
今回作成するゲームにおいては、上記のような当たり判定は Character
クラスで行うようにしたいと思います。当たり判定を行うメソッドを isCollided
とすれば、下記のように Character
クラスを変更することで、ここまで解説した当たり判定を行う機能を Chracter
クラスに持たせることができます。
class Character:
#↓これを追加
def isCollided(self, opponent):
sx = max(self.x, opponent.x)
sy = max(self.y, opponent.y)
ex = min(self.x + self.width, opponent.x + opponent.width)
ey = min(self.y + self.height, opponent.y + opponent.height)
if sx < ex and sy < ey:
return True
else:
return False
#↑これを追加
この isCollided
メソッドは、実行したオブジェクトのキャラクターと引数 opponent
で指定されたオブジェクトのキャラクターが当たったかどうかを判定するメソッドになります。
当たっている場合は True
を、当たっていない場合は False
を返却します。
まだ大雑把ではありますが当たり判定が行えるようになりましたので、キャラクターが移動する度に上記の isCollided
を実行するようにすれば、移動した際にキャラクター同士が当たったかどうかを判定できるようになります。
他の全てのキャラクターとの当たり判定の実行
ただし、この当たり判定は、移動後のキャラクターと当たる可能性がある全てのキャラクターに対して行う必要があります。
現状では登場するキャラクターが操作キャラクターとゴールのみなので、操作キャラクターとゴールとの当たり判定のみ行うのでも問題ありません。
ですが、今後敵キャラクターを作成してゲーム内に登場するキャラクターが増えていくことになりますので、それを見据えて “移動したキャラクター以外の全キャラクター” と当たり判定を行うようにしていきたいと思います。
本当に当たり判定を行う必要があるのは当たる可能性のあるキャラクター同士のみです
ですので、遠くにいるキャラクター同士の当たり判定は省いても良いです
ただ、省くためには遠くにいるかどうかの判断を行う必要があり、それはそれで処理が複雑になるため、今回は “移動したキャラクター” と “移動したキャラクター以外の全キャラクター” との当たり判定を行うようにしています
Game
クラスではデータ属性のリスト characters
で全キャラクターのオブジェクトを管理するようにしているため、上記のような “移動したキャラクター” と “移動したキャラクター以外の全キャラクター” との当たり判定は、Game
クラスに下記の collisionDetect
メソッドを追加することで実現することができます。
class Game:
#↓これを追加
def collisionDetect(self, character):
for opponent in self.characters:
if opponent is character:
continue
if character.isCollided(opponent):
pass
#↑これを追加
引数 character
は、当たり判定を行う際の1つ目のキャラクターとなります。より具体的には、直前に移動を行ったキャラクターのオブジェクトを指定することを想定しています。
また、isCollided
は前述で追加した、実行オブジェクトと引数で指定したオブジェクトとの当たり判定を行うメソッドになります。
したがって、上記のように Game
クラスのデータ属性 characters
からオブジェクト opponent
を1つ1つ取得しながら character.isCollided(opponent)
を実行するようにループを組めば、移動直後のキャラクターと全キャラクターとの当たり判定を実施することができることになります。
ただし、同じオブジェクト同士の当たり判定は不要ですので(必ず当たったと判定されてしまう)、character
と opponent
が同じオブジェクトの場合は continue
で当たり判定をスキップするようにしています。
isCollided
メソッドが True
の場合の処理が pass
になっていますが、ここには後述の キャラクター同士が当たった時の処理 で作成する「当たった時の処理を実行するメソッドの実行」を追加することになります。
今後の解説ページで敵キャラクターを作成した場合にも、敵キャラクターのオブジェクトをデータ属性 characters
に追加さえすれば、自動的に上記の collisionDetect
メソッドで当たり判定の対象として扱われるようになります。
キャラクター移動後の当たり判定の実行
あとは、先ほど追加した collisionDetect
メソッドをキャラクターの移動直後に実行するようにすれば、キャラクターが移動するたびにキャラクター同士の当たり判定が行われるようになります。
キャラクターが移動するのは、Character
クラスの move
メソッド(左右上キー入力時にキャラクターを移動させるメソッド)と Character
クラスの update
メソッド(ジャンプ中にキャラクターを上下方向に移動させるメソッド)の2つですので、これらのメソッド実行直後に collisionDetect
を実行するようにしていきます。
具体的には、まず Game
クラスの press
メソッドを下記のように変更します。
class Game:
def press(self, event):
if event.keysym == "Left":
self.player.move(Character.DIRECTION_LEFT)
elif event.keysym == "Right":
self.player.move(Character.DIRECTION_RIGHT)
elif event.keysym == "Up":
self.player.move(Character.DIRECTION_UP)
#↓これを追加
self.collisionDetect(self.player)
#↑これを追加
さらに続いて、Game
クラスの update
メソッドを下記のように変更します。
class Game:
def update(self):
for character in self.characters:
character.update()
#↓これを追加
self.collisionDetect(character)
#↑これを追加
#略
キャラクター同士が当たった時の処理
ここまでの変更により、キャラクターが移動するたびに当たり判定が実行されるようになりました。続いては、当たり判定によりキャラクター同士が当たったと判断された際の処理を実現していきたいと思います。
操作キャラクターとゴールが当たった時の処理
今回は、操作キャラクターとゴールとが当たった時の処理を実現していきます。
操作キャラクターとゴールが当たったのですから、この場合はゲームクリアということになります。ですので、この際には操作キャラクターの状態をゲームクリア状態に更新するようにしていきたいと思います(ここでは状態の更新のみを行い、次の ゲームクリア画面の表示 で、ゲームクリア画面を表示するようにしていきます)。
まず、キャラクター同士が当たった時の処理は Game
クラスに行わせるようにしたいと思います。
この辺りは主に敵キャラクターが登場してからの話になるのですが、キャラクター同士が当たった時にはキャラクターの移動等のキャラクターのオブジェクトに対する依頼が発生します。
各キャラクターのオブジェクトへの処理の依頼は Game
クラスから行うようにしたいため、今回はキャラクター同士が当たった時の処理は Game
クラスで行うものとしたいと思います。
このキャラクター同士が当たった時の処理を実行するメソッドを collide
とすれば、Game
クラスを下記のように変更することで、collide
メソッド実行時に操作キャラクターの状態をゲームクリア状態に更新することができるようになります。
class Game:
#↓これを追加
def collide(self, character):
character.gameClear()
#↑これを追加
ただし、これだと collide
メソッドが実行されると必ず操作キャラクターの状態がゲームクリア状態に更新されることになります。
この collide
メソッドは、他のキャラクター同士が当たった時の処理も実行するメソッドとしたいので、当たったキャラクターが操作キャラクターとゴールである場合のみ操作キャラクターの状態をゲームクリアにするようにしたいと思います。
これを行うためには、上記の Game
クラスの collide
メソッドを次のように変更すれば良いです(引数を変更しているので注意してください)。
class Game:
def collide(self, character, opponent): #←ここを変更
#↓これを追加
if isinstance(character, Player) and isinstance(opponent, Goal):
character.gameClear()
elif isinstance(character, Goal) and isinstance(opponent, Player):
opponent.gameClear()
#↑これを追加
#↓これは削除
#character.gameClear()
#↑これは削除
collide
メソッドの引数 character
は1つ目のキャラクターのオブジェクト、引数 opponent
は2つ目のキャラクターのオブジェクトとなります。
現状だと当たり判定を行うキャラクターは操作キャラクターとゴールだけですが、以降で敵キャラクターが登場した際にも同じメソッドで当たり判定が行えるよう、オブジェクトは引数で受け取るようにしています。
また、isinstance
関数は第1引数で指定したオブジェクトが第2引数で指定したクラス(もしくはそのサブクラス)のインスタンスであるかどうかを判断する関数になります。
上記のように判断を加えることで、Player
クラスのオブジェクトと Goal
クラスのオブジェクトが当たった時のみ、gameClear
メソッドが実行され、操作キャラクターの状態 state
が Character.STATE_CLEAR
に更新されるようになります。
ただし、あくまでも gameClear
メソッドを実行するのは Player
クラスのオブジェクトであることに注意してください。
当たった時の処理の実行
先程作成した collide
メソッドはキャラクター同士が当たった時の処理を行うメソッドですので、次は実際にキャラクター同士が当たったと判定された時に collide
メソッドが実行されるようにしていきます。
当たり判定は isCollided
メソッドで実施されており、この isCollided
メソッドは Game
クラスの collisionDetect
メソッドから実行されています。
ですので、この Game
クラスの collisionDetect
メソッドを下記のように変更すれば、キャラクター同士が当たったと判定された時に collide
メソッドが実行されるようになります。
class Game:
def collisionDetect(self, character):
for opponent in self.characters:
if opponent is character:
continue
if character.isCollided(opponent):
#↓これは削除
#pass
#↓これは削除
#↓これを追加
self.collide(character, opponent)
#↑これを追加
ここまでの変更により、操作キャラクターが移動してゴールに当たった際には、操作キャラクターの状態がゲームクリア状態に変化するようになります。
ただし、状態が変化するだけで、現状ではまだ画面には何も変化がありません。
次は、キャラクターの状態がゲームクリア状態に変わった際にゲームクリアの画面を表示し、さらにゲームの動作を停止させるようにしていきたいと思います。
ゲームクリア画面の表示
では、キャラクターの状態がゲームクリアに遷移した際にゲームクリア画面の表示を行うようにしていきたいと思います。
ゲームクリア画面といっても、今回は簡単のため、”GAME CLEAR” というメッセージを画面に表示させるだけにさせていただきたいと思います。
スポンサーリンク
メッセージの表示
まずは “GAME CLEAR” というメッセージを画面に表示させる機能を作成していきたいと思います。
メッセージの表示方法
こういったメッセージの表示は、tkinter のキャンバスを利用した場合、create_text
メソッドによる文字列の描画により行うことができます。今回もこの create_text
メソッドを利用して “GAME CLEAR” というメッセージを表示させるようにしていきたいと思います。
create_text
メソッドに関しては下記ページで詳しく解説していますので、詳細を知りたい方はこちらをご参照いただければと思います。
文字列の描画位置
create_text
メソッドでは、第1引数と第2引数に文字列を描画する座標を指定する必要があります。今回作成するゲームにおいては、”GAME CLEAR” の文字列は表示領域の中央に描画するように座標を指定するようにしたいと思います。
表示領域の中央ですので、縦方向の座標はゲーム画面の高さを2で割れば求めることが可能です。
また下記ページで紹介した自動スクロールの導入により、ゲーム画面の端付近でない限り、操作キャラクターは表示領域の中心に描画されるように画面が自動スクロールされるようになっています。
【Python/tkinter】横スクロールアクションゲームを作る(画面の自動横スクロール)したがって、横方向の座標には操作キャラクターの中央の座標を指定してやれば良いことになります。ただし、前述のようにゲーム画面の端付近に操作キャラクターが存在する場合は操作キャラクターが表示領域の中央に表示されないため、端付近のみ下の図のように横方向の座標を調整するようにしたいと思います。
今回ゴールはゲーム画面の右端に設置するようにしていますので、「表示領域がゲーム画面の右端にある場合」の座標である ゲーム画面の幅 - 表示領域の幅 / 2
を毎回 create_text
メソッドの第1引数に指定しても良いかもしれません。
ですが、ゴールの位置がカスタマイズで変更されることや今後ゲームオーバー画面の表示を行うようにすることを見据え、右端以外でも文字列を描画できるよう、あらゆるケースの描画位置を計算できるようにしておきたいと思います。
メッセージ表示の実装
ここまでの考え方に基づき、メッセージの表示を行うメソッドを Screen
クラスの message
メソッドとして作成したいと思います。
具体的には、Screen
クラスを下記のように変更し、クラス変数の追加と messege
メソッドの追加を行います。
class Screen:
#↓これを追加
TYPE_GAMECLEAR = 0
TYPE_GAMEOVER = 1
#↑これを追加
#↓これを追加
def message(self, type, player_x):
if player_x < self.view_width / 2:
# 表示領域が左端にある
x = self.view_width // 2
elif player_x >= self.game_width - self.view_width / 2:
# 表示領域が右端にある
x = self.game_width - self.view_width // 2
else:
# 表示領域が端以外にある
x = player_x
y = self.game_height // 2
if type == Screen.TYPE_GAMECLEAR:
self.canvas.create_text(
x, y,
font=("", 40),
fill="blue",
text="GAME CLEAR",
anchor=tkinter.CENTER
)
#↑これを追加
上記の message
メソッドは、引数 type
で指定されたタイプに応じたメッセージを表示領域の中央に表示するメソッドになります。
表示領域の中央の座標は、引数 player_x
で指定された操作キャラクターの中央の座標(横方向)に基づいて計算を行なっています。
また、引数 type
には上記で追加したクラス変数のいずれかを指定されることを想定しており、さらに、この type
に応じて描画する文字列を切り替えるようにすることを想定しています。
Screen.TYPE_GAMECLEAR
:”GAME CLEAR” を描画Screen.TYPE_GAMEOVER
:”GAME OVER” を描画
このページでは “GAME CLEAR” の描画のみを行うので type
が Screen.TYPE_GAMECLEAR
の場合の処理のみを実装していますが、type
が Screen.TYPE_GAMEOVER
の場合の処理は、下記ページで敵キャラクターと当たった際の処理を実現する際に実装していきます。
ゲームクリア画面表示の実行
次は操作キャラクターの状態がゲームクリアになった際にゲームクリア画面が表示できるよう、先ほど作成した message
メソッドの実行部分の実装を行なっていきたいと思います。
今回作成するゲームにおいては、各キャラクターの画像の描画後、操作キャラクターの状態がゲームクリア状態の場合のみ message
メソッドを実行するようにしていきたいと思います。
各キャラクターの画像の描画は Screen
クラスの update
メソッドで行われており、さらにこの Screen
クラスの update
メソッドは Game
クラスの update
メソッドで実行されています(さらにこのメソッドは 100
ms ごとに定期実行されている)。
したがって、Game
クラスの update
メソッドの最後部分を下記のように変更すれば、各キャラクターの画像の描画が終わった後に message
メソッドを実行してゲームクリア画面を表示することができるようになります(message
メソッドが実行されるのは操作キャラクターの状態がゲームクリア状態の場合のみ)。
class Game:
def update(self):
#略
self.screen.update(image_infos, self.player.x + self.player.width / 2)
#↓これを追加
if self.player.state == Character.STATE_CLEAR:
self.screen.message(Screen.TYPE_GAMECLEAR, self.player.x + self.player.width // 2)
#↑これを追加
message
メソッドの第1引数には、先程追加した Screen
クラスのクラス変数を描画したい文字列に合わせて指定します。今回は “GAME CLEAR” を描画したいので、Screen.TYPE_GAMECLEAR
を指定しています。
また、第2引数には操作キャラクターの中央の座標(横方向)を指定しています(Player
クラスのデータ属性 x
と width
を用いて中央の座標を計算して指定)。
ゲームクリアの判定 で解説したように、操作キャラクターの状態がゲームクリアに遷移するのは操作キャラクターとゴールが当たった時です(Game
クラスの collide
メソッドで状態が変更される)。
操作キャラクターの状態がゲームクリアに遷移している場合は、上記で追加した条件文が YES になるので message
メソッドが実行され、ゲームクリア画面が表示されることになります。
実際に、変更後のスクリプトを実行して操作キャラクターを右方向に移動し、さらに操作キャラクターをゴールに当ててやれば、画面に “GAME CLEAR” と表示されることが確認できると思います!
まだ大雑把にしか当たり判定を行なっていないので、実際にはゴールに触れる前にゲームクリア画面が表示されてしまうと思います。より詳細な当たり判定は下記ページで実現しますので、これによりもうちょっと当たり判定の精度は上がるはずです。
【Python/tkinter】横スクロールアクションゲームを作る(より詳細な当たり判定)ゲームクリア後のキャラクターの移動の停止
ただし、現状では画面に “GAME CLEAR” が表示されても操作キャラクターが移動できてしまいます(さらに操作キャラクターの移動に伴って “GAME CLEAR” の文字列がどんどん描画されていく…)。
これだとあまりゲームクリアした感じがしないので、キャラクターの状態がゲームクリア状態になった後は、キャラクターの移動、さらには移動後の当たり判定が行われないようにしていきたいと思います。
移動の停止
まず、キャラクターの移動を行なっているのは、下記の2つの Character
クラスのメソッドになります。
move
メソッドupdate
メソッド
したがって、上記の2つのメソッドの先頭を下記のように変更すれば、キャラクターの状態 state
が Character.STATE_CLEAR
の場合はキャラクターが移動しなくなります(即座にメソッドが終了するようになる)。
class Character:
def move(self, direction):
#↓これを追加
if self.state == Character.STATE_CLEAR:
return
#↑これを追加
#略
def update(self):
#↓これを追加
if self.state == Character.STATE_CLEAR:
return
#↑これを追加
#略
当たり判定の停止
また、当たり判定を行なっているのは、下記の Character
クラスのメソッドになります。
isCollided
メソッド
したがって、isCollided
メソッドの先頭を下記のように変更すれば、キャラクターの状態 state
が Character.STATE_CLEAR
の場合は当たり判定が行われなくなります(即座にメソッドが終了するようになる)。
class Character:
def isCollided(self, opponent):
#↓これを追加
if self.state == Character.STATE_CLEAR:
return False
if opponent.state == Character.STATE_CLEAR:
return False
#↑これを追加
#略
キャラクターの状態 state
が Character.STATE_CLEAR
の場合は必ず False
を返却するようになっています。つまり、必ずキャラクター同士が当たっていないと判断されるので、当たった時の処理である Game
クラスの collide
メソッドも必ず実行されないようになります。
また、isCollided
の場合、このメソッドを実行したオブジェクトだけでなく、当たり判定相手のオブジェクト opponent
の state
が Character.STATE_CLEAR
の場合も当たり判定を行わないようにしています(念のため)。
ここまでの変更を行なったスクリプトを実行すれば、操作キャラクターがゴールに当たった際に、操作キャラクターが移動しなくなるようになったことが確認できると思います。
スポンサーリンク
ゲームクリア後の定期更新とキー入力の停止
上記の変更により、操作キャラクターがゴールに当たった際にゲームクリア画面が表示され、さらにキャラクターが移動できなくなるようになり、それ以降画面表示が変化しなくなりました。
画面表示は変化しませんが、実はまだ画像の描画は定期的に実行されています。同じ位置に同じ画像が 100
ms 毎(after
メソッドの第1引数に指定した時間の間隔)に描画されています。
これだとゲームとしては終了しているものの、CPU(パソコン)への負荷がかかったままになってしまいますので、このページの最後にゲームクリア後に画像の定期的な描画を停止させるようにしたいと思います。
また同様に、キーボードのキー入力も受け付けられる状態になっていますので(受け付けても Game
クラスの press
メソッドで即座に関数は終了するようにはなっていますが)、ゲームクリア後にキーボードのキー入力も受け付けないようにしていきたいと思います。
定期更新の停止
まずは画像の定期的な描画を停止させていきたいと思います。
画像の描画が定期的に実行されているのは、Game
クラスの update
メソッドが定期的に実行されているためです(この update
メソッドから Screen
クラスに画像の描画が依頼されている)。
さらに、Game
クラスの update
メソッドが定期的に実行されているのは、Game
クラスの update
メソッドの中の下記の処理により、UPDATE_TIME
ms 後に再度 Game
クラスの update
メソッドが実行されるよう after
メソッドに依頼しているからです
self.master.after(UPDATE_TIME, self.update)
ですので、操作キャラクターの状態が通常状態から変化した際に上記の after
メソッドを実行しないようにすれば、ゲームクリア後に Game
クラスの update
メソッドの定期的な実行を停止することができます。
つまり、Game
クラスの update
メソッドを下記のように変更すれば、ゲームクリア後の画像の定期的な描画を停止させることができるようになります。
class Game:
def update(self):
#↓これは削除
#self.master.after(UPDATE_TIME, self.update)
#↑これは削除
#↓これを追加
if self.player.state == Character.STATE_NORMAL:
self.master.after(UPDATE_TIME, self.update)
#↑これを追加
#略
キー入力受付の停止
続いてキーボードのキー入力の受付の停止を行なっていきたいと思います。
このゲームでキーボードのキー入力が受け付けられているのは、下記のように bind
メソッドを実行しているためです。
class Game:
def __init__(self, master):
#略
self.master.bind("<KeyPress-Left>", self.press)
self.master.bind("<KeyPress-Right>", self.press)
self.master.bind("<KeyPress-Up>", self.press)
#略
bind
メソッドで設定したイベントの受付を停止するためには、unbind
メソッドを実行する必要があります。unbind
メソッドの引数には、受け付けを停止したいイベントシーケンスを指定します。要は、上記の bind
メソッドの第1引数に指定している文字列を指定すれば良いです。
キー入力の受付を停止するのは、操作キャラクターの状態がゲームクリアに変化した時(通常状態から変化した時)ですので、先ほど変更した Game
クラスの update
メソッドをさらに下記のように変更すれば、ゲームクリア後にキー入力の受付を停止することができます。
class Game:
def update(self):
if self.player.state == Character.STATE_NORMAL:
self.master.after(UPDATE_TIME, self.update)
#↓これを追加
else:
self.master.unbind("<KeyPress-Left>")
self.master.unbind("<KeyPress-Right>")
self.master.unbind("<KeyPress-Up>")
#↑これを追加
#略
以上の変更により、ゲームクリア後の定期的な処理とキーボードのキー入力を停止させることができたことになります。
見た目が変化しないので効果が分かりにくかもしれませんが、アプリ毎の CPU 使用率を計測するツール(Windows だとタスクマネージャ、MacOSX だとアクティビティモニタなど)を使用すれば、ゲームクリア後に CPU 使用率が一気に低下することが確認できると思います。
例えば私の環境では、アプリ起動後からゲームクリアまでにおけるこのアプリの CPU 使用率は 8 % 程度ですが、ゲームクリア後には 1 % 以下まで低下することが確認できました。おそらくイベントの受付はそこまで負荷が高くないと思うので、画像の定期的な描画を停止することで CPU 負荷が低下するようになったのだと考えられます。
このページで作成したスクリプト
以上で、このページの解説は終了です。
最後に、ここまでの解説を踏まえて作成したスクリプトの全体を下記に掲載しておきます。次のページではこのスクリプトをベースに解説を進めていきたいと思います。
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"
GOAL_IMAGE_PATH = "car_animals_flag.png"
class Character:
DIRECTION_LEFT = 0
DIRECTION_RIGHT = 1
DIRECTION_UP = 2
JUMP_NO = 0
JUMP_UP = 1
JUMP_DOWN = 2
STATE_NORMAL = 0
STATE_CLEAR = 1
STATE_DEFEATED = 2
def __init__(self, path, size, is_right=True):
self.prepareImage(path, size, is_right)
self.base_y = VIEW_HEIGHT - self.right_image.height()
self.x = 0
self.y = self.base_y
self.speed_x = 30
self.speed_y = 20
self.jump_state = Character.JUMP_NO
self.jump_height = 200
self.direction = Character.DIRECTION_RIGHT
self.state = Character.STATE_NORMAL
def getImage(self):
if self.direction == Character.DIRECTION_RIGHT:
return self.right_image
elif self.direction == Character.DIRECTION_LEFT:
return self.left_image
def prepareImage(self, path, size, is_right=True):
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)
mirrored_image = ImageOps.mirror(resized_image)
if is_right:
self.right_image = ImageTk.PhotoImage(resized_image)
self.left_image = ImageTk.PhotoImage(mirrored_image)
else:
self.left_image = ImageTk.PhotoImage(resized_image)
self.right_image = ImageTk.PhotoImage(mirrored_image)
self.width = self.right_image.width()
self.height = self.right_image.height()
def move(self, direction):
if self.state == Character.STATE_CLEAR:
return
if direction == Character.DIRECTION_LEFT:
self.x = max(0, self.x - self.speed_x)
self.direction = Character.DIRECTION_LEFT
elif direction == Character.DIRECTION_RIGHT:
self.x = min(GAME_WIDTH - self.right_image.width(), self.x + self.speed_x)
self.direction = Character.DIRECTION_RIGHT
elif direction == Character.DIRECTION_UP:
if self.jump_state == Character.JUMP_NO:
self.jump_state = Character.JUMP_UP
def update(self):
if self.state == Character.STATE_CLEAR:
return
if self.jump_state == Character.JUMP_UP:
self.y -= self.speed_y
if self.y <= self.base_y - self.jump_height:
self.jump_state = Character.JUMP_DOWN
self.y = self.base_y - self.jump_height
elif self.jump_state == Character.JUMP_DOWN:
self.y += self.speed_y
if self.y >= self.base_y:
self.jump_state = Character.JUMP_NO
self.y = self.base_y
def isCollided(self, opponent):
if self.state == Character.STATE_CLEAR:
return False
if opponent.state == Character.STATE_CLEAR:
return False
sx = max(self.x, opponent.x)
sy = max(self.y, opponent.y)
ex = min(self.x + self.width, opponent.x + opponent.width)
ey = min(self.y + self.height, opponent.y + opponent.height)
if sx < ex and sy < ey:
return True
else:
return False
class Player(Character):
def __init__(self):
super().__init__(PLAYER_IMAGE_PATH, (100, 100))
def gameClear(self):
self.state = Character.STATE_CLEAR
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):
super().__init__(GOAL_IMAGE_PATH, (200, 200), False)
self.direction = Character.DIRECTION_LEFT
self.x = GAME_WIDTH - self.width
class Screen:
TYPE_GAMECLEAR = 0
TYPE_GAMEOVER = 1
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)
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, player_x):
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)
scroll_x = (player_x - self.view_width / 2) / self.game_width
self.canvas.xview_moveto(max(0, scroll_x))
def message(self, type, player_x):
if player_x < self.view_width / 2:
x = self.view_width // 2
elif player_x >= self.game_width - self.view_width / 2:
x = self.game_width - self.view_width // 2
else:
x = player_x
y = self.game_height // 2
if type == Screen.TYPE_GAMECLEAR:
self.canvas.create_text(
x, y,
font=("", 40),
fill="blue",
text="GAME CLEAR",
anchor=tkinter.CENTER
)
class Game:
def __init__(self, master):
self.master = master
self.screen = Screen(self.master)
self.characters = []
self.player = Player()
self.characters.append(self.player)
goal = Goal()
self.characters.append(goal)
self.master.bind("<KeyPress-Left>", self.press)
self.master.bind("<KeyPress-Right>", self.press)
self.master.bind("<KeyPress-Up>", self.press)
self.update()
def update(self):
if self.player.state == Character.STATE_NORMAL:
self.master.after(UPDATE_TIME, self.update)
else:
self.master.unbind("<KeyPress-Left>")
self.master.unbind("<KeyPress-Right>")
self.master.unbind("<KeyPress-Up>")
for character in self.characters:
character.update()
self.collisionDetect(character)
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, self.player.x + self.player.width / 2)
if self.player.state == Character.STATE_CLEAR:
self.screen.message(Screen.TYPE_GAMECLEAR, self.player.x + self.player.width // 2)
def press(self, event):
if event.keysym == "Left":
self.player.move(Character.DIRECTION_LEFT)
elif event.keysym == "Right":
self.player.move(Character.DIRECTION_RIGHT)
elif event.keysym == "Up":
self.player.move(Character.DIRECTION_UP)
self.collisionDetect(self.player)
def collide(self, character, opponent):
if isinstance(character, Player) and isinstance(opponent, Goal):
character.gameClear()
elif isinstance(character, Goal) and isinstance(opponent, Player):
opponent.gameClear()
def collisionDetect(self, character):
for opponent in self.characters:
if opponent is character:
continue
if character.isCollided(opponent):
self.collide(character, opponent)
def main():
app = tkinter.Tk()
game = Game(app)
app.mainloop()
if __name__ == "__main__":
main()
基本的には、ここまで紹介してきたスクリプトを寄せ集めてきたものですが、ゴールの画像のファイルパスをグローバル変数として定義するようにしています。
今回定義したグローバル変数の意味合いは下記のようになります。
GOAL_IMAGE_PATH
:ゴールの画像のファイルパス
もし、ここまでの解説の中でこれらを自身の環境に合わせて変更された方は、こちらの定義値も変更しておく必要があるので注意してください。
まとめ
このページでは、横スクロールアクションゲームにおける「ゴールの作成」および「ゲームクリアの判定」「ゲームクリア画面の表示」について解説しました。
もっと細かく言えば、スーパークラスとサブクラスの分割や当たり判定の実装、当たった時の処理の実装、状態管理の実装、文字列の描画、ゲームクリア後の移動と当たり判定およびキー入力と定期処理実行の停止を行なったことになります。
かなり多くのことを行なってきたので解説の分量も多くなってしまいましたが、特に当たり判定や当たった時の処理を実現したことで、よりゲームらしさが出てきたのでは無いかと思います。
現状では、当たり判定は矩形同士の重なりの有無のみで大雑把に行なっていますが、矩形同士の当たり判定自体は色んなゲームでも利用できると思いますので、当たり判定を行う際の考え方は覚えておくと良いと思います。
次のページでは、ついに基本編の最後となる敵キャラクターの作成を行なっていきます!次のページでも色んな事が学べると思いますので、是非次のページも読んでみてください!
【Python/tkinter】横スクロールアクションゲームを作る(敵キャラクターの作成)オススメ参考書(PR)
簡単なアプリやゲームを作りながら Python について学びたいという方には、下記の Pythonでつくる ゲーム開発 入門講座 がオススメです!ちなみに私が Python を始めるときに最初に買った書籍です!
下記ようなゲームを作成しながら Python の基本が楽しく学べます!素材もダウンロードして利用できるため、作成したゲームの見た目にも満足できると思います。
- すごろく
- おみくじ
- 迷路ゲーム
- 落ち物パズル
- RPG
また本書籍は下記のような構成になっているため、Python 初心者でも内容を理解しやすいです。
- プログラミング・Python の基礎から解説
- 絵を用いた解説が豊富
- ライブラリの使い方から解説(tkitner と Pygame)
- ソースコードの1行1行に注釈
ゲーム開発は楽しくプログラミングを学べるだけでなく、ゲームで学んだことは他の分野のプログラミングにも活かせるものが多いですし(キーボードの入力受付のイベントや定期的な処理・画像や座標を扱い方等)、逆に他の分野のプログラミングで学んだ知識を活かしやすいことも特徴だと思います(例えばコンピュータの動作に機械学習を取り入れるなど)。
プログラミングを学ぶのにゲーム開発は相性抜群だと思います。
Python の基礎や tkinter・Pygame の使い方をご存知なのであれば、下記の 実践編 をいきなり読むのもアリです。
実践編 では「シューティングゲーム」や「アクションゲーム」「3D カーレース」等のより難易度の高いゲームを作りながらプログラミングの力をつけていくことができます!
また、単にゲームを作るのではなく、対戦相手となるコンピュータの動作のアルゴリズムにも興味のある方は下記の「Pythonで作って学べるゲームのアルゴリズム入門」がオススメです。
この本はゲームのコンピュータ(AI)の動作アルゴリズム(思考ルーチン)に対する入門解説本になります。例えばオセロゲームにおけるコンピュータが、どのような思考によって石を置く場所を決めているか等の基本的な知識を得ることが出来ます。
プログラミングを挫折せずに続けていくためには楽しさを味わいながら学習することが大事ですので、特にゲームに興味のある方は、この辺りの参考書と一緒に Python を学んでいくのがオススメです!