このページでは、Python で tkinter を利用した簡単な「横スクロールアクションゲーム」の作り方を解説していきます。
このページは「横スクロールアクションゲームの作り方の解説」の4ページ目となります。
3ページ目は下記ページとなり、3ページ目では主に操作キャラクターの表示の解説を行なっています。
【Python/tkinter】横スクロールアクションゲームを作る(キャラクターの表示)また、このページでは、上記ページの このページで作成したスクリプト で紹介しているスクリプトを変更していきながらゲームを作成していきます。
ですので、事前に上記のページを読んでおくことをオススメします。
この「横スクロールアクションゲームの作り方の解説」の4ページ目では、主に操作キャラクターの移動について解説をしていきたいと思います。
今回作成するゲームでは、キーボードのキー入力に応じて操作キャラクターの移動を行うようにしていきたいと思います。
左右キーが押された時には、そのキーに応じた方向にキャラクターを移動させ、さらに上キーが押された時には、キャラクターをジャンプさせるようにしていきます。
まずはキーボードのキー入力受付を行い、続いて左右方向の移動を実現し、その後ジャンプを実現させるという流れで解説していきます。
Contents
キーボード入力の受付
それでは、操作キャラクターの移動を実現していきたいと思います。
前述の通り、今回作成するゲームでは、操作キャラクターに関してはキーボードのキー入力により移動させるようにしたいと思います。具体的には、左右キーが押された時には、そのキーに応じた方向にキャラクターを移動させ、さらに上キーが押された時にはキャラクターをジャンプさせるようにしていきます。
で、これを実現するためには、まずアプリがキーボードのキー入力を受け付けられるようにする必要があります。
tkinter では、bind
メソッドを利用することで、キーボードのキー入力などのイベントを受け付けることができます。
もう少し具体的にいうと、bind
メソッドを実行すれば、それ以降 bind
メソッドの第1引数に指定したイベントが発生した際に第2引数に指定した関数やメソッドが自動的に実行されるようにすることができます。第1引数に指定する文字列はイベントシーケンスと呼ばれます。
widget.bind("イベントシーケンス", 関数名 or メソッド名)
ですので、例えば第1引数にキーボードの左キー入力を表すイベントシーケンスを指定し、さらに第2引数にキャラクターを左に移動する関数を指定してやれば、左キー入力された時に自動的にキャラクターの位置が左に移動するようになります。
このイベント関連の解説は下記ページで解説していますので、詳しく知りたい方は別途下記ページを参照していただければと思います。
Tkinterの使い方:イベント処理を行う今回作成するゲームでは、キーボードの左キー入力時には操作キャラクターを左に移動し、キーボードの右キー入力時には操作キャラクターを右に移動し、さらにキーボードの上キー入力時には操作キャラクターをジャンプさせるようにしたいため、下記の3つのイベントの受付を行う必要があります。
- キーボードの左キー入力(
"<KeyPress-Left>"
) - キーボードの右キー入力(
"<KeyPress-Right>"
) - キーボードの上キー入力(
"<KeyPress-Up>"
)
上記の括弧内で記述したものは、bind
メソッドの第1引数に指定するイベントシーケンスになります。
今回はアプリ起動直後からキーボードのキー入力を受け付けるようにするため、アプリ起動時に行われる Game
クラスのオブジェクト生成時、すなわち Game
クラスの __init__
実行時に bind
メソッドを実行するようにしたいと思います。
これを行うために、まずは Game
クラスの __init__
を下記のように変更します。
class Game:
def __init__(self, master):
self.master = master
self.screen = Screen(self.master)
self.characters = []
player = Character()
self.characters.append(player)
#↓これを追加
self.master.bind("<KeyPress-Left>", self.press)
self.master.bind("<KeyPress-Right>", self.press)
self.master.bind("<KeyPress-Up>", self.press)
#↑これを追加
self.update()
bind
メソッドは、tkinter のウィジェットに実行させる必要があるので注意してください。上記では self.master
、すなわち今回作成するゲームのメインウィンドウウィジェットに実行させています。
上記のように変更を行えば、キーボードの左キー・右キー・上キー入力時に Game
クラスの press
メソッドが自動的に実行されるようになります。
ただし、この press
メソッドは Game
クラスにまだ用意されていません。そのため、この press
を下記のように Game
クラスに追加したいと思います。
class Game:
#↓これを追加
def press(self, event):
pass
#↑これを追加
press
はまだ空のメソッドなのでキーを入力しても何も実行されませんが、ひとまずキーボードのキー入力を受け付ける枠組みは出来上がりです。
あとは、press
メソッドの中から操作キャラクターの位置を変更するようにすれば、キー入力時にキャラクターが移動するようになります。
ただし、キャラクターの位置を管理するクラスは Character
クラス(およびそのサブクラス)としたいので、キャラクターを移動するためのメソッドを Character
クラスに追加し、そのメソッドを上記で用意した press
メソッドから実行させるような構成にしていきたいと思います。
キャラクターの移動(左右)
ということで、次はキャラクターを移動するためのメソッドを Character
クラスに実装していきます。まずはこのメソッドを追加し、操作キャラクターを左右に移動できるようにしていきたいと思います(ジャンプについては次の章で解説します)。
スポンサーリンク
方向の定義
ここから作成していくキャラクターの移動を行うメソッドでは、引数として方向を表す値を受け取り、その値に応じた方向にキャラクターを移動させるようにしていきたいと思います。
まずはそのために、方向を表す値をクラス変数として定義したいと思います。
今回はクラス変数を利用しますが、enum
で値を定義するのでも良いです
具体的には、Character
クラスの先頭に下記を追加します。
class Character:
#↓これを追加
DIRECTION_LEFT = 0
DIRECTION_RIGHT = 1
DIRECTION_UP = 2
#↑これを追加
以降では、方向を表したり指定したりする際には、上記のクラス変数を用いるようにしていきます。
具体的には上記のクラス変数それぞれは次に示す方向を表す変数となります。
Character.DIRECTION_LEFT
:左方向Character.DIRECTION_RIGHT
:右方向Character.DIRECTION_UP
:上方向
キャラクターの位置変更
続いて実際にキャラクターの移動を実現していきたいと思います。
キャラクターの移動は、キャラクターの位置を変更することで行います。
下記ページの キャラクターの位置に応じた画像の描画 で解説している通り、キャラクターの位置は Character
クラスのデータ属性 x
と y
によって管理されるようになっています。さらに、このデータ属性 x
と y
の位置にキャラクターの画像が描画されるようになっています。
したがって、この x
と y
を変化させてやれば画面上のキャラクターの位置が変化することになります。ここでは左右方向の移動のみを行うので、x
のみの変更を行なっていきます。
また、キャンバスの座標的に、横方向の正方向は右方向になります。したがって、左方向にキャラクターの位置を変化させるには x
を減少させ、右方向にキャラクターの位置を変化させるには x
を増加させてやれば良いことになります。
つまり、キャラクターの位置を変更するメソッドの名前を move
とすれば、キャラクターの位置の変更を行うためには Character
クラスに下記の move
メソッドを追加してやれば良いことになります。
class Character:
#↓これを追加
def move(self, direction):
if direction == Character.DIRECTION_LEFT:
self.x = self.x - 1
elif direction == Character.DIRECTION_RIGHT:
self.x = self.x + 1
#↑これを追加
ただ、これだけだと、データ属性 x
が際限なく増減してしまうので、ゲームの画面外までキャラクターが移動できることになってしまいます。
そのため、x
は 0
〜 GAME_WIDTH - キャラクターの画像の幅
の間のみ変化するようにしたいと思います。
具体的には、上記の move
メソッドを下記のように変更します。
class Character:
def move(self, direction):
if direction == Character.DIRECTION_LEFT:
self.x = max(0, self.x - 1) #←ここを変更
elif direction == Character.DIRECTION_RIGHT:
self.x = min(GAME_WIDTH - self.right_image.width(), self.x + 1) #←ここを変更
GAME_WIDTH
はゲーム画面の幅を表すグローバル変数となります。また、データ属性 right_image
は tkinter 用の画像オブジェクトを参照しており、これに width
メソッドを実行させることでその画像の幅を取得することができます。
上記のように変更することで、x
は次の図で示す範囲内のみでしか変化しなくなるので、キャラクターが画面外にはみ出てしまうことを防ぐことができます。
キャラクターの位置変更の実行
現状は左右方向のみですが、Character
クラスの move
メソッドを実行すればキャラクターが移動するようになりました。
また、キーボードのキー入力が実行された際には Game
クラスの press
メソッドが実行されますので、このメソッドから Character
クラスの move
メソッドを実行するようにすれば、キー入力に応じてキャラクターが移動するようになります。
具体的には、Game
クラスの press
メソッドを下記のように変更すれば、キー入力に応じてキャラクターが移動するようになります。move
メソッドの引数には、方向の定義 で定義したクラス変数を、押されたキーに応じて指定するようにしています。
class Game:
def press(self, event):
#↓これは削除
#pass
#↑これは削除
#↓これを追加
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)
#↑これを追加
今回作成するゲームでキーボードのキー入力により移動を行うのは操作キャラクターのみです(敵キャラクターは定期的に移動する。ゴールは移動しない)。
下記ページの Character クラスのオブジェクトの生成 でも解説したように、Game
クラスのデータ属性 player
には操作キャラクターのオブジェクトを参照させていますので、この player
のみに move
メソッドを実行させることで、キーボードのキー入力時に操作キャラクターのみを移動させるようにしています。
また、まだ上方向の移動(ジャンプ)の処理は実装していませんが、左右方向の移動と同様に Character
クラスの move
メソッドの中で処理を実装していくので、上記のように上キーが押された場合(event.keysym == "Up"
が成立する場合)も move
メソッドを実行するようにしています。
ここまでの変更を行なった後のスクリプトを実行し、キーボードで左右キーを押せば、操作キャラクターが押した方向に移動することが確認できると思います。
ただし、実際に試してみると分かると思うのですが、移動速度がめちゃめちゃ遅いです…。流石にこれだと操作が大変なので、次はキャラクターの移動速度の調整を行なっていきたいと思います。
スポンサーリンク
キャラクターの移動速度の設定
キャラクターの移動速度が遅いのは、Character
クラスの move
メソッドで x
の増減量を 1
にしているからです。これだと、左右キー入力時にキャラクターが 1
ピクセルしか移動してくれません。
もちろん、この増減量を直接 10
や 20
などに変更しても良いのですが、今回は Character
クラスのデータ属性として増減量を持たせ、そのデータ属性の値に応じて move
メソッドでの x
の増減を行うようにしていきたいと思います。
わざわざデータ属性として用意するのは、サブクラスごとに移動速度を設定できるようにするためです
特に敵キャラクターを作成する際に活躍するデータ属性となります
このために、まず Character
クラスの __init__
を下記のように変更します。
class Character:
def __init__(self):
#略
self.y = self.base_y
#↓これを追加
self.speed_x = 30
self.speed_y = 20
#↑これを追加
speed_x
が左右キーを1回押された時の x
の増減量となります。
現状だと move
メソッドは左右方向にしか対応していないので speed_x
があれば十分なのですが、次の章で実装するジャンプ処理も見据えて speed_y
の追加も行っています(speed_y
はジャンプ時の上昇・下降速度となります)。
さらに、Character
クラスの move
メソッドを下記のように変更します。
class Character:
def move(self, direction):
if direction == Character.DIRECTION_LEFT:
self.x = max(0, self.x - self.speed_x) #←ここを変更
elif direction == Character.DIRECTION_RIGHT:
self.x = min(GAME_WIDTH - self.right_image.width(), self.x + self.speed_x) #←ここを変更
上記のように変更を行なってからスクリプトを実行し、さらに左右キーを入力すれば、先ほどよりも操作キャラクターの移動速度が速くなっていることが確認できると思います。
もっと早く移動させたいのであれば、speed_x
をもっと大きな値に、逆にもっと遅く移動させたいのであれば、speed_x
をもっと小さな値に設定することで調整することも可能です。
キャラクターのジャンプ
続いて、キャラクターのジャンプを実現していきたいと思います。
ジャンプの実現方法
今回は、上キーが入力された後に、一定時間間隔でキャラクターが上方向に speed_y
ずつ移動し、さらに上方向に “一定量” 移動した後は、一定時間間隔でキャラクターが下方向に speed_y
ずつ移動するようにジャンプ処理を作成していきたいと思います(speed_y
は先ほど Character
クラスに追加したデータ属性になります)。
キャラクターの縦方向の位置は Character
クラスのデータ属性 y
で管理していますので、要は一定間隔でこの y
の値を speed_y
増減させることになります。
以降では、上記で “一定量” と示したジャンプの頂点となる移動量を “ジャンプの高さ” と呼ばせていただきます。
定期的にキャラクターの位置を変化させていくことになるので、tkinter の場合は after
メソッドを利用して上記のジャンプ処理を実現していくことになります。
既に Game
クラスの update
メソッドは、after
メソッドにより定期的に実行されるようになっています(この辺りは解説の3ページ目の 定期的なキャラクターの描画 で解説しています)。
ですので、その Game
クラスの update
メソッドから上記のようなキャラクターの縦方向の移動を行うメソッドを実行するようにすれば、定期的な縦方向の移動を実現することができます。
今後、このキャラクターの縦方向の移動を行うメソッドを Character
クラスの update
メソッドとして解説していきます。
スポンサーリンク
キャラクターのジャンプ状態の管理
ただし、上記のようなジャンプを行う処理を実現するためには、Character
クラスの update
メソッドの中でキャラクターが現在どういう状態なのかを判断できるようにしなければなりません。
例えば、上キー入力後にまだジャンプの高さ分上方向に移動していないのであれば、キャラクターを上方向に移動するためにデータ属性 y
を speed_y
減少させる必要があります。
逆に、ジャンプの高さ分上方向に移動した後は、キャラクターを下方向に移動させるためにデータ属性 y
を speed_y
増加させる必要があります。
また、キャラクターがジャンプ中でないのであれば、データ属性 y
の値を変化させないようにする必要があります。
こんな感じで、キャラクターが今どういう状態かを判断しながら、データ属性 y
変化させていく必要があります。
そのために、まずはキャラクターのジャンプの状態を表す値をクラス変数として定義したいと思います。
さらに、このジャンプ状態を表す値を保持するデータ属性として Character
クラスに jump_state
を追加したいと思います。ついでにはなりますが、ジャンプの高さを示すデータも属性もあった方が便利なので、Character
クラスにデータ属性 jump_height
も追加したいと思います。
具体的には、Chracter
クラスの先頭部分および __init__
を下記のように変更します。
class Character:
DIRECTION_LEFT = 0
DIRECTION_RIGHT = 1
DIRECTION_UP = 2
#↓これを追加
JUMP_NO = 0
JUMP_UP = 1
JUMP_DOWN = 2
#↑これを追加
def __init__(self):
#略
self.speed_y = 20
#↓これを追加
self.jump_state = Character.JUMP_NO
self.jump_height = 200
#↑これを追加
追加した各クラス変数は下記の状態を表す値として扱っていきます。
Character.JUMP_NO
:キャラクターはジャンプ中でないCharacter.JUMP_UP
:キャラクターはジャンプ中かつ上昇中Character.JUMP_DOWN
:キャラクターはジャンプ中かつ降下中
Character
クラスのオブジェクト生成時は、この状態 jump_state
は Character.JUMP_NO
に設定しています。
また、jump_height
を 200
に設定していますので、この Character
クラスのオブジェクトは、上キーが押された時に 200
ピクセル分上に移動してから着地することになります。
キャラクターのジャンプの実装
では、先ほど追加した下記のジャンプの状態を用いてジャンプ処理の実装を行なっていきたいと思います。
Character.JUMP_NO
:キャラクターはジャンプ中でないCharacter.JUMP_UP
:キャラクターはジャンプ中かつ上昇中Character.JUMP_DOWN
:キャラクターはジャンプ中かつ降下中
上キー入力時の処理の実装
まず、上キーが押された時に、キャラクターの状態を Character.JUMP_UP
に遷移させるようにします。
上キーが押された時には Character
クラスの move
メソッドが実行されますので、つまりは Character
クラスの move
メソッドを下記のように変更します。
class Character:
def move(self, direction):
if direction == Character.DIRECTION_LEFT:
self.x = max(0, self.x - self.speed_x)
elif direction == Character.DIRECTION_RIGHT:
self.x = min(GAME_WIDTH - self.right_image.width(), self.x + self.speed_x)
#↓これを追加
elif direction == Character.DIRECTION_UP:
if self.jump_state == Character.JUMP_NO:
self.jump_state = Character.JUMP_UP
#↑これを追加
jump_state
が Character.JUMP_NO
以外の場合に jump_state
を変更しないようにしていますが、これは2段階ジャンプなどが行われないようにするためです。
縦方向の移動処理の実装
続いて Character
クラスの update
メソッドを作成していきます。
念のための復習ですが、まずキャンバスの縦方向に対する正方向は下方向ですので、縦方向の座標値が小さいほど上側の座標を表すことになります
また、Character
クラスのデータ属性 base_y
は基準位置の縦方向の座標(キャラクターがジャンプしていない時に存在する位置の縦方向の座標)となります
詳細は解説の3ページ目の キャラクターの初期位置の調整 を参照してください
まず、キャラクターの jump_state
が Character.JUMP_UP
の状態で Character
クラスの update
メソッドが実行された際には、キャラクターの縦方向の位置を示すデータ属性 y
を speed_y
減少させるようにします。
これにより、上キーが押された後に Character
クラスの update
メソッドが実行されればキャラクターが上方向に移動していくようになります。これは、先程の Character
クラスの move
メソッドの変更により、上キーが押された際にキャクターの jump_state
を Character.JUMP_UP
に設定するようにしたからです。
ただし、y
を speed_y
減少させることで y
が base_y - jump_height
以下になった場合は、既にジャンプの高さ分キャラクターが上方向に移動したことになります(ジャンプの頂点に達した)。
したがって、以降ではキャラクターが下方向に移動するよう、この際にはキャラクターの状態を Character.JUMP_DOWN
に変化させます。
また、キャラクターの jump_state
が Character.JUMP_DOWN
の状態で Character
クラスの update
メソッドが実行された際には、キャラクターの縦方向の位置を示すデータ属性 y
を speed_y
増加させるようにします。
これにより、ジャンプの高さ分移動した後に Character
クラスの update
メソッドが実行されればキャラクターが下方向に移動していくようになります(ジャンプの高さ分移動した際に jump_state
が Character.JUMP_DOWN
に設定されるため)。
ただし、y
を speed_y
増加させることで y
が base_y
以上になった場合は、キャラクターがジャンプしていない時に存在する位置まで下降したことになります(地面に着地した)。
この際には、キャラクターの jump_state
を Character.JUMP_NO
に変化させ、さらに jump_state
が Character.JUMP_NO
の際に y
が変更されないようにしておけば、着地後にキャラクターの縦方向の移動が行われないようになります。
上記の動作を図でまとめたものが下の図になりますので、上記の処理を整理するのに見ていただければと思います(むしろごちゃごちゃしすぎてて複雑かも…)。
また、上記の処理は、下記の update
メソッドを Character
クラスに追加することで実現することができます。
class Character:
#↓これを追加
def update(self):
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
#↑これを追加
上記の解説に加えて、y
が base_y - jump_height
よりも小さくならないように、さらに y
が base_y
よりも大きくならないように調整していますのでご注意ください。
キャラクターの状態の定期更新
ここまでの変更により、上キーを押した際にキャラクターの状態が Character.JUMP_UP
に変化し、それ以降で上記の Character
クラスの update
が定期的に実行されれば、その実行間隔でキャラクターの縦方向の位置およびキャラクターの状態が変化してジャンプを実現できる状態になりました。
あとは、Character
クラスの update
を定期的に実行できればジャンプの実現の完了です。
この定期的な実行を行うために、Game
クラスの update
メソッドを下記のように変更します。
class Game:
def update(self):
self.master.after(UPDATE_TIME, self.update)
#↓これを追加
for character in self.characters:
character.update()
#↑これを追加
image_infos = []
#略
これにより、定期的に実行されるキャラクターの画像描画の前にキャラクターの縦方向の位置および状態の更新が行われるようになり、その更新後の位置にキャラクターが描画されるようになります。
また、今後登場する敵キャラクターにもジャンプを行わせるようにしたいため、上記のように update
メソッドは、オブジェクトのリスト characters
の全要素に対して実行させるようにしています。
以上の変更を行なった後にスクリプトを起動して上キー入力を行えば、操作キャラクターがジャンプするようになったことが確認できると思います(上キー入力直後に左右キーを押せば、ジャンプ中でも横に移動できるはずです)。
キャラクターの向きの調整
ここまでの変更によって、操作キャラクターをキーボードのキー入力により移動できるようになりました。
ただ、左右のどちらに移動している時でもキャラクターがずっと同じ方向を向いてしまっているので、このページの最後として、移動方向に合わせて操作キャラクターの向きも切り替えるように改良していきたいと思います。
スポンサーリンク
左右反転した画像オブジェクトの生成
移動方向に合わせてキャラクターの向きを変更するために、まずは左右反転したキャラクターの画像を用意し、さらにキャラクターの移動方向に合わせて描画する画像を選択できるようにしていきたいと思います(元画像 or 左右反転後の画像から選択)。
左右反転したキャラクターの画像の生成
まずは左右反転したキャラクターの画像を用意していきたいと思います。
画像の左右反転に関しては、PIL の ImageOps
の mirror
関数を実行することで実現することができます。mirror
関数の引数には、左右反転したい PIL 用の画像オブジェクトを指定します(tkinter 用の画像オブジェクトではないことに注意)。
下記ページの キャラクターの画像オブジェクト生成 でも解説している通り、キャラクターの画像オブジェクトの生成は Character
クラスの prepareImage
メソッドで行なっています。
さらに、prepareImage
メソッドの中で生成している resized_image
が拡大縮小後の PIL 用の画像オブジェクトになります。
したがって、この resized_image
に対して mirror
関数を実行し、さらに mirror
関数によって生成された画像オブジェクトを tkinter 用の画像オブジェクトに変換してやれば、キャンバスに描画可能な左右反転後の画像オブジェクトを生成することができます。
具体的には、下記のように Character
クラスの prepareImage
メソッドを変更することで、キャンバスに描画可能な左右反転後の画像オブジェクトが生成できるようになります。
class Character:
def prepareImage(self, path, size):
#略
resized_image = image.resize(resize_size)
#↓これを追加
mirrored_image = ImageOps.mirror(resized_image)
#↑これを追
self.right_image = ImageTk.PhotoImage(resized_image)
#↓これを追加
self.left_image = ImageTk.PhotoImage(mirrored_image)
#↑これを追加
上記の変更により、右方向に移動している時に表示する画像オブジェクトがデータ属性 right_image
に、左方向に移動している時に表示する画像オブジェクトがデータ属性 left_image
に参照されるようになります。
画像の向きに応じた画像オブジェクトの生成
ただし、上記の prepareImage
メソッドでは、path
で指定された画像のキャラクターが右を向いていることを前提とした作りになってしまっています。
ですので、もし path
で指定された画像のキャラクターが左を向いている場合、左を向いた状態の画像オブジェクトが right_image
に、さらにそれを左右反転させて右を向くようになった画像オブジェクトが left_image
に参照されてしまうことになります。
そうなると、逆方向を向いたままキャラクターが移動することになってしまいます…。
これを防ぐために、prepareImage
メソッドの引数により path
で指定する画像のキャラクターが右を向いているかどうかを指定できるようにし、さらにその指定に応じて right_image
と left_image
に参照させる画像オブジェクトを切り替えるようにしたいと思います。
具体的には、上記の Character
クラスの prepareImage
メソッドを次のように変更します(引数も変更しているので注意してください)。
class Character:
def prepareImage(self, path, size, is_right=True): #←ここを変更
# 略
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.right_image = ImageTk.PhotoImage(resized_image)
#self.left_image = ImageTk.PhotoImage(mirrored_image)
#↑これを削除
引数 is_right
に False
を指定すれば、左右反転後の画像オブジェクトが right_image
に、元画像のままの向きの画像オブジェクトが left_image
にそれぞれ参照されるようになります。
ですので、ご自身が用意した画像のキャラクターの向きが左向きの場合、prepareImage
の第3引数 is_right
には False
を指定するようにしてください。キャラクターの向きが右向きの場合は、第3引数は省略して問題ありません。
キャラクターの移動方向の管理
右向きと左向きそれぞれの画像オブジェクトが生成できるようになりましたので、次はキャラクターの移動方向に応じて描画に使用する画像を切り替えられるようにしていきたいと思います。
まずは、キャラクターが今現在どちらに移動しようとしているかの情報が管理できるよう、Character
クラスに移動方向を示すデータ属性の追加を行います。
具体的には Character
クラスの __init__
を下記のように変更します。
class Character:
def __init__(self):
#略
self.jump_height = 200
#↓これを追加
self.direction = Character.DIRECTION_RIGHT
#↑これを追加
追加したデータ属性 direction
には、方向の定義 で追加した下記のクラス変数のいずれかを指定します。
Character.DIRECTION_LEFT
:左方向Character.DIRECTION_RIGHT
:右方向
上記では direction
に Character.DIRECTION_RIGHT
を指定しているので、キャラクターのオブジェクト生成時の移動方向は右方向となります。
さらに、キャラクターが移動する際に、その移動方向に合わせてデータ属性 direction
を変更するようにします。
具体的には、Character
クラスの move
メソッドを下記のように変更します。
class Character:
def move(self, direction):
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
#↑これを追加
#略
キーボードの左キー or 右キーの入力時には上記の Character
クラスの move
メソッドが実行されますので、上記の変更により、左右キーが押された時に、その方向に合わせて自動的に direction
が設定されるようになります。
移動方向を考慮した画像オブジェクトの取得
次は描画時に使用する画像オブジェクトを移動方向(データ属性 direction
)に合わせて切り替えられるようにしていきます。
下記ページの キャラクターの画像オブジェクトの取得 でも解説している通り、キャラクターの画像オブジェクトは Character
クラスの getImage
メソッドにより取得できるようになっており、描画時に使用する画像オブジェクトはこのメソッドを実行して取得されるようになっています(Game
クラスの update
メソッドから実行される)。
ただし、現状の Character
クラスの getImage
メソッドは何も考えずに right_image
を返却するようになっているため、キャラクターの移動方向に合わせて返却する画像オブジェクトを right_image
or left_image
から選択するようにしていきます。
具体的には、Character
クラスの getImage
メソッドを下記のように変更します。
class Character:
def getImage(self):
#↓これを追加
if self.direction == Character.DIRECTION_RIGHT:
return self.right_image
elif self.direction == Character.DIRECTION_LEFT:
return self.left_image
#↑これを追加
#↓これを削除
#return self.right_image
#↑これを削除
この Character
クラスの getImage
メソッドは、定期的に実行される画像描画の直前で Game
クラスから実行されるように既になっています。
ですので、上記のように変更してやれば、キャラクターの移動方向、すなわちキー入力された方向(右 or 左)に合わせた向きの画像が自動的に描画されるようになります。
ということで、ここまでの変更を加えたスクリプトを実行し、左右キーの入力を行えば、キャラクターの移動方向に合わせて画像の向きが変化するようになったことを確認できると思います。
ただし、キャラクターを右方向に移動していけば分かると思うのですが、まだ画面が自動でスクロールされないため、キャラクターが右側に行きすぎると画面外に消えてしまいます。
スクロールバーを右方向に移動すればキャラクターがまた画面内に表示されるようになると思いますが、流石にこれだとゲームとして不便なので、次のページではキャラクターの移動に伴って自動的に画面がスクロールされるようにしていきたいと思います。
スポンサーリンク
このページで作成したスクリプト
以上で、このページの解説は終了です。
最後に、ここまでの解説を踏まえて作成したスクリプトの全体を下記に掲載しておきます。次のページではこのスクリプトをベースに解説を進めていきたいと思います。
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:
DIRECTION_LEFT = 0
DIRECTION_RIGHT = 1
DIRECTION_UP = 2
JUMP_NO = 0
JUMP_UP = 1
JUMP_DOWN = 2
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
self.speed_x = 30
self.speed_y = 20
self.jump_state = Character.JUMP_NO
self.jump_height = 200
self.direction = Character.DIRECTION_RIGHT
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)
def move(self, direction):
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.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
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.master.bind("<KeyPress-Left>", self.press)
self.master.bind("<KeyPress-Right>", self.press)
self.master.bind("<KeyPress-Up>", self.press)
self.update()
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)
def update(self):
self.master.after(UPDATE_TIME, self.update)
for character in self.characters:
character.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()
まとめ
このページでは、横スクロールアクションゲームにおける「キャラクターの移動」について解説しました。
実際にキーボード入力によってキャラクターが移動するようになり、少しはゲームを作ってる感が出てきたのではないかと思います。
今回はキャラクターの移動は全て等速度で行うようにしていますが、加速度などを考慮して動作するようにするのも面白いと思います!
また、今回はキーボード入力をアプリが受け付けられるよう、イベントの受付設定を行いました。このイベントを利用すれば、キーボード入力だけでなくマウス操作などもアプリが受け付けられるようにすることができます。
こういったイベントの受付を簡単に実現できる点も tkinter の魅力だと思いますので、イベント受付設定を行う手順についてもこの機会に覚えておくと良いと思います!
次のページでは、操作キャラクターの移動に応じて画面を自動で横スクロールするようにしていきます!ちょっと横スクロールゲームを作ってる感が出てくると思いますので、次のページもぜひ読んでみてください!
【Python/tkinter】横スクロールアクションゲームを作る(画面の自動横スクロール)オススメ参考書(PR)
簡単なアプリやゲームを作りながら Python について学びたいという方には、下記の Pythonでつくる ゲーム開発 入門講座 がオススメです!ちなみに私が Python を始めるときに最初に買った書籍です!
下記ようなゲームを作成しながら Python の基本が楽しく学べます!素材もダウンロードして利用できるため、作成したゲームの見た目にも満足できると思います。
- すごろく
- おみくじ
- 迷路ゲーム
- 落ち物パズル
- RPG
また本書籍は下記のような構成になっているため、Python 初心者でも内容を理解しやすいです。
- プログラミング・Python の基礎から解説
- 絵を用いた解説が豊富
- ライブラリの使い方から解説(tkitner と Pygame)
- ソースコードの1行1行に注釈
ゲーム開発は楽しくプログラミングを学べるだけでなく、ゲームで学んだことは他の分野のプログラミングにも活かせるものが多いですし(キーボードの入力受付のイベントや定期的な処理・画像や座標を扱い方等)、逆に他の分野のプログラミングで学んだ知識を活かしやすいことも特徴だと思います(例えばコンピュータの動作に機械学習を取り入れるなど)。
プログラミングを学ぶのにゲーム開発は相性抜群だと思います。
Python の基礎や tkinter・Pygame の使い方をご存知なのであれば、下記の 実践編 をいきなり読むのもアリです。
実践編 では「シューティングゲーム」や「アクションゲーム」「3D カーレース」等のより難易度の高いゲームを作りながらプログラミングの力をつけていくことができます!
また、単にゲームを作るのではなく、対戦相手となるコンピュータの動作のアルゴリズムにも興味のある方は下記の「Pythonで作って学べるゲームのアルゴリズム入門」がオススメです。
この本はゲームのコンピュータ(AI)の動作アルゴリズム(思考ルーチン)に対する入門解説本になります。例えばオセロゲームにおけるコンピュータが、どのような思考によって石を置く場所を決めているか等の基本的な知識を得ることが出来ます。
プログラミングを挫折せずに続けていくためには楽しさを味わいながら学習することが大事ですので、特にゲームに興味のある方は、この辺りの参考書と一緒に Python を学んでいくのがオススメです!