こんにちは。
「Pythonでつくる ゲーム開発 入門講座」の
Chapter11,12(本格RPGを作ろう!全編・後編)を基にRPGを作っていきます。
前回は戦闘(battle.py)を変更しました。
ただ、まだまだ不十分でしたので、引き続き戦闘を修正します。
今回は物理攻撃について修正します。
目次
攻撃対象の誤り
最初にキャラが攻撃する
対象について修正します。
仲間がいる状態の場合、
今は最後の仲間が選択した
モンスターを全員が攻撃してしまいます。

攻撃対象
ハロ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
何となくで、特に意味はないです。。
とにもかくにも物理攻撃だけは
できるようになったので、、
今回は以上です!(`・ω・´)
最後に
今回は戦闘における物理攻撃の
対象選択を修正しました。
呪文関連が全く手つかずなので、
次回はここを修正します。
【ご注意】
プログラムやデータなどは著作権法により保護されています。
著作者の許諾を得ずに、プログラムおよびデータそのものまたは改変したものを
配布したり販売したりすることはできません。
また、これらを利用して発生した損害などに関して、著作者は一切責任を負いません。