CODE Python RPG

初心者がPythonでRPGを作成する(第15回)全員攻撃する

更新日:

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

前回は倒したモンスターには攻撃できないようにしました。

今回はモンスターの攻撃について、
今は何匹出現しても攻撃は1回(1匹)ですが、出現したモンスターが
1匹につき1回ずつ攻撃するように変更します。

目次

  1. 攻撃の順番を決める
  2. 攻撃するオブジェクトを取得
  3. main関数の変更
  4. 倒したモンスターを削除
  5. 最後に

攻撃の順番を決める

出現したモンスターが1匹につき1回ずつ攻撃しますが、
全員が同時に攻撃するわけではなく、
プレイヤーも含めて全員が順番に攻撃をします。

順番は攻撃力の高い順にします。

なので、まずは攻撃力の高い順に並んだリストを作成する
関数set_battle_turnを用意します。

battle_order = {} # 攻撃力が高い順にオブジェクトを並べた変数

def set_battle_turn(): # 攻撃力が高い順にオブジェクトを並べ変える
    global battle_order
    tmp_order = {} # プレイヤーと出現モンスター全てを入れる
    tmp_order[player] = player.atk # {オブジェクト:オブジェクトの攻撃力}の辞書型
    for mon in monster:
        tmp_order[mon] = mon.atk
    # tmp_orderを値(攻撃力)の高い順でソートする
    battle_order = sorted(tmp_order.items(), key=lambda x:x[1], reverse=True)
    print(tmp_order) # 確認用
    # 結果
    # {<chara.Brave object at xxx>: 1000, <chara.Monster object at xxx>: 10, <chara.Monster object at xxx>: 10, <chara.Monster object at xxx>: 10, <chara.Monster object at xxx>: 11}
    print(battle_order) # 確認用
    # 結果
    # [(<chara.Brave object at xxx>, 1000), (<chara.Monster object at xxx>, 11), (<chara.Monster object at xxx>, 10), (<chara.Monster object at xxx>, 10), (<chara.Monster object at xxx>, 10)]

グローバル変数(battle_order)を用意して
オブジェクト:オブジェクトの攻撃力
の辞書型にしてプレイヤーとモンスターを入れます。

その後、値(攻撃力)の高い順(reverse=True)にソートします。
(ソート方法の詳細はこちらなど)

ソートにはsortとsortedとがありますが、sortを使えるのはリストのみのようです。
sortedは元のリストの順番は変わりませんので、仮の変数(tmp_order)に
値を入れていき、ソートした値をbattle_orderに入れています。

コメントに記載した結果は以下の時です。

プレイヤーの攻撃力:1000
Green slimeの攻撃力:10
Red slimeの攻撃力:11

tmp_order(値でソートされていない、代入した順番のまま)
{<chara.Brave object at xxx>: 1000, <chara.Monster object at xxx>: 10, ・・・

battle_order(値でソートされている)
[(<chara.Brave object at xxx>, 1000), (<chara.Monster object at xxx>, 11),・・・

これで、攻撃力の高い順にソートされたオブジェクトリストを作成できました。

ちなみにソートされた後(battle_order)は辞書型ではなくタプルになっています。
※tmp_orderは{}、battle_orderは()
items()を使ってソートするとタプルになると説明のあるサイトもありましたが
理由はよく分かりません・・・(ノ´∀`*)

ただ、攻撃力の高い順にオブジェクトを取得できれば何でも良いので、
とりあえず辞書型でもタプルでも問題ないです。

 

 

攻撃するオブジェクトを取得

次に攻撃するオブジェクトを取得する関数get_battle_turnを用意します。
戻り値は攻撃の順番が回ってきたオブジェクトとidxの2つです。

# 攻撃の順番になったオブジェクトを取得する
# 戻り値は攻撃するオブジェクトとidx
def get_battle_turn():
    global battle_order
    if not battle_order: # battle_orderが空になったら、コマンド選択に遷移する
        return None, 11
    # [(obj, atk), (ojb, atk), ...]のリスト、[0][0]で先頭(ターン)のobjを取得
    turn_key = battle_order[0][0]
    battle_order.pop(0) # 先頭を削除
    if turn_key is player:
        return turn_key, 12
    else:
        return turn_key, 13

battle_orderが攻撃力の高い順に並んでいるので、
先頭から順に取得して行きます。

battle_orderは
[(obj, atk),
(ojb, atk),
…]
のリストになっています。
battle_order[0]は先頭の(obj, atk)なので、
battle_order[0][0]は先頭のobj、
battle_order[0][1]は先頭のatk
ということになります。
turn_key = battle_order[0][0]

そして、攻撃するのがプレイヤーの場合はidx12に移り、
モンスターの場合はidx13に移ります。

攻撃したらそのオブジェクトのターンは終わりなので、
battle_orderから削除します。
battle_order.pop(0)
(popの詳細はこちら

battle_orderが空になった時、全員が攻撃したと
判断します。
全員の攻撃が終わったら、そのターンは終了となり、
再度コマンドを選択する画面に遷移(idx11)します。
if not battle_order:
  return None, 11

 

 

main関数の変更

攻撃するオブジェクトを取得する関数を用意したので
main関数を変更して行きます。

まずは、攻撃の順番が回ってきたオブジェクトを入れる変数を用意します。
get_battle_turn関数の戻り値にオブジェクトがありますが
この値を入れる変数です。

def main():
(省略)
    turn_obj = "" # 追加

 

次にコマンドを選択した後の動きです。
今までは「こうげき」を選択したら、そのままプレイヤーがモンスターを
攻撃していましたが、攻撃順を決める関数set_battle_turnに移ります。
(idx23)
その後、攻撃するオブジェクトを取得するget_battle_turnに移ります。
(idx24)

        elif idx == 11: # プレイヤーのターン(入力待ち)
(省略)
            if battle_command(screen, key) == True:
                if COMMAND[btl_cmd_y][btl_cmd_x] == "こうげき":
                    idx = 19
                    tmr = 0

        elif idx == 19: # 敵の選択
            draw_battle(screen, fontS)
            if battle_select(screen, key) == True:
                idx = 23 # 12から23に変更 「こうげき」を選択したらset_battle_turn()へ移る
                tmr = 0

(追加)
        elif idx == 23: # ターンセット
            set_battle_turn() # 攻撃力の高い順に並んだオブジェクトのリストを取得する
            idx = 24
        elif idx == 24: # ターン確認
            tmr = 0
            turn_obj, idx = get_battle_turn()

 

あとは変数(turn_obj)を修正します。

        elif idx == 12: # プレイヤーの攻撃
            draw_battle(screen, fontS)
            if tmr == 1:
                set_message(turn_obj.name + " の攻撃!") # playerをturn_objに変更
                dmg = battle_cal(turn_obj, monster[btl_enemy]) # playerをturn_objに変更
(省略)
                    set_message(turn_obj.name + " は " + monster[btl_enemy].name + " をやっつけた!") # playerをturn_objに変更

ここはplayerのままでも問題ないですが、一応です・・

 

        elif idx == 13: # 敵のターン、敵の攻撃
            draw_battle(screen, fontS)
            if tmr == 5:
                set_message(turn_obj.name + " の攻撃!") # monster[0]をturn_objに変更
                emy_step = 30
            if tmr == 9:
                dmg = battle_cal(turn_obj, player) # monster[0]をturn_objに変更

モンスター側の変更は必須ですね。

あと、今までの流れは
コマンド選択「こうげき」

プレイヤーの攻撃

モンスターの攻撃

コマンド選択
でしたが、プレイヤーもモンスターも攻撃が終わったら
get_battle_turn(idx24)に移る流れになります。

        elif idx == 12: # プレイヤーの攻撃
(省略)
            if tmr == 16:
                init_message()
                idx = 24 # 13から24に変更
                tmr = 0

        elif idx == 13: # 敵のターン、敵の攻撃
(省略)
            if tmr == 20:
                init_message()
                idx = 24 # 11から24に変更
                tmr = 0

        elif idx == 14: # 逃げられる?
(省略)
            if tmr == 10:
                init_message()
                idx = 24 # 13から24に変更
                tmr = 0
(省略)
        elif idx == 18: # ぼうぎょ
(省略)
            if tmr == 5:
                init_message()
                idx = 24 # 13から24に変更
                tmr = 0

「にげる」(idx14)や「ぼうぎょ」(idx18)も
ついでに変更しました。

ただ、今時点では「にげる」や「ぼうぎょ」を選択すると
idx23に移らず、idx14やidx18に移ります。
なので、battle_orderが空のままの為、ターンが終わってしまいます。
近いうちに修正しようと思います。

 

 

倒したモンスターを削除

攻撃力の高い順に攻撃をして、全員の攻撃が終わったら
再度コマンドを選択する流れになりました。

ただ、今のままですとプレイヤーが先に攻撃して倒したモンスターも
プレイヤーを攻撃してしまいます。
これは倒したモンスターのオブジェクトがbattle_orderに
残っているからです。
※ターンが終わったら倒したモンスターは攻撃しません。
 倒したらmonsterリストから削除するからです。
 前回のmonster.pop(btl_enemy)です。

というわけで、battle_orderから倒したモンスターを
削除する関数del_battle_turnを用意します。
引数は倒したモンスターのオブジェクトです。

def del_battle_turn(obj1): # 倒したモンスターが攻撃しないようにターン内に倒したオブエクトを削除
    for i, obj2 in enumerate(battle_order):
        # battle_orderは[(obj, atk), (ojb, atk), ...]のリスト
        if obj1 is obj2[0]: # obj2[0]はオブジェクト、obj2[1]は攻撃力
            battle_order.pop(i) # i行目を削除

enumerateでbattle_orderの要素を順に取り出します。
(enumerateの詳細はこちら

battle_orderは
[(obj, atk),
(ojb, atk),
…]
のリストなので、obj2には
(obj, atk)
の形式で入っています。
この為、obj2[0]はオブジェクト、obj2[1]はその攻撃力となります。

倒したモンスターobj1とobj2[0]を比較して一致したら
battle_orderから削除します。
battle_order.pop(i)
(popの詳細はこちら

なお、オブジェクトの比較には「==」ではなく「is」を使いました。
この2つの違いは
「==」はオブジェクトの値を比較
「is」はオブジェクトを比較
です。
(詳細はこちら


今回は値ではなく、オブジェクトが一致しているかを
判定したい為、「is」を利用しました。

あとはモンスターを倒した時にこの関数を実行します。

def main():
(省略)
        elif idx == 12: # プレイヤーの攻撃
(省略)
                    set_message(turn_obj.name + " は " + monster[btl_enemy].name + " をやっつけた!")
                    del_battle_turn(monster[btl_enemy]) # (追加)倒したモンスターをbattle_orderから削除する
                    dead_monster.append(monster[btl_enemy])
                    monster.pop(btl_enemy)

 

これでプレイヤーも含めて出現したモンスター全員が順番に攻撃をするようになりました。
変更前と変更後の動画をご参考までにこちらのページに載せました。

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

 

 

最後に

今回は出現したモンスターが1匹につき1回ずつ攻撃するように変更しました。
ただ、モンスターが攻撃すると、全員が同時に動いてしまうので、
次回はここを修正します。

また、攻撃の順番は攻撃力の高い順にしましたが、
素早さのパラメータをキャラに追加して、
素早さの高い順に攻撃するように変更したいと思います。

まとめサイトへ

 

 

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

 

  

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

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