CODE Python RPG

独学でPythonでRPGを作成する(第56回)戦闘(攻撃対象の修正)

投稿日:

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

前回は戦闘に関する修正で、攻撃など相手に使う呪文を使えるようにしました。

今回も戦闘に関する修正で以下に対応します。
・同一ターン内に対象モンスターを仲間が倒した場合の対象変更
・戦闘勝利後、仲間もレベルアップする
・その他、バグ修正

目次

  1. 攻撃対象(単体)の変更
  2. 攻撃対象(グループ)の変更
  3. 呪文の対象変更
  4. レベルアップ
  5. 不意打ちのバグ修正
  6. 最後に

攻撃対象(単体)の変更

同一ターン内に対象モンスターを
仲間が倒した場合、
同じ種類のモンスターの中から
ランダムで選択するように変更します。
※同じ種類のモンスターがいないなら
 生存モンスターの中からランダムで選択する。

 

コマンド選択が終わった後、
素早さの高い順に
各オブジェクトの行動が
始まります。

この時、攻撃対象のモンスターを
仲間が先に倒した場合、
モンスターの種類に関係なく
生存モンスターからランダムに
選択していました。

例:
モンスターA, モンスターB, モンスターC
キャラ1の攻撃対象:モンスターA
キャラ2の攻撃対象:モンスターA
キャラ3の攻撃対象:モンスターA
キャラ4の攻撃対象:モンスターA

上記の場合、キャラ1がモンスターAを倒すと
残りのキャラ2〜4はモンスターBとCのどちらかを
ランダムで選択して攻撃します。

変更前

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

        elif idx == 12: # プレイヤーの攻撃
            draw_battle(screen, fontS, turn_obj, player)
            if tmr == 1:
                set_message(turn_obj.name + " のこうげき!")
                alive_flag = False # 攻撃対象が生存しているか
                for i, mon in enumerate(monster):
                    if mon is turn_obj.target:
                        btl_enemy = i # エフェクトの位置に使う
                        alive_flag = True # 攻撃対象が生存している
                        break
                if not alive_flag:
                    btl_enemy = random.randint(0, len(monster)-1)
                    turn_obj.target = monster[btl_enemy]
                dmg, get_message = turn_obj.attack(turn_obj.target)
btl_enemy = random.randint(0, len(monster)-1)

特に条件を設けずランダムで
攻撃対象を選択しています。

これを
同じ種類のモンスターが生存している場合
同じ種類のモンスターの中から
ランダムで選択するように変更します。

また、この機能を関数にして
攻撃呪文の場合も呼び出すようにします。

def dead_alive(obj): # (攻撃)対象のモンスターが生存していればそのまま返す、そうでなければ別のモンスターを選んで返す
    global btl_enemy
    mon_no = []
    for i, mon in enumerate(monster):
        if mon is obj:
            btl_enemy = i # エフェクトの位置に使う
            return obj # 攻撃対象が生存している
        elif mon.num == obj.num:
            mon_no.append(i)
    if mon_no: # 同じ種類のモンスターがいる場合は同じ種類からランダム
        btl_enemy = random.randint(mon_no[0], mon_no[-1])
    else: # 同じ種類のモンスターがいない場合は生存モンスターからランダム
        btl_enemy = random.randint(0, len(monster)-1)
    obj = monster[btl_enemy]        
    return obj
(省略)

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

        elif idx == 12: # プレイヤーの攻撃
            draw_battle(screen, fontS, turn_obj, player)
            if tmr == 1:
                set_message(turn_obj.name + " のこうげき!")
                turn_obj.target = dead_alive(turn_obj.target)
                dmg, get_message = turn_obj.attack(turn_obj.target)
for i, mon in enumerate(monster):
(省略)
    elif mon.num == obj.num:
        mon_no.append(i)

対象オブジェクトが既にいない場合
同じ種類のモンスターがいるなら
リストmon_noに
モンスターの位置(左から何番目か)を
格納しておきます。
※enumerateについてはこちら

if mon_no: # 同じ種類のモンスターがいる場合は同じ種類からランダム
    btl_enemy = random.randint(mon_no[0], mon_no[-1])
else: # 同じ種類のモンスターがいない場合は生存モンスターからランダム
    btl_enemy = random.randint(0, len(monster)-1)
obj = monster[btl_enemy]

同じ種類のモンスターがいれば
その中からランダムで選択、
同じ種類のモンスターがいないなら
生存モンスターからランダムで選択します。
※mon_no[-1]はリストの最後の値です。

 

以下のような流れになります。

キャラ1の攻撃対象:Green slime A
キャラ2の攻撃対象:Green slime A
キャラ3の攻撃対象:Green slime A
キャラ4の攻撃対象:Green slime A

 

最初のキャラがGreen slime Aを攻撃して倒す

mon_noの値は[0,1]となり、
0もしくは1を選択します。
random.randint(mon_no[0], mon_no[-1])
 →random.randint(0, 1) となります。
※mon_noは左からの位置なので
 同じ種類のモンスター(Green slime)の
 位置である0と1となります。

 

次のキャラがGreen slime Cを攻撃して倒す
Green slime Cの位置である「1」を選択された

mon_noの値は[0]となります。
random.randint(mon_no[0], mon_no[-1])
 →random.randint(0, 0) となります。

 

次のキャラがGreen slime Bを攻撃して倒す

mon_noの値は[]となり
生存モンスターから選択します。
※random.randint(0, len(monster)-1)
 →random.randint(0, 0)となります。

 

最後のキャラがRed slimeを倒して戦闘終了

 

単体の攻撃対象の変更は以上です。

 

 

攻撃対象(グループ)の変更

続いて、
呪文攻撃で対象がグループの時、
行動の際には、そのグループが
既にいなくなっている場合、
生存している他のグループを
選択して攻撃するようにします。

今は以下のようになっています。

def mon_overlap():
    # 呪文の対象のモンスターNoを取得(list_typに格納する)
    mon_no = [] # モンスターNoを格納
    for mon in monster: # 既にfor文の中であり、monを使っているので、ここでmonは使えない
        mon_no.append(mon.num)
    dic_typ = collections.Counter(mon_no) # 重複を抽出(倒すとmonsterが変わるので毎回取得) 例{9:1、1:1}
    list_typ = list(dic_typ.keys()) # キーをリストで取得(キーがモンスターNo)list_typ[0]=9,list_typ[1]=1
    return list_typ
(省略)

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

        elif idx == 26: # 呪文発動
(省略)

                    elif spell_target_no == 3: # 呪文の対象がモンスターグループ
                        # 呪文の対象のモンスターNoを取得(list_typに格納する)して、対象モンスターNoの数を取得する→ dic_typ[list_typ[spell_target_i]]
                        list_typ = mon_overlap() # 例{9:1、1:1}、→ [9, 1]を取得
                        for i, mon in enumerate(monster):
                            if mon.num == list_typ[turn_obj.target]: # グループの時だけturn_obj.targetにはインデックスが入っている
                                spell_point, spell_message = turn_obj.use_spell(turn_obj.spell, mon, spell_target_no)
(省略)
list_typ = mon_overlap() # 例{9:1、1:1}、→ [9, 1]を取得

上記の例の場合、
攻撃対象は
turn_obj.targetが0の場合
list_typ[0] → モンスター種別No9
turn_obj.targetが1の場合
list_typ[1] → モンスター種別No1
のグループとなります。

モンスター種別Noは
「モンスター」シートのA列です。

 

ちなみに以下のような場合、
攻撃対象がGreen slimeだと
問題はありません。
list_typ = [1, 2]
 1:Green slimeのNo
 2:Red slimeのNo

キャラ1の攻撃呪文対象:Green slime
キャラ2の攻撃呪文対象:Green slime
キャラ3の攻撃呪文対象:Green slime
キャラ4の攻撃呪文対象:Green slime
※いずれもturn_obj.targetは「0」

 

最初のキャラが呪文の
グループ攻撃でGreen slimeを倒す

Green slimeがいなくなると
list_typ = [2]
となるので
list_typ[0] = 2となり
次のキャラは
Red slimeを攻撃します。

 

ただ、同じ例で
先にRed slimeを倒すと
エラーとなります。

キャラ1の攻撃呪文対象:Red slime
キャラ2の攻撃呪文対象:Red slime
キャラ3の攻撃呪文対象:Red slime
キャラ4の攻撃呪文対象:Red slime
※いずれもturn_obj.targetは「1」

最初のキャラが呪文の
グループ攻撃でRed slimeを倒す

Re slimeがいなくなると
list_typ = [1]
となりますが
turn_obj.targetは「1」の為
list_typ[1]は
「list index out of range」の
エラーとなってしまいます。

 

これを解消する為に
グループの攻撃対象を
変える関数を作成します。

def dead_alive_g(no): # (攻撃)対象のモンスターグループが生存していればそのまま返す、そうでなければ別のモンスターグループを選んで返す
    global btl_enemy
    mon_no = mon_overlap()
    if len(mon_no)-1 >= no: # noはグループのインデックス
        for i in mon_no:
            if i == mon_no[no]: # 対象のグループが生存
                return no # 攻撃対象が生存している
    return random.randint(0, len(mon_no)-1)

引数「no」はturn_obj.targetの値です。

if len(mon_no)-1 >= no:

この条件は先程の例で
エラーの原因となった
「list index out of range」
を回避する為です。
※この条件がないと
 mon_no[no]でエラーとなります。

for i in mon_no:
    if i == mon_no[no]: # 対象のグループが生存
        return no # 攻撃対象が生存している

対象のグループが生存していれば
そのまま攻撃します。

return random.randint(0, len(mon_no)-1)

対象のグループが生存していないと
残りのグループからランダムで選択します。

 

グループの対象変更は以上です。

 

 

呪文の対象変更

呪文発動のときに
攻撃対象を変更できるように
関数を呼び出します。

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

        elif idx == 26: # 呪文発動
(省略)

                    elif spell_target_no == 2: # 呪文の対象がモンスター単体
                        turn_obj.target = dead_alive(turn_obj.target) # 対象が生存しているか、していないなら別の対象を取得
                        spell_point, spell_message = turn_obj.use_spell(turn_obj.spell, turn_obj.target, spell_target_no)
(省略)


                    elif spell_target_no == 3: # 呪文の対象がモンスターグループ
                        # 呪文の対象のモンスターNoを取得(list_typに格納する)して、対象モンスターNoの数を取得する→ dic_typ[list_typ[spell_target_i]]
                        list_typ = mon_overlap() # 例{9:1、1:1}、→ [9, 1]を取得
                        turn_obj.target = dead_alive_g(turn_obj.target) # 対象グループが生存しているか、していないなら別の対象グループを取得、turn_obj.targetはグループ(list_typ)のインデックス
(省略)
turn_obj.target = dead_alive(turn_obj.target)
turn_obj.target = dead_alive_g(turn_obj.target)

を追加しただけです。

 

攻撃対象の変更については以上です。

 

 

レベルアップ

戦闘勝利後に
獲得する経験値が
一定数を超えると
レベルアップしますが
対象が全員ではないので
修正します。

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

    p_lvup = "" # レベルアップしたキャラ
(省略)

        elif idx == 16: # 勝利
(省略)

            if tmr == 5:
                if len(player) == 1:
                    set_message(str(btl_exp) + "ポイントの けいけんちを かくとく!")
                else:
                    set_message("それぞれ" + str(btl_exp) + "ポイントの けいけんちを かくとく!")
                for p in player:
                    if p.exp >= p.lv_exp:
                        p_lvup = p
                        idx = 17
                        tmr = 0
                        break
            if tmr == 10:
                idx = 22 # 戦闘終了

        elif idx == 17: # レベルアップ
            draw_battle(screen, fontS, turn_obj, player)
            if tmr == 5:
                set_message(p_lvup.name + " はレベルが上がった!") # player[0]からp_lvupに変更
                p_lvup.lv_up() # player[0]からp_lvupに変更
            if tmr == 21:
                init_message()
                set_message("最大HP:"+str(p_lvup.maxhp)) # player[0]からp_lvupに変更
            if tmr == 26:
                set_message("素早さ:"+str(p_lvup.quick)) # player[0]からp_lvupに変更
            if tmr == 30:
                set_message("攻撃力:"+str(p_lvup.atk)) # player[0]からp_lvupに変更
            if tmr == 34:
                set_message("防御力:"+str(p_lvup.dfs)) # player[0]からp_lvupに変更
            if tmr == 38:
                tmp_spell = p_lvup.master_spell() # player[0]からp_lvupに変更
                if tmp_spell != "":
                    set_message(tmp_spell + " を覚えた")
            if tmr == 40:
                for p in player:
                    if p.exp >= p.lv_exp:
                        p_lvup = p
                        idx = 17
                        tmr = 0
                        break
            if tmr == 45:
                idx = 22 # 戦闘終了
for p in player:
    if p.exp >= p.lv_exp:
        p_lvup = p
        idx = 17
        tmr = 0
        break

変更前は
if player[0].exp >= player[0].lv_exp:
と1人しか確認していませんでした。

全員確認して
ひとまず1人でも
レベルアップしたら
対象オブジェクトを
p_lvupに格納して
idx17に移ります。

if tmr == 40:
    for p in player:
        if p.exp >= p.lv_exp:
            p_lvup = p
            idx = 17
            tmr = 0
            break

idx17でも同様に確認して
他にレベルアップした
キャラがいれば再度idx17に
戻ります。

あと、player[0]を修正した行に
コメントに記載しました。
※ # player[0]からp_lvupに変更

 

レベルアップについては以上です。

 

 

不意打ちのバグ修正

モンスターと遭遇した時に
不意打ちの場合、
おかしくなっていたので
修正します。

変更前

def set_battle_turn(num, player): # 0:通常、1:先制攻撃、2:不意打ち
    global battle_order
    tmp_order = {}
    # 素早さの順に並べる(戦闘順)
    if num == 0 or num == 1: # 不意打ち(2)の場合は行動できない
        for p in player:
            r = random.randint(66, 100)
            tmp_order[p] = int(p.quick * r/100)
    if num == 0 or num == 2:
        for mon in monster:
            r = random.randint(66, 100)
            tmp_order[mon] = int(mon.quick * r/100)
    # [(obj, quick), (ojb, quick), ...]のリストになる
    battle_order = sorted(tmp_order.items(), key=lambda x:x[1], reverse=True)
(省略)

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

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

                if btl_start == 2: # 不意打ち
                    idx = 23
                else:
                    idx = 11
                tmr = 0
(省略)

        elif idx == 23: # ターンセット
            if btl_player == len(player)-1: # 全員のコマンド選択完了
                set_battle_turn(btl_start, player)
                for mon in monster:
                    mon.set_monster_cmd()
                btl_player = 0 # コマンドを選択するプレイヤーを最初に戻す
                btl_start = 0 # 戦闘は通常に戻す
                idx = 24
            else: # 選択していなプレイヤーがいる(眠っているときはactが28がセットされている)
                idx = 11
                btl_player += 1
if btl_start == 2: # 不意打ち
    idx = 23

不意打ちの場合、
コマンドは選択せずに
idx23となります。

else: # 選択していなプレイヤーがいる(眠っているときはactが28がセットされている)
    idx = 11
    btl_player += 1

ただ、idx23では
不意打ちの条件が無い為
idx11のコマンド選択に
戻ってしまいます。

if num == 0 or num == 1: # 不意打ち(2)の場合は行動できない
    for p in player:
        r = random.randint(66, 100)
        tmp_order[p] = int(p.quick * r/100)

なお、関数set_battle_turnでは
不意打ちの条件がある為
キャラは行動しません。
※コマンド選択はできますが
 行動はできない状態です。

 

この為、idx23の条件を変えます。

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

        elif idx == 23: # ターンセット
            if (btl_start == 0 or btl_start == 1) and btl_player != len(player)-1:
                idx = 11
                btl_player += 1
            else:
                set_battle_turn(btl_start, player)
                for mon in monster:
                    mon.set_monster_cmd()
                btl_player = 0 # コマンドを選択するプレイヤーを最初に戻す
                btl_start = 0 # 戦闘は通常に戻す
                idx = 24
if (btl_start == 0 or btl_start == 1) and btl_player != len(player)-1:

不意打ちではない
かつ
コマンド選択していないキャラがいる
場合はコマンド選択する
に修正しました。

 

あちこちで同じことを確認して
あまり良くないと思いますけど、、
今回は以上です!(`・ω・´)

 

 

最後に

今回も仲間を追加したことに伴う
変更・修正を行いました。

次回ももう少し変更・修正をしたいと思います。

まとめサイトへ

 

 

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

 

 

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

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