CODE Python RPG

独学でPythonでRPGを作成する(第41回)モンスターの戦闘行動

投稿日:

こんにちは。
「Pythonでつくる ゲーム開発 入門講座」の
Chapter11,12(本格RPGを作ろう!全編・後編)を基にRPGを作っていきます。

前回は補助系の呪文のラリホー・ラリホーマを追加しました。

ただ、やり残しがあるので今回はそれを追加します。
もう一つ、今まで戦闘時のモンスターの行動は攻撃のみでしたが、他の行動もできるようにします。

目次

  1. モンスターの行動(1)
  2. 眠り状態の残りの設定
  3. モンスターの行動(2)
  4. 一定時間停止
  5. バグ修正
  6. 最後に

モンスターの行動(1)

前回、ラリホー・ラリホーマで
相手を眠り状態にしました。

ただ、眠り状態でも攻撃できて
しまうので、
戦闘中に眠っている場合は
攻撃しないようにします。

攻撃しないようにしますが、
今まで戦闘中のモンスターの行動は
攻撃のみでした。

def get_battle_turn(player):
(省略)

    if turn_key is player:
        return turn_key, player.act
    else:
        return turn_key, 13
if turn_key is player:
    return turn_key, player.act

プレイヤーの行動は
「攻撃」や「呪文」などによって
actに入る数字が変わります。
※攻撃:12、呪文:26

else:
    return turn_key, 13

ただ、モンスターの場合は
全て13にしていました。
※モンスターの攻撃:13

なので、モンスターにも
変数actを追加します。

 

 

charaの変更

プレイヤーと同様にモンスターも
変数actを利用することになるので、
Charaクラスに変数actを追加して、
Braveクラスから変数actを削除します。

class Chara():
    def __init__(self):
        self.act = 0 # 戦闘のコマンド(行動)12:プレイヤー攻撃、13:モンスター攻撃、18:防御
(省略)

class Brave(Chara):
    def __init__(self):
(省略)

        # self.act = 0 # 削除

 

ラリホー・ラリホーマが効いた場合
actを28にします。
※後程、眠り状態の場合の
 行動をidx28で設定します。

class Chara():
(省略)

    def rariho(self, target): # ラリホー
        if random.randint(0, 99) < spell_up_dict["ラリホー"]: # 呪文の上限値が成功率
            target.act = 28 # 眠っている行動
(省略)

    def rarihoma(self, target): # ラリホーマ
        if random.randint(0, 99) < spell_up_dict["ラリホーマ"]: # 呪文の上限値が成功率
            target.act = 28 # 眠っている行動
(省略)
target.act = 28

を追加しました。

 

次に、モンスターの行動を決める
set_monster_comメソッドを
追加します。

class Monster(Chara):
(省略)

    def attack(self, obj):
(省略)

    def set_monster_com(self): # モンスターの戦闘コマンド
        if self.flag_sleep:
            self.act = 28 # 眠っている(無くても良い)
        else:
            self.act = 13 # 攻撃
if self.flag_sleep:
    self.act = 28 # 眠っている(無くても良い)

眠り状態の場合は
actを28にします。
※眠り状態になった時に
 actを28にしているので
 ここで改めて28にしなくても
 問題ないはず、たぶん(^_^;)

else:
    self.act = 13 # 攻撃

眠り状態でない(起きている)場合は
actを13にして、通常攻撃します。

 

chara.pyの変更は以上です。

 

 

battleの変更

battle.pyを変更します。

def main(screen, clock, font, fontS, player, area):
(省略)

        elif idx == 23: # ターンセット
            set_battle_turn(btl_start, player)
            for mon in monster: # 追加
                mon.set_monster_com() # 追加
            btl_start = 0 # 戦闘は通常に戻す
            idx = 24
for mon in monster:
    mon.set_monster_com()

の2行を追加しました。

生存モンスターの行動を
set_monster_comメソッドで
確認します。
※変数actを指定します。

 

そして、get_battle_turn関数の
戻り値を変更します。

変更前

def get_battle_turn(player):
(省略)

    if turn_key is player:
        return turn_key, player.act
    else:
        return turn_key, 13
(省略)

def main(screen, clock, font, fontS, player, area):
(省略)

        elif idx == 24: # ターン確認
            tmr = 0
            turn_obj, idx = get_battle_turn(player)

変更後

def get_battle_turn():
(省略)

    return turn_key, turn_key.act

def main(screen, clock, font, fontS, player, area):
(省略)

        elif idx == 24: # ターン確認
            tmr = 0
            turn_obj, idx = get_battle_turn()
return turn_key, turn_key.act

今までモンスターの行動は
全て13(攻撃)でしたが
プレイヤーもモンスターも
変数actを返します。
※引数のplayerも不要になります。

 

次に、眠り状態の時の
行動を設定します。
actが28の時、眠り状態なので
idx28に設定します。

def main(screen, clock, font, fontS, player, area):
(省略)

        elif idx == 28: # 眠り状態
            draw_battle(screen, fontS, turn_obj, player)
            if tmr == 1:
                if turn_obj.flag_sleep: # 眠っている
                    set_message(turn_obj.name + "は ねむっている")
                else: # ラリホーは効いたけど、攻撃などで起きた
                    idx = 24 # 何も表示させない
            if tmr == 6:
                init_message()
                idx = 24 # ターン確認へ
if turn_obj.flag_sleep: # 眠っている
    set_message(turn_obj.name + "は ねむっている")

眠り状態の場合、
何もせずメッセージを表示します。

else: # ラリホーは効いたけど、攻撃などで起きた
    idx = 24 # 何も表示させない

前回、設定しましたが、
攻撃で目を覚ますことがあります。

目を覚ました時に眠り状態は
invalid_sleepメソッドで解除しますが、
行動(act)は28のままなので、
ここで設定します。
※何もせず、次のキャラの行動に移ります。

 

これで眠り状態のモンスターは
攻撃しなくなりました。

 

 

眠り状態の残りの設定

眠り状態の時の設定を
もう少し追加しておきます。

(現状では)
モンスターがラリホーなどで
プレイヤーを眠り状態に
することはありませんが、
プレイヤーが
眠り状態になった時のことを
追加しておきます。

まず、プレイヤーが眠り状態の時に
攻撃されたら目を覚ますことが
あるようにしておきます。

def main(screen, clock, font, fontS, player, area):
(省略)

        elif idx == 13: # 敵のターン、敵の攻撃
(省略)

            if tmr == 15:
                player.hp = player.hp - dmg
                if player.hp <= 0:
                    player.hp = 0
                    idx = 15 # 敗北
                    tmr = 0
                else:
                    if dmg != 0 and player.flag_sleep: # 攻撃を受けた かつ 生きている かつ 眠っている
                        if random.randint(0, 99) < 55:
                            player.invalid_sleep()
                            set_message(player.name + " はめをさました!")
(省略)

前回のモンスターの時とほぼ同じです。

あと、眠り状態の時は
コマンド選択できないようにします。

変更前

def main(screen, clock, font, fontS, player, area):
(省略)

        elif idx == 27: # 呪文効果の確認
(省略)

            init_message()
            idx = 11

変更後

def main(screen, clock, font, fontS, player, area):
(省略)

        elif idx == 27: # 呪文効果の確認
(省略)

            init_message()
            if player.flag_sleep:
                idx = 23 # ターンセット(眠っている時はコマンド選択できない)
            else:
                idx = 11

眠り状態の場合は
idx11のコマンド選択には
行かないようにします。

 

もう一つ、眠り状態の時に
相手の攻撃をかわさないようにします。

chara.pyです。

class Brave(Chara):
(省略)

    def attack(self, obj):
(初略)

        if not obj.flag_sleep: # 眠っていないなら(眠っていたらかわさない)
            if obj.quick <= 400:
                avoid = 1/64 + obj.quick//80 * (1/192)
            elif obj.quick <= 500:
                avoid = 1/64 + obj.quick//80 * (1/192) + (obj.quick-400)//25 * (1/32)
            else:
                avoid = 1/6
            if random.random() < avoid:
                dmg = 0
                if random.randint(0, 1) == 0:
                    message.append(obj.name + "は ひらりと みをかわした!")
                else:
                    message.append(obj.name + "は すばやく みをかわした!")
                return dmg, message
(省略)

class Monster(Chara):
(省略)

    def attack(self, obj):
(省略)

        if not obj.flag_sleep: # 眠っていないなら(眠っていたらかわさない)
            if obj.quick <= 400:
(省略)
if not obj.flag_sleep: # 眠っていないなら(眠っていたらかわさない)

attackメソッドに1行追加しただけです。

 

眠り状態については以上です。

 

 

モンスターの行動(2)

戦闘時のモンスターの行動について
今回で眠り状態を追加しましたが、
今までは基本的に攻撃のみでした。

が、攻撃以外の行動もできるようにします。

最初に「モンスター」シートの
M列に特技を追加します。

特技 = 戦闘中の行動とします。

複数の行動がある場合は
「,」で区切ります。

Green slimeの行動は「攻撃」と「その他」、
Red slimeの行動は「攻撃」のみ
です。
※「その他」は今回用に適当に作ったもので、
 今後変えると思います。

 

 

charaの変更

作った特技をchara.pyで読み込みます。

(省略)

mon_area = ws_mon.col_values(12) # L列 エリア
mon_skill = ws_mon.col_values(13) # M列 特技 ←追加
(省略)

class Monster(Chara):
    def __init__(self, num):
(省略)

        self.skill = mon_skill[num].split(",")

M列をリストmon_skillに入れて
それをsplitを使って「,」で分割します。
※splitの詳細はこちら

Green slimeは['攻撃', 'その他']、
Red slimeは['攻撃']
となります。

これを今回で追加した
set_monster_comメソッドで
使います。

class Monster(Chara):
(省略)

    def set_monster_com(self): # モンスターの戦闘コマンド
        if self.flag_sleep:
            self.act = 28 # 眠っている(無くても良い)
        else:
            skill_no = random.randint(0, len(self.skill)-1) 
            if self.skill[skill_no] == "攻撃":
                self.act = 13 # 攻撃
            else:
                self.act = 29 # 特技
skill_no = random.randint(0, len(self.skill)-1) 

分割したskillをランダムで選択します。

if self.skill[skill_no] == "攻撃":
    self.act = 13 # 攻撃
else:
    self.act = 29 # 特技

skillが「攻撃」であれば13、
skillが「その他」であれば29
にします。

13はモンスターの攻撃(idx13)
29はこの後、新しく追加します。

 

charaの変更は以上です。

 

 

battleの変更

特技の行動をするidx29を追加します。

def main(screen, clock, font, fontS, player, area):
(省略)

        elif idx == 29:
            draw_battle(screen, fontS, turn_obj, player)
            if tmr == 1:
                dmg_eff = 0
                set_message(turn_obj.name + "は どうしていいのか わからない!")
            if tmr == 6:
                init_message()
                idx = 24

今回、追加した特技で「攻撃」の他は
「その他」のみです。

「その他」の行動は
何もしないことにしています。
こちらを参考にさせて頂きました。

変数dmg_effはモンスターの攻撃時に
画面を揺らす為に使います。
今回は画面を揺らさないので
0にしています。

とりあえず、これでモンスターも
「攻撃」以外の行動ができるようになりました。

こんな感じです。

ちなみにモンスターも攻撃以外の
行動をとれるようにしたいことは
19回目で記載していました。
長かったです。。(^_^;)

 

battleの変更も以上です。

 

 

一定時間停止

ここから話が変わって
時間停止についてです。

今まで戦闘メッセージで
少しだけ時間を停止する時は
time.sleepを使っていました。

これを
pygame.time.delay
に変更します。
※詳細はこちら

特に理由は無く、何となくです(^_^;)

(省略)
delay_speed = 500 # 止める時間(ms)
escape_rate = 100 # 逃げる確率(100は100%逃げる)

delay_speedに止める時間を
入れておきます。
※大文字にすべきかもしれません。

あとは、time.sleep を pygame.time.delay に
変更します。

time.sleepは使っている場所によって
止める時間を変えていましたが
変更後は全てdelay_speedを使います。

変更前

def main(screen, clock, font, fontS, player, area):
(省略)

        elif idx == 10: # 戦闘開始
(省略)

            elif tmr == 6:
                if len(monster) >= 2:
                    set_message(monster[1].name + " が現れた!")
                    draw_battle(screen, fontS, turn_obj, player)
                    time.sleep(0.3)
            elif tmr == 7:
                if len(monster) >= 3:
                    set_message(monster[2].name + " が現れた!")
                    draw_battle(screen, fontS, turn_obj, player)
                    time.sleep(0.3)
            elif tmr == 8:
                if len(monster) == 4:
                    set_message(monster[3].name + " が現れた!")
                    draw_battle(screen, fontS, turn_obj, player)
                    time.sleep(0.3)

変更後

def main(screen, clock, font, fontS, player, area):
(省略)

        elif idx == 10: # 戦闘開始
(省略)

            elif tmr == 6:
                if len(monster) >= 2:
                    set_message(monster[1].name + " が現れた!")
                    draw_battle(screen, fontS, turn_obj, player)
                    pygame.time.delay(delay_speed)
            elif tmr == 7:
                if len(monster) >= 3:
                    set_message(monster[2].name + " が現れた!")
                    draw_battle(screen, fontS, turn_obj, player)
                    pygame.time.delay(delay_speed)
            elif tmr == 8:
                if len(monster) == 4:
                    set_message(monster[3].name + " が現れた!")
                    draw_battle(screen, fontS, turn_obj, player)
                    pygame.time.delay(delay_speed)

その他の time.sleepも
すべて変更しました。

 

 

バグ修正

最後にバグ修正をします。

グループ攻撃の呪文で
攻撃対象のモンスターを選択します。

この時、以下のように赤枠を
選択(点滅)しているところから
さらに右に行くとエラーになってしまいます。

これは右に行く判定で
使う変数を誤っていました。

def spell_target_select(bg, key, player): # 呪文の対象を選択
(選択中)
    elif chara.get_spell_target(player.mas_spell[sel_spell_i]) == 3: # 呪文の対象がモンスターグループ
        dic_typ = collections.Counter(monster)
        if key[K_LEFT] and spell_target_i > 0: # ←キー
            spell_target_i -= 1
        elif key[K_RIGHT] and spell_target_i < len(dic_typ)-1: # →キー
            spell_target_i += 1
elif key[K_RIGHT] and spell_target_i < len(dic_typ)-1: # →キー

右に行く判定でlen(dic_typ)を
使っていますが、上記画像では
値が4になります。
※モンスターのオブジェクトは
 1つずつ違うので重複とはならない為

でも、攻撃対象がグループの呪文の場合
モンスターグループを選択します。

なのに、上記画像ではグループは2ですが
3以上を選択できてしまうので
エラーになってしまいます。

というわけで、以下に変更します。

def spell_target_select(bg, key, player): # 呪文の対象を選択
(省略)

    elif chara.get_spell_target(player.mas_spell[sel_spell_i]) == 3: # 呪文の対象がモンスターグループ
        list_typ = mon_overlap()
        if key[K_LEFT] and spell_target_i > 0: # ←キー
            spell_target_i -= 1
        elif key[K_RIGHT] and spell_target_i < len(list_typ)-1: # →キー
            spell_target_i += 1

mon_overlap関数です。

def mon_overlap():
    # 呪文の対象のモンスターNoを取得(list_typに格納する)
    mon_no = [] # モンスターNoを格納
    for mon in monster:
        mon_no.append(mon.num)
    dic_typ = collections.Counter(mon_no) # 重複を抽出 例{9:1、1:1}
    list_typ = list(dic_typ.keys()) # キーをリストで取得(キーがモンスターNo)list_typ[0]=9,list_typ[1]=1
    return list_typ

これでlen(list_typ)の値は
2になるので、
先程の画像で右を押しても、
これ以上は右に行けないので
エラーになりません。

 

 

という感じで、今回は以上です!(`・ω・´)

 

 

最後に

今回は眠り状態で前回残していた設定を追加しました。
また、戦闘時のモンスターが攻撃以外の行動をとることができるようにしました。

次回はモンスターの行動をもう少し追加しようと思います。

まとめサイトへ

 

 

【ご注意】
プログラムやデータなどは著作権法により保護されています。
著作者の許諾を得ずに、プログラムおよびデータそのものまたは改変したものを
配布したり販売したりすることはできません。
また、これらを利用して発生した損害などに関して、著作者は一切責任を負いません。

 

 

-CODE, Python, RPG
-, , , , , , , ,

Copyright© kerublog , 2022 All Rights Reserved Powered by STINGER.