【実践OOPプロジェクト 完結編】RPGを拡張!継承で職業とアイテムを追加しよう

ALL

こんにちは、Tech Samuraiです!
前回の記事「【実践OOPプロジェクト #2】RPGの心臓部!ターン制バトルシステムを構築しよう」では、キャラクターたちが自動で戦うための戦闘ループを実装しましたね。

私たちのRPGは動くようになりましたが、まだ少しシンプルすぎます。プレイヤーも敵も画一的で、回復する手段もありません。シリーズ最終回となる今回は、これまで学んだ**継承**の力を最大限に活用して、この世界に深みと戦略性を与える、以下の3つの要素を追加します!

  • 独自のスキルを持つ専門職**「戦士 (Warrior)」クラス**
  • 特殊な攻撃を仕掛けてくる強敵**「オーク (Orc)」クラス**
  • 戦闘中にHPを回復できる**「回復薬 (Potion)」アイテム**

この最終章を通して、オブジェクト指向設計が、いかにプログラムを柔軟で拡張性の高いものにするかを、ぜひ体感してください!


ステップ1:継承で専門職を創る – 「Warrior」クラス

まずは、Playerクラスを継承して、より攻撃に特化した「戦士」クラスを作成します。戦士には、通常攻撃よりも強力ですが、少しだけ失敗する可能性もある必殺技「スマッシュ」を授けましょう。

# Playerクラスを継承
class Warrior(Player):
    def __init__(self, name, hp, attack_power):
        # 親クラス(Player)の初期化処理を呼び出す
        super().__init__(name, hp, attack_power)
        print(f"百戦錬磨の {self.name} が戦場に現れた!")

    # Warrior独自のメソッド
    def smash_attack(self, target):
        print(f"💥 {self.name} の必殺技! スマッシュ!")
        # 80%の確率で成功
        if random.random() < 0.8:
            damage = self.attack_power * 2 # ダメージは通常攻撃の2倍
            target.take_damage(damage)
        else:
            print("   しかし、攻撃は外れてしまった!")

Playerを継承しているので、通常のattack()ももちろん使えます。後ほど、戦闘ループの中で、プレイヤーが戦士だった場合にのみ、この「スマッシュ」を選択できるように変更します。


ステップ2:手強い敵を創る – 「Orc」クラス

同様に、Enemyクラスを継承して、少し賢い強敵「オーク」を作成します。オークは、時々、痛恨の一撃(クリティカルヒット)を放ってくるという特徴を持たせましょう。

ここでは、親のattackメソッドの振る舞いを変更する**オーバーライド**を使います。

# Enemyクラスを継承
class Orc(Enemy):
    def __init__(self, name, hp, attack_power):
        super().__init__(name, hp, attack_power)
        print(f"屈強な {self.name} が雄叫びをあげている!")

    # 親のattackメソッドをオーバーライド(上書き)
    def attack(self, target):
        print(f"💥 {self.name} の brutalな攻撃!")
        # 25%の確率でクリティカルヒット
        if random.random() < 0.25:
            print("   痛恨の一撃!")
            damage = self.attack_power * 2
        else:
            damage = self.attack_power

        target.take_damage(damage)

ステップ3:アイテムの導入とインベントリ

次に、戦闘の戦略性を高める「アイテム」を導入します。ここでもOOPの考え方を活かし、「アイテム」という大きな概念のクラスを作り、それを継承して具体的な「回復薬」クラスを作成します。

class Item:
    """アイテムの親クラス"""
    def __init__(self, name):
        self.name = name

    def use(self, target):
        # アイテムを使うという基本動作
        print(f"{target.name} は {self.name} を使った!")

class Potion(Item):
    """回復薬クラス"""
    def __init__(self, name, healing_amount):
        super().__init__(name)
        self.healing_amount = healing_amount

    def use(self, target):
        super().use(target) # 親のuseメソッドを呼び出す
        print(f"   HPが {self.healing_amount} 回復した!")
        target.hp += self.healing_amount # セッター経由で安全にHPを更新

そして、プレイヤーがアイテムを持てるように、Playerクラスの__init__を修正して、「インベントリ(所持品リスト)」を追加します。

# Playerクラスの__init__メソッドを修正
class Player(Character):
    def __init__(self, name, hp, attack_power):
        super().__init__(name, hp, attack_power)
        self.inventory = [] # インベントリを追加

ステップ4:最終形態!進化したバトルシステム

新しい職業、敵、アイテムに対応するため、戦闘を司るBattleクラスのstartメソッドを大幅にアップグレードします。プレイヤーの行動選択肢を増やし、より複雑な判断ができるようにします。

# Battleクラスのstartメソッドを修正
class Battle:
    # ... (initは同じ) ...
    def start(self):
        # ... (戦闘開始のメッセージなど) ...
        while self.player.is_alive() and self.enemy.is_alive():
            # ... (ステータス表示など) ...

            # --- プレイヤーの行動選択 ---
            print("どうする?")
            action_list = ["1: 攻撃"]
            # isinstance()で、プレイヤーがWarriorクラスのオブジェクトか判定
            if isinstance(self.player, Warrior):
                action_list.append("2: スマッシュ")
            if self.player.inventory: # インベントリにアイテムがあれば
                action_list.append("3: アイテム")
            action_list.append("4: 逃げる")

            action = input(" ".join(action_list) + ": ")

            # --- 行動の分岐 ---
            if action == '1':
                self.player.attack(self.enemy)
            elif action == '2' and isinstance(self.player, Warrior):
                self.player.smash_attack(self.enemy)
            elif action == '3' and self.player.inventory:
                # アイテムを使う処理
                for i, item in enumerate(self.player.inventory):
                    print(f"{i+1}: {item.name}")
                item_choice = int(input("どのアイテムを使いますか?: ")) - 1
                if 0 <= item_choice < len(self.player.inventory):
                    item = self.player.inventory.pop(item_choice)
                    item.use(self.player)
            elif action == '4':
                print(f"{self.player.name}は逃げ出した!")
                break
            else:
                print("コマンドが認識できませんでした。")

            # ... (敵のターンと戦闘終了の処理は同じ) ...

isinstance(player, Warrior)という関数を使うことで、「もしプレイヤーが戦士なら」という、オブジェクトの種類に応じた条件分岐が実現できています。これぞポリモーフィズム(多態性)の入り口です。


まとめ:そして、冒険は続く

3回にわたる実践OOPプロジェクト、お疲れ様でした!私たちは、シンプルなクラスの定義から始め、継承とカプセル化を駆使して、職業、強敵、アイテムといった要素を持つ、拡張性の高いRPGの世界を創造しました。

  • **継承**を使い、PlayerEnemyから専門的なクラス(Warrior, Orc)を派生させる方法。
  • 親クラスのメソッドを**オーバーライド**し、子クラス独自の振る舞いを実装する方法。
  • アイテムのような概念もクラスとして設計し、キャラクターの**属性(インベントリ)**として持たせる方法。
  • isinstance()を使い、オブジェクトの種類に応じて処理を分岐させる、より高度なロジック。

このプロジェクトで学んだ「**変更や拡張がしやすいように、役割ごとにクラスを設計する**」という考え方は、あらゆるソフトウェア開発の現場で通用する、非常に強力なスキルです。

このゲームには、まだまだ拡張の余地が無限に残されています。魔法使いクラス、新しいアイテム、複数の敵との戦闘、経験値とレベルアップ…。ぜひ、あなたがゲームマスターとなって、この世界をさらに豊かにしてみてください。

これにて、テキストベースRPG開発シリーズは完結です。素晴らしい冒険でしたね! Happy Coding!

コメント

タイトルとURLをコピーしました