見出し画像

【Pyxel】作りながら学ぶゲームプログラム (クリックゲームを改造する)

今回は以前に作成したクリックゲームを改造してたくさんのニャンコが出現するようにします。

元のコードと画像データはこちらからダウンロードできます。
http://syun777.sakura.ne.jp/tmp/pyxel/click.zip

このようにニャンコが4体出るようにします。

■ニャンコをクラス化する

前回のコードでは、ニャンコを制御するためのデータを全て App クラスの中に定義していました。この書き方ではより機能を拡張したい場合に Appクラスがどんどん肥大化してしまいます。
そこで、ニャンコをクラス化してコードをスッキリさせます。

Appクラスに定義した、ニャンコの情報を Catクラスに抜き出します。

import pyxel

class Cat:
    def __init__(self):
        # 変数初期化
        self.x = 0
        self.y = 0
        self.vx = 1
        self.vy = 1
        self.exists = True # ニャンコ様存在フラグ

    def update(self):
        # 移動する
        self.x += self.vx
        self.y += self.vy
        # 画面外に出ないようにする
        if self.x < 0:
            self.x = 0
            self.vx *= -1 # 移動方向を反転する
        if self.y < 0:
            self.y = 0
            self.vy *= -1 # 移動方向を反転する
        if self.x > pyxel.width - 16:
            self.x = pyxel.width - 16
            self.vx *= -1 # 移動方向を反転する
        if self.y > pyxel.height - 16:
            self.y = pyxel.height - 16
            self.vy *= -1 # 移動方向を反転する
            
    def checkHit(self, x, y):
        # 当たり判定
        left   = self.x
        top    = self.y
        right  = self.x + 16
        bottom = self.y + 16
        if left <= x <= right:
            if top <= y <= bottom:
                return True # 当たり
        return False # 外れ

    def draw(self):
        # ニャンコ様を描画する
        pyxel.blt(self.x, self.y, 0, 0, 0, 16, 16, 5)

class App:
    def __init__(self):
        pyxel.init(160, 120, fps=30)
        pyxel.image(0).load(0, 0, "cat.png") # 画像読み込み
        self.cat = Cat() # ニャンコオブジェクト生成
        pyxel.mouse(True) # マウスカーソルを表示する
        pyxel.run(self.update, self.draw)

    def update(self):
        if pyxel.btnp(pyxel.MOUSE_LEFT_BUTTON):
            if self.cat.checkHit(pyxel.mouse_x, pyxel.mouse_y):
                # 命中したので、ニャンコを消す
                self.cat.exists = False
        if self.cat.exists:
            # 生存している場合のみ更新
            self.cat.update()

    def draw(self):
        pyxel.cls(0)
        if self.cat.exists:
            # 生存している場合のみ描画
            self.cat.draw()
        else:
            pyxel.text(60, 50, "GAME CLEAR", 7)

App()

修正後のコードは上記のようになりますが、前回と変わりすぎてしまったので、順を追って説明します。

まず、App.__init__() の「self.x から self.exists」までを Cat.__init__() に移動します。

次に、App.update() の「移動する」から「画面外に出ないようにする」の部分を Cat.update() に移動します。

そして App.update() のクリックによる当たり判定の部分を 新たに定義した Cat.checkHit() に移動します。

 Cat.checkHit() はクリックした座標を引数として渡すようにしているので、pyxel.mouse_x を 引数の x pyxel.mouse_y を 引数の y に変更します。

        if left <= x <= right:
            if top <= y <= bottom:
                return True # 当たり

最後に、App.draw() のニャンコ描画処理を Cat.draw() に移動します。

App 側は、抜き出した部分を Cat クラスに置き換える修正をします。
まずは、App.__init__() から……

self.cat に Cat クラスのインスタンス (実体) を生成して代入します。
続けて、App.update() の変更点です。こちらはクリックの部分と Catクラスの更新の 2つの修正があります。
まずは、クリックの部分 (pyxel.btnp(pyxel.MOUSE_LEFT_BUTTON)) です。

self.cat.checkHit() にマウスの座標を渡し、Trueを返したら当たりなので、self.cat.exsits を False にしてニャンコを消滅させます。

そしてすぐ下の部分です。

self.cat.exists が Trueの時のみ、ニャンコを移動させる (self.cat.update() を呼び出す)ようにしています。

最後に、App.draw() の修正です。

ニャンコが存在している時は、self.cat.draw() を呼び出して描画するようにします。そうでなければ、"GAME CLEAR" のテキストを表示しています。

では実行して、前回と同じ動作することを確認してください。

■余談:リファクタリングについて

ここで行なった作業は、前回と同じ動作を保ちながら、コードを修正しました。このように「同じ動作結果を維持しながら、コードを読みやすいように修正する」ことをリファクタリングと呼びます。

「同じ動作をしているのに、なぜコードを変更する必要があるのか?」
と疑問に思うかもしれませんが、この修正は、これから実装する「ニャンコをたくさん出現させる」という機能を実装しやすくためのものです。

リファクタリングの機能として、「今後の機能拡張をやりやすくする」という効果があります。

もしリファクタリングについて、もっと理解したい場合は
新装版 リファクタリング―既存のコードを安全に改善する―
リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック
といった書籍で勉強することをオススメします。

■ニャンコを配列で管理する

ニャンコをたくさん表示するために、配列を使ってニャンコを管理します。まずはコード先頭に import random という定義を追加してください。

import pyxel
import random # これを追加する!

class Cat:
    def __init__(self):

これは乱数を使用するための定義となります。
次に、 App.__init__() の修正です。

class App:
    def __init__(self):
        pyxel.init(160, 120, fps=30)
        pyxel.image(0).load(0, 0, "cat.png") # 画像読み込み
        # ここから修正
        self.cat_list = []
        for i in range(4):
            cat = Cat() # ニャンコオブジェクト生成
            cat.x = random.randint(32, pyxel.width-32)
            cat.y = random.randint(32, pyxel.height-32)
            self.cat_list.append(cat)
        # ここまで修正
        pyxel.mouse(True) # マウスカーソルを表示する
        pyxel.run(self.update, self.draw)

self.cat_list という変数にCatクラスの配列 (リスト) を格納するようにしました。そして、初期位置を random.randint() でランダムな座標に出現させるようにしています。random.randint() は渡された 2つの引数の値の間でランダムな値を返す関数です。

続いて、App.update() の修正です。

    def update(self):
        for cat in self.cat_list:
            if cat.exists:
                if pyxel.btnp(pyxel.MOUSE_LEFT_BUTTON):
                    if cat.checkHit(pyxel.mouse_x, pyxel.mouse_y):
                        # 命中したので、ニャンコを消す
                        cat.exists = False
                if cat.exists:
                    # 生存している場合のみ更新
                    cat.update()

for文で cat_list のイテレーターを回し、その中で self.cat で処理していた部分を cat 変数に置き換えます

最後に App.draw() です。

    def draw(self):
        pyxel.cls(0)
        for cat in self.cat_list:
            if cat.exists:
                # 生存している場合のみ描画
                cat.draw()
        
        is_wiped = True # 全滅したかどうか
        for cat in self.cat_list:
            if cat.exists:
                is_wiped = False # 全滅していない

        if is_wiped:
            # 全滅させたのでクリア表示
            pyxel.text(60, 50, "GAME CLEAR", 7)

こちらも cat_list をイテレーターで回して、self.cat を cat変数に置き換えます。
後半は、全滅チェックした上で、全滅していた場合のみ (is_wipedが Trueの時のみ) クリア表示を行います。

なお全滅チェックですが、filter() を使うと一行で書くことができます

# 全滅チェック
is_wiped = len(list(filter(lambda cat:cat.exists, self.cat_list))) == 0

ただ……、これはプログラム初心者には難しい書き方ですので、ひとまず for文で問題ないです。
では、実行してニャンコが4体表示され、クリックして絶滅させると "GAME CLEAR" が表示されることを確認します。

ひとまずこれで終了。

今回作成したプログラムとデータは以下の場所にアップしていますので、うまく動作しない場合はこちらを参考にしてみてください。
http://syun777.sakura.ne.jp/tmp/pyxel/click2.zip

この記事が気に入ったらサポートをしてみませんか?