【初心者向け】KivyによるWindowsアプリ作成28 ゲームを繰り返し行うためのコード修正

今回は、前回検出された「ゲームを繰り返し行えない」という問題を解消するために、コードを修正していきたいと思います。

コード全量については、例によってGitHubをどうぞ。

<スポンサーリンク>

問題点の整理

まず、なぜゲームを繰り返し行えないのか、問題点を簡単に整理しておきましょう。

何度かゲームを実行してみると、問題点は以下の4点だと当たり付けができると思います。

①ゲームがすぐに終了してしまう

ゲームの終了判定は、Targetインスタンスが格納されているtargetsリストの長さが、20以上かそうでないかにより行っていました。ですので、ゲームがすぐに終了して原因は、このtargetsリストの長さがゲーム後も20のままで、初期化されていない点にあります。

そこで、ゲーム再開時に、このリストをきちんと初期化してやれば解消できそうです。

②スコアとして、前回実施時の最終スコアを引き継いでしまう

これも、score変数の値が初期化されていないことに起因していますので、適切に初期化すれば問題は解決できそうです。

③前回ゲーム終了時に表示されている文字が、再ゲーム時にそのまま残ってしまう

これも同様に、ゲーム画面の初期化が適切に行われていないことに起因しています。きちんと初期化するようにしましょう。

④ゲーム中に「to Title」ボタンを押しても(裏で)文字の表示処理が続いてしまう

これは繰り返し処理に直接関係するわけではないのですが、現在のプログラムでは、ゲームオーバー前にゲーム画面右上の「to Title」ボタンを押してタイトル画面に戻った際、updateメソッドが継続して実施されてしまうため、タイトル画面にいるのにゲームオーバー画面が表示されたりなどのおかしな動作をします。

この点については、ボタンを押したときの処理に終了処理を入れることで解消します。

それでは、これらの内容を踏まえて、順にコードを修正していきます。

コード修正

①targetsリストの初期化

これは非常に対応が簡単で、ゲーム開始時に初期化処理を行うstartメソッドに、targetsリストを初期化する行を入れてやれば良いです。

class GameScreen(Screen):
#省略
    def start(self):
        self.targetExist = [[False for i in range(10)] for j in range(7)]
        self.targets.clear()#この行を追加
#省略

②スコアの初期化

これも、startメソッドにスコアの初期化処理(self.score = 0)を追加してやれば、動くことは動きます。

しかしながら、この方法では(小さいながらも)問題が残ります。

それは、ゲーム開始時のポップアップが出現した時点で、内部的にはスコアが初期化されているものの、その背面にあるゲーム画面上では古いスコアが一時的に表示されてしまうということです。

ゲームを開始すると、内部的に初期化されたスコアが表示され、0に戻りますが、少し気持ち悪い感じもしますので、直しておきましょう。

この原因は、ゲーム開始時の各処理の実行順序にあります。

タイトル画面で「Start」ボタンを押すと、ゲーム画面が描画され、その段階で画面上には内部のスコアが表示されます。続いて、その上にポップアップが表示されます。そして、ここでポップアップ上のYesボタンを押したときに、初めてstartメソッドが呼び出されます。

したがって、startメソッド内でスコアの初期化を行うのでは遅いのです

これを解消するための対応方法はいくつか考えられますが、ここは素直に、ゲーム画面描画前、すなわち、タイトル画面で「Start」ボタンを押したタイミングで初期化を行うよう、コードを修正してみます。

まずは、TitleScreenクラス内に、初期化処理を行うためのメソッドを定義します。

class TitleScreen(Screen):
    def startButtonClicked(self):
#省略
    def preStart(self): #このメソッドを追加
        tempGame = sm.get_screen("game")
        tempGame.score = 0

内容についてはこれまでの説明からなんとなくイメージはつかめるかと思いますが、GameScreenインスタンスのscore変数を0に初期化しています。

さらにこれを、タイトル画面の「Start」ボタンを押したときに呼ばれるメソッド、startButtonClickedメソッド内で呼び出してやります。

class TitleScreen(Screen):
    def startButtonClicked(self):
        self.preStart() #この行を追加
        sm.transition.direction = "left"
#省略

これで2点目の問題は解決できました。

③表示文字の初期化

最後に、ゲーム再スタート時に、前回ゲーム時に最終的に表示されていた文字が画面上に残ってしまう問題を解消しましょう。

まず、この処理をどこに入れるかですが、もちろん②と同様、TitleScreenクラスのpreStartメソッド内に記述し、初期化処理の一環として行うことが考えられます。

しかし、今回は色々なやり方を紹介できればと思いますので、ゲームオーバー時の終了処理の一環としてこの処理を実装することを考えてみたいと思います。

ここでは、処理をGameScreenクラスのendメソッド内に記述します。

②に書いた、処理の順序を考えると、初期化処理としての実装でも、終了処理としての実装でも、正しく動作するような印象を持って頂けると思います

さて、処理をendメソッド内に記述するとして、具体的にはどのような処理内容にすればよいでしょうか?

まず考えられるのは、keyboardDownメソッド内に記述した、正しいキーがタイプされた時の処理方法である、self.remove_widgetメソッドを用いるやり方です。この場合、for文などのループを活用して、すべての表示文字列に対して削除処理を行えば、問題は解決できそうです。

しかしながらここでは少し工夫して、for文等で処理を回すのではなく、表示されている文字を一括で削除できるようにしたいと思います

具体的には、文字の描画対象であるGameScreen Widgetにダイレクトに文字を表示するのではなく、まずはGameScreen Widgetに別の描画用フィールドとなるWidgetを置き、その上に文字を表示することとします。そして、文字の全消去時には、そのWidgetを丸ごと削除してしまいます。

実際にコーディングしてみましょう。

まず、文字の描画対象となるクラスを定義しておきます。このクラスは「Widget」という、様々な種類のWidgetの大本となっているクラスを継承して作成します。

クラスの定義場所は、個人的にはTargetクラスの直後が良いと思います。

class Target(Button):
#省略

class TargetField(Widget): #このクラスを追加
    pass

ここでは、TargetFieldという名称のクラスを、Targetクラスの直後に作成しました。

また、先ほど説明したWidgetクラスがPythonファイル上で認識できるよう、Widgetクラスをインポートしておきます。

#省略
from kivy.core.window import Window
from kivy.properties import NumericProperty
from kivy.uix.widget import Widget #この行を追記

続いて、GameScreenのstartメソッドにこのTargetFieldクラスのインスタンス化処理とゲーム画面への登録処理を、また逆に、endメソッドにゲーム画面からの削除処理とインスタンス変数の初期化処理を追加します。

class GameScreen(Screen):
#省略
    def start(self):
        self.targetField = TargetField() #追加
        self.add_widget(self.targetField) #追加
#省略
    def end(self):
        self.remove_widget(self.targetField) #追加
        self.targetField = None #追加
#省略

さらに、updateメソッド内の文字表示処理と、keyboardDownメソッド内の文字削除処理を、このtargetFieldインスタンスを用いた処理に置き換えてやります。

updateメソッド内
    self.add_widget(target)を
    self.targetField.add_widget(target)に変更
keyboardDownメソッド内
    self.remove_widget(target)を
    self.targetField.remove_widget(target)に変更

これで、文字をすべて消したい場合にループを用いるのではなく、一括で消去できるようになりました。

④「to Title」ボタン押下時の終了処理適切化

最後の、ゲーム途中でタイトル画面に戻ったとしても正しくゲームが再開できるようにするための対応ですが、単純に、GameScreenクラスのreturnButtonClickedメソッド内で、endメソッドを呼び出すようにしてやるだけです。

def returnButtonClicked(self):
    self.end() #この行を追加
    sm.transition.direction = "right"
    sm.current = "title"

これでプログラムの修正は完了です。

テスト実行

それでは、意図したとおりに正しくプログラムが修正できているか、実際にプログラムを動かして確認してみましょう。

前回同様、ゲームオーバーになるまでゲームを進めます。

そしてタイトル画面に戻り、Startボタンでゲームを再開してみます。

バックの画面上、スコアが0になっており、また、文字も表示されていないことが確認できます。

ゲーム自体も、問題なく進められますね。

念のために、ゲームの途中で「to Title」ボタンを押してみて、正しくゲームが終了することも確認してみて下さい。

終わりに

見つかったバクも今回修正することができ、これである程度普通に楽しめるゲームが完成しました。

ところで、ここまでプログラムを作成した段階で、私の小学生の子供にこのゲームをやらせてみました。

始め、隣に座って子供がプレイする様子を見ていたのですが、さすがにまだタイピングには不慣れで、スコア100を超えるのが関の山でした。

ところが、席を外してしばらくすると、子供から「パパ!600点超えたよ!!」との声が。私もそんな点数取ったことが無いのに、何事かと思って事情を聞いてみると、画面に表示された文字とは無関係に適当にキーを叩きまくっていたら、この高得点が出たとのこと。

そうです、現時点のプログラムではミスタイプに対するペナルティがないため、まじめにプレイするより適当にタイプした方が高得点が出るのです

この点はゲームとして致命的ですので、次回の記事では、ミスタイプ時にスコア減算というペナルティを与える処理を実装していきたいと思います。

<スポンサーリンク>

シェアする

フォローする