#5 PythonでRPGを作ろう!基礎が固まるコマンドラインで動くゲーム開発 | 初心者向け

python_rpg

皆さんこんにちは。小学生の頃、自由帳いっぱいに迷路を描きまくっていた小幡です。迷路を描いていたという話をたまに聞くのですが、迷路を描いてしまうのは何か共通性があるのでしょうかねぇ…。

前回までの記事はコチラからどうぞ。

前回までの作業は…

  1. リストを学ぶ
  2. for文でリストの要素を取り出す
  3. フィールドマップを標準出力で表現する

という事を行ってきました。

前回に引き続き今回は、フィールドマップを充実させていきます。半角文字で表示されているため細長くなった全体を全角文字に置換したり、Eの文字列は空の文字列に変えたり、アイテムを配置したりということを、列挙型を使い一気に、そして拡張しやすい形で実装していきます。

早速ですが、フィールドマップをさらに充実させて行きます。

マップクラスを作成する

ここからはマップで行う処理量が増えると予想されますので、処理をマップクラスとして切り出します。

新しくmap.pyのファイルを作成します。現在のファイル構成は以下のようになります。

python_rpg
┣ main.py
┣ map.py
┗ player.py

map.pyを作成したらmap_listsと言う変数に2次元配列で記述したフィールドマップを入れます。

ちなみに、クラスを宣言している中で定義する変数をクラス変数やクラスのメンバと呼んだりします。関数の中で作成する変数とは少し扱いが異なります。今は「クラスの変数なんだなぁ」くらいで大丈夫です。

とても紛らわしい話ですが、クラスのメンバ(変数)だけではなく、メソッド(関数)のメンバ(変数)もありますし、さらにグローバル(どこでも使える的)な変数が存在します。それぞれ使える範囲が違ったりします。

class Map:

    # クラス変数
    map_lists= [
        ['B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B'],
        ['B', 'P', 'E', 'B', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'H', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'H', 'B'],
        ['B', 'E', 'E', 'B', 'E', 'E', 'E', 'E', 'E', 'B', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'B'],
        ['B', 'H', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'B'],
        ['B', 'H', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'B', 'H', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'B'],
        ['B', 'H', 'E', 'E', 'E', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'B'],
        ['B', 'E', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'B'],
        ['B', 'E', 'E', 'E', 'E', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'B'],
        ['B', 'E', 'S', 'B', 'E', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'B', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'B'],
        ['B', 'E', 'E', 'E', 'E', 'E', 'B', 'E', 'E', 'E', 'E', 'E', 'B', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'B'],
        ['B', 'E', 'E', 'E', 'E', 'H', 'B', 'W', 'E', 'E', 'E', 'E', 'B', 'E', 'H', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'G', 'B'],
        ['B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B'],
    ]

次に、フィールドマップを標準出力する関数を、マップクラスの中に記述します。

class Map:

    # クラス変数
    map_lists= [
""" ~省略~ """

    def show(self):
        """マップを表示
        """
        # 二次元配列の文字列を、コマンドライン表示用に変換
        for array in self.map_lists:
            map = ''
            for string in array:
                map = map + string
                # map = map + MapItem(string).map_item
            print(map)

マップクラスはフィールドマップに関わる変数や関数を入れておくイメージです。このプロジェクトの事を全く知らない人が、このクラスを使いやすいように考えて作業することが重要です。

と言っても、実際問題オブジェクト指向を完璧に理解し実践することはとても難しいことですので、今回はマップの事はマップクラスに任せよう、くらいの気持ちで行きましょう。

メイン関数でマップクラスを使う

さて、マップクラスにはマップの事を任せる事にしました。メイン関数で実際にマップクラスを呼び出して使って見ましょう。

そのためには、まずマップクラスのインポートが必要になります。インポートは以前にお伝えした通り、ファイル名とクラス名で呼び出すという認識で今は大丈夫です。それ以上の事は、また必要になりしだいお伝えしたいと思います。

インポート文は以下のようになります。

from map import Map # 追加したインポート文
from player import Player


def main():
    # タイトルを表示
""" ~省略~ """

1行目に新しくMapクラスをインポートする記述を追加しました。これで先ほど作成したマップクラスを呼び出して使う事ができるようになりました。

フィールドマップを標準出力するだけなら、インスタンス(実態)を作る必要はありませんが、これからフィールドマップはプレイヤーが移動したり、移動した結果アイテムを拾ったり、様々な状況変化が予想されますので、インスタンスを作成します。

インスタンスは以下のように作ります。次の行ではマップを表示させる関数も呼び出しています。

    # マップを表示
    map = Map() # インスタンス作成
    map.show() # マップを表示

これは以前お伝えした方法そのままですが、今回は引数がありません。プレイヤーのインスタンスを作成した時とは違い、名前を与えたりしていないためです。先頭小文字のmapの変数と、先頭大文字のMapクラスは、最初見分けがつけずらいかもしれませんが、何度も使っているうちに慣れてきます。

次のmap.show()は、作成したマップクラスのインスタンスの関数を呼び出しています。この場合の関数は、インスタンスメソッドとも呼ばれます。クラスの中の関数は他にクラスメソッド、スタティックメソッドが存在します。今回はインスタンスを作成して呼び出した関数なので、インスタンスメソッドという認識で大丈夫です。

インスタンスメソッド、クラスメソッド、スタティックメソッド

少し難しい話になるかもしれませんが、先にこの三種類のメソッドの説明をしておきます。

全てクラスの中にあることが前提としてあります。

インスタンスメソッドは、以下のようになります。

def show(self): # selfを引数に取る
    print('インスタンスメソッド')

クラスメソッドは、以下のようになります。

@classmethod # デコレータが付く
def show(cls): # clsを引数に取る
    print('クラスメソッド')

スタティックメソッドは、以下のようになります。

@staticmethod # デコレータが付く
def show():
    print('スタティックメソッド')

ほんの少しの違いですが、慣れてくると大きな違いに見えてくるはずです。重要なのは引数にself, clsが入っていることです。このself, clsとは何でしょうか?

self は、インスタンスとして作成する自分自身の事を指します。先ほどMapクラスでインスタンスを作成しました。変数はmapとしましたが、mapをselfとして読み替えると少しわかりやすくなるかと思います。Mapクラスのshowメソッドを見てください。

for array in self.map_lists:

この”self.map_lists”というリストからリストを取り出しているのは、わかるかと思いますが、これは一体どういうことでしょうか?

”map_lists”と言うのは、Mapクラスで定義したフィールドマップを表す2次元配列のことですね。これにselfが付くことで、インスタンスとして作成された自分自身の値として保持されるようになります。これは実体として操作可能です。例えばプレイヤーが移動してアイテムを入試したら、マップにあったアイテムを取り除くという操作が可能になります。

設計図としてのMapクラスの”map_lists”は実体ではないので、アイテムを減らしたりする操作ができません。もし、この操作が不要な場合はクラスメソッドが使えます。”cls”と言うのは、設計図のクラスであるというイメージを持つとわかりやすかもしれません。

実態ではないので、見て眺めることくらいしかできないというイメージです。みんなで使う設計図なので書込みしたり、ちょっと端っこを切ってみたりすることはできません。クラスメソッドは”cls”を使い、設計図を利用しようという考えです。もっと具体的に説明すると、クラス変数しか使えません。selfで記述したインスタンス変数は使えません。

最後にスタティックメソッドは、クラス変数も使えません。インスタンス変数はもちろん使えません。だから引数には何もありません。インスタンスメソッドは引数にインスタンス(self)を取りますので、全ての変数が使えます。クラスメソッドは引数にクラス(cls)を取りますのでクラス変数だけが使えます。スタティックメソッドは引数に何も取りませんので、クラス変数も使えません。

もっと深堀すると、クラスを継承した時の挙動がわかりやすいなど、話の終わりが見えなくなるのでここまでにしておきます。ここまでが理解できると、GitHubに置いてあるpython_rpgの中で使われている@staticmethodや@classmethodの意味がわかると思います。

次回、列挙型で定数化したアイテムを作る

さて、話はフィールドマップを表示するところに戻ります。

上のコードを実行すると、前回同様に半角英字の細長いフィールドマップが表示されます。

お世辞にも見やすいとは言えないフィールドマップ

・・・これは、見にくいですね(汗)

ということで、次回は列挙型を使用して半角英字を全角文字に変換したり、アイテムを配置したりという処理を一気に、しかも拡張性がある形で実装していきましょう。

まとめ

今回はマップクラスを作成し、メイン関数でクラスを呼び出す処理を追加しました。

作れば作る程、こうできたらいいなぁ、こうしておけば良かったかなぁ、という様な気持ちが出てくるかと思いますが、本来ならば、プログラミングの最初の一歩である全体の設計をしっかりと行うべきだと考えています。

しかしながら、まだまだ開発経験が浅い場合などは、全体の見通しを付けることが難しいと思います。そして今回の目標は基礎の定着ですので、クラスメソッドやインスタンスメソッドの違いの様な箇所をしっかり学習できたら良いと考えています。

次回もわかっているようで、わかっていないPythonの列挙対について説明していきます。

また皆さんにお会いできる事を楽しみにしております。

以下のDiscordサーバでも質問を受け付けています。お気軽に声をかけて頂けると幸いです!

この記事を書いた人

小幡 知弘

1990年茨城県神栖市生まれ
2013年大阪芸術大学卒業
Python×Webエンジニア