CODE Python RPG

独学でPythonでRPGを作成する(第25回)呪文の説明を表示

更新日:

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

前回は呪文の表示について、
覚えている(使える)呪文が9つ以上の時は
2ページ目、3ページ目に表示できるように変更しました。

今回は選択している(「▶︎」が指している)呪文の
説明を表示するように変更します。

目次

  1. 呪文の説明の表示
  2. キャラに新たに変数を追加
  3. 呪文の説明を表示
  4. バグ修正
  5. 呪文の攻撃対象
  6. 最後に

呪文の説明の表示

選択している(「▶︎」が指している)呪文の
説明を表示するように変更していきます。
(変更の対象は戦闘中の画面です)

また、あわせてその呪文の消費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を表示するようにしました。

次回は回復呪文を使えるように変更しようと思います。

まとめサイトへ

 

 

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

 

 

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

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