0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ドラクエ風RPGを作る (第十八回) 戦闘処理の実装

Last updated at Posted at 2024-10-23

はじめに

今回は戦闘処理を実装します。
コマンドウィンドウで「たたかう」を選択すると、敵と戦えます。

コードの場所

GitHub に置いています。

作成したスクリプト

実行画面

screen.jpg

今回の内容

戦闘の実装

戦闘の実装するために、状態ハンドラ内のコードを修正します。
戦闘が終わった状態として、BATTLE_END を追加します。
以下は、状態ハンドラの呼び出し部分のコードです。
(変更点は前回から、BATTLE_END を追加しただけです)

main.py
class PyRPG:
    ...
    def check_event(self):
        # 表示されているウィンドウに応じてイベントハンドラを変更
        if game_state == TITLE:
            ....
        elif game_state == BATTLE_INIT:
            self.battle_init_handler(event)
        elif game_state == BATTLE_COMMAND:
            self.battle_cmd_handler(event)
        elif game_state == BATTLE_PROCESS:
            self.battle_proc_handler(event)
        elif game_state == BATTLE_END:
            self.battle_end_handler(event)

次に、戦闘の状態ハンドラのコード部分です。
長いですが、全体を見てみます。

main.py
class PyRPG:
    ...
    def calculate_damage(self, attack, defense):
        # 基本ダメージ = (攻撃力 / 2) - (守備力 / 4)
        # 最終ダメージ = 基本ダメージ ± (0~4のランダムな値)
        # 但し、0以下にならないようにする
        damage = attack // 2 - defense // 4
        damage = max(damage + random.randint(0, 4) * random.choice([-1, 1]), 1)
        return damage
    def battle_cmd_handler(self, event):
        """戦闘コマンドウィンドウが出ているときのイベントハンドラ"""
        global game_state
        # バトルコマンドのカーソル移動
        ...
        # バトルコマンドの決定
        if event.type == KEYDOWN and event.key == K_SPACE:
            if not self.msgwnd.next():
                #sounds["pi"].play()
                if self.battle.cmdwnd.command == BattleCommandWindow.ATTACK:  # たたかう
                    damage = self.calculate_damage(self.battle.player.player_status["attack"],
                        self.battle.monster_status["defense"])
                    self.battle.monster_status["hp"] -= damage
                    if self.battle.monster_status["hp"] < 0:  # 負にならないようにする
                        self.battle.monster_status["hp"] = 0
                    self.msgwnd.set(f"{self.battle.monster_status['name']}{damage}のダメージ!")
                    if self.battle.monster_status["hp"] <= 0:
                        self.battle.is_monster_dead = True
                        self.battle.is_player_turn = False
                        self.battle.cmdwnd.hide()
                        game_state = BATTLE_PROCESS
                        return
                elif self.battle.cmdwnd.command == BattleCommandWindow.SPELL:  # じゅもん
                    self.msgwnd.set("じゅもんを おぼえていない。")
                elif self.battle.cmdwnd.command == BattleCommandWindow.ITEM:  # どうぐ
                    self.msgwnd.set("どうぐを もっていない。")
                elif self.battle.cmdwnd.command == BattleCommandWindow.ESCAPE:  # にげる
                    self.msgwnd.set("ゆうしゃは にげだした。")
                self.battle.is_player_turn = False
                self.battle.cmdwnd.hide()
                game_state = BATTLE_PROCESS
    def battle_proc_handler(self, event):
        """戦闘の処理"""
        global game_state
        if event.type == KEYDOWN and event.key == K_SPACE:
            if not self.msgwnd.next():
                self.msgwnd.hide()
                if self.battle.is_monster_dead:
                    self.msgwnd.set(f"{self.battle.monster_status['name']}をたおした!")
                    game_state = BATTLE_END  # 戦闘終了してフィールドに戻る
                    return
                elif self.battle.cmdwnd.command == BattleCommandWindow.ESCAPE:
                    # フィールドへ戻る
                    #self.fieldMap.play_bgm()
                    game_state = FIELD
                else:
                    if self.battle.is_player_turn:
                        game_state = BATTLE_COMMAND
                        self.battle.cmdwnd.show()
                    else:
                        # モンスターが攻撃してくる
                        damage = self.calculate_damage(self.battle.monster_status["attack"],
                            self.battle.player.player_status["defense"])
                        self.battle.player.player_status["hp"] -= damage  # プレイヤーのHPを減らす
                        if self.battle.player.player_status["hp"] < 0:  # HPが負にならないようにする
                            self.battle.player.player_status["hp"] = 0
                        self.msgwnd.set(f"{self.battle.monster_status['name']}のこうげき! ゆうしゃに{damage}のダメージ!")
                        if self.battle.player.player_status["hp"] <= 0:
                            self.battle.is_player_deading = True
                            game_state = BATTLE_END
                        self.battle.is_player_turn = True
    def battle_end_handler(self, event):
        global game_state
        """バトルが終了したときの処理を行う"""
        # フィールドに戻る処理
        if event.type == KEYDOWN and event.key == K_SPACE:
            if not self.msgwnd.next():
                if self.battle.is_player_deading:
                    if self.battle.is_player_dead:
                        self.msgwnd.hide()
                        game_state = TITLE  # ゲーム状態をとりあえずタイトルに戻す
                    else:
                        # 一度に1回しか文字列を設定出来ないのでis_player_deadingとis_player_deadの2つに分ける
                        self.msgwnd.set("ゆうしゃはしんでしまった!")
                        self.battle.is_player_dead = True
                else:
                    game_state = FIELD  # ゲーム状態をフィールドに戻す
                    self.battle.cmdwnd.hide()  # コマンドウィンドウを隠す

ゲーム状態によって、処理を分岐しています。
また、ゲーム状態だけで分岐出来ない場合、例えば、self.battle.is_player_deading (プレイヤー死に途中) や self.battle.is_player_dead (プレイヤー死亡) などのフラグでも処理を分岐しています。

これは、メッセージ処理が少し使いづらい事に起因しています。
メッセージ処理は以下のように、まず self.msgwnd.set(...) で、メッセージウィンドウの文字列を設定します。これは1回だけ呼ばれるようにします。
次に、game_state を変更して(つまり、状態を分岐して)、「スペースキーが押されて」かつ「メッセージが空」の場合に、次の処理に進みます。
このような手順になるため、状態分岐が大量に発生します。
今後、攻撃回避や魔法処理など、もっとコードを追加していく必要があるので、より複雑になります。
その対策として次回では、「イベントスクリプト」を導入します。

def battle_cmd_handler(self, event):
    ...
    self.msgwnd.set(f"{self.battle.monster_status['name']}{damage}のダメージ!")
    ...
    game_state = BATTLE_PROCESS

def battle_proc_handler(self, event):
    ...
    if event.type == KEYDOWN and event.key == K_SPACE:
        if not self.msgwnd.next():
            # ここで次の処理

プレイヤーのステータスの追加

次のようなプレイヤーのステータス情報をデータ化しておきます。
GPT に聞いたら、ドラクエ1は最大レベルが30でステータスがデータ化されているとの事です。
本当かどうかは不明ですが、GPT にデータ表を作ってもらいました。

player_level_data.py
# プレイヤーのレベル1から30までのステータス(最大Lv30)
MAX_LEVEL = 30
level_stats = {
    1: {"hp": 15, "attack": 4, "defense": 2, "mp": 0, "experience": 0, "speed": 5},
    2: {"hp": 20, "attack": 5, "defense": 4, "mp": 2, "experience": 7, "speed": 6},
    3: {"hp": 25, "attack": 8, "defense": 6, "mp": 3, "experience": 23, "speed": 7},
    ...
    30: {"hp": 350, "attack": 140, "defense": 120, "mp": 60, "experience": 143000, "speed": 34},
}

次のように、ゲーム開始時にプレイヤーのステータスをリセットします。
Plyaer クラスに start() というメソッドを追加して、ゲーム開始時に呼び出します。

main.py
class Player(pygame.sprite.Sprite):
    def __init__(self, filename):
        ...
        self.start()
    def get_player_level_status(self, level):
        return {"name": "ゆうしゃ",
            "lv": level,
            "hp": player_level_data.level_stats[level]["hp"],
            "mp": player_level_data.level_stats[level]["mp"],
            "attack": player_level_data.level_stats[level]["attack"],
            "defense": player_level_data.level_stats[level]["defense"],
            "speed": player_level_data.level_stats[level]["speed"]}
    def start(self):
        # プレイヤーのステータス
        self.player_status = self.get_player_level_status(1)  # HPや攻撃力をリセット

ゲーム開始時の呼び出し部分は以下です。

main.py
class PyRPG:
    ...
    def title_handler(self, event):
        """タイトル画面のイベントハンドラ"""
        ...
        if event.type == KEYDOWN and event.key == K_SPACE:
            ...
            if self.title.menu == Title.START:
                self.player.start()  # Playerを初期化

おわりに

次回は、戦闘処理に対して、イベントスクリプトを使用して実装を変更します。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?