こんにちは。
「Pythonでつくる ゲーム開発 入門講座」の
Chapter11,12(本格RPGを作ろう!全編・後編)を基にRPGを作っていきます。
前回は仲間を追加しました。
前回の変更はマップを移動中のみであり、
戦闘になるとエラーになってしまうので、
今回は戦闘の変更をします。
目次
名前
前回、名前をスプレッドシートから
取得しているところは変更しなかったので
全員同じ名前になっていました。

これは分かりにくいので
1人1人名前を変えます。
one_hour_dungeon.py
(変更前)
player = []
player.append(chara.Brave())
player.append(chara.Brave())
player.append(chara.Brave())
player.append(chara.Brave())
(変更後)
player = []
player.append(chara.Brave("ハロ1"))
player.append(chara.Brave("ハロ2"))
player.append(chara.Brave("ハロ3"))
player.append(chara.Brave("ハロ4"))
chara.py
(変更前)
class Brave(Chara):
def __init__(self):
super(Brave, self).__init__()
self.name = brave_name
(変更後)
class Brave(Chara):
def __init__(self, name): # 引数にnameを追加
super(Brave, self).__init__()
self.name = name
オブジェクトを作る時に
名前を指定するようにしました。
それに伴いBraveクラスの
コンストラクタでnameの
引数を追加しました。
print(player[0].name)
# ハロ1
print(player[1].name)
# ハロ2
のようになります。

battleの変更
今回のメインになるbattle.pyを変更します。
前回と同じように
「player」から「player[0]」への
変更が大量にあります。
※元は変数の「player」が
リストに変わった為です。
今回もこの変更は最後に回します。
ステータス表示
最初にステータス表示を変更します。
戦闘画面

残りHPによって
色を変えます。
変更前
def draw_battle(bg, fnt, obj, player):
(省略)
if party == 1: # 一人の時の全体の表示幅
x_w *= 1
col = WHITE
if player.hp < player.maxhp/4:
col = DANGER
elif player.hp < player.maxhp/2:
col = WARNING
変更後
WHITE = (255, 255, 255)
WARNING = (255, 191, 0)
DANGER = (255, 101, 101)
(省略)
def draw_battle(bg, fnt, obj, player):
(省略)
# if party == 1: # 不要なので削除 len(player)で取得
# x_w *= 1 # 不要なので削除 len(player)で取得
col = WHITE
for p in player:
if p.hp < p.maxhp/4:
col = DANGER
break
elif p.hp < p.maxhp/2:
col = WARNING
前回の移動画面と同様です。
MAXHPの1/4未満が1人でもいる:DANGER
MAXHPの1/2未満が1人でもいる:WARNING
ステータスの枠です。
変更前
def draw_battle(bg, fnt, obj, player):
(省略)
# プレイヤーのパラメータ表示
x_i = int(bg.get_width()/4) # x座標の開始位置
x_w = int(bg.get_width()/8) # 幅
y_i = 20 # y座標の開始位置
y_h = 120 # 高さ
x_i_t = x_i+x_w/2-50 # テキストのx座標の開始位置
pygame.draw.rect(bg, col, [x_i, y_i, x_w, y_h], 2, 5)
pygame.draw.line(bg, col, [x_i, y_i+33], [x_i+x_w-1, y_i+33], 2) # -1がないと少し出る
変更後
def draw_battle(bg, fnt, obj, player):
(省略)
# プレイヤーのパラメータ表示
x_i = int(bg.get_width()/4) # x座標の開始位置
x_b = int(bg.get_width()/8) # 1人分の幅
# x座標の開始位置 + 横幅 = 0.95にする(右端を0.05空ける)
if len(player) == 1:
x_w = x_b # 横幅
elif len(player) == 2:
x_w = x_b * 2 # 横幅
elif len(player) == 3:
x_w = x_b * 3 # 横幅
elif len(player) == 4:
x_w = x_b * 4 # 横幅
y_i = 20 # y座標の開始位置
y_h = 120 # 高さ
x_i_t = 0 # テキストのx座標の開始位置
y_i_t = y_i + 6 # テキストのy座標の開始位置
pygame.draw.rect(bg, col, [x_i, y_i, x_w, y_h], 2, 5)
pygame.draw.line(bg, col, [x_i, y_i+33], [x_i+x_w-1, y_i+33], 2) # -1がないと少し出る
人数によって枠の横幅を
変えています。
if len(player) == 1:
x_w = x_b # 横幅
elif len(player) == 2:
x_w = x_b * 2 # 横幅
elif len(player) == 3:
x_w = x_b * 3 # 横幅
elif len(player) == 4:
x_w = x_b * 4 # 横幅
枠に続いて、
ステータスの表示です。
変更前
def draw_battle(bg, fnt, obj, player):
(省略)
# 名前の表示(回復呪文の対象を選択中の時は点滅)
if idx == 25 and chara.get_spell_target(player.mas_spell[sel_spell_i]) == 0: # 呪文の対象の選択中,呪文の対象が味方なら
draw_text(bg, "{}".format(player.name), x_i_t, y_i+6, fnt, BLINK[tmr%6])
else: # 呪文の対象の選択中でなければ
draw_text(bg, "{}".format(player.name), x_i_t, y_i+6, fnt, col)
if len(str(player.hp)) == 3:
draw_text(bg, mojimoji.han_to_zen("H{}".format(player.hp)), x_i_t, y_i+36, fnt, col)
elif len(str(player.hp)) == 2:
draw_text(bg, mojimoji.han_to_zen("H {}".format(player.hp)), x_i_t, y_i+36, fnt, col)
elif len(str(player.hp)) == 1:
draw_text(bg, mojimoji.han_to_zen("H {}".format(player.hp)), x_i_t, y_i+36, fnt, col)
if len(str(player.mp)) == 3:
draw_text(bg, mojimoji.han_to_zen("M{}".format(player.mp)), x_i_t, y_i+66, fnt, col)
elif len(str(player.mp)) == 2:
draw_text(bg, mojimoji.han_to_zen("M {}".format(player.mp)), x_i_t, y_i+66, fnt, col)
elif len(str(player.mp)) == 1:
draw_text(bg, mojimoji.han_to_zen("M {}".format(player.mp)), x_i_t, y_i+66, fnt, col)
if len(str(player.lv)) == 2:
draw_text(bg, mojimoji.han_to_zen("L:{}".format(player.lv)), x_i_t, y_i+96, fnt, col)
elif len(str(player.lv)) == 1:
draw_text(bg, mojimoji.han_to_zen("L: {}".format(player.lv)), x_i_t, y_i+96, fnt, col)
変更後
def draw_battle(bg, fnt, obj, player):
(省略)
for i, p in enumerate(player):
x_i_t = x_i + x_b*i + 10 # テキストのx座標の開始位置
# 名前の表示(回復呪文の対象を選択中の時は点滅)
if idx == 25 and chara.get_spell_target(p.mas_spell[sel_spell_i]) == 0: # 呪文の対象の選択中,呪文の対象が味方なら
draw_text(bg, "{}".format(p.name), x_i_t, y_i_t, fnt, BLINK[tmr%6])
else: # 呪文の対象の選択中でなければ
draw_text(bg, "{}".format(p.name), x_i_t, y_i_t, fnt, col)
if len(str(p.hp)) == 3:
draw_text(bg, mojimoji.han_to_zen("H{}".format(p.hp)), x_i_t, y_i_t+30, fnt, col)
elif len(str(p.hp)) == 2:
draw_text(bg, mojimoji.han_to_zen("H {}".format(p.hp)), x_i_t, y_i_t+30, fnt, col)
elif len(str(p.hp)) == 1:
draw_text(bg, mojimoji.han_to_zen("H {}".format(p.hp)), x_i_t, y_i_t+30, fnt, col)
if len(str(p.mp)) == 3:
draw_text(bg, mojimoji.han_to_zen("M{}".format(p.mp)), x_i_t, y_i_t+60, fnt, col)
elif len(str(p.mp)) == 2:
draw_text(bg, mojimoji.han_to_zen("M {}".format(p.mp)), x_i_t, y_i_t+60, fnt, col)
elif len(str(p.mp)) == 1:
draw_text(bg, mojimoji.han_to_zen("M {}".format(p.mp)), x_i_t, y_i_t+60, fnt, col)
if len(str(p.lv)) == 2:
draw_text(bg, mojimoji.han_to_zen("L:{}".format(p.lv)), x_i_t, y_i_t+90, fnt, col)
elif len(str(p.lv)) == 1:
draw_text(bg, mojimoji.han_to_zen("L: {}".format(p.lv)), x_i_t, y_i_t+90, fnt, col)
for i, p in enumerate(player):
x_i_t = x_i + x_b*i + 10 # テキストのx座標の開始位置
テキストの開始位置を
1人1人変えます。
※縦の位置は変わりません。
また、オブジェクトは「p」に
入っているので
名前:p.name
HP:p.hp
MP:p.mp
のようになります。

ステータス表示は以上です。
コマンド選択
次にコマンドの選択について
変更します。
選択するのは1人のみでしたが
複数人が選択することになるので
1つグローバル変数を追加します。
今、誰がコマンドを
選択しているのか、です。
※リストplayerの添字になります。
btl_player = 0 # コマンド選択しているキャラ(playerの添字)
この変数を使って
変更していきます。
def draw_battle(bg, fnt, obj, player):
(省略)
if idx == 11 or idx == 19: # コマンド選択 or モンスター選択(攻撃)
# コマンド表示
x_i = 50 # x座標の開始位置
x_w = bg.get_width()*0.4
y_i = bg.get_height()*0.6 + 40
y_h = bg.get_height()*0.4 - 40 # 高さ
if len(player[btl_player].name) <= 2: # 変更前:if len(player.name) <= 2:
x_i_t = x_i+x_w/2-25 # テキストのx座標の開始位置
else:
x_i_t = x_i+x_w/2-50 # テキストのx座標の開始位置
y_i_t = y_i + 6
pygame.draw.rect(bg, col, [x_i, y_i, x_w, y_h], 2, 5)
pygame.draw.line(bg, col, [x_i, y_i+33], [x_i+x_w-1, y_i+33], 2) # -1がないと少し出る
draw_text(bg, "{}".format(player[btl_player].name), x_i_t, y_i_t, fnt, col) # 変更前:player[btl_player].name
コマンド選択時の名前の位置です。
名前の文字数によって変えています。
コマンドを選択しているキャラの
オブジェクトは「player[btl_player]」に
なります。

コマンド選択です。(idx11)
def main(screen, clock, font, fontS, player, area):
global btl_cmd_x, btl_cmd_y, btl_enemy, btl_player # btl_playerを追加
(省略)
elif idx == 11: # プレイヤーのターン(入力待ち)
btl_enemy = 0 # モンスターの選択位置を初期化
player[btl_player].use_defense = False # ぼうぎょを元に戻す(ぼうぎょ効果を消す)
player[btl_player].check_dfs()
if not player[btl_player].flag_sleep: # 眠っていないならコマンド選択 眠っているとactは28にセットされている
draw_battle(screen, fontS, turn_obj, player)
if battle_command(screen, key) == True:
if COMMAND[btl_cmd_y][btl_cmd_x] == "こうげき":
idx = 19
tmr = 0
if COMMAND[btl_cmd_y][btl_cmd_x] == "じゅもん":
idx = 21 # 呪文の選択
tmr = 0
if COMMAND[btl_cmd_y][btl_cmd_x] == "ぼうぎょ":
player[btl_player].act = 18 # get_battle_turnの戻り値idxを18
player[btl_player].use_defense = True
player[btl_player].check_dfs() # 選択した時点で防御力2倍(ターンが回ってきてからではない)
idx = 23
tmr = 0
if COMMAND[btl_cmd_y][btl_cmd_x] == "にげる":
idx = 14
tmr = 0
変更点です。
player[btl_player].use_defense = False # ぼうぎょを元に戻す(ぼうぎょ効果を消す)
player[btl_player].check_dfs()
ぼうぎょについて
コマンド選択する
オブジェクト(player[btl_player])を
確認しています。
player[btl_player].act = 18 # get_battle_turnの戻り値idxを18
player[btl_player].use_defense = True
player[btl_player].check_dfs() # 選択した時点で防御力2倍(ターンが回ってきてからではない)
防御を選択した時の
オブジェクトを「player[btl_player]」
にします。
あと、眠っている時の
確認をidx11に持ってきました。
1人1人確認するには
ここのほうが都合が良かった為です。
if not player[btl_player].flag_sleep: # 眠っていないならコマンド選択 眠っているとactは28にセットされている
眠っている場合は
コマンド選択を飛ばします。
今まではidx27の
最後に確認していましたので
これを削除します。
elif idx == 27: # 呪文効果の確認
check_message = check_spell_effect(player) # 関数がないと全ての呪文効果ごとに↓を記載する必要がある
for mes in check_message:
set_message(mes)
draw_battle(screen, fontS, turn_obj, player)
pygame.display.update() # if turn_obj is None: にしてidxを11にするとここに来ない
pygame.time.delay(delay_speed)
init_message()
# if player.flag_sleep: # 削除
# idx = 23 # ターンセット(眠っている時はコマンド選択できない) # 削除
# else: # 削除
# idx = 11 # 削除
攻撃対象の選択です。
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
ここも「player[btl_player]」に
変更しています。
elif idx == 23: # ターンセット
if btl_player == len(player)-1: # 全員のコマンド選択完了
set_battle_turn(btl_start, player)
for mon in monster:
mon.set_monster_com()
btl_start = 0 # 戦闘は通常に戻す
idx = 24
else: # 選択していなプレイヤーがいる(眠っているときはactが28がセットされている)
idx = 11
btl_player += 1
最後のキャラのコマンド選択が
終わったら、攻撃開始となります。
まだ、コマンド選択が
終わっていないキャラがいる時は
次のキャラのコマンド選択となります。
選択済みのキャラの
コマンドキャンセルです。
def battle_command(bg, key): # コマンドの入力と表示
global btl_cmd_x, btl_cmd_y, btl_player # btl_player を追加
(省略)
elif key[K_b]: # キャンセル
if btl_player > 0:
btl_player -= 1
コマンド選択時に
キャンセル(「b」キーを押下)すると
前のキャラに戻します。
「btl_player」関連の変更は以上です。
その他
いくつか細かいところの変更です。
素早さについて、
全員分を確認をします。
※素早さの高い順に行動します。
def set_battle_turn(num, player): # 0:通常、1:先制攻撃、2:不意打ち
(省略)
# 素早さの順に並べる(戦闘順)
if num == 0 or num == 1:
for p in player:
r = random.randint(66, 100)
tmp_order[p] = int(p.quick * r/100)
モンスターと遭遇した時に
不意打ちの場合の
メッセージを人数によって
少し変えます。
def main(screen, clock, font, fontS, player, area):
(省略)
elif idx == 10: # 戦闘開始
(省略)
else:
set_message(mon_typ + "は")
if len(player) == 1:
set_message(player[0].name + "が みがまえるまえに")
else:
set_message(player[0].name + "たちが みがまえるまえに")
set_message("おそいかかってきた!")
キャラの攻撃です。
elif idx == 12: # プレイヤーの攻撃
draw_battle(screen, fontS, turn_obj, player)
if tmr == 1:
set_message(turn_obj.name + " のこうげき!") # playerから変更
「player」から「turn_obj」に
変更しました。
ここはもともと「turn_obj」が
正でした。。
※turn_obj:行動中のオブジェクト
にげる、全滅時のメッセージです。
elif idx == 14: # 逃げられる?
draw_battle(screen, fontS, turn_obj, player)
if tmr == 1:
if len(player) == 1:
set_message(player[0].name + "は にげだした!")
else:
set_message(player[0].name + "たちは にげだした!")
elif idx == 15: # 敗北
draw_battle(screen, fontS, turn_obj, player)
if tmr == 1:
pygame.mixer.music.stop()
if len(player) == 1:
set_message(player[0].name + " は気絶した...")
else:
set_message(player[0].name + "たち は全滅した...")
戦闘からにげる時や
全滅した時のメッセージも
人数によって変更しました。
戦闘が終わった時の呪文効果について
全員リセットに変更します。
elif idx == 22: # 戦闘終了
idx = 1
monster.clear() # モンスターオブジェクトを削除
dead_monster.clear()
btl_exp = 0
for p in player:
p.spell_reset()
return 1
このセクションの最後になります。
戦闘に勝利した時の経験値です。
import math
(省略)
elif idx == 16: # 勝利
draw_battle(screen, fontS, turn_obj, player)
if tmr == 1:
if len(dead_monster) != 1: # 1匹の時はいらない
set_message(mon_typ + " をやっつけた!")
btl_exp = math.ceil(btl_exp/len(player)) # 切り上げ
for p in player:
p.exp += btl_exp
if tmr == 5:
if len(player) == 1:
set_message(str(btl_exp) + "ポイントの けいけんちを かくとく!")
else:
set_message("それぞれ" + str(btl_exp) + "ポイントの けいけんちを かくとく!")
btl_exp = math.ceil(btl_exp/len(player))
獲得した経験値は仲間同士で
等分します。
ゲーム内で少数は扱わないので
整数にしますが、ceilで切り上げました。
※ceilの詳細はこちら。
※mathのimportが必要です。
intでも整数になりますが、
intは切り捨てになります。
4人の時に獲得経験値が4未満だと
0になってしまうなどの理由で
切り上げにしました。
import math
btl_exp1 = int(3/4)
btl_exp2 = math.ceil(3/4)
print(f"btl_exp1:{btl_exp1}, btl_exp2:{btl_exp2}")
# btl_exp1:0, btl_exp2:1
上記のように
int(3/4):0
math.ceil(3/4):1
になります。
「その他」については
以上です。
playerをリストに
今回も前回同様、最後に
変数だった「player」を
「player[0]」に変更します。
ただ、今回の変更はとりあえずです。。
「player」もおかしいですが、
「player[0]」でもない箇所が
いくつもあります。
ので、動作としてはおかしかったり、
止まってしまったりします。
というわけで、ご参考程度に・・
battle.pyを添付します。
という感じで、今回は以上です(´・ω・ `)
最後に
今回は仲間追加に伴い
battle.pyを変更しました。
ただ、残した修正点が多いです。
※気付いているバグだけでもたくさんあります。
気付いていないのも含めると・・。
とうわけで、次回も戦闘(battle.py)です。
【ご注意】
プログラムやデータなどは著作権法により保護されています。
著作者の許諾を得ずに、プログラムおよびデータそのものまたは改変したものを
配布したり販売したりすることはできません。
また、これらを利用して発生した損害などに関して、著作者は一切責任を負いません。