皆さん、こんにちは。ジョブチェンジのシステムがあるゲームが好きな小幡です。
前回までにやったことは、以下です。
- 大まかな流れを決める
- input関数を使う
- プレイヤーの名前を入力して出力する
- +演算子、format関数で文字列の結合
- 自作関数を作る
前回までは関数を使ったり、作ったりしてゲームを開発してきました。ゲームと言っても、まだプレイヤーの入力を受け付けて、入力されたものを表示するだけなので、まだまだ序盤の序盤と言ったところでしょうか?
ここまでご紹介した基礎が出来ていれば後から続く作業は、ほとんどその繰り返しです。個人開発であれば、これからご紹介するクラスなどは使用しなくても最後までゲーム開発が出来ると思いますが、後でゲームのおおまかな流れを変更したくなった時や、バグの修復が楽になりますので、頑張ってマスターしていきましょう。
クラスとは何か?
クラストは何か?を一言で説明すると、何かモノを作るための「設計図」や「金型」のイメージです。
詳しく説明しようとすると、恐らく全てを正確にお伝えすることは難しいので、今回のRPGを作るためだけの説明とします。今回はまずプレイヤークラスを作りますので、プレイヤーを作るための設計図や金型だと考えてみてください。
プレイヤーはゲームが始まると、名前が決定され設計図を基に作成されます。設計図(クラス)を作成するのは、プログラマーであるあなたですので、あなたがどんなプレイヤーを作成するか大まかに決めておく、という事になります。
例えば、最初のHPの値はいくらにするのか?最初のプレイヤーのレベルはいくつか?と言ったことをクラスとして設計図にまとめておくと、いざプレイヤーを作る時になった場合、この設計図のプレイヤーを作成することができるという訳です。
ここで一歩先に未来の話をしておくと、このプレイヤーのクラスは設計図ということで実際に設計図を基に実態を作るのは、クラス(設計図)を作ったあなたではないかもしれません。これは大規模開発の場合ではよくあることです。その時をイメージして、あなた以外の開発者がこのクラス(設計図)を使う事を想像してみましょう。クラスだけでなく、変数名や関数名でも同じことがいえるかもしれませんが、誰かに再利用される意識を持つことが重要です。そうすることで、変数名はどうしよう?と悩むことになると思いますが、これを繰り返すことで可読性が高く、修正しやすいコードが実現できると思います。
プレイヤークラスを作る
さて、話を戻して早速プレイヤークラスを作りましょう。
まず今回作成するRPGでプレイヤーを作成する場合、プレイヤーにどのようなステータスとアクションを持たせてあげれば良いでしょうか?プレイヤーは1人の登場キャラクターとしてイメージしやすいと思います。
まずは以下のステータス持たせたいと考えます。表の左が与えたいステータスで、右がそれを変数名にしたものです。
与えたいステータス | 変数名 | デフォルト値 |
名前 | name | ※入力された名前 |
最大ヒットポイント | max_hp | 200 |
ヒットポイント | hp | 200 |
最大マジックポイント | max_mp | 5 |
マジックポイント | mp | 5 |
攻撃力 | power | 10 |
防御力 | defense | 0 |
経験値 | exp | 0 |
レベル | level | 1 |
アイテムリスト | item_list | [] |
最後のアイテムリストは、ステータスではないので、もっと良い設計があると思いますが、あまり最初に作るプレイヤーの設計図で複雑な事をやるのも大変なので、このような形とします。
そして、この設計図に最初に与えてあげたいステータスの実際の数値を考えます。この最初に与える数値をデフォルト値と呼ぶことにします。初期値と呼んでも良いかもしれません。どちらも同じような意味だと考えます。
デフォルト値を一番右の列に表示しています。プレイヤーの名前はinput関数によって入力を受け付ける予定なので、デフォルト値はありません。
またアイテムリストのデフォルト値の”[]”は何も入っていない、(空の)リスト型という値です。
それでは今作成した表をもとにクラスを実装していきましょう。
player.pyのファイルを新規作成
まずmain.pyのファイルがある同じ場所にplayer.pyのファイルを新規作成しましょう。functionフォルダを作成するなど、プロジェクト全体の見通しをよくするための工夫はいくつか考えられますが、今回はシンプルにmain.pyと同じ階層にどんどんファイルを増やしていきます。
階層構造は以下のようになります。python_rpgフォルダの中に二つのファイル、main.pyとplayer.pyが存在している構造です。
python_rpg
┣ main.py
┗ player.py
そして、これまで考えたクラスの最も単純な設計図をコードに書いてみます。もちろんコードはmain.pyではなく、新しく作成したplayer.pyに記述します。これは1つのクラスを1つのファイルにまとめたいという事もねらいとして持っています。
class Player:
def __init__(self, name: str):
self.name = name
self.max_hp = 200
self.hp = 200
self.max_mp = 5
self.mp = 5
self.power = 10
self.defense = 0
self.exp = 0
self.level = 1
self.item_list = []
1行目にclassを記述後クラス名を大文字始まりで書きます。これでPlayerクラスの作成ができました。
コンストラクタ(__init__)
2行目ではコンストラクタと呼ばれる、この設計図(クラス)をもとに実態(インスタンス)を作成する際に呼び出されるものを定義しています。”def __init__”が見えたら、「これはコンストラクタなんだ」と言う意識が持てると良いでしょう。
そして引数は”(self, name: str)”となっています。これは1つ目の引数は”self”(セルフ)と呼ばれる、自分自身を引数に詰め込んでいるというイメージです。「自分自身を詰め込む?」と言う少しわかりずらい箇所ですが、基本的にはクラスの中で定義される関数は、1つ目の引数に自分自身の”self”を指定することが多いと覚えておきましょう。
これは先に説明しておくと、設計図によって作り出された実態の事を”self”として表現しているという考えです。つまり上のコードで考えると・・・4行目に”self.max_hp = 200”と言う記述がありますが、これは「自分自身(設計図によって作り出されたもの:インスタンス)の最大HPは”200”だ」と定義していることになります。逆に言えば、設計図(クラス)の最大HPを設定している訳ではありません。
この考え方はかなり難しく感じるかもしれませんので、先にクラスからインスタンスと呼ばれる、今まで「設計図から作られる実態(もの)」と表現していたものを作成するコードを書いてしまいましょう。
その前に2つ目の引数”name: str”ですが、これは「”name”という2つ目の引数を受け取りますよ(中身は文字列型です)」と言うように、(中身は文字列型です)というヒントを”: str”で表現しています。これはあくまでも型を表すヒントでうsので、コードとしては拘束力もありませんし、間違ってint型なども渡すことも可能です。わからない場合は削除しておいても構いません。
main.pyでplayerインスタンスを作成する
player.pyのPlayerクラスはあくまでも設計図です。この設計図をもとに、最初に作ったおおまかな流れの中でplayerの実態(インスタンス)を作成してみましょう。
再びコードはmain.pyに戻ります。Playerクラスをmain関数で使用するためには、Playerクラスのインポートが必要になります。インポートというのは先ほど作成した別のファイルにあるものを、こっちのファイルで使いますよと宣言するようなものです。
これがないと、別のファイルで作成たコードは、こちらのファイルでは機能しません。
インポートが完了したら、実際にplayerのインスタンスを作成します。ここまでのコードを以下に記します。
from player import Player
def main():
# タイトルを表示
input('=== Game Start! ===')
# プレイヤー名の入力
print('プレイヤー名を入力してください。')
player_name = input()
player = Player(player_name)
print('ようこそ、{}さん!'.format(player.name ))
1行目に「player.pyからPlayerクラスをインポートする」という意味のインポート文を記述します。”from player”が「player.pyから」を意味していて、”import Player”が「Playerクラスをインポートする」を意味しています。
2行目と3行目は改行しておきます。詳しいことは省略しますが、見やすいコードのために改行をしておくという意味で理解してもらっておければと思います。
そして前回までに作成していたmain関数が始まります。
10行目にinput関数を使用して、名前の入力を促している処理までは同じです。
11行目に注目しましょう。ここで新しく出てきた”player”(先頭小文字)の変数に何かを入れているという事は、簡単にわかると思います。しかし、何を入れているのでしょうか?詳しく説明していきます。
”Player(player_name)”となっていますので、”Player”(先頭大文字)の引数(括弧の中身)にinput関数で入力を受け取ったプレイヤーの名前の文字列が入っていることは理解できるかと思います。
引数を取るということは、”Player”は今まで勉強した関数なのかと聞かれたら、そうではありません。これは先ほど作成しインポートした”Player”クラスを意味しています。
そしてこの”Player”クラスの引数にプレイヤーの名前を入れているということになります。”Player()”というのは、__init__で説明した様に、一番最初に呼び出されるコンストラクタを意味しています。この渡された引数であるプレイヤー名と設計した設計図をもとにして、”player”が誕生するというイメージです。
コンストラクタで登場した”self”は1つ目の引数でしたが、”Player(player_name)”で渡される”player_name”は2つ目の引数として処理されます。これも非常にわかりずらい話だと思いますが、この場合だと”player_name”は__init__では”name”として扱われることになります。
def __init__(self, name: str): # name = player_name として扱われる
self.name = name # 渡されたプレイヤー名を入れる
ということで”Player(player_name)”によって、player.pyにあるPlayerクラスの__init__が呼び出される流れが理解できたでしょうか?これは1回で理解できるものではないと思います。
さて、こうして”player_name”は2つ目の引数として渡された結果、”name”として扱われる事になりました。この”name”は次の行で”self.name”に入れられています。
これは__init__の説明でお伝えした通り、設計図によって作成された実態”self”の”name”が”name”になるということです。非常にややこしいですね。
これはmain関数で作成された”player”の名前がinput関数によって入力された文字列になるということです。こうすることで、最終的に以下のコードが実現できます。
print('ようこそ、{}さん!'.format(player.name ))
format関数の中身に注目してください。前回までは引数が”player_name”(中央アンダーバー)だったものが、”player.name”(中央ドット)に変わっていることがわかるでしょうか?
この”player.name”は今まで理解を難しくしてきた”self.name”のセルフの部分がインスタンスに変わっていると見ることができれば、ここまでの流れを完全に理解できたと言えるかもしれません。
余談ですが、本来は”player.name”の様に、作成したインスタンスの値を直接使用することは避けた方が良いです。これはクラス変数とインスタンス変数というクラスの中で使用する2つの変数を理解できたときに改めて、その意味を理解できれば良いと考えます。今回はこのまま進みます。
ここまでを実行して確認する
ここまで作業できたら実際にコードを実行して、動作確認してみましょう。
何もエラーが出なければ良いですが、今回は先頭大文字にしなければならなかったり、”__init__(self……”など、少し紛らわしい記述がありますので、エラーが発生する可能性が高いです。基本的にはタイピングミスが多いと思いますので、記述間違いが無いか確認してみましょう。
本当はエラーコードを確認し、理解し、修正するのが良いですが、最初のうちはエラーコードの意味を理解するのは難しいと思います。プログラミングの辛いところはバグ修正かもしれませんが、1つ1つエラーが無くなるように動作確認していきましょう。
無事に動作確認できたでしょうか?
するとどうでしょう?新しくクラスを作成しましたが、残念ながら前回の最後と同じ結果になります。input関数で入力した文字列が、printされる事に変わりありませんね。
では、クラスを使ったことで感じることのできるメリットは何でしょうか?
それは、設計図を作る時に考えたデフォルト値が”player”に設定された状態で作成されるということです。コードを以下のように修正してみましょう。クラスによって作成された”player”のステータスを表示することができます。
# プレイヤー名の入力
print('プレイヤー名を入力してください。')
player_name = input()
player = Player(player_name)
print('ようこそ、{}さん!'.format(player.name ))
print('HP: [{}/{}]'.format(player.hp, player.max_hp))
print('MP: [{}/{}]'.format(player.mp, player.max_mp))
print('POW: {}'.format(player.power))
print('DEF: {}'.format(player.defense))
print('LEVEL: {}'.format(player.level))
print('EXP: {}'.format(player.exp))
ここで記述している”player.~~”という箇所は__init__で使用した”self”と同じイメージで捉えることができれば、__init__で行われた数値の代入を読み解くことができるのではないでしょうか。
実行結果は以下のようになります。
プレイヤーの名前を入力した後に「ようこそ、〇〇さん!」と表示されるのは、今までと同じですが、その後にプレイヤーのステータスが表示されるようになりました。名前、HPからEXPまで表示されているのが確認できると思います。
これでようやくPlayerクラス(設計図)から実態(インスタンス)を作成することが出来たという実感が持てたでしょうか?まだまだ理解するのは難しいかもしれません。というのも、Playerクラスではインスタンスを1つしか作成していません。これでは、あまりインスタンスの素晴らしさを感じる事はできないかもしれません。
またクラスの中にはまだステータスとして作成されたインスタンス変数しか記述されていません。このほかにもクラスの中には関数を記述したりできます。今回のゲーム内ではプレイヤーがレベルアップするときに、クラス内にレベルアップ関数を作成する予定です。イメージとしては、モンスターと戦闘し勝利した後、経験値を取得します。その取得した経験値に応じてプレイヤーのレベルが上昇するというものです。
他にもプレイヤーの取るアクションはどんなものが想定されるでしょうか?RPGにおいてプレイヤーは様々な行動をとることになると思いますが、その行動を関数として定義してあげることで、プレイヤーはその行動が可能になるというイメージです。
まとめ
いかがだったでしょうか?今回はクラスのさわりの部分を紹介しました。Playerクラスを作成しプレイヤーの実態(インスタンス)を作成し、ステータスを表示させることに成功しました。クラスで出来ることは、まだまだたくさんありますし学習できることはたくさんあります。
今回はクラスそのものは設計図であり、クラスのコンストラクタを使うとインスタンスである実態が作成できるという事を、理解頂けたら第三回は大成功という想いです。
コードに記述する内容も急にたくさん出てきてしまい、混乱が起きているかもしれませんが、記述する場所やスペルミスが無いように気を付けてもらえればと思います。
次回はいよいよフィールドマップの作成に入ります。
フィールドマップはコマンドラインでマップを表示するために配列を使用します。一度わかれば、どんどんマップを大きくすることもできますし、マップを表示させる要素を作成するために列挙対(Enum)というものも組み合わせることで、一気に馴染みのあるフィールドマップが作成可能です。
それではまた次回、お会いできることを楽しみにしております。次回は以下リンクよりどうぞ
以下のDiscordサーバでも質問を受け付けています。お気軽に声をかけて頂けると幸いです!