初めに。
どうも、Python歴10か月で実務歴1か月の者です。前職在籍中にプログラミングスクールを卒業し、今年2月から働いております。幸いにも早い段階でPython/ITに関わる事が出来ているのですが、メンバーとの実力差を実感し苦戦を強いられる日々を送っております。その差を少しでも埋めるために再び執筆を再開しました。
そして今回はクラスとオブジェクト指向について触れます。
「今更このレベル…」と思うかもしれませんが、私データ分析選考しでオブジェクト指向について深入りせず卒業しました。
しかし実務では根底にオブジェクト指向がある事に気づき、それが業務における糸口と判断し本記事にまとめる事にしました。
この記事の目的
- RPGを通してクラスやオブジェクト指向に慣れる。
- 実践的で親しみやすいコードから理解を試みる。概念や語彙は後回し。
- 作成したキャラクタと敵でRPGのバトルを体験する。
Pythonのクラスやオブジェクト指向について、実践的でイメージしやすいコードを通して理解することを目指します。
オブジェクト指向の概念や語彙に馴染むことは容易ではありません。私も多くの記事や動画を参照しましたが、動物や車の例を提示されてもその先何をしたいのかイメージできず苦戦を強いられました。そこで今回はRPGに焦点を当て、より親しみと目的をもってオブジェクト指向の理解を深めようと思います。RPGを知らない人はここにはいませんからね。
対象
- pythonを扱っているけど、クラスやオブジェクト指向についてあいまいな人。
- RPGのシステムを最低限把握している人。
- 納得していない部分がある場合でもスキップして先に進める人。
- 結論や要点を急いで求めず、ゆっくり順々と実装して頂ける人。
クラスやオブジェクト指向の概念や結論のみ聞かされても、芯に理解できず分かった風になると考えております。特に selfや__ init __ は全体像や流れを把握してから見るとより理解しやすいと思います。それ故急いで結論を求めず、本記事で確かな目標を持ち順々実装しながら理解を深めていきましょう。
本編
第1章。 前提と設定。
第2章。キャラクタを作ってみる。クラス作成
class warrior():
def __init__(self):
print("私は戦士である!")
def warrior_attack(self):
print("これが剣技だ!")
def warrior_deffense(self):
print("これが戦士の守りだ!")
Kevin = warrior()
# 私は戦士である
現段階でコードを解読をしなくても大丈夫です。
何も分からない状態で selfとは?init とは?と調べ始めると詰まりますので。
見てほしい点は、
- クラスwarriorというものからKevinという戦士のキャラクタが出来上がった。
- 完成された瞬間、print等していないのに("私は戦士である!")で出力された。
イメージとしては、warriorは役職でKevinは実際のキャラクタです。
RPGの鉄板、「役職選択 ⇒ 名前を付けてキャラクリエイト完了」
です。
「役職選択⇒ 名前を付けてキャラクリエイト完了」は専門用語で言うと、
オブジェクト作成 又は インスタンス化と言います。詳細は4章にて。
それではこのKevin(object)を使ってテストしてみましょう。
Kevin.warrior_attack()
Kevin.warrior_deffense()
# 結果
これが剣技だ!
これが戦士の守りだ!
細かい所は後述しますが、
Kevinがwarrior_attack()
を使うと、"これが剣技だ!"と表示されました。
ここで言う、warrior_attack()はクラスwarriorから作成された戦士のみが使用できる特技です。
この役職(クラス)毎の特技をメソッド
と言います。例:戦士のメソッドは「剣技」 魔術師は「詠唱」 盗賊は「奇襲」等。
少しづつRPGのキャラクタを作っていく…!というイメージが湧きましたでしょうか。
「本当にこれクラスやオブジェクト指向? 本題は?」
と思う気持ちも分かります。
かつて私も最短で要点のみ絞って学習しましたがかえって身につかないものでしたのでもう少し付き合って下さい。
第3章。 キャラクタのパラメータ設定や内容を深める。クラスからオブジェクト大量生産
class warrior():
def __init__(self,hp,mp,power,speed):
print("私は戦士である!")
self.hp = hp
self.mp = mp
self.power = power
self.speed = speed
def attack(self,target):
target.hp = target.hp - self.power #ダメージとして相手のhpから自分のpowerの値を引く
def warrior_attack(self,target):
print("これが戦士の力だ!")
target.hp = target.hp - self.power*1.5 # ダメージとして相手のhpから自分のpower*1.5の値を引く
self.hp -= 15 # 自傷ダメージ
Kevin = warrior(200,50,20,20)
見てほしい所は
-
「hp,mp,power,speed」
が引数に追加されている。 - それらがどうやら下記の部分、__ init __ の範囲内にて定義されている。
- attackとwarrior_attack という特技(メソッド)が設定され、内容はhpとpowerが関係しているらしい。
詳細は第4章で説明します。
RPGで攻撃を行うとすれば普通
- 対象を指定
- 攻撃力が高いほどダメージを与えられる。
その背景を熟知しているからこそ、説明せずとも下記コードが見えてきて、selfがフワッと分かってきませんか?
def attack(self,target): #引数 target 攻撃対象を指定
target.hp = target.hp - self.power #ダメージとして相手のhpから自分のpowerの値を引く
def warrior_attack(self,target): #引数 target 攻撃対象を指定
print("これが戦士の力だ!")
target.hp = target.hp - self.power*1.5 # ダメージとして相手のhpから自分のpower*1.5の値を引く
self.hp -= 15 # 必殺技による自傷ダメージ
そして下記のように記載するとステータスが確認できます。
ここも理屈というより書き方で覚えたほうが良いと思います。object.hp (Kevinのhp)
print(Kevin.hp)
print(Kevin.mp)
print(Kevin.power)
一度クラスを作成すれば、このように大量の戦士を作成する事ができます。
これがクラスのメリットの一つで、一度設定すればキャラクタ(object)
を大量生産する事ができます。同じ戦士でも個体差があったほうがRPGとしての深みが増しますよね。
Kevin = warrior(200,50,20,20) # バランス型
Stan = warrior(120,60,10,40) # パワースピード型
Guts = warrior(600,280,5,120) # 狂戦士
Tanaka = warrior(80,20,5,30) # ハズレ
第4章 ここまでのロジックや語彙の確認。 self と __init__ インスタンス化
-
__ init __
キャラクタ(object)作成時に自動的に行われる初期設定です。
キャラクタ作成時に自動的に print("私は戦士である!") の処理の実行や、ステータス設定が設定されるのは、それらの処理が__ init __ の範囲にあるからです。
クラスの__ init __ 範囲内はキャラクタ(object)作成時に自動で実行される。
-
self
self とはキャラクタ,ここでいうKevin自身の事です。キャラクタ作成後、その作成元のクラスのself は全てキャラクタの名前を当てはめてみると、引数がキャラクタのステータスに対応している事が確認できるはずです。
前述したinitにて引数がそのままキャラクタ(object)のステータスに設定されております。 -
オブジェクト化(インスタンス化)
クラスからキャラクタ作成 = オブジェクト生成/オブジェクト化=インスタンス化
言い方が異なるのみで基本的に一緒です。
今回はwarrior というクラスからKevinというオブジェクトを生成(インスタンス化)しました。
重複しますが、クラスwarrior(役職)からKevinというキャラをクリエイトをした という事です。 -
メソッド
クラスwarriorから作成されたキャラの特技です。
戦士は剣技、魔術師は魔法詠唱、僧侶は奇跡を使いますよね。
クラスwarrior から作成されたキャラクタはwarrior特有の特技を発動する事ができます。その特技をメソッドといいます。現段階ではwarriorしか作成してないのでイメージは湧きにくいかもしれませんが、大量のクラスを作成すると分かってきます。
第5章。様々な役職を設定し、作成してみる。
class Wizard():
def __init__(self,hp,mp,power,m_pow,speed):
print("私は魔術師")
self.hp = hp
self.mp = mp
self.power = power
self.m_pow = m_pow
self.speed = speed
def attack(self,target):
target.hp = target.hp - self.power #ダメージとして相手のhpから自分のpowerの値を引く
def fire(self,target):
print("ボヤ騒ぎ")
target.hp = target.hp - self.m_pow * 2 # ダメージとして相手のhpから自分のm_pow*1.5の値を引く
self.mp -= 6 # 魔法攻撃によるmp消費。
class Thief():
def __init__(self,hp,mp,power,m_pow,speed):
print("盗賊見参")
self.hp = hp
self.mp = mp
self.power = power
self.m_pow = m_pow
self.speed = speed
def attack(self,target):
target.hp = target.hp - self.power #ダメージとして相手のhpから自分のpowerの値を引く
def fire(self,target):
print("バックスタブ")
target.hp = target.hp - self.speed * 1.3 # ダメージとして相手のhpから自分のpower*1.5の値を引く
Kanako = Wizard(60,5,30,70,10)
Jitan = Thief(120,20,10,10,50)
このように好きな役職を設定しどんどんキャラクタを作成してみてください。回復を行う聖職者や魔法剣士等…楽しくなってくるはずです。
第6章 役職の共通項目を一本化する。(クラスの継承)
その概念こそが継承です。実際に見て感じたほうが早いです。
#全キャラクタの共通項目
class Character():
def __init__(self,hp,mp,power,m_pow,speed):
self.hp = hp
self.mp = mp
self.m_pow = m_pow
self.speed = speed
def attack(self,target):
target.hp = target.hp - self.hp
class Warrior(Character):
def warrior_attack(self,target):
target.hp = target.hp - self.power *1.5
self.hp -= 15
class Wizard(Character):
def fire(self,target):
print("ボヤ騒ぎ")
target.hp = target.hp - self.m_pow * 2
self.mp -= 6
class Thief(Character):
def fire(self,target):
print("バックスタブ")
target.hp = target.hp - self.speed * 1.3
随分とすっきりしたと思いませんか。継承を行っていないコードと比較すると、無駄が少ないですよね。
まとめ
ここまでが大まかな流れです。今回RPGを通して学んだ事は下記3つです。
- クラスからオブジェクトを生成(インスタンス化)とそのメリット
- __ init __ のやselfの役割
- 継承
最後に今まで学習した内容をよりブラッシュアップしてRPGらしく、
具体的にはキャラクタが敵にダメージを与える部分まで再現しました。
今回学習した.selfとf文字列を理解していれば把握できるはずです。是非実行したりコードを分解してみてください。
class Character():
def __init__(self,hp,mp,power,m_pow,speed):
self.hp = hp
self.mp = mp
self.power = power
self.m_pow = m_pow
self.speed = speed
def attack(self,target):
target.hp = target.hp - self.hp
class Warrior(Character):
def warrior_attack(self,target):
print("これが剣技だ")
damage = self.power * 2
target.hp = target.hp - damage
self.hp -= 15
print(f"{damage} のダメージ")
print(f"敵の残りHP: {target.hp}")
class Wizard(Character):
def fire(self,target):
print("ボヤ騒ぎ")
damage = self.m_pow * 1.3
target.hp = target.hp - damage
self.mp -= 6
print(f"{damage} のダメージ")
print(f"敵の残りHP: {target.hp}")
class Thief(Character):
def Stelth_attack(self,target):
print("バックスタブ")
damage = self.speed * 1.3
target.hp = target.hp - damage
print(f"{damage} のダメージ")
print(f"敵の残りHP: {target.hp}")
class Enemy(Character):
def Breath(self,target):
damage = self.power + 50
target.hp = target.hp - damage
print(f"{damage} のダメージ")
Kevin = Warrior(60,5,30,70,10)
Kanako = Wizard(60,5,30,70,10)
Jitan = Thief(120,20,10,10,50)
Dragon = Enemy(400,0,40,0,10)
# Kevinが敵に攻撃
Kevin.warrior_attack(Dragon)
print("------------------------")
# Kanakoが敵に攻撃
Kanako.fire(Dragon)
print("------------------------")
# Jitanが敵に攻撃
Jitan.Stelth_attack(Dragon)
次回は
キャラクタの作成と簡単な攻撃のやり取りが出来たので、次回はfor文やwhile文を用いて実際に戦闘を行ってみようと思います!
またオブジェクト指向としてまだ完成度を高める事や誤字脱字説明不足等も気づき次第
どんどん修正していこうと思います。
更新履歴
日付 | 更新内容 |
---|---|
2023/03/12 | 全体公開 |