こんにちは。
「Pythonでつくる ゲーム開発 入門講座」のChapter11,12(本格RPGを作ろう!全編・後編)を基にRPGを作っていきます。
前回は一定の経験値が貯まるとプレイヤーのレベルを上げるようにしました。
今回は前回の最後に追加した変数actを使って、戦闘の行動順の仕組みを変更していきます。
また、戦闘が始まった時の先制攻撃や不意打ちを追加します。
目次
変数actの追加
最初に変数actについての変更です。
前回、クラスの変数にself.actを追加しましたが、
コメントの通り、idxの値を入れるので
何の行動(idx)を選択したかを保持します。
class Brave(Chara):
def __init__(self):
(省略)
self.act = 0 # 戦闘のコマンド(行動)12:攻撃、18:防御(数字はidxの値)
とりあえず、この変数を使って
one_hour_dungeon.pyを変更します。
変更前
def get_battle_turn():
(省略)
if turn_key is player:
return turn_key, 12 # idx=12(攻撃)
else:
return turn_key, 13
(省略)
def main(): # メイン処理
(省略)
if COMMAND[btl_cmd_y][btl_cmd_x] == "ぼうぎょ":
idx = 18
tmr = 0
(省略)
elif idx == 19: # モンスターの選択
draw_battle(screen, fontS, turn_obj)
if battle_select(screen, key) == True:
set_battle_turn(0)
idx = 24
tmr = 0
elif idx == 24: # ターン確認
tmr = 0
turn_obj, idx = get_battle_turn() # 全てidx=12
変更後
def get_battle_turn():
(省略)
if turn_key is player: # プレイヤーの場合
return turn_key, player.act # 12をplayer.actに変更
else: # モンスターの場合
return turn_key, 13 # モンスターは攻撃のみ(idx=13)
(省略)
def main(): # メイン処理
(省略)
if COMMAND[btl_cmd_y][btl_cmd_x] == "ぼうぎょ":
player.act = 18 # idx=18から変更
set_battle_turn(0) # 追加
idx = 24 # 追加
tmr = 0
(省略)
elif idx == 19: # モンスターの選択
draw_battle(screen, fontS, turn_obj)
if battle_select(screen, key) == True:
player.act = 12 # 追加
set_battle_turn(0)
idx = 24
tmr = 0
elif idx == 24: # ターン確認
tmr = 0
turn_obj, idx = get_battle_turn() # idx = player.act なので色々な行動が可能!
get_battle_turn関数の戻り値は12の固定にしていたので
「こうげき(idx=12)」のみしか行動できませんでした。
変更前ですと
elif idx == 24
のところで、get_battle_turn関数の戻り値が
全て「こうげき(idx=12)」になるからです。
なので、ここを変数player.actに変更しました。
turn_obj, idx = get_battle_turn() # idx = player.act なので色々な行動が可能!
※モンスターの行動は攻撃のみです。
そのうち、モンスターも攻撃以外の行動が
出来るようにしようと思っています。
return turn_key, 13 # モンスターは攻撃のみ(idx=13)
これで、idx19の時にモンスターを選択した後に
player.actに12を入れることで
get_battle_turn関数の戻り値はこうげき(idx=12)になり
モンスターに攻撃することになります。
ぼうぎょの行動
変数actについてもう一つ、ぼうぎょです。
ぼうぎょ(idx=18)を選択した時も
player.actに18を入れることで
get_battle_turn関数の戻り値はぼうぎょ(idx=18)になり
身を守る行動になります。
そして、player.actに18を入れた後にset_battle_turn関数を実行して
モンスターも含めた行動順を決めるようにしました。
変更前では、ぼうぎょの時にset_battle_turn関数を実行していない為
モンスターは行動せずに身を守ってターンが終わっていました。
set_battle_turn関数についてはこちらとこちら。
def set_battle_turn(num): # 0:通常、1:先制攻撃、2:不意打ち
global battle_order
tmp_order = {}
if num == 0 or num == 1:
r = random.randint(66, 100)
tmp_order[player] = int(player.quick * r/100)
if num == 0 or num == 2:
for mon in monster:
r = random.randint(66, 100)
tmp_order[mon] = int(mon.quick * r/100)
# [(obj, quick), (ojb, quick), ...]のリストになる
battle_order = sorted(tmp_order.items(), key=lambda x:x[1], reverse=True)
変更後は、player.actに18を入れた後にset_battle_turn関数を実行するので
ぼうぎょ(idx=18)が実行されるようになりました。
あと、防御についてもう一つ、ぼうぎょ(idx=18)は選択した時点で
防御力を2倍にします。(順番が回ってきたら2倍ではない)
なので、一番最初にモンスターに攻撃されたとしても
防御力が2倍の状態で攻撃を受けることになります。
if COMMAND[btl_cmd_y][btl_cmd_x] == "ぼうぎょ":
player.act = 18
player.defense() # 追加 選択した時点で防御力2倍にする
set_battle_turn(0)
idx = 24
tmr = 0
(省略)
elif idx == 18: # ぼうぎょ 順番が回ってきたら戦闘のメッセージを出すだけ、ここで2倍にしない
draw_battle(screen, fontS, turn_obj)
if tmr == 1:
set_message(player.name + "は みをまもっている!")
if tmr == 5:
init_message()
idx = 24 # ターンの確認
tmr = 0
変数actについては以上です。
先制攻撃・不意打ちのメッセージ
次に、先制攻撃や不意打ちです。
まず、戦闘開始時のメッセージですが、
それぞれ以下の2種類にします。
どちらのメッセージにするかは半々の確率で
表示するようにします。
先制攻撃


不意打ち


あと、モンスターの種類が複数の場合は「まもののむれ」に変更します。

それでは変更していきます。
モンスターが何種類なのかはinit_battle関数から取得するようにします。
この関数は以下を返すようにします。
・1種類ならモンスターの名前
・2種類以上なら「まもののむれ」
変数dic_typにモンスターの種類の数が入っているので
これで判定します。
※一番最後の箇所を追加しました。
def init_battle(bg): # 戦闘に入る準備をする
global monster
num_enemy = random.randint(1, 4) # 出現するモンスターの数
sum_x = 0 # モンスターの表示位置
list_typ = [] # モンスター番号を入れる
for _ in range(num_enemy):
if floor >= 10:
typ = random.randint(1, 10)
else:
typ = random.randint(1, floor+1)
list_typ.append(typ)
dic_typ = collections.Counter(list_typ) # 重複を抽出 何種類のモンスターがいるか
i = 0
for k, v in dic_typ.items(): # k:typ、v:数(typの種類がvの数だけ存在)
for j in range(v):
monster.append(chara.Monster(k))
if v != 1: # 同じ種類が複数いる時
monster[i].set_name(j)
sum_x += monster[i].img.get_width() + 35 # 35はモンスターの間隔
monster[i].set_y(bg.get_height()*0.6 - monster[i].img.get_height()) # どのモンスターも画面の7割が一番下に揃えられる
i += 1
sum_x -= 35 # 最後のモンスターは間隔を開ける必要がない
x_i_i = bg.get_width()/2 - sum_x/2 # 一番左のモンスターのx座標
monster[0].set_x(x_i_i) # 1匹目
for i in range(num_enemy-1): # 2匹目
x_i_i += monster[i].img.get_width() + 35 # 2匹目以降のモンスターの横幅+間隔 (2匹目は1匹目の横幅+間隔分をずらす)
monster[i+1].set_x(x_i_i) # 各モンスターのx座標 2匹目以降なので i+1
#####追加#####
if len(dic_typ) == 1:
return monster[0].name[:-2] # モンスターが1種類
else:
return "まもののむれ" # モンスターが複数種類
呼び出す側で戻り値を受け取る為に
変数mon_typを用意します。(モンスターの名前 or 「まもののむれ」)
また、通常 or 先制攻撃 or 不意打ちを入れる変数も用意しておきます。(btl_start)
def main(): # メイン処理
(省略)
btl_start = 0 # 0:通常、1:先制攻撃、2:不意打ち
mon_typ = "" # 1種類:モンスターの名前、モンスターの種類が複数:"まもののむれ"
(省略)
elif idx == 10: # 戦闘開始
if tmr == 1:
mon_typ = init_battle(screen) # 戻り値を受け取る
この変数mon_typを使って、先制攻撃・不意打ちの
メッセージを表示します。
※「elif tmr == 9:」から追加です。
elif idx == 10: # 戦闘開始
if tmr == 1:
mon_typ = 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, turn_obj)
elif tmr == 6:
if len(monster) >= 2:
set_message(monster[1].name + " が現れた!")
draw_battle(screen, fontS, turn_obj)
time.sleep(0.3)
elif tmr == 7:
if len(monster) >= 3:
set_message(monster[2].name + " が現れた!")
draw_battle(screen, fontS, turn_obj)
time.sleep(0.3)
elif tmr == 8:
if len(monster) == 4:
set_message(monster[3].name + " が現れた!")
draw_battle(screen, fontS, turn_obj)
time.sleep(0.3)
#####ここから追加#####
elif tmr == 9:
btl_start = random.randint(0, 2) # 通常・先制攻撃・不意打ちをそれぞれ1/3
if btl_start == 1: # 先制攻撃
init_message()
if random.randint(0, 1) == 0:
set_message("しかし " + mon_typ + "は")
set_message("まだ こちらに きづいていない!")
else:
set_message("しかし " + mon_typ + "は")
set_message("おどろき とまどっている!")
draw_battle(screen, fontS, turn_obj)
time.sleep(0.5)
elif btl_start == 2: # 不意打ち
init_message()
if random.randint(0, 1) == 0:
set_message(mon_typ + "は")
set_message("いきなり おそいかかってきた!")
else:
set_message(mon_typ + "は")
set_message(player.name + "が みがまえるまえに")
set_message("おそいかかってきた!")
draw_battle(screen, fontS, turn_obj)
time.sleep(0.5)
randomを使って、通常・先制攻撃・不意打ちを
それぞれ1/3の確率で発生するようにします。
btl_start = random.randint(0, 2)
randint(a, b)は
a <= n <= b
です。
詳細はこちら。
先制攻撃・不意打ち、それぞれ2種類のメッセージも
randomを使って半々(1/2)の確率で表示します。
if random.randint(0, 1) == 0:
これで戦闘が始まった時、先制攻撃・不意打ちの
メッセージを表示するようになりました。
先制攻撃・不意打ち
メッセージが表示されるようになったので、
あとは先制攻撃・不意打ちを実行します。
これを実現する関数はこちらで既に作成しました。
def set_battle_turn(num): # 引数numを追加 0:通常、1:先制攻撃、2:不意打ち
global battle_order
tmp_order = {}
if num == 0 or num == 1: # 通常と先制はプレイヤーを追加
r = random.randint(66, 100)
tmp_order[player] = int(player.quick * r/100)
if num == 0 or num == 2: # 通常と不意打ちはモンスターを追加
for mon in monster:
r = random.randint(66, 100)
tmp_order[mon] = int(mon.quick * r/100)
# [(obj, quick), (ojb, quick), ...]のリストになる
battle_order = sorted(tmp_order.items(), key=lambda x:x[1], reverse=True)
print(battle_order)
set_battle_turn関数を呼び出す側で引数を指定すれば
通常・先制攻撃・不意打ちとなります。
で、set_battle_turn関数を実行する場所ですが、
こちらで一度は削除したidx23にします。
あちこちでset_battle_turn関数を実行するより
idx23にまとめたくなったからです。
それでは変更していきます。
elif idx == 10: # 戦闘開始
(省略)
#####再掲ここから#####
elif tmr == 9:
btl_start = random.randint(0, 2)
btl_start = 2
if btl_start == 1: # 先制攻撃
init_message()
if random.randint(0, 1) == 0:
set_message("しかし " + mon_typ + "は")
set_message("まだ こちらに きづいていない!")
else:
set_message("しかし " + mon_typ + "は")
set_message("おどろき とまどっている!")
draw_battle(screen, fontS, turn_obj)
time.sleep(0.5)
elif btl_start == 2: # 不意打ち
init_message()
if random.randint(0, 1) == 0:
set_message(mon_typ + "は")
set_message("いきなり おそいかかってきた!")
else:
set_message(mon_typ + "は")
set_message(player.name + "が みがまえるまえに")
set_message("おそいかかってきた!")
draw_battle(screen, fontS, turn_obj)
time.sleep(0.5)
#####再掲ここまで#####
elif tmr <= 16:
draw_battle(screen, fontS, turn_obj)
time.sleep(1)
init_message()
if btl_start == 2: # 追加
idx = 23 # 不意打ちの時はプレイヤーはコマンドを選択せずにset_battle_turn関数を実行
else:
idx = 11 # 不意打ち以外はプレイヤーのコマンド選択
tmr = 0
(省略)
elif idx == 23: # ターンセット 追加(復活)
set_battle_turn(btl_start) # btl_start 0:通常、1:先制攻撃、2:不意打ち
btl_start = 0 # 2ターン目からは通常に戻す
idx = 24
tmr = 0
戦闘が始まった後の行先として、以下を追加しています。
・不意打ち:プレイヤーはコマンドを選択せずにset_battle_turn関数を実行へ(idx23)
・不意打ち以外(通常 or 先制攻撃)
:プレイヤーのコマンド選択へ(idx11)
if btl_start == 2:
idx = 23
else:
idx = 11
次にidx==23を復活して、set_battle_turn関数を実行しています。
また、先制攻撃・不意打ちであっても、
2ターン目からは通常に戻します。(btl_start = 0)
elif idx == 23:
set_battle_turn(btl_start)
btl_start = 0
あと、他にset_battle_turn関数を実行している箇所を
idx23に集めます。
※にげるに失敗した時は不意打ちと同じ効果にする為に
btl_startに2をセットしています。
if COMMAND[btl_cmd_y][btl_cmd_x] == "ぼうぎょ":
player.act = 18 # get_battle_turnの戻り値idxを18
player.defense() # 選択した時点で防御力2倍(ターンが回ってきてからではない)
idx = 23 # set_battle_turn(0)から変更
tmr = 0
(省略)
elif idx == 19: # 敵の選択
draw_battle(screen, fontS, turn_obj)
if battle_select(screen, key) == True:
player.act = 12 # get_battle_turnの戻り値idxを12
idx = 23 # set_battle_turn(0)から変更
tmr = 0
(省略)
elif idx == 14: # にげるを選択
(省略)
if tmr == 10: # 逃げられなかった時
init_message()
btl_start = 2 # 不意打ちと同じにする為に2をセット
idx = 23 # set_battle_turn(2)から変更
tmr = 0
これで先制攻撃、不意打ちを追加することができました。
ご参考までにこちらに動画を載せました。
とういうわけで、今回は以上です!(`・ω・´)
最後に
今回は先制攻撃・不意打ちの機能を追加しました。
ここしばらく戦闘画面を変更してきましたが、
そろそろ違うことをやりたくなってきました。
ので、次回くらいからマップ作りとか
別のことを進めようかと思っています。
【ご注意】
プログラムやデータなどは著作権法により保護されています。
著作者の許諾を得ずに、プログラムおよびデータそのものまたは改変したものを
配布したり販売したりすることはできません。
また、これらを利用して発生した損害などに関して、著作者は一切責任を負いません。