【初心者向け】KivyによるWindowsアプリ作成20 ゲーム画面の設計と定期処理の実装

それでは今回からゲーム画面の実装に入り、ゲーム内容を作り込んでいきたいと思います。

今回はまずゲーム画面の設計を簡単に行った上で、後半に、時の経過とともに定期的に実施させたい処理を一部実装します。

コードはGitHubにアップしていますので、必要に応じてご参照下さい。

<スポンサーリンク>

ゲーム画面の設計

レイアウト設計

まずは、ゲーム画面のレイアウトを設計したいと思います。

すでに土台は実装しており、あとは、画面上のどこにどのような形式でタイプ対象となる文字を表示するかがポイントになります。

先に、実装済みのレイアウトを明確化しておくと、下図のようになります。

画面全体のサイズは800 X 600ですが、下側の800 X 540の領域に、以下のような文字を表示する正方形の領域を並べて配置したいと思います。

1ボックスを75 X 75の領域とし、その中に73 X 73の文字を表示するButtonを設置します。

Buttonそのものを75 X 75としないのは、Button間に余白を入れることにより、各Buttonを見た目上明確に切り離し、見やすくするためです。

このようなボックスを、ゲーム画面下側の領域に、下図のように配置します。

少々分かりづらいかと思いますが、このように配置することにより、ウインドウと(各正方形の領域ではなく)文字Buttonとの余白が、左側が26px、右側も24 + 2で26px、下側は1px、そして、上側との余白は14 + 2 = 16pxとなります。また、文字Button間の余白は2pxになります。

機能設計

引き続き、この画面で実装すべき機能を設計していきましょう。

今回は、以下のような機能を持たせることとします。

【タイプ対象文字の表示】

  • 一定の時間間隔で、各ボックスのいずれか1つにランダムな文字を表示する。
  • 表示する文字種類は、設定で以下の3段階から選べるものとする。
    • Easy : アルファベット小文字のみ
    • Normal : アルファベット大文字小文字
    • Hard : アルファベット大文字小文字 + 数字
  • デフォルトはEasy設定とする。
  • 時間間隔はデフォルトで1秒とし、正解タイプが1回行われる毎に1/60秒ずつその間隔を短縮する。

【スコアの加減算と表示】

  • 正解タイプが行われる毎にスコアを10点加算、逆にミスタイプが行われる毎に10点減算する。
  • スコアは、画面上部の「0」と表示されているLabel領域に、リアルタイムで反映する。

【ゲームオーバー時の処理】

  • 画面上表示されている未タイプの文字が20個を超えた時点でゲームを終了する。
  • ゲーム終了時には、文字の追加表示を止め、その時点のスコアを記載したポップアップを表示する

一定の時間間隔での文字表示処理の実装

それでは、簡単な設計が終わったところで、この設計内容に基づき処理を実装していきましょう。

ゲーム画面で実装すべき内容は多岐に渡りますので、複数回に分けて実装を行っていきますが、今回は定期的な表示処理の第一歩ということで、アルファベット「a」を1秒間隔でランダムな場所に表示する処理を実装していきたいと思います。

文字クラスの実装

まずは、画面に表示する文字クラスを実装します。本ゲームでは、文字クラスは、Buttonクラスをベースとしますので、Buttonクラスを継承した新たなクラスとして、Targetという名称のクラスをPythonファイル上に定義します。

事前にButtonクラスをインポートする必要があることに注意して下さい。

#省略
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.modalview import ModalView
from kivy.uix.button import Button #追加
#省略
sm = ScreenManager()

class Target(Button):#このクラスを追加
    pass
#省略

現時点で、Targetクラスの見た目は何も設定されていません。そこで、KvファイルにTarget Widgetのレイアウト定義を追記します。

<Target>: #このWidgetを追加
    size_hint: None, None
    size: 73, 73
    font_size: 32

<TitleScreen>:
#省略

上の記述では、Widgetそのもののサイズを73 X 73pxとし、そこに表示する文字サイズを32ptに指定しています。

定期的な処理の実装

ここからが今回のメインです。定期的に処理を行うメソッドを実装します。

簡単に流れだけ説明しておきますと、大きく分けて2つの実装を行う必要があります。

まず、定期的に行いたい処理内容を、それを実行したいクラス内にメソッドとして定義します。今回はゲーム画面で行う処理が必要ですので、GameScreenクラス内にそのメソッドを定義します。

さらにその処理を開始したいタイミングで、「Clock」というクラスの「schedule_interval」メソッドに、その定期処理メソッドと頻度を与えて呼び出すことで、定期処理を開始します

まずは定期処理メソッドの実装から行いましょう。

最初にやるべきことは、今回、文字を表示する位置の指定に乱数を使いますが、そのために必要なクラスをインポートしておきます。先ほど追加したimport文の後に1行追加しましょう。

#省略
from kivy.uix.button import Button
from random import randint #追加
#省略

続いて、GameScreenクラス内に、定期処理メソッドを実装します。ここではメソッド名をupdateとし、以下のような記述とします。

class GameScreen(Screen):
    def update(self, dt): #このメソッドを追加
        target = Target()
        target.pos = (randint(0, 9) * 75 + 26, randint(0, 6) * 75 + 1)
        target.text = "a"
        self.add_widget(target)

    def returnButtonClicked(self):
#省略

内容を説明します。

まず2行目、updateメソッドの引数ですが、お決まりのselfに加え、dtという変数を指定しています。この変数には、このメソッドが前回呼び出されてから何秒後に再び呼び出されたかという実時間が入ります。このタイピングゲームでは、updateメソッドの実行間隔を1秒と指定するのですが、厳密には多少のタイムラグがあり、必ずしも1秒にはなりません(例えば、1.0049240396562178や0.9996784203883067のような値になります。)

なお、本タイピングゲームでは、直接的にこの変数の値を使用するわけではないのですが、この「実際の実行間隔」を受ける引数を指定しないと、後述するschedule_intervelメソッドの実行時にエラーになってしまいますので、必ず指定するようにして下さい。

3行目では、先ほど定義したTargetクラスをtargetという名称でインスタンス化しています。

さらに、4、5行目で、このtargetインスタンスの初期設定を行い、6行目でこれを表示する処理を行います。

4行目ではまず、targetのpos属性を指定していますが、この属性に与えた2つの値は、targetの描画領域となるWidget(今回はGameScreen)の左下を起点として、それぞれ、描画するtargetの左下のx座標とy座標を表しています。

さらに、座標指定にrandintというメソッドを使っています。これは先ほどインポートしたもので、randint(整数A, 整数B)という記述により、整数A~整数Bの間のランダムな整数を取得することができます

例えば、プログラム中の「randint(0, 9) * 75 + 26」という表現で、26(75*0 + 26)から75刻みで701(75*9 + 26)までの整数がランダムに返されます。これらの具体的な数字は、画面設計で指定したpx数を反映したものです。

続いて5行目では、targetのtext属性、つまり、表示する文字に「a」を指定しています。

そして最後の6行目ですが、自分自身(GameScreenインスタンス)のadd_widgetというメソッドを使って、GameScreen上の描画対象として、targetを追加しています。このadd_widgetというメソッドはGameScreenクラスでは定義していませんが、これはGameScreenクラスの継承元となっているScreenクラスの、さらに上位のクラスにて実装されているため、明示的に定義する必要はありません。

少し長くなりましたが、これで定期処理そのものの実装は完了です。

定期処理呼び出しの実装

最後に、この定期処理を呼び出すための実装を行います。

基本的には先ほど書きました通り、Clockクラスのschedule_intervalメソッドにて、定期処理を開始したいタイミング、今回の場合は、ReadyViewポップアップにてYesボタンが押されたときに呼び出せばよいです。ですが、後ほど、このボタンを押した時にやらせる初期化処理をいくつか追加したいので、これらをまとめて実施するstartというメソッドをGameScreenクラスに定義し、ReadyViewからはそれを呼び出す形にしたいと思います。

まず、Pythonファイルに、Clockクラスを使用するためのimport文を追加します。

#省略
from random import randint
from kivy.clock import Clock #追加
#省略

次に、startメソッドをGameScreenクラスに定義します。

class GameScreen(Screen):
    def start(self): #このメソッドを追加
        Clock.schedule_interval(self.update, 1.0/1.0)

    def update(self, dt):
#省略

3行目にある、「Clock.schedule_interval(定期処理メソッド, 実行間隔(秒))」という表現で、定期処理を指定された間隔で呼び出すようにすることができます

今回は1.0/1.0秒、つまり1秒間隔で、GameScreenクラスのupdateメソッドを呼び出すように指定しています。

さて、最後にReadyViewクラスのyesButtonClickedメソッドに、このstartメソッドを呼び出すよう記述を追加します。

class ReadyView(ModalView):
    def yesButtonClicked(self):
        sm.get_screen("game").start() #追加
        self.dismiss()

startメソッドをReadyViewから呼び出そうと思った時、startメソッドがGameScreenクラスという、ReadyViewクラスから見ると親でも子でもない、ましてや、自分自身ではないクラスに定義されているため、どのようにstartメソッドを呼び出してよいか、少し困ると思います。

ここで、ScreenManagerインスタンスには、get_screenという、ScreenManagerに登録されたScreenのインスタンス名を指定することで、そのインスタンスを取得できるメソッドがあります。このメソッドを利用し、「sm.get_screen(“game”)」という記述でGameScreenインスタンスを取得し、さらに「.start()」と続けることで、GameScreenインスタンス中のstartメソッドを呼び出しています。

実行

ここまで完了した段階で、プログラムを実行してみましょう。

ゲーム画面に遷移した後、「a」と書かれたボタンがランダムに、1秒間隔で延々と表示されるにようになっていれば、プログラムの更新は成功です。

終わりに

今回はだいぶ長くなってしまいましたが、Kivyアプリに一定の時間間隔で処理を行わせる方法について紹介しました。

さて、もしかしたらプログラムを更新している最中、あるいは、プログラム実行中にお気付きになられた方もいらっしゃるかもしれませんが、現時点では新たに文字が表示される際、乱数の生成状況によっては既に文字が存在している場所に被さって表示されてしまいます。

そこで、次回は同じ場所に重複して文字が表示されないよう、プログラムを修正したいと思います。

<スポンサーリンク>

シェアする

フォローする