【疑似3DダンジョンRPG開発日誌 05】探索中のスキル使用システムとUI統一化
疑似3DダンジョンRPG「グラヴェンの傷跡」の開発ログです。 今回はポーズメニューから回復魔法を使えるようにする機能を実装しました。
今回やったこと
ポーズメニューから探索中にスキルを使えるようにした
使用時に回復エフェクトが出るようにした
システムメッセージの表示をウィンドウに統一した
1. 探索中スキル使用システムの実装

設計方針
戦闘中と同じ3ステップの選択フローを探索中でも再現しました。
ポーズメニュー「スキル」
↓
① だれが使う?(メンバー選択)
↓
② スキル一覧(味方対象スキルのみ表示、MP不足はグレー)
↓
├─ self / all_allies → そのまま発動
└─ one_ally → ③ だれに使う?(対象選択)→ 発動
探索中に使えるスキルは以下の種類に限定しています。
スキルタイプ 代表スキル heal ヒール(単体回復) heal_all アルデスの光(全体全回復) revive 団長の号令(戦闘不能を復活)
実装コード
PAUSE_COMMANDSに「スキル」を追加
PAUSE_COMMANDS = ["ステータス", "アイテム", "スキル", "そうび", "セーブ", "ロード", "まちへ"]
__init__に状態変数を追加
# ── 探索中スキル使用 ──
self.show_skills = False # スキルメニューを表示中か
self.skill_exp_phase = "member" # "member"→"skill"→"target" の順
self.skill_exp_member_cursor = 0
self.skill_exp_cursor = 0
self.skill_exp_target_cursor = 0
self.skill_exp_selected = ""
self.skill_exp_actor = None
update()のポーズメニュー処理を修正
インデックスがずれるため全体を書き直します。
if self._btn_confirm():
if self.pause_cursor == 0: # ステータス
self.scene = "status"
elif self.pause_cursor == 1: # アイテム
self.show_items = True
self.scene = "explore"
elif self.pause_cursor == 2: # スキル ← 新規追加
self.show_skills = True
self.skill_exp_phase = "member"
self.skill_exp_member_cursor = 0
self.scene = "explore"
elif self.pause_cursor == 3: # そうび
self.scene = "equip"
elif self.pause_cursor == 4: # セーブ
self.save_game()
self.scene = "explore"
elif self.pause_cursor == 5: # ロード
self.load_game()
self.scene = "explore"
elif self.pause_cursor == 6: # まちへ
self._load_town_image()
self.scene = "town"
スキルメニューの入力処理(update内)
if self.show_items: ブロックの直後に追加します。
if self.show_skills:
# ① メンバー選択
if self.skill_exp_phase == "member":
members = self.party.members
if pyxel.btnp(pyxel.KEY_UP) or pyxel.btnp(pyxel.KEY_W):
self.skill_exp_member_cursor = (self.skill_exp_member_cursor - 1) % len(members)
elif pyxel.btnp(pyxel.KEY_DOWN) or pyxel.btnp(pyxel.KEY_S):
self.skill_exp_member_cursor = (self.skill_exp_member_cursor + 1) % len(members)
elif self._btn_confirm():
actor = members[self.skill_exp_member_cursor]
if actor.hp <= 0:
self.system_message = "たおれているので使えない…"
self.system_message_timer = 60
else:
self.skill_exp_actor = actor
self.skill_exp_phase = "skill"
self.skill_exp_cursor = 0
elif self._btn_cancel():
self.show_skills = False
return
# ② スキル選択
if self.skill_exp_phase == "skill":
actor = self.skill_exp_actor
ALLY_TYPES = {"heal", "heal_all", "revive",
"buff_atk", "buff_def", "buff_res"}
usable = [s for s in actor.skills
if SKILL_DATA[s]["type"] in ALLY_TYPES]
if usable:
if pyxel.btnp(pyxel.KEY_UP) or pyxel.btnp(pyxel.KEY_W):
self.skill_exp_cursor = (self.skill_exp_cursor - 1) % len(usable)
elif pyxel.btnp(pyxel.KEY_DOWN) or pyxel.btnp(pyxel.KEY_S):
self.skill_exp_cursor = (self.skill_exp_cursor + 1) % len(usable)
if self._btn_confirm() and usable:
skill_name = usable[self.skill_exp_cursor]
sdata = SKILL_DATA[skill_name]
if actor.mp < sdata.get("mp_cost", 0):
self.system_message = "MPが足りない…"
self.system_message_timer = 60
elif sdata["target"] in ("self", "all_allies"):
self._use_skill_in_explore(actor, skill_name, actor)
else:
self.skill_exp_selected = skill_name
self.skill_exp_target_cursor = 0
self.skill_exp_phase = "target"
elif self._btn_cancel():
self.skill_exp_phase = "member"
return
# ③ 対象選択
if self.skill_exp_phase == "target":
members = self.party.members
if pyxel.btnp(pyxel.KEY_UP) or pyxel.btnp(pyxel.KEY_W):
self.skill_exp_target_cursor = (self.skill_exp_target_cursor - 1) % len(members)
elif pyxel.btnp(pyxel.KEY_DOWN) or pyxel.btnp(pyxel.KEY_S):
self.skill_exp_target_cursor = (self.skill_exp_target_cursor + 1) % len(members)
elif self._btn_confirm():
target = members[self.skill_exp_target_cursor]
self._use_skill_in_explore(
self.skill_exp_actor, self.skill_exp_selected, target)
elif self._btn_cancel():
self.skill_exp_phase = "skill"
return
スキル発動ヘルパーメソッド
def _use_skill_in_explore(self, actor, skill_name, target):
"""探索中にスキルを発動する(回復・バフ系のみ対応)"""
sdata = SKILL_DATA[skill_name]
stype = sdata["type"]
mp_cost = sdata.get("mp_cost", 0)
actor.mp -= mp_cost
if stype == "heal":
amount = int(sdata["power"])
target.hp = min(target.hp + amount, target.hp_max)
self._start_heal_effect(target=target, all_members=False)
self.system_message = f"{actor.name}の{skill_name}!{target.name}のHPが{amount}回復した"
elif stype == "heal_all":
for m in self.party.members:
m.hp = m.hp_max
m.mp = m.mp_max
self._start_heal_effect(target=None, all_members=True)
self.system_message = f"{actor.name}の{skill_name}!全員のHP・MPが全回復した!"
elif stype == "revive":
if target.hp > 0:
self.system_message = "すでに起きている!"
actor.mp += mp_cost
else:
target.hp = max(1, int(target.hp_max * sdata["power"]))
self._start_heal_effect(target=target, all_members=False)
self.system_message = f"{actor.name}の{skill_name}!{target.name}が復活した!"
else:
self.system_message = f"{skill_name}は戦闘中しか使えない…"
actor.mp += mp_cost
self.system_message_timer = 90
self.show_skills = False
draw()に描画処理を追加
if self.show_items: ブロックの直後に追加します。
if self.show_skills:
if self.skill_exp_phase == "member":
self.draw_window(60, 48, 136,
16 + len(self.party.members) * WIN_LINE_HEIGHT,
title="だれが使う?")
for i, m in enumerate(self.party.members):
cur = "≫" if i == self.skill_exp_member_cursor else " "
col = 5 if m.hp <= 0 else WIN_TEXT_COL
self.jtext(64, 66 + i * WIN_LINE_HEIGHT,
f"{cur}{m.name} MP{m.mp}/{m.mp_max}", col)
elif self.skill_exp_phase == "skill":
actor = self.skill_exp_actor
ALLY_TYPES = {"heal", "heal_all", "revive",
"buff_atk", "buff_def", "buff_res"}
usable = [s for s in actor.skills
if SKILL_DATA[s]["type"] in ALLY_TYPES]
self.draw_window(60, 48, 136,
16 + max(1, len(usable)) * WIN_LINE_HEIGHT,
title=f"{actor.name}のスキル")
if not usable:
self.jtext(64, 66, "使えるスキルがない", 5)
for i, name in enumerate(usable):
cur = "≫" if i == self.skill_exp_cursor else " "
mp = SKILL_DATA[name]["mp_cost"]
col = 5 if actor.mp < mp else WIN_TEXT_COL
self.jtext(64, 66 + i * WIN_LINE_HEIGHT,
f"{cur}{name} {mp}MP", col)
elif self.skill_exp_phase == "target":
self.draw_window(60, 48, 136,
16 + len(self.party.members) * WIN_LINE_HEIGHT,
title="だれに使う?")
for i, m in enumerate(self.party.members):
cur = "≫" if i == self.skill_exp_target_cursor else " "
col = 14 if m.hp <= 0 else WIN_TEXT_COL
self.jtext(64, 66 + i * WIN_LINE_HEIGHT,
f"{cur}{m.name} {m.hp}/{m.hp_max}", col)
2. システムメッセージの表示統一
セーブ・ロード・回復・階段移動など、あちこちで jtext で直接描いていたメッセージを全て draw_window() に統一しました。
変更内容

draw() の _draw_hud() 呼び出しの直後に1箇所だけ追加します。
self._draw_minimap()
self._draw_hud()
# ── システムメッセージウィンドウ(追加)──
if self.system_message_timer > 0 and not self._msg_active:
self.draw_window(28, 196, 200, 36, message=self.system_message)
not self._msg_active の条件で戦闘メッセージと重なるのを防いでいます。
これで統一されたメッセージ一覧:
メッセージ タイミング ぼうけんのきろくをきざんだ… セーブ時
きろくのきおくがよみがえる… ロード時
扉を開いた… 扉を開けたとき
B2Fへ降りた… 階段移動時 ゆっくりと休んだ…
宿屋利用時 ○○のHPが○○回復した
探索中 スキル使用時
ゴールドがたりない… 購入失敗時
まとめ
今回の実装ポイントは、戦闘中と探索中でスキルの選択フローを共通化したことです。 同じ「メンバー選択 → スキル選択 → 対象選択」の流れにすることで、プレイヤーが操作を覚えやすくなります。
また _use_skill_in_explore() をヘルパーメソッドとして独立させたことで、今後スキルを追加するときもここに処理を書き足すだけで済む設計になっています。
いいなと思ったら応援しよう!
もし記事が参考になったり、開発を応援してもいいと思ってくれた方がいたら、noteのサポートをいただけると次の記事を書く原動力になります。もちろん読んでくれるだけで十分嬉しいです。