こんにちは。
「Pythonでつくる ゲーム開発 入門講座」の
Chapter11,12(本格RPGを作ろう!全編・後編)を基にRPGを作っていきます。
前回は呪文の表示について、
覚えている(使える)呪文が9つ以上の時は
2ページ目、3ページ目に表示できるように変更しました。
今回は選択している(「▶︎」が指している)呪文の
説明を表示するように変更します。
目次
呪文の説明の表示
選択している(「▶︎」が指している)呪文の
説明を表示するように変更していきます。
(変更の対象は戦闘中の画面です)
また、あわせてその呪文の消費MPも表示します。
変更前

変更後

説明の下にある「2/10」について
2が消費MP
10がプレイヤーの残りのMP
です。
5/2のように消費MPの方が残りのMPより
大きい場合はその呪文を使えません。
※今回の変更では表示だけであり
上記の制約はそのうちになります(^_^;)
また、呪文の説明文については
こちらのサイトを利用させて頂きました。
キャラに新たに変数を追加
最初にキャラに変数を追加します。
まずはスプレッドシートに新しく列を追加します。

N列:消費MP
O列:ターゲット
P列:説明
を追加しました。
ターゲットは
0:味方にかける呪文
1:モンスターにかける呪文
です。
今回は利用しませんが
そのうち必要になる気がしたので追加しました。
次にchara.pyで
追加した列の値を変数に入れていきます。
スプレッドシートの値を読み込みます。
(省略)
chara1_spell = ws_chara.col_values(13) # M列 覚える呪文
chara1_usemp = ws_chara.col_values(14) # N列 消費MP
chara1_spell_target = ws_chara.col_values(15) # O列 呪文のターゲット(0:味方,1:モンスター)
chara1_spell_explain = ws_chara.col_values(16) # P列 呪文の説明
キャラに変数を持たせます。
class Brave(Chara):
def __init__(self):
(省略)
self.mas_spell = [] # 覚えた呪文
if chara1_spell[self.lv] != "": # レベル1で覚える呪文があるなら
self.mas_spell.append(chara1_spell[1])
self.usemp = []
if chara1_usemp[self.lv] != "": # レベル1で覚える呪文があるなら
self.usemp.append(chara1_usemp[1])
self.spell_target = []
if chara1_spell_target[self.lv] != "": # レベル1で覚える呪文があるなら
self.spell_target.append(chara1_spell_target[1])
self.spell_explain = []
if chara1_spell_explain[self.lv] != "": # レベル1で覚える呪文があるなら
self.spell_explain.append(chara1_spell_explain[1])
(省略)
def master_spell(self):
if chara1_spell[self.lv] != "": # レベルアップして覚える呪文があるなら
self.mas_spell.append(chara1_spell[self.lv])
self.usemp.append(chara1_usemp[self.lv])
self.spell_target.append(chara1_spell_target[self.lv])
self.spell_explain.append(chara1_spell_explain[self.lv])
return chara1_spell[self.lv]
return ""
メソッドmaster_spellで
レベルが上がった時に覚える呪文があると
キャラのそれぞれの変数に値を追加します。
ちなみにテスト段階では
毎回レベルアップするのが面倒なので
最初から覚えている状態にしています(^_^;)
class Brave(Chara):
def __init__(self):
(省略)
# self.mas_spell = [] # 覚えた呪文
# if chara1_spell[self.lv] != "": # レベル1で覚える呪文があるなら
# self.mas_spell.append(chara1_spell[1]) # mas:マスター
# self.usemp = []
# if chara1_usemp[self.lv] != "": # レベル1で覚える呪文があるなら
# self.usemp.append(chara1_usemp[1])
# self.spell_target = []
# if chara1_spell_target[self.lv] != "": # レベル1で覚える呪文があるなら
# self.spell_target.append(chara1_spell_target[1])
# self.spell_explain = []
# if chara1_spell_explain[self.lv] != "": # レベル1で覚える呪文があるなら
# self.spell_explain.append(chara1_spell_explain[1])
# テストではゲーム開始時点で呪文を覚えている状態にしておく
self.mas_spell = ['ホイミ', 'ベホイミ', 'ベホマ', 'メラ', 'メラミ', 'メラゾーマ', 'ギラ', 'ベギラマ']
self.usemp = [3, 5, 10, 2, 4, 10, 2, 5]
self.spell_target = [0, 0, 0, 1, 1, 1, 1, 1]
self.spell_explain = ["仲間ひとりのHPを\n回復する", "仲間ひとりのHPを\nかなり回復する", "仲間ひとりのHPを\n全快にする", "小さな火の玉で\n敵1体に小ダメージを\n与える", "複数の火の玉で\n敵1体に小ダメージを\n与える", "巨大な火の玉で\n敵1体に小ダメージを\n与える", "小さな炎で\n敵1グループに\n小ダメージを与える", "はげしい炎で\n敵1グループに\n小ダメージを与える"]
chara.pyは以上です。
呪文の説明を表示
battle.pyを変更して
戦闘画面で呪文の説明を表示します。
まず、グローバル変数を追加します。
(省略)
# 1ページに表示する呪文の数
SPELL_SHOW_NUM = 8
(省略)
sel_spell_x = 0 # コマンドで選択する呪文の列位置
sel_spell_y = 0 # コマンドで選択する呪文の行位置
sel_spell_p = 0 # コマンドで選択する呪文のページ
tmp_sel_spell_i = 0 # 選択中の呪文(添字) 追加
sel_spell_i = -1 # 選択した呪文(添字) 追加
3つの変数を追加しました。
SPELL_SHOW_NUM
tmp_sel_spell_i
sel_spell_i
SPELL_SHOW_NUMは
1ページに表示する呪文の数です。
前回、この数を8としましたので
SPELL_SHOW_NUMには8を入れます。
プログラムも前回、8とした箇所を
全てSPELL_SHOW_NUMに変更します。
なお、この8はゲームが進行しても
変わることはありませんので、
変数というより定数です。
定数は大文字にするのが暗黙の了解のようなので
「SPELL_SHOW_NUM」のように全て大文字にしました。
※他にも定数にした方が良い数字があった気がしますが
そのうち直していきたいと思います(^_^;)
tmp_sel_spell_iは呪文の選択中に
「▶︎」が指している呪文です。
sel_spell_iは選択した呪文です。
「▶︎」が指している呪文にて決定した時に
その値をsel_spell_iに入れます。
tmp_sel_spell_iもsel_spell_iも
mas_spell
usemp
spell_target
spell_explain
の添字に使います。(キャラの変数)
次に前回変更した関数def spell_selectについて
新しく追加したグローバル変数を使います。
def spell_select(key, player):
global idx, sel_spell_x, sel_spell_y, sel_spell_p, tmp_sel_spell_i, sel_spell_i
ent = False
tmp_sel_spell_i = sel_spell_x+sel_spell_y*2+sel_spell_p*SPELL_SHOW_NUM # 選択している呪文(player.mas_spellの添字)
max_p = len(player.mas_spell)//SPELL_SHOW_NUM # 呪文表示の最大のページ 8で割った商(整数)
if len(player.mas_spell)%SPELL_SHOW_NUM == 0:
max_p -= 1
# 列は2列
if key[K_UP] and sel_spell_y > 0: # ↑キー
sel_spell_y -= 1
elif key[K_DOWN] and sel_spell_y < 3 and tmp_sel_spell_i+2 < len(player.mas_spell): # ↓キー sel_spell_xとsel_spell_yの初期値は0(条件に=は入らない)
sel_spell_y += 1
elif key[K_LEFT] and sel_spell_x > 0: # ←キー
sel_spell_x -= 1
elif key[K_LEFT] and sel_spell_x == 0 and sel_spell_p > 0: # ←キー ページが0より大きければページを変える
sel_spell_x = 1
sel_spell_p -= 1
elif key[K_LEFT] and sel_spell_x == 0 and sel_spell_p == 0: # ←キー ページが0の場合、maxにする
sel_spell_x = 1
sel_spell_p = max_p
# ページ遷移後に覚えた呪文のmax数を超えたか
# 行の確認・修正
if len(player.mas_spell) % SPELL_SHOW_NUM == 0:
tmp_sel_spell_y = SPELL_SHOW_NUM // 2
else:
tmp_sel_spell_y = (len(player.mas_spell) % SPELL_SHOW_NUM) // 2
if sel_spell_y >= tmp_sel_spell_y:
sel_spell_y = tmp_sel_spell_y
if len(player.mas_spell)%2 != 0: # 呪文の数が奇数の時は列を1列目にする
sel_spell_x = 0
elif key[K_RIGHT] and sel_spell_x < 1 and tmp_sel_spell_i+1 < len(player.mas_spell): # →キー sel_spell_xとsel_spell_yの初期値は0(条件に=は入らない)
sel_spell_x += 1
elif key[K_RIGHT] and sel_spell_x == 1 and sel_spell_p < max_p: # →キー ページが最大でなければページを変える
sel_spell_x = 0
sel_spell_p += 1
# ページ遷移後に覚えた呪文のmax数を超えたか、超えていれば行を少なくする
if sel_spell_p == max_p:
if len(player.mas_spell) % SPELL_SHOW_NUM == 0:
tmp_sel_spell_y = SPELL_SHOW_NUM // 2
else:
tmp_sel_spell_y = (len(player.mas_spell) % SPELL_SHOW_NUM) // 2 # 最大ページの最大行
if sel_spell_y >= tmp_sel_spell_y: # 元々の行より最大行が小さい場合
sel_spell_y = tmp_sel_spell_y # 最大行にあわせる
elif key[K_RIGHT] and sel_spell_p == max_p: # →キー ページが最大の場合、0にする。 奇数の時にページ遷移しないので「sel_spell_x == 1」はいらない(ページ遷移しない時の「sel_spell_x == 0」は2つ上で判定するのでここに来ない)
sel_spell_x = 0
sel_spell_p = 0
elif key[K_SPACE] or key[K_RETURN]: # 決定
sel_spell_i = tmp_sel_spell_i
ent = True
elif key[K_b]: # キャンセル
tmp_sel_spell_i = 0
idx = 11
return ent
前回から以下のように変えています。
8 → SPELL_SHOW_NUM
tmp_sel_spell → tmp_sel_spell_i
その他に1行加えています。
呪文を決定すると、sel_spell_iに格納する為です。
sel_spell_i = tmp_sel_spell_i # 追加
あと、前回の変更ではバグがありましたので
後ほど触れます(;ω;)
対象の呪文の添字が分かったので
それを使って表示します。
(3箇所ほど8をSPELL_SHOW_NUMに修正しています)
def draw_battle(bg, fnt, obj, player):
(省略)
elif idx == 21:
(省略)
if (sel_spell_p+1)*SPELL_SHOW_NUM > len(player.mas_spell):
end_i = len(player.mas_spell)
else:
end_i = (sel_spell_p+1)*SPELL_SHOW_NUM
for i, spell in enumerate(player.mas_spell[sel_spell_p*SPELL_SHOW_NUM:end_i]): # 途中から最後まで表示
if i%2 == 0: # 1列目
draw_text(bg, "{}".format(spell), x_i_t, y_i_t+y_h*int(i/2), fnt, col)
else: # 2列目
draw_text(bg, "{}".format(spell), x_i_t+int(x_w/2), y_i_t+y_h*int(i/2), fnt, col)
if tmr%5 != 0:
draw_text(bg, "▶︎", x_i_t-50+int(x_w/2)*sel_spell_x, y_i_t+y_h*sel_spell_y, fnt, col)
# 呪文説明の枠
x_i = bg.get_width()*0.7 + 5 # x座標の開始位置
x_w = bg.get_width()*0.3 - 35 # 幅
y_i = bg.get_height()*0.6 + 40 # 6割+40のところからメッセージ枠を出す
y_h = bg.get_height()*0.1 - 10 # 高さ(一個分の高さ)
x_i_t = x_i + 20 # テキストの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*4], 2, 5)
pygame.draw.line(bg, col, [x_i, bg.get_height()*0.9], [x_i+x_w-1, bg.get_height()*0.9], 2) # -1がないと少し出る
# 呪文の説明
tmp_explain = player.spell_explain[tmp_sel_spell_i].split("\n")
for i, tmp in enumerate(tmp_explain):
draw_text(bg, "{}".format(tmp), x_i_t, y_i_t+y_h*i, fnt, col)
# 呪文の消費MP/残りMP
draw_text(bg, "{}/{}".format(player.usemp[tmp_sel_spell_i], player.mp), x_i_t, bg.get_height()*0.9+20, fnt, col)
「# 呪文説明の枠」のコメント以降ついては
以下の通りです。

「# 呪文の説明」のコメント以降で
呪文の説明を記載しています。
改行「\n」で文を分けます。
tmp_explain = player.spell_explain[tmp_sel_spell_i].split("\n")
splitの詳細はこちら。
それをfor文で1行ずつ表示しています。
※最大で3行まで。4行目は消費MPの表示。
for i, tmp in enumerate(tmp_explain):
draw_text(bg, "{}".format(tmp), x_i_t, y_i_t+y_h*i, fnt, col)
enumerateの詳細はこちら。
最後に消費MPを表示しています。
以上で呪文の説明が表示されました。

バグ修正
先程、少し触れましたがspell_select関数の
バグを修正します。
def spell_select(key, player):
global idx, sel_spell_x, sel_spell_y, sel_spell_p, tmp_sel_spell_i, sel_spell_i
ent = False
tmp_sel_spell_i = sel_spell_x+sel_spell_y*2+sel_spell_p*SPELL_SHOW_NUM # 選択している呪文(player.mas_spellの添字)
max_p = len(player.mas_spell)//SPELL_SHOW_NUM # 呪文表示の最大のページ 8で割った商(整数)
if len(player.mas_spell)%SPELL_SHOW_NUM == 0:
max_p -= 1
以下を追加しています。
覚えた呪文の数が8の倍数の時は
max_pを-1します。
if len(player.mas_spell)%SPELL_SHOW_NUM == 0:
max_p -= 1
例えば覚えた呪文が8の時、
max_p = len(player.mas_spell)//SPELL_SHOW_NUM
max_p = 8 // 8 (商の整数部分)
は1となりますが、呪文の数が8の時は
1ページに収まりますので
max_pは0が正です。
※max_pは0始まり、1の場合は2ページになる。
追加したif文が無いとmax_pが1となり(最大2ページ)、
2ページ目(9個目の呪文)に移ろうとしてしまいます。
すると、リストspell_explainなどの要素には
9つ以上の値が無い為、エラーになってしまいます。

なので、先程のif文を追加しました。
もう2箇所あります。
※変更内容は同じですが、2箇所あります。
elif key[K_LEFT] and sel_spell_x == 0 and sel_spell_p == 0: # ←キー ページが0の場合、maxにする
sel_spell_x = 1
sel_spell_p = max_p
# ページ遷移後に覚えた呪文のmax数を超えたか
# 行の確認・修正
if len(player.mas_spell) % SPELL_SHOW_NUM == 0:
tmp_sel_spell_y = SPELL_SHOW_NUM // 2
else:
tmp_sel_spell_y = (len(player.mas_spell) % SPELL_SHOW_NUM) // 2
if sel_spell_y >= tmp_sel_spell_y:
sel_spell_y = tmp_sel_spell_y
if len(player.mas_spell)%2 != 0: # 呪文の数が奇数の時は列を1列目にする
sel_spell_x = 0
elif key[K_RIGHT] and sel_spell_x < 1 and tmp_sel_spell_i+1 < len(player.mas_spell): # →キー sel_spell_xとsel_spell_yの初期値は0(条件に=は入らない)
sel_spell_x += 1
elif key[K_RIGHT] and sel_spell_x == 1 and sel_spell_p < max_p: # →キー ページが最大でなければページを変える
sel_spell_x = 0
sel_spell_p += 1
# ページ遷移後に覚えた呪文のmax数を超えたか、超えていれば行を少なくする
if sel_spell_p == max_p:
if len(player.mas_spell) % SPELL_SHOW_NUM == 0:
tmp_sel_spell_y = SPELL_SHOW_NUM // 2
else:
tmp_sel_spell_y = (len(player.mas_spell) % SPELL_SHOW_NUM) // 2 # 最大ページの最大行
if sel_spell_y >= tmp_sel_spell_y: # 元々の行より
変更前
tmp_sel_spell_y = (len(player.mas_spell) % SPELL_SHOW_NUM) // 2
変更後
if len(player.mas_spell) % SPELL_SHOW_NUM == 0:
tmp_sel_spell_y = SPELL_SHOW_NUM // 2
else:
tmp_sel_spell_y = (len(player.mas_spell) % SPELL_SHOW_NUM) // 2
tmp_sel_spell_yは表示する呪文の最大行を
格納します。
詳細はこちら。
覚えた呪文の数が8の倍数の時
if文が無いとtmp_sel_spell_yは
0になります。
※ 8 % 8 = 0 (商の余り)
が、8の倍数の時、最大行は4行が正の為、
if文を追加しました。
if文が無い場合、最大行が0(1行)になるので、
以下の状態で→キーを押下すると

以下のように「▶︎」が1行目に
なってしまいます。

もっとスマートなやり方がありそうですし、
他の箇所にもバグがあるんだろうなと思いつつ
ひとまずバグ修正は以上にします(^_^;)
呪文の攻撃対象
使う呪文を選択した後、
呪文を使う対象を選択します。
変更前
(省略)
def main(screen, clock, font, fontS, player):
(省略)
elif idx == 21: # コマンドで呪文を選択
draw_battle(screen, fontS, turn_obj, player)
if spell_select(key, player) == True:
player.act = 25 # get_battle_turnの戻り値idxを25
idx = 23
tmr = 0
変更後
(省略)
def main(screen, clock, font, fontS, player):
(省略)
elif idx == 21: # コマンドで呪文を選択
btl_enemy = 0 # 攻撃対象のモンスターをリセット
draw_battle(screen, fontS, turn_obj, player)
if spell_select(key, player) == True:
idx = 25
tmr = 0
player.act = 25
を削除してidxを 23から25に変更しました。
※idx=23はターン内の行動の順番を決めます。
idxの25と26を追加します。
elif idx == 23: # ターンセット(行動の順番を決める)
set_battle_turn(btl_start, player)
btl_start = 0 # 戦闘は通常に戻す
idx = 24
tmr = 0
elif idx == 24: # 誰のターンか確認(誰が行動するか確認)
tmr = 0
turn_obj, idx = get_battle_turn(player)
elif idx == 25: # 呪文の対象を決める
draw_battle(screen, fontS, turn_obj, player)
if spell_target_select(key, player) == True:
player.act = 26 # get_battle_turnの戻り値idxを26
idx = 23
tmr = 0
elif idx == 26: # 呪文発動
idx = 24 # ターン確認
idx25で呪文を使う対象を決めます。
関数spell_target_selectを新しく作ります。
※spell_target_selectは後述します。
player.actには26を入れて、
ターンが回ってきた時にidx26で
呪文を使います。
idx26の動作は別回にします。
ここではidxを24にして、
何もせずに行動終了となります。
idx25で使う関数spell_target_selectです。
def spell_select(key, player):
(省略)
def spell_target_select(key, player):
global idx, btl_enemy
ent = False
if player.spell_target[sel_spell_i] == 0: # 呪文の対象が味方
pass
else: # 呪文の対象がモンスター
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 = 21
return ent
対象が味方の場合、今回はスルー(pass)にします。
※次回にします。
if player.spell_target[sel_spell_i] == 0: # 呪文の対象が味方
pass
対象がモンスターの時は
関数attack_selectとほぼ同じです。
def attack_select(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
使う呪文を選択した後の画面で
対象のモンスターを選択します。
↑キーと↓キーでbtl_enemyの値を決めます。

これで攻撃対象は選択できるようになりました。
という感じで、今回は以上です!(`・ω・´)
最後に
今回は呪文の説明と消費MPを表示するようにしました。
次回は回復呪文を使えるように変更しようと思います。
【ご注意】
プログラムやデータなどは著作権法により保護されています。
著作者の許諾を得ずに、プログラムおよびデータそのものまたは改変したものを
配布したり販売したりすることはできません。
また、これらを利用して発生した損害などに関して、著作者は一切責任を負いません。