CODE Python RPG

独学でPythonでRPGを作成する(第53回)戦闘(物理攻撃の修正)

投稿日:

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

前回は戦闘(battle.py)を変更しました。

ただ、まだまだ不十分でしたので、引き続き戦闘を修正します。
今回は物理攻撃について修正します。

目次

  1. 攻撃対象の誤り
  2. モンスター側の攻撃対象
  3. バグ修正
  4. 最後に

攻撃対象の誤り

最初にキャラが攻撃する
対象について修正します。

仲間がいる状態の場合、
今は最後の仲間が選択した
モンスターを全員が攻撃してしまいます。

攻撃対象
ハロ1:Red slime
ハロ2:Green slimeA
ハロ3:Green slimeB
ハロ4:Green slimeC

とした場合でも
最後のハロ4が選択した
Green slimeC(図の赤枠)を
全員が攻撃してしまいます。

 

 

原因

原因はidx19で攻撃対象の
モンスターを選択しますが
関数attack_selectで
グローバル変数btl_enemyを
決めている為です。

monster = [] # 敵のオブジェクト
btl_enemy = 0 # 敵選択の時の"▶︎"の位置
(省略)

def attack_select(bg, key): # 物理攻撃の対象を選択
    global idx, btl_enemy # ここでbtl_enemyを決定する
    ent = False
    if key[K_UP] and btl_enemy > 0: # ↑キー
        btl_enemy -= 1
    elif key[K_DOWN] and btl_enemy < len(monster)-1: # ↓キー
        btl_enemy += 1
    elif key[K_SPACE] or key[K_RETURN]: # 決定
        ent = True
    elif key[K_b]: # キャンセル
        idx = 11
    return ent
(省略)

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

        elif idx == 19: # 敵の選択
            draw_battle(screen, fontS, turn_obj, player)
            if attack_select(screen, key) == True:
                player[btl_player].act = 12 # get_battle_turnの戻り値idxを12
                idx = 23
                tmr = 0

btl_enemyは攻撃対象の
選択時の「▶」の位置ですが
これがそのまま攻撃対象の
モンスター(リストmonster)の
インデックスになります。

btl_enemyはリストでも
インスタンス変数でも無いので
次々と上書きされていきます。

ので、最後の仲間が選択した
btl_enemyが全員に採用されるので
攻撃対象がおかしくなっていました。

 

 

解消

この原因を解消する為に
chara.pyにインスタンス変数を
追加します。

攻撃対象モンスターの
オブジェクトを格納します。

class Chara():
    def __init__(self):
        self.act = 0 # 戦闘のコマンド(行動)12:プレイヤー攻撃、13:モンスター攻撃、18:防御
        self.target = 0 # 攻撃対象(オブジェクト)

self.target = 0
を追加しました。

 

idx19でtargetに
格納します。

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

        elif idx == 19: # 敵の選択
            draw_battle(screen, fontS, turn_obj, player)
            if attack_select(screen, key) == True:
                player[btl_player].act = 12 # get_battle_turnの戻り値idxを12
                player[btl_player].target = monster[btl_enemy] # 各キャラの攻撃対象
                idx = 23
                tmr = 0

player[btl_player].target = monster[btl_enemy]
を追加しました。

これでとりあえず、プレイヤー側の
攻撃対象は選択したモンスターを
攻撃するはず。。

これに伴って他の箇所も
monster[btl_enemy]からtargetに
修正します。

 

変更後

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 + " のこうげき!")
                dmg, get_message = turn_obj.attack(turn_obj.target)
            if 2 <= tmr and tmr <= 4:
                screen.blit(imgEffect[0], [turn_obj.target.x+turn_obj.target.img.get_width()-tmr*80, tmr*80])
            if tmr == 5:
                emy_blink = 5
                for mes in get_message:
                    set_message(mes) # ここにdpygame.time.delayを入れると emy_blink のエフェクトが止まってしまう
            if tmr == 11:
                tmp_exp = do_attack(turn_obj.target, dmg, turn_obj)
                if dmg != 0 and tmp_exp == 0 and turn_obj.target.flag_sleep: # 攻撃を受けた かつ 生きている かつ 眠っている
                    if random.randint(0, 99) < 55:
                        turn_obj.target.invalid_sleep()
                        set_message(turn_obj.target.name + " はめをさました!")
                btl_exp += tmp_exp
                check_monster()
            if tmr == 16:
                init_message()
                idx = 24 # ターンの確認

変更した点は基本的に」
monster[btl_enemy] → turn_obj.target
です。

1箇所だけ少し違うのが
do_attack関数の引数です。

tmp_exp = do_attack(turn_obj.target, dmg, turn_obj)

btl_enemy → turn_obj.target
と、インデックスからオブジェクトに
しています。

この為、do_attack関数も
少し変更します。

変更後

def do_attack(target, dmg, obj): # 物理攻撃、呪文攻撃にも使う
    tmp_exp = 0
    target.hp -= dmg
    if target.hp <= 0:
        target.hp = 0
        set_message(obj.name + " は " + target.name + " をやっつけた!")
        tmp_exp += target.exp
    return tmp_exp

monster[target] → target
に変更しています。
※変更後の引数targetは
 オブジェクトです。

なお、コメントの通り
do_attack関数は
呪文攻撃のときも呼び出しますが
呪文の修正は後回しにします・・
※今回の変更は物理攻撃のみです。

 

以上で攻撃対象はキャラごとに
選択できるようになりました。

 

 

攻撃対象の変更

攻撃対象についてもう少し変更します。

攻撃対象を選択できるようには
なりました。

が、1ターンの中で
他のキャラの攻撃で倒して、
画面上から攻撃対象がいなくなっても、
同じ対象を攻撃してしまいます。
※倒したモンスターを攻撃し続ける。

攻撃対象
ハロ1:Red slime
ハロ2:Red slime
ハロ3:Red slime
ハロ4:Red slime

ハロ1の攻撃でRed slimeを倒しても、
残りの仲間も全員Red slimeを
攻撃してしまいます。

というわけで、この場合は
まだ残っているモンスターの中から
ランダムで攻撃対象を選択するようにします。

変更後

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 mon in monster:
                    if mon is turn_obj.target:
                        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)
for i, mon in enumerate(monster):
    if mon is turn_obj.target:
        alive_flag = True # 攻撃対象が生存している
        break

攻撃対象が生存しているか確認しています。
※オブジェクトの比較は「is」「is not」です。
 「==」の場合、オブジェクトが違っても
 値が同じだとTrueになるようです。

tmp1 = [0, 1, 2]
tmp2 = [0, 1, 2]
print(tmp1 == tmp2)
print(tmp1 is tmp2)

# 結果
# True ←tmp1とtmp2は同じ値なので「==」はTrue
# False ←tmp1とtmp2は違うオブジェクトなので「is」はFalse
if not alive_flag:
    btl_enemy = random.randint(0, len(monster)-1)
    turn_obj.target = monster[btl_enemy]

攻撃対象がいない場合は
ランダムで攻撃対象を選んで
targetに格納し直します。
※randintの詳細はこちら

 

各キャラの攻撃対象の修正は以上です。

 

 

モンスター側の攻撃対象

続いてモンスター側の攻撃対象です。

モンスター側から見て、
対象が複数になったので
攻撃対象を決めます。

といってもランダムに
選択するのみです。

変更後

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

        elif idx == 13: # 敵のターン、敵の攻撃
            draw_battle(screen, fontS, turn_obj, player)
            if tmr == 1:
                p_target = player[random.randint(0, len(player)-1)] # モンスターの攻撃対象はランダム
                if turn_obj.skill[turn_obj.skill_no] == "攻撃":
                    dmg, message = turn_obj.attack(p_target)
                elif turn_obj.skill[turn_obj.skill_no] == "痛恨の一撃":
                    dmg, message = turn_obj.grief(p_target)
                set_message(message[0])
                emy_step = 30
            if tmr == 9:
                for i, mes in enumerate(message):
                    if i == 0:
                        continue
                    set_message(mes)
                if dmg > 0:
                    dmg_eff = 5
                else:
                    dmg_eff = 0
                emy_step = 0
            if tmr == 15: # party
                p_target.hp = p_target.hp - dmg
                if p_target.hp <= 0:
                    p_target.hp = 0
                    idx = 15 # 敗北
                    tmr = 0
                else:
                    if dmg != 0 and p_target.flag_sleep: # 生きている かつ 攻撃を受けた かつ 眠っている
                        if random.randint(0, 99) < 55:
                            p_target.invalid_sleep()
                            set_message(p_target.name + " はめをさました!")
p_target = player[random.randint(0, len(player)-1)]

ランダムに選択して、
変数p_targetに格納します。

if turn_obj.skill[turn_obj.skill_no] == "攻撃":
    dmg, message = turn_obj.attack(p_target)
elif turn_obj.skill[turn_obj.skill_no] == "痛恨の一撃":
    dmg, message = turn_obj.grief(p_target)

前回、とりあえず「player[0]」に
変更しましたが、これを「p_target」に
修正しています。

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

この辺りも同様に「p_target」に
修正しています。

 

モンスター側の攻撃対象も以上です。

 

 

バグ修正

バグをいくつか修正します。

ターン内で全員の行動が終わった後
止まってしまい、次のターンの
コマンド選択の画面に移りませんでした。

 

前回、idx27で削除した箇所がありますが
「idx = 11」も削除してしまったので
無限ループしていました。。

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

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

            init_message()
            idx = 11 # 追加

            # コマンド選択の時に確認するので不要
            # if player[0].flag_sleep:
            #     idx = 23 # ターンセット(眠っている時はコマンド選択できない)
            # else:
            #     idx = 11

というわけで
「idx = 11」のみ復活させました。

 

で、ターンが終わったら終わったで
コマンド選択が最後のキャラからに
なっていました。
※「b」キーでキャンセルしないと
 最初のキャラのコマンドを選択できない

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

        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

というわけで
「btl_player = 0」を追加しました。

 

あと、バグではないですが
Monsterクラスのメソッド名を
少し変更しました。

変更前:set_monster_com
変更後:set_monster_cmd

何となくで、特に意味はないです。。

 

 

とにもかくにも物理攻撃だけは
できるようになったので、、
今回は以上です!(`・ω・´)

 

 

最後に

今回は戦闘における物理攻撃の
対象選択を修正しました。

呪文関連が全く手つかずなので、
次回はここを修正します。

まとめサイトへ

 

 

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

 

 

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

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