CODE Python RPG

初心者がPythonでRPGを作成する(第13回)攻撃対象を選択

更新日:

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

前回はモンスターを複数出現するようにしました。(最大4匹)
今回はどのモンスターを攻撃するかを選択できるように変更します。

目次

  1. モンスター名の表示
  2. 攻撃対象の選択
  3. 対象モンスターへの攻撃
  4. 戦闘開始時のメッセージ
  5. 戦闘後の処理
  6. 最後に

モンスター名の表示

まずはモンスター名を表示します。
前々回にモンスター名を表示するようにしましたが、
1匹のみの為、複数のモンスター名を表示するようにします。

以下の赤枠のようにします。

 

それでは変更して行きます。
表示のところなので、draw_battle関数です。

(変更前)
def draw_battle(bg, fnt): # 戦闘画面の描画
(省略)
        # モンスターの名前表示
        x_i = bg.get_width()*0.4 + 60 # x座標の開始位置
        x_w = bg.get_width()*0.6 - 100 # 100は両端の50の合計
        y_i = bg.get_height()*0.6 + 40
        y_h = bg.get_height()*0.1 - 10 # 高さ(モンスターの名前1個分の高さ:62)
        x_i_t = x_i + 50 # テキストのx座標の開始位置
        y_i_t = y_i + 30
        pygame.draw.rect(bg, col, [x_i, y_i, x_w, y_h], 2, 5)
        draw_text(bg, "{}".format(monster.name), x_i_t, y_i+21, fnt, col) # font:20(20+21+21=62)
(変更後)
def draw_battle(bg, fnt): # 戦闘画面の描画
(省略)
        # モンスターの名前表示
        x_i = bg.get_width()*0.4 + 60 # x座標の開始位置
        x_w = bg.get_width()*0.6 - 100 # 100は両端の50の合計
        y_i = bg.get_height()*0.6 + 40
        y_h = bg.get_height()*0.1 - 10 # 高さ(モンスターの名前1個分の高さ:62)
        x_i_t = x_i + 50 # テキストのx座標の開始位置
        y_i_t = y_i + 21 # テキストのy座標の開始位置 20+21+21=62の21
        pygame.draw.rect(bg, col, [x_i, y_i, x_w, y_h*len(monster)], 2, 5)
        for i, mon in enumerate(monster):
            draw_text(bg, "{}".format(mon.name), x_i_t, y_i_t+y_h*i, fnt, col) # font:20(20+21+21=62)
        if idx == 19:
            if tmr%5 != 0:
                draw_text(bg, "▶︎", x_i_t-50, y_i_t+y_h*btl_enemy, fnt, col) # btl_enemy(モンスターの名前の位置)

位置で変わるのはy座標のみで、x座標は変わりません。

枠について、モンスターの数によって高さが変わるので
高さにlen(monster)を掛けています。

pygame.draw.rect(bg, col, [x_i, y_i, x_w, y_h*len(monster)], 2, 5)

draw.rectは
pygame.draw.rect(bg, 色, [x座標, y座標, 横幅, 高さ], 線の太さ, 四隅の丸み)
です。枠なので線の太さなどを指定します。
詳しくはこちら

次にfor文を使って、各モンスターごとの名前を表示しています。
※高さに「i」を掛けています。

for i, mon in enumerate(monster):
  draw_text(bg, "{}".format(mon.name), x_i_t, y_i_t+y_h*i, fnt, col)

最後にコマンドの時と同様に、「▶︎」を使って選択します。
前回、作成した変数btl_enemyを使って、高さの位置を決めます。

以下のような感じです。

 

また、「▶︎」は点滅させたいので、tmrが5の倍数ではない時に表示します。
(5の倍数の時は表示させない)

if idx == 19:
  if tmr%5 != 0:
    draw_text(bg, "▶︎", x_i_t-50, y_i_t+y_h*btl_enemy, fnt, col)

idx == 19は後述しますが、モンスターを選択するのはidxが19の時とします。

 

 

攻撃対象の選択

全てのモンスター名が表示されるようになったので、
どのモンスターを攻撃するかを決めます。
つまり、変数btl_enemyの値を指定します。

この変数btl_enemyを指定する為に
新たにbattle_select関数を作成します。

def battle_select(bg, key):
    global idx, 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

battle_command関数と同じ動きです。
「↑」「↓」キーを押すと矢印「▶︎」を上下に移動させて
btl_enemyの値を指定します。

btl_enemyの値の範囲は
0 <= btl_enemy <= モンスターの数「len(monster)」
です。

また、キャンセル(bを押下)したらidxを11にして、
プレイヤーのコマンド選択に戻します。

先程、少し記載しましたが、idxが19の時にモンスターを選択しますので、
この関数をidxが19の時に呼び出します。

def main(): # メイン処理
(省略)
        elif idx == 19: # モンスターの選択
            draw_battle(screen, fontS)
            if battle_select(screen, key) == True:
                idx = 12 # プレイヤーの攻撃
                tmr = 0

battle_select関数がTrueを返した時にプレイヤーの攻撃に移ります。
(戦闘メッセージが表示されます)

また、idxを19にするタイミングはコマンドで「こうげき」を選択した時です。
今までは「こうげき」を選択したらidxは12となり、モンスターへ攻撃となっていました。

def main(): # メイン処理
(省略)
        elif idx == 11: # プレイヤーのターン(入力待ち)
            btl_enemy = 0 # 初期値に戻す(「▶︎」を一番上にする)
            player.re_defense() # ぼうぎょを元に戻す(ぼうぎょ効果を消す)
            draw_battle(screen, fontS)
            if battle_command(screen, key) == True:
                if COMMAND[btl_cmd_y][btl_cmd_x] == "こうげき":
                    idx = 19 # 変更前は12だった
                    tmr = 0

ついでに
btl_enemy = 0
を入れて、「▶︎」を一番上の位置に戻します。
(プレイヤーのターンになったら一番上に戻す)

 

 

対象モンスターへの攻撃

攻撃対象を選択できるようになったので、選択されたモンスターへ攻撃します。
モンスターへ攻撃するのはidxが12の時です。

def main(): # メイン処理
(省略)

        elif idx == 12: # プレイヤーの攻撃
            draw_battle(screen, fontS)
            if tmr == 1:
                set_message(player.name + " の攻撃!")
                dmg = battle_cal(player, monster[btl_enemy])
            if 2 <= tmr and tmr <= 4:
                screen.blit(imgEffect[0], [600-tmr*100, tmr*100])
            if tmr == 5:
                emy_blink = 5
                if dmg > 0:
                    set_message(monster[btl_enemy].name + "に " + str(dmg)+"ポイントのダメージを与えた!")
                else:
                    set_message("ミス!" + monster[btl_enemy].name + "にダメージを与えられない!")

            if tmr == 11:
                monster[btl_enemy].hp = monster[btl_enemy].hp - dmg
                if monster[btl_enemy].hp <= 0:
                    monster[btl_enemy].hp = 0
                    set_message(player.name + " は " + monster[btl_enemy].name + " をやっつけた!")
                for i, mon in enumerate(monster):
                    if mon.hp != 0:
                        break
                    if i+1 == len(monster): # 全員倒したら(hpが0になったら)勝利
                        idx = 16 # 勝利
                        tmr = 0
            if tmr == 16:
                init_message()
                idx = 13
                tmr = 0

 

monsterオブジェクトはリストになっています。
そして、攻撃対象は変数btl_enemyの値のため、
monster → monster[btl_enemy]
としています。

また、変更前はモンスターが1匹のため、倒せば戦闘勝利(idx = 16)と
していましたが、変更後は複数出現するため、全員倒したかを確認しています。

for i, mon in enumerate(monster):
  if mon.hp != 0:
  break
  if i+1 == len(monster): # 全員倒したら(全員のhpが0になったら)勝利
    idx = 16 # 勝利
    tmr = 0

enumerateはリストの要素と一緒にインデックス番号も取得できます。
(詳しくはこちら

あと、モンスターを倒したメッセージは1匹ごとに表示します。

if monster[btl_enemy].hp <= 0:
  monster[btl_enemy].hp = 0
  set_message(player.name + " は " + monster[btl_enemy].name + " をやっつけた!")

 

あとあと、モンスター側ですが、ひとまず今回はmonster[0]だけが
攻撃するようにしています。
攻撃の時の動作も全員同時です。(∀`*ゞ)

        elif idx == 13: # モンスターのターン、モンスターの攻撃
            draw_battle(screen, fontS)
            if tmr == 5:
                set_message(monster[0].name + " の攻撃!") # monster[0]だけが攻撃
                emy_step = 30
            if tmr == 9:
                dmg = battle_cal(monster[0], player) # monster[0]だけが攻撃
                if dmg > 0:
                    set_message(player.name + "は " + str(dmg) + "ポイントのダメージを受けた!")
                    dmg_eff = 5
                else:
                    set_message("ミス!" + player.name + " はダメージを受けない!")
                    dmg_eff = 0
                emy_step = 0
            if tmr == 15:
                player.hp = player.hp - dmg
                if player.hp <= 0:
                    player.hp = 0
                    idx = 15 # 敗北
                    tmr = 0
            if tmr == 20:
                init_message()
                idx = 11
                tmr = 0

以下の2箇所です。

set_message(monster[0].name + " の攻撃!") # monster[0]だけが攻撃
dmg = battle_cal(monster[0], player) # monster[0]だけが攻撃

 

 

戦闘開始時のメッセージ

次に、戦闘開始した時のメッセージです。
「〜が現れた」、です。

出現するモンスター名を1匹ずつ表示します。

def main(): # メイン処理
(省略)

        elif idx == 10: # 戦闘開始
            if tmr == 1:
                init_battle(screen)
                init_message()
            elif tmr <= 4:
                bx = (4-tmr)*220
                by = 0
                screen.blit(imgBtlBG, [bx, by]) # バトル画面を右から左へ挿入していく
                draw_text(screen, "Encounter!", 350, 200, font, WHITE)
            elif tmr == 5:
                set_message(monster[0].name + " が現れた!")
                draw_battle(screen, fontS)
            elif tmr == 6:
                if len(monster) >= 2: # 出現モンスターが2匹以上の時
                    set_message(monster[1].name + " が現れた!") # 2匹目のメッセージ
                    draw_battle(screen, fontS)
                    pygame.time.delay(delay_speed)
            elif tmr == 7:
                if len(monster) >= 3: # 出現モンスターが3匹以上の時
                    set_message(monster[2].name + " が現れた!") # 3匹目のメッセージ
                    draw_battle(screen, fontS)
                    pygame.time.delay(delay_speed)
            elif tmr == 8:
                if len(monster) == 4: # 出現モンスターが4匹の時
                    set_message(monster[3].name + " が現れた!") # 4匹目のメッセージ
                    draw_battle(screen, fontS)
                    pygame.time.delay(delay_speed)
            elif tmr == 9:
                draw_battle(screen, fontS)
                pygame.time.delay(delay_speed)
                init_message()
                idx = 11
                tmr = 0

 

tmrが5〜8の間にメッセージを表示します。
(詳細はコメントを見てみて下さい)

また、それぞれのメッセージの後に
pygame.time.delay(delay_speed)
を追加しています。
(pygame.time.delayの詳細はこちら

これは、(出現モンスターが4匹の場合)「〜が現れた!」を
4匹同時に表示するのではなく、時間差で順番に表示していく為に
少しだけプログラムを一時的に止めています。
(プログラムを止めることは良くないかもしれないです・・)

tmrで時間差を作ろうとすると、最後の「elif tmr == 9:」に
行き着くまでの空白時間がモンスターの数によって変わってしまいます。
※プレイヤーのコマンド選択(idx=11)が表示されるまでの時間

出現モンスターが1匹ですと、2〜4匹目の「〜が現れた!」が表示されない分だけ
空白時間が長くなってしまいます。
出現モンスターが4匹ですと、2〜4匹目の「〜が現れた!」が全て表示される為、
空白時間が短くなります。

この為、「pygame.time.delay(delay_speed)」を使ってみました。
ちなみにdelay_speedはグローバル変数にして「delay_speed = 500」としています。

何か良い方法がありそうな気もしますが、これで行ってみます。

 

あと、戦闘勝利のメッセージを少し変えました。

(変更前)
        elif idx == 16: # 勝利
            draw_battle(screen, fontS)
            if tmr == 1:
                set_message(player.name + " は " + monster.name + " を倒した!")
(変更後)
        elif idx == 16: # 勝利
            draw_battle(screen, fontS)
            if tmr == 1:
                set_message(player.name + " は 魔物の群れ をやっつけた!")

ただ、モンスターが1匹の時も「魔物の群れ」になってしまいます・・・(ノ∀`*)
そのうち修正します。

 

 

戦闘後の処理

あともう一つ、戦闘後の処理です。

モンスターのリストにモンスターオブジェクトが入っています。
このまま次の戦闘に入ってしまうと、前の戦闘で倒したモンスターが
リストに残っている為、正常に表示されません。

なので、戦闘終了後に削除します。
モンスターの表示位置を決める変数も削除します。

def main(): # メイン処理
(省略)
        elif idx == 22: # 戦闘終了
            idx = 1
            monster.clear() # モンスターオブジェクトを削除
            emy_x.clear() # モンスターの表示位置(x座標)を削除
            emy_y.clear() # モンスターの表示位置(y座標)を削除

リストの要素の削除についてはこちら

 

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

 

 

最後に

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

ただ、いくつか問題が残っています。。

今のままですと、倒したモンスターの名前が残ってしまいます。
倒したモンスター名を選択すると攻撃できてしまいます。

↓のように2番目のモンスターを倒しているのに、
名前が残っています。(赤枠)

なので、次回は倒したモンスターは選択できないようにします。

モンスターの攻撃が1匹だけなのも、そのうち直したいと思います。

まとめサイトへ

 

 

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

 

 

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

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