装備機能の実装②遺跡編! Unityで1日1ステップ!ノンフィールドRPG開発日記

1日1歩開発日記

ふりかえり

前回は、新たに実装する「装備機能」の核となる要素について整理しました。

実装前に全体像を明確にしておくことで、開発の道筋が見えやすくなりますね!

遺跡を実装!

今回は、装備を入手する新しいコンテンツ「遺跡」を実装していきます。

遺跡とは、それぞれの防具部位に対応した特別なダンジョンのようなものです。

▼ 基本仕様

  • 各防具(頭・腕・胴・脚・足)に対応する遺跡が存在します。
  • モンスターを10体倒すとクリア。その間にドロップした防具を獲得できます。
  • 途中でやられてしまうと、それまでに得た防具はすべてロスト
  • 挑戦には「遺跡の鍵」が必要で、最大5個所持可能
  • 鍵は2時間ごとに1つ回復します。

今回は初級・中級の計10種類(5部位×2段階)の遺跡を追加します!

コードの目的と処理概要

以下では、主に2つのスクリプトが登場します。

DungeonManager.cs

遺跡の進行管理・鍵の消費/回復・報酬付与などを制御する中心的なマネージャーです。

  • 鍵の管理 UpdateKeyRegeneration()で、2時間ごとの鍵の自動回復を実装しています。最大数を超えないよう調整し、時間経過で正しく加算されるようになっています。
  • 遺跡の開始と進行 StartDungeon()で遺跡に挑戦でき、モンスターを倒すたびにOnDungeonMonsterDefeated()が呼ばれ、一定確率で装備がドロップされます。
  • 遺跡クリア時 CompleteDungeon()ではドロップした装備をプレイヤーに付与し、遺跡を終了状態にします。
  • 敗北時 OnDungeonFailed()ではドロップ装備を破棄して、進行状態をリセットします。
public class DungeonManager : MonoBehaviour
{
    [Header("遺跡の鍵設定")]
    public int maxKeys = 5;                 // 最大鍵数
    public float keyRegenerationHours = 2f; // 鍵回復時間(時間)

    [Header("遺跡データ")]
    public DungeonData currentDungeon;     // 現在の遺跡

    [Header("鍵管理")]
    public int currentKeys = 0;     // 時間回復の鍵
    public int adKeys = 0;          // 広告で獲得した鍵
    private DateTime lastKeyRegen;  // 最後に鍵が回復した時刻

    // シングルトン
    public static DungeonManager Instance { get; private set; }

    private void Awake()
    {
        if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); }
        else Destroy(gameObject);
    }

    private void Update()
    {
        UpdateKeyRegeneration();
    }

    // --- 鍵回復 ---
    private void UpdateKeyRegeneration()
    {
        if (currentKeys >= maxKeys) return;

        TimeSpan elapsed = DateTime.UtcNow - lastKeyRegen;
        float hours = (float)elapsed.TotalHours;

        if (hours >= keyRegenerationHours)
        {
            int add = Mathf.FloorToInt(hours / keyRegenerationHours);
            add = Mathf.Min(add, maxKeys - currentKeys);

            currentKeys += add;
            lastKeyRegen = DateTime.UtcNow;
        }
    }

    // --- 鍵消費 ---
    public bool ConsumeKey(int amount = 1)
    {
        int total = currentKeys + adKeys;
        if (total < amount) return false;

        int useTime = Mathf.Min(currentKeys, amount);
        currentKeys -= useTime;

        int remain = amount - useTime;
        if (remain > 0) adKeys -= remain;

        return true;
    }

    // --- 遺跡開始 ---
    public bool StartDungeon(DungeonType type, DungeonDifficulty diff)
    {
        if (currentKeys < 1) return false;
        if (currentDungeon != null && currentDungeon.isInProgress) return false;

        // 遺跡データ作成
        currentDungeon = new DungeonData
        {
            dungeonType = type,
            difficulty = diff,
            isInProgress = true,
            currentMonstersDefeated = 0,
            currentDungeonStage = 1
        };

        ConsumeKey(1);
        return true;
    }

    // --- モンスターを倒した時の処理 ---
    public void OnDungeonMonsterDefeated()
    {
        if (currentDungeon == null || !currentDungeon.isInProgress) return;

        currentDungeon.currentMonstersDefeated++;

        // 装備ドロップ処理(例:10%)
        if (UnityEngine.Random.Range(0f, 1f) < currentDungeon.armorDropRate)
        {
            ArmorSlotType slot = GetArmorSlotType(currentDungeon.dungeonType);
            ArmorData armor = ArmorManager.Instance.GenerateRandomArmor(slot, currentDungeon.difficulty);
            currentDungeon.obtainedArmors.Add(armor);
        }

        // 遺跡クリア判定
        if (currentDungeon.CanComplete())
        {
            CompleteDungeon();
        }
    }

    // --- 遺跡クリア ---
    private void CompleteDungeon()
    {
        if (currentDungeon == null) return;

        currentDungeon.isCompleted = true;
        currentDungeon.isInProgress = false;

        // 報酬付与処理(装備追加など)
        foreach (var armor in currentDungeon.obtainedArmors)
        {
            ArmorManager.Instance.AddArmor(armor);
        }

        currentDungeon = null;
    }

    // --- 遺跡敗北時 ---
    public void OnDungeonFailed()
    {
        if (currentDungeon == null) return;

        currentDungeon.isInProgress = false;

        // 獲得した装備は消失
        currentDungeon.obtainedArmors.Clear();

        currentDungeon = null;
    }

    // 遺跡種類→装備部位の変換
    private ArmorSlotType GetArmorSlotType(DungeonType type)
    {
        switch (type)
        {
            case DungeonType.HeadDungeon: return ArmorSlotType.Head;
            case DungeonType.ArmDungeon:  return ArmorSlotType.Arm;
            case DungeonType.BodyDungeon: return ArmorSlotType.Body;
            case DungeonType.LegDungeon:  return ArmorSlotType.Leg;
            case DungeonType.FootDungeon: return ArmorSlotType.Foot;
        }
        return ArmorSlotType.Head;
    }
}

DungeonUIManager

遺跡画面のUI制御を行います。選択ボタン、難易度表示、鍵の残量UI、クリア/敗北パネルなどを管理します。

  • 遺跡ボタン(頭/腕/胴/脚/足)と、それに応じた難易度選択を連動。
  • 鍵の残数や、次回回復までの時間表示機能も実装済み。
  • クリア時・敗北時に、それぞれ獲得(または失った)装備リストを表示します。
public class DungeonUIManager : MonoBehaviour
{
    [Header("UI")]
    public GameObject dungeonPanel;
    public GameObject difficultySelectionPanel;

    [Header("遺跡ボタン")]
    public Button headDungeonButton;
    public Button armDungeonButton;
    public Button bodyDungeonButton;
    public Button legDungeonButton;
    public Button footDungeonButton;

    [Header("難易度")]
    public Button beginnerButton;
    public Button intermediateButton;
    public Button advancedButton;
    public Button expertButton;

    [Header("鍵表示")]
    public TextMeshProUGUI keyCountText;
    public Button gainKeyAdButton;

    [Header("クリア/敗北パネル")]
    public GameObject clearPanel;
    public GameObject deathPanel;
    public Transform clearListParent;
    public Transform deathListParent;
    public DungeonClearArmorItem armorItemPrefab;

    private DungeonType selectedDungeonType;
    public DungeonManager dungeonManager;

    public static DungeonUIManager Instance { get; private set; }

    private void Awake()
    {
        Instance = this;
    }

    private void Start()
    {
        // 遺跡ボタン
        headDungeonButton.onClick.AddListener(() => ShowDifficulty(DungeonType.HeadDungeon));
        armDungeonButton.onClick.AddListener(() => ShowDifficulty(DungeonType.ArmDungeon));
        bodyDungeonButton.onClick.AddListener(() => ShowDifficulty(DungeonType.BodyDungeon));
        legDungeonButton.onClick.AddListener(() => ShowDifficulty(DungeonType.LegDungeon));
        footDungeonButton.onClick.AddListener(() => ShowDifficulty(DungeonType.FootDungeon));

        // 難易度ボタン
        beginnerButton.onClick.AddListener(() => StartDungeon(DungeonDifficulty.Beginner));
        intermediateButton.onClick.AddListener(() => StartDungeon(DungeonDifficulty.Intermediate));
        advancedButton.onClick.AddListener(() => StartDungeon(DungeonDifficulty.Advanced));
        expertButton.onClick.AddListener(() => StartDungeon(DungeonDifficulty.Expert));

        // 広告で鍵獲得
        gainKeyAdButton.onClick.AddListener(() => dungeonManager.GainKeyFromAd());

        dungeonPanel.SetActive(false);
        difficultySelectionPanel.SetActive(false);
    }

    private void Update()
    {
        // パネル表示中は鍵の残り時間を更新
        if (dungeonPanel.activeSelf)
        {
            UpdateKeyInfo();
        }
    }

    // --- 遺跡UIパネル ---

    public void OpenDungeonPanel()
    {
        dungeonPanel.SetActive(true);
        UpdateKeyInfo();
    }

    public void CloseDungeonPanel()
    {
        dungeonPanel.SetActive(false);
        difficultySelectionPanel.SetActive(false);
    }

    // --- 遺跡種類を選んだとき ---

    private void ShowDifficulty(DungeonType type)
    {
        selectedDungeonType = type;
        difficultySelectionPanel.SetActive(true);
    }

    // --- 遺跡開始 ---

    private void StartDungeon(DungeonDifficulty difficulty)
    {
        if (dungeonManager.StartDungeon(selectedDungeonType, difficulty))
        {
            CloseDungeonPanel();
        }
    }

    // --- 鍵情報更新(残り時間含む) ---

    public void UpdateKeyInfo()
    {
        int timeKeys = dungeonManager.GetTimeKeys();
        int adKeys = dungeonManager.GetAdKeys();
        int maxKeys = dungeonManager.GetMaxKeys();

        string timePart;
        if (timeKeys >= maxKeys)
        {
            timePart = $"{timeKeys}/{maxKeys}(MAX)";
        }
        else
        {
            float sec = dungeonManager.GetNextKeyRegenerationTime();
            int m = Mathf.FloorToInt(sec / 60);
            int s = Mathf.FloorToInt(sec % 60);
            timePart = $"{timeKeys}/{maxKeys}({m:00}:{s:00})";
        }

        keyCountText.text = $"遺跡の鍵: {timePart} + {adKeys}";
    }

    // --- クリアパネル ---

    public void ShowClearPanel(List<ArmorData> armors, int autoSellCount, int autoSellDiamond)
    {
        clearPanel.SetActive(true);

        foreach (Transform t in clearListParent)
            Destroy(t.gameObject);

        if (armors != null)
        {
            foreach (var armor in armors)
            {
                var item = Instantiate(armorItemPrefab, clearListParent);
                item.SetData(armor);
            }
        }
    }

    public void CloseClearPanel()
    {
        clearPanel.SetActive(false);
    }

    // --- 敗北パネル ---

    public void ShowDeathPanel(List<ArmorData> armors, bool adAvailable, int autoSellCount, int autoSellDiamond)
    {
        deathPanel.SetActive(true);

        foreach (Transform t in deathListParent)
            Destroy(t.gameObject);

        if (armors != null)
        {
            foreach (var armor in armors)
            {
                var item = Instantiate(armorItemPrefab, deathListParent);
                item.SetData(armor);
            }
        }
    }

    public void CloseDeathPanel()
    {
        deathPanel.SetActive(false);
    }
}

Unityでの設定

  1. 空のGameObjectを2つ作成
    • それぞれにDungeonManagerとDungeonUIManagerスクリプトをアタッチ。
  2. 必要なUIやオブジェクトをインスペクターで紐付け
    • 遺跡ボタン、難易度ボタン、パネル、鍵表示テキストなどを設定。

動作確認

  • UIの見た目は仮状態ですが、遺跡ボタンを押すと、対応する遺跡の難易度選択パネルが表示されます。
  • 画面左上には「遺跡の鍵」の残数が表示され、残り時間も確認可能。
  • 頭・腕・胴・脚・足それぞれの防具に対応した遺跡を選択でき、初級/中級の難易度も選択可能です。
  • 遺跡に挑戦すると背景とモンスターが遺跡用に切り替わり、クリア時はクリアパネルが表示され、獲得した防具を一覧で確認できます。

まとめ

今回は、装備獲得の新要素として「遺跡機能」を実装しました!

装備を集めたいけど緊張感も味わいたい、そんな遊びの幅が広がるシステムとなっています。

次回予告

次回は「ステータス保存機能」を実装予定です!

これにより、転生後の弱体化タイミングでも快適に遺跡へ挑戦できるようになります。

ぜひお楽しみに!

タイトルとURLをコピーしました