はじめに
今回は戦闘処理を実装します。
コマンドウィンドウで「たたかう」を選択すると、敵と戦えます。
コードの場所
GitHub に置いています。
実行画面
今回の内容
戦闘の実装
戦闘の実装するために、状態ハンドラ内のコードを修正します。
戦闘が終わった状態として、BATTLE_END を追加します。
以下は、状態ハンドラの呼び出し部分のコードです。
(変更点は前回から、BATTLE_END を追加しただけです)
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)
次に、戦闘の状態ハンドラのコード部分です。
長いですが、全体を見てみます。
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 にデータ表を作ってもらいました。
# プレイヤーのレベル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() というメソッドを追加して、ゲーム開始時に呼び出します。
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や攻撃力をリセット
ゲーム開始時の呼び出し部分は以下です。
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を初期化
おわりに
次回は、戦闘処理に対して、イベントスクリプトを使用して実装を変更します。