0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Unityでつくる2DRPG ~⑭戦闘システムの構築(スクリプト編)

Last updated at Posted at 2025-12-24

はじめに

前回は、プレハブの作成を行いました。

今回は、スクリプトを作成します。

BattleManager.csの作成

新しく「BattleManager」という名前のスクリプトを作成してください。
その後、長めのコードですが以下をコピペしてください。

//BattleManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Linq;
using static UnityEngine.GraphicsBuffer;

public class BattleManager : MonoBehaviour
{
    public static BattleManager Instance { get; private set; }

    [Header("UI (assign in inspector)")]
    public GameObject battleRoot;
    public Text messageText;
    public RectTransform playerStatusPanel;
    public GameObject playerStatusEntryPrefab;
    public RectTransform enemiesContainer;
    public RectTransform alliesContainer;
    public GameObject actionMenuPanel;
    public Text[] actionOptions;
    public GameObject magicMenuPanel;
    public RectTransform magicGridContainer; 
    public GameObject itemMenuPanel;
    public RectTransform itemGridContainer; 
    public GameObject targetMenuPanel;
    public RectTransform targetListContainer;
    [Header("Prefabs")]
    public GameObject enemyPortraitPrefab;
    public GameObject allyPortraitPrefab; 
    public GameObject menuOptionPrefab; 
    public GameObject magicArrowPrefab;
    [Header("Settings")]
    public float defaultWaitAfterMessage = 0.25f;
    bool inBattle = false;
    public bool InBattle => inBattle;
    bool fledThisBattle = false;

    List<BattleEnemy> enemies = new List<BattleEnemy>();
    List<PlayerStats> allies = new List<PlayerStats>();
    Dictionary<PlayerStats, PlayerAction> pendingActions = new Dictionary<PlayerStats, PlayerAction>();
    Dictionary<PlayerStats, PlayerStatusUI> statusUIs = new Dictionary<PlayerStats, PlayerStatusUI>();
    Coroutine hudCoroutine = null;
    class BattleEnemy
    {
        public EnemyData data;
        public int hp;
        public int mp;
        public int id;
        public string displayName;
        public GameObject visualGO;
    }
    public enum ActionType { Attack, Magic, Item, Flee, None }
    public class PlayerAction
    {
        public ActionType type = ActionType.None;
        public MagicData magic = null;
        public ItemData item = null;
        public object target = null;
    }
    void Awake()
    {
        if (Instance != null && Instance != this) Destroy(this.gameObject);
        else Instance = this;
        if (battleRoot != null) battleRoot.SetActive(false);
    }
    public void StartBattleWithEnemyDatas(List<EnemyData> enemyDatas)
    {
        if (inBattle) return;
        StartCoroutine(BattleRoutine(enemyDatas));
    }
    IEnumerator BattleRoutine(List<EnemyData> enemyDatas)
    {
        inBattle = true;
        if (battleRoot != null) battleRoot.SetActive(true);
        if (playerStatusPanel != null) playerStatusPanel.gameObject.SetActive(true);
        if (enemiesContainer != null) enemiesContainer.gameObject.SetActive(true);
        if (alliesContainer != null) alliesContainer.gameObject.SetActive(true);
        if (magicMenuPanel != null) magicMenuPanel.SetActive(false);
        if (itemMenuPanel != null) itemMenuPanel.SetActive(false);
        if (targetMenuPanel != null) targetMenuPanel.SetActive(false);
        if (actionMenuPanel != null) actionMenuPanel.SetActive(false);
        fledThisBattle = false;
        allies = (PartyManager.Instance != null)
            ? PartyManager.Instance.partyMembers.Where(p => p != null).ToList()
            : new List<PlayerStats>();
        enemies.Clear();
        if (enemiesContainer != null) foreach (Transform t in enemiesContainer) Destroy(t.gameObject);
        if (alliesContainer != null) foreach (Transform t in alliesContainer) Destroy(t.gameObject);
        Dictionary<string, int> nameCounter = new Dictionary<string, int>();
        int id = 0;
        foreach (var ed in enemyDatas)
        {
            if (ed == null) continue;
            string baseName = ed.enemyName;
            if (!nameCounter.ContainsKey(baseName))
                nameCounter[baseName] = 0;
            int index = nameCounter[baseName]++;
            char suffix = (char)('A' + index);
            string displayName = $"{baseName}{suffix}";
            var be = new BattleEnemy()
            {
                data = ed,
                hp = ed.maxHP,
                mp = ed.maxMP,
                id = id++,
                displayName = displayName
            };
            enemies.Add(be);
            if (enemiesContainer == null) continue;
            GameObject go = null;
            if (ed.portraitPrefab != null)
            {
                go = Instantiate(ed.portraitPrefab, enemiesContainer, false);
            }
            else if (enemyPortraitPrefab != null)
            {
                go = Instantiate(enemyPortraitPrefab, enemiesContainer, false);
                var img = go.GetComponentInChildren<Image>();
                if (img != null && ed.portraitSprite != null)
                    img.sprite = ed.portraitSprite;
            }
            if (go != null)
            {
                go.name = $"Enemy_{displayName}";
                var txt = go.GetComponentInChildren<Text>();
                if (txt != null)
                    txt.text = displayName;
                ForceFixUI(go, new Vector2(180, 180));
                LayoutRebuilder.ForceRebuildLayoutImmediate(enemiesContainer);
                be.visualGO = go;
            }
        }
        if (alliesContainer != null)
        {
            alliesContainer.gameObject.SetActive(true);
            var layout = alliesContainer.GetComponent<HorizontalLayoutGroup>();
            if (layout != null) layout.enabled = false;
            foreach (Transform t in alliesContainer)
                Destroy(t.gameObject);
            yield return null;
            if (allyPortraitPrefab != null)
            {
                foreach (var a in allies)
                {
                    var go = Instantiate(allyPortraitPrefab, alliesContainer, false);
                    go.name = $"Ally_{a.stats.charName}";
                    var txt = go.GetComponentInChildren<Text>();
                    if (txt != null)
                        txt.text = a.stats.charName;
                    Image img = null;
                    var portraitTf = go.transform.Find("PortraitImage");
                    if (portraitTf != null) img = portraitTf.GetComponent<Image>();
                    if (img == null) img = go.GetComponentInChildren<Image>();
                    if (img != null && a.stats.portrait != null)
                    {
                        img.sprite = a.stats.portrait;
                        img.SetNativeSize();
                    }

                    var rt = go.GetComponent<RectTransform>();
                    rt.anchorMin = rt.anchorMax = new Vector2(0.5f, 0.5f);
                    rt.pivot = new Vector2(0.5f, 0.5f);
                    rt.localScale = Vector3.one;
                    rt.sizeDelta = new Vector2(160, 160);
                }
            }
            yield return null; 
            if (layout != null) layout.enabled = true;
            LayoutRebuilder.ForceRebuildLayoutImmediate(
                alliesContainer.GetComponent<RectTransform>()
            );
        }
        if (playerStatusPanel != null && playerStatusEntryPrefab != null)
        {
            foreach (Transform t in playerStatusPanel) Destroy(t.gameObject);
            statusUIs.Clear();
            foreach (var ally in allies)
            {
                var go = Instantiate(playerStatusEntryPrefab, playerStatusPanel, false);
                ForceFixUI(go, new Vector2(320, 48));
                var ui = go.GetComponent<PlayerStatusUI>();
                if (ui != null)
                {
                    ui.UpdateFrom(ally);
                    statusUIs[ally] = ui;
                }
            }
            LayoutRebuilder.ForceRebuildLayoutImmediate(playerStatusPanel);
            if (hudCoroutine != null) StopCoroutine(hudCoroutine);
            hudCoroutine = StartCoroutine(UpdateHUDCoroutine());
        }
        yield return ShowMessage("敵と遭遇した!");
        while (true)
        {
            if (fledThisBattle)
            {
                var sm = FindObjectOfType<SceneManager>();
                if (sm != null)
                {
                    var f = sm.GetType().GetField("suppressNextEncounter");
                    if (f != null) f.SetValue(sm, true);
                }
                yield return ShowMessage("うまくにげられた!");
                break;
            }
            if (enemies.All(e => e.hp <= 0))
            {
                yield return ShowMessage("てきをたおした!");
                int totalExp = enemies.Sum(e => e.data.giveExp);
                int totalMoney = enemies.Sum(e => e.data.giveMoney);
                foreach (var a in allies) a.GainExp(totalExp);
                if (PartyManager.Instance != null) PartyManager.Instance.partyMoney += totalMoney;
                foreach (var e in enemies)
                {
                    foreach (var d in e.data.drops)
                    {
                        if (d.item != null && Random.value <= d.dropRate)
                        {
                            if (PartyManager.Instance != null) PartyManager.Instance.AddToBag(d.item, 1);
                        }
                    }
                }
                yield return ShowMessage($"戦闘終了。獲得経験値: {totalExp} お金: {totalMoney}");
                break;
            }
            if (allies.All(a => !a.stats.IsAlive))
            {
                yield return ShowMessage("まけた...");
                if (PartyManager.Instance != null) PartyManager.Instance.partyMoney /= 2;
                foreach (var a in allies)
                {
                    a.stats.hp = a.stats.maxHP;
                    a.stats.mp = a.stats.maxMP;
                }
                yield return ShowMessage("拠点へ戻された...");
                break;
            }
            pendingActions.Clear();
            yield return SelectActionsForAllAllies();
            var order = new List<(int speed, bool isAlly, object actor)>();
            foreach (var a in allies) if (a.stats.IsAlive) order.Add((a.stats.speed, true, (object)a));
            foreach (var e in enemies) if (e.hp > 0) order.Add((e.data.speed, false, (object)e));
            order = order.OrderByDescending(x => x.speed).ToList();
            foreach (var entry in order)
            {
                if (entry.isAlly)
                {
                    var actor = (PlayerStats)entry.actor;
                    if (actor.stats.IsAlive && pendingActions.ContainsKey(actor))
                    {
                        var act = pendingActions[actor];
                        yield return ExecuteAllyAction(actor, act);
                    }
                }
                else
                {
                    var be = (BattleEnemy)entry.actor;
                    if (be.hp > 0)
                        yield return ExecuteEnemyAction(be);
                }
                if (enemies.All(e => e.hp <= 0) || allies.All(a => !a.stats.IsAlive)) break;
            }
        }
        if (hudCoroutine != null) { StopCoroutine(hudCoroutine); hudCoroutine = null; }
        statusUIs.Clear();
        if (playerStatusPanel != null)
        {
            foreach (Transform t in playerStatusPanel) Destroy(t.gameObject);
        }
        if (battleRoot != null) battleRoot.SetActive(false);
        inBattle = false;
        yield break;
    }
    IEnumerator ShowMessage(string msg)
    {
        if (messageText != null) messageText.text = msg;
        bool advanced = false;
        while (!advanced)
        {
            if (Input.GetKeyDown(KeyCode.Space)) advanced = true;
            yield return null;
        }
        yield return new WaitForSeconds(defaultWaitAfterMessage);
    }
    IEnumerator ShowMessageTimed(string msg, float minSeconds)
    {
        if (messageText != null) messageText.text = msg;
        float t = 0f;
        while (t < minSeconds)
        {
            if (Input.GetKeyDown(KeyCode.Space)) break;
            t += Time.deltaTime;
            yield return null;
        }
        yield break;
    }
    IEnumerator SelectActionsForAllAllies()
    {
        foreach (var ally in allies)
        {
            if (!ally.stats.IsAlive)
            {
                pendingActions[ally] = new PlayerAction() { type = ActionType.None };
                continue;
            }
            yield return ShowActionMenuAndWait(ally);
        }
    }
    IEnumerator UpdateHUDCoroutine()
    {
        while (inBattle)
        {
            // update each mapped UI
            foreach (var kv in new List<KeyValuePair<PlayerStats, PlayerStatusUI>>(statusUIs))
            {
                var ps = kv.Key;
                var ui = kv.Value;
                if (ps == null || ui == null) continue;
                ui.UpdateFrom(ps);
            }
            yield return null; 
        }
    }
    void ForceFixUI(GameObject go, Vector2 size)
    {
        if (go == null) return;
        var rt = go.GetComponent<RectTransform>();
        if (rt == null) return;
        rt.localScale = Vector3.one;
        rt.localRotation = Quaternion.identity;
        rt.anchorMin = new Vector2(0.5f, 0.5f);
        rt.anchorMax = new Vector2(0.5f, 0.5f);
        rt.pivot = new Vector2(0.5f, 0.5f);
        if (size.x > 0 && size.y > 0) rt.sizeDelta = size;
        else rt.sizeDelta = Vector2.zero;
        var img = go.GetComponent<Image>();
        if (img != null) img.enabled = true;
        foreach (Transform c in go.transform)
        {
            var crt = c.GetComponent<RectTransform>();
            if (crt != null)
            {
                crt.localScale = Vector3.one;
                crt.localRotation = Quaternion.identity;
            }
        }
    }
    IEnumerator ShowActionMenuAndWait(PlayerStats actor)
    {
        actionMenuPanel.SetActive(true);
        if (actionOptions != null)
        {
            for (int i = 0; i < actionOptions.Length; i++)
            {
                var txt = actionOptions[i];
                if (txt == null) continue;
                var parent = txt.transform.parent;
                if (parent == null) continue;
                var dot = parent.Find("Dot");
                if (dot != null) dot.gameObject.SetActive(false);
                var parentImg = parent.GetComponent<Image>();
                if (parentImg != null) parentImg.color = Color.clear;
            }
        }
        int idx = 0;
        UpdateActionMenuSelection(idx);
        bool decided = false;
        PlayerAction selectedAction = new PlayerAction();
        while (!decided)
        {
            if (Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown(KeyCode.LeftArrow))
            {
                idx = (idx - 1 + actionOptions.Length) % actionOptions.Length;
                UpdateActionMenuSelection(idx);
            }
            else if (Input.GetKeyDown(KeyCode.DownArrow) || Input.GetKeyDown(KeyCode.RightArrow))
            {
                idx = (idx + 1) % actionOptions.Length;
                UpdateActionMenuSelection(idx);
            }
            else if (Input.GetKeyDown(KeyCode.Space))
            {
                yield return null;
                string choice = actionOptions[idx].text;
                if (choice.Contains("こうげき"))
                {
                    actionMenuPanel.SetActive(false);
                    PlayerAction result = null;
                    yield return StartCoroutine(ShowTargetMenuForActor(actor, new PlayerAction() { type = ActionType.Attack }, true, (pa) => { result = pa; }));
                    if (result == null)
                    {
                        actionMenuPanel.SetActive(true);
                        UpdateActionMenuSelection(idx);
                    }
                    else
                    {
                        selectedAction = result;
                        decided = true;
                    }
                }
                else if (choice.Contains("まほう"))
                {
                    PlayerAction result = null;
                    yield return StartCoroutine(ShowMagicMenuAndTarget(actor, (pa) => { result = pa; }));
                    if (result == null)
                    {
                        actionMenuPanel.SetActive(true);
                        UpdateActionMenuSelection(idx);
                    }
                    else
                    {
                        selectedAction = result;
                        decided = true;
                        actionMenuPanel.SetActive(false);
                    }
                }
                else if (choice.Contains("アイテム"))
                {
                    PlayerAction result = null;
                    yield return StartCoroutine(ShowItemMenuAndTarget(actor, (pa) => { result = pa; }));
                    if (result == null)
                    {
                        actionMenuPanel.SetActive(true);
                        UpdateActionMenuSelection(idx);
                    }
                    else
                    {
                        selectedAction = result;
                        decided = true;
                        actionMenuPanel.SetActive(false);
                    }
                }
                else if (choice.Contains("にげる") || choice.Contains("逃げる"))
                {
                    bool success = AttemptEscape(actor);
                    if (success)
                    {
                        selectedAction = new PlayerAction() { type = ActionType.Flee };
                        decided = true;
                        actionMenuPanel.SetActive(false);
                    }
                    else
                    {
                        yield return ShowMessage("にげられなかった!");
                        // remain in action selection
                        actionMenuPanel.SetActive(true);
                        UpdateActionMenuSelection(idx);
                    }
                }
            }
            else if (Input.GetKeyDown(KeyCode.Escape))
            {
                selectedAction = new PlayerAction() { type = ActionType.None };
                decided = true;
                actionMenuPanel.SetActive(false);
            }
            yield return null;
        }
        pendingActions[actor] = selectedAction;
        actionMenuPanel.SetActive(false);
        yield return null;
    }
    void UpdateActionMenuSelection(int idx)
    {
        if (actionOptions == null || actionOptions.Length == 0) return;
        for (int i = 0; i < actionOptions.Length; i++)
        {
            var txt = actionOptions[i];
            if (txt == null) continue;
            var parent = txt.transform.parent;
            if (parent != null)
            {
                var parentImg = parent.GetComponent<Image>();
                if (parentImg != null) parentImg.color = (i == idx) ? new Color(1f, 1f, 1f, 0.6f) : Color.clear;
                var dotTf = parent.Find("Dot");
                if (dotTf != null)
                {
                    dotTf.gameObject.SetActive(i == idx);
                }
            }
        }
    }
    IEnumerator ShowMagicMenuAndTarget(PlayerStats actor, System.Action<PlayerAction> onComplete)
    {
        if (magicArrowPrefab == null || menuOptionPrefab == null || magicGridContainer == null)
        {
            Debug.LogWarning("ShowMagicMenuAndTarget: missing prefab or container assignments.");
            onComplete(null);
            yield break;
        }
        magicMenuPanel.SetActive(true);
        foreach (Transform t in magicGridContainer) Destroy(t.gameObject);
        var known = actor.stats.knownMagic;
        const int COLS = 3;
        const int ROWS = 4;
        const int EXTRA_ROWS = 1;
        const int TOTAL_ROWS = ROWS + EXTRA_ROWS;
        const int PAGE_ITEMS = COLS * ROWS;
        int totalItems = known != null ? known.Length : 0;
        int itemsPerPage = PAGE_ITEMS;
        int totalPages = Mathf.Max(1, Mathf.CeilToInt((float)totalItems / itemsPerPage));
        int page = 0;
        System.Func<int, bool> SlotIsActive = (i) =>
        {
            if (i < 0 || i >= magicGridContainer.childCount) return false;
            var txt = magicGridContainer.GetChild(i).GetComponentInChildren<UnityEngine.UI.Text>();
            return txt != null && txt.gameObject.activeSelf;
        };
        System.Action<int> buildPage = (p) =>
        {
            foreach (Transform t in magicGridContainer) Destroy(t.gameObject);
            int startItemIndex = p * itemsPerPage;
            int placed = 0;
            for (int r = 0; r < ROWS; r++)
            {
                for (int c = 0; c < COLS; c++)
                {
                    int globalIdx = startItemIndex + placed;
                    var go = Instantiate(menuOptionPrefab, magicGridContainer);
                    var txt = go.GetComponentInChildren<UnityEngine.UI.Text>();
                    if (globalIdx < totalItems)
                    {
                        if (txt != null)
                        {
                            txt.gameObject.SetActive(true);
                            txt.text = $"{known[globalIdx].magicName} (MP {known[globalIdx].mpCost})";
                        }
                    }
                    else
                    {
                        if (txt != null) txt.gameObject.SetActive(false);
                    }
                    var dot = go.transform.Find("Dot");
                    if (dot != null) dot.gameObject.SetActive(false);
                    placed++;
                }
            }
            for (int c = 0; c < COLS; c++)
            {
                if (c == 0 && p > 0)
                {
                    var go = Instantiate(magicArrowPrefab, magicGridContainer);
                    var txt = go.GetComponentInChildren<UnityEngine.UI.Text>();
                    if (txt != null) { txt.gameObject.SetActive(true); txt.text = "↩"; }
                    var marker = go.GetComponent<MagicPageArrow>();
                    if (marker != null) marker.isNext = false;
                }
                else if (c == COLS - 1 && p < totalPages - 1)
                {
                    var go = Instantiate(magicArrowPrefab, magicGridContainer);
                    var txt = go.GetComponentInChildren<UnityEngine.UI.Text>();
                    if (txt != null) { txt.gameObject.SetActive(true); txt.text = "↪"; }
                    var marker = go.GetComponent<MagicPageArrow>();
                    if (marker != null) marker.isNext = true;
                }
                else
                {
                    var go = Instantiate(menuOptionPrefab, magicGridContainer);
                    var txt = go.GetComponentInChildren<UnityEngine.UI.Text>();
                    if (txt != null) txt.gameObject.SetActive(false);
                    var dot = go.transform.Find("Dot");
                    if (dot != null) dot.gameObject.SetActive(false);
                }
            }
        };
        buildPage(page);
        yield return null;
        int gridCount = TOTAL_ROWS * COLS;
        int idx = 0;
        {
            int found = -1;
            for (int i = 0; i < gridCount; i++) if (SlotIsActive(i)) { found = i; break; }
            idx = (found >= 0) ? found : 0;
        }
        bool decided = false;
        PlayerAction selected = null;
        System.Func<int, int> GetRow = (i) => i / COLS;
        System.Func<int, int> GetCol = (i) => i % COLS;
        while (!decided)
        {
            if (Input.GetKeyDown(KeyCode.UpArrow))
            {
                int r = GetRow(idx), c = GetCol(idx);
                int nr = Mathf.Max(0, r - 1);
                int cand = nr * COLS + c;
                while (cand >= 0 && !SlotIsActive(cand))
                {
                    nr--;
                    if (nr < 0) break;
                    cand = nr * COLS + c;
                }
                if (cand >= 0 && SlotIsActive(cand)) idx = cand;
            }
            else if (Input.GetKeyDown(KeyCode.DownArrow))
            {
                int r = GetRow(idx), c = GetCol(idx);
                if (r == ROWS - 1)
                {
                    int arrowRowStart = ROWS * COLS;
                    if (c == 0)
                    {
                        int prevIdx = arrowRowStart + 0;
                        if (prevIdx < magicGridContainer.childCount && SlotIsActive(prevIdx))
                        {
                            idx = prevIdx;
                            goto postNav;
                        }
                    }
                    if (c == COLS - 1)
                    {
                        int nextIdx = arrowRowStart + (COLS - 1);
                        if (nextIdx < magicGridContainer.childCount && SlotIsActive(nextIdx))
                        {
                            idx = nextIdx;
                            goto postNav;
                        }
                    }
                }
                else
                {
                    int nr = Mathf.Min(TOTAL_ROWS - 1, r + 1);
                    int cand = nr * COLS + c;
                    while (cand < TOTAL_ROWS * COLS && !SlotIsActive(cand))
                    {
                        nr++;
                        if (nr >= TOTAL_ROWS) break;
                        cand = nr * COLS + c;
                    }
                    if (cand < TOTAL_ROWS * COLS && SlotIsActive(cand)) idx = cand;
                }
            }
            else if (Input.GetKeyDown(KeyCode.LeftArrow))
            {
                int row = GetRow(idx);
                int startRow = row * COLS;
                int cand = idx - 1;
                while (cand >= startRow && !SlotIsActive(cand)) cand--;
                if (cand >= startRow && SlotIsActive(cand)) idx = cand;
            }
            else if (Input.GetKeyDown(KeyCode.RightArrow))
            {
                int row = GetRow(idx);
                int startRow = row * COLS;
                int endRow = startRow + (COLS - 1);
                int cand = idx + 1;
                while (cand <= endRow && cand < TOTAL_ROWS * COLS && !SlotIsActive(cand)) cand++;
                if (cand <= endRow && cand < TOTAL_ROWS * COLS && SlotIsActive(cand)) idx = cand;
            }
            else if (Input.GetKeyDown(KeyCode.Space))
            {
                yield return null;
                var selGo = magicGridContainer.GetChild(idx);
                var arrow = selGo.GetComponent<MagicPageArrow>();
                var txt = selGo.GetComponentInChildren<UnityEngine.UI.Text>();
                if (arrow != null)
                {
                    if (arrow.isNext && page < totalPages - 1)
                    {
                        page++;
                        buildPage(page);
                        yield return null;
                        int found = -1;
                        for (int i = 0; i < TOTAL_ROWS * COLS; i++) if (SlotIsActive(i)) { found = i; break; }
                        idx = (found >= 0) ? found : 0;
                    }
                    else if (!arrow.isNext && page > 0)
                    {
                        page--;
                        buildPage(page);
                        yield return null;
                        int found = -1;
                        for (int i = 0; i < TOTAL_ROWS * COLS; i++) if (SlotIsActive(i)) { found = i; break; }
                        idx = (found >= 0) ? found : 0;
                    }
                }
                else
                {
                    int topAreaIdx = idx;
                    if (topAreaIdx >= ROWS * COLS) { /* bottom row or placeholder clicked */ }
                    int globalIdx = page * itemsPerPage + topAreaIdx;
                    if (globalIdx >= 0 && globalIdx < totalItems)
                    {
                        var chosen = known[globalIdx];
                        if (actor.stats.mp < chosen.mpCost)
                        {
                            StartCoroutine(ShowMessage("MPが足りない!"));
                        }
                        else
                        {
                            var pa = new PlayerAction() { type = ActionType.Magic, magic = chosen };
                            PlayerAction targetResult = null;
                            yield return StartCoroutine(ShowTargetMenuForActor(actor, pa, true, (res) => { targetResult = res; }));
                            if (targetResult == null)
                            {
                                buildPage(page);
                                yield return null;
                                int found = -1;
                                for (int i = 0; i < TOTAL_ROWS * COLS; i++) if (SlotIsActive(i)) { found = i; break; }
                                idx = (found >= 0) ? found : 0;
                            }
                            else
                            {
                                selected = targetResult;
                                decided = true;
                                magicMenuPanel.SetActive(false);
                            }
                        }
                    }
                }
            }
            else if (Input.GetKeyDown(KeyCode.Escape))
            {
                magicMenuPanel.SetActive(false);
                onComplete(null);
                yield break;
            }

            postNav:
            for (int i = 0; i < magicGridContainer.childCount; i++)
            {
                var child = magicGridContainer.GetChild(i);
                var t = child.GetComponentInChildren<UnityEngine.UI.Text>();
                if (t != null) t.color = (i == idx && t.gameObject.activeSelf) ? Color.yellow : Color.white;
                var dotTf = child.Find("Dot");
                if (dotTf != null) dotTf.gameObject.SetActive(i == idx && t != null && t.gameObject.activeSelf);
            }
            yield return null;
        }
        onComplete(selected);
    }
    IEnumerator ShowItemMenuAndTarget(PlayerStats actor, System.Action<PlayerAction> onComplete)
    {
        itemMenuPanel.SetActive(true);
        foreach (Transform t in itemGridContainer) Destroy(t.gameObject);
        const int COLS = 2;
        const int ROWS = 7;
        const int SLOTS = COLS * ROWS; // 14
        var inv = actor?.Inventory;
        if (inv == null)
        {
            inv = actor.GetComponent<CharacterInventory>();
            if (inv == null)
            {
                Debug.LogWarning($"ShowItemMenuAndTarget: actor {actor?.name ?? "null"} に CharacterInventory が見つかりません。");
                inv = new CharacterInventory();
            }
        }
        int invCount = (inv != null) ? inv.slots.Count : 0;
        for (int i = 0; i < SLOTS; i++)
        {
            var go = Instantiate(menuOptionPrefab, itemGridContainer);
            go.name = $"ItemSlot_{i}";
            var txt = go.GetComponentInChildren<Text>();
            InventorySlot slot = (i < invCount) ? inv.slots[i] : null;
            if (slot != null && slot.item != null && slot.item.usableInBattle && slot.count > 0)
            {
                if (txt != null)
                {
                    txt.gameObject.SetActive(true);
                    txt.text = $"{slot.item.itemName} x{slot.count}";
                }
            }
            else
            {
                if (txt != null) txt.gameObject.SetActive(false);
            }
            var dot = go.transform.Find("Dot");
            if (dot != null) dot.gameObject.SetActive(false);
        }
        bool any = false;
        if (inv != null)
        {
            foreach (var s in inv.slots)
            {
                if (s != null && s.item != null && s.item.usableInBattle && s.count > 0) { any = true; break; }
            }
        }
        if (!any)
        {
            yield return ShowMessageTimed("戦闘で使えるアイテムがない。", 1.0f);
            itemMenuPanel.SetActive(false);
            onComplete(new PlayerAction() { type = ActionType.None });
            yield break;
        }
        int idx = 0;
        System.Func<int, bool> SlotIsActive = (i) =>
        {
            if (i < 0 || i >= itemGridContainer.childCount) return false;
            var t = itemGridContainer.GetChild(i).GetComponentInChildren<Text>();
            return t != null && t.gameObject.activeSelf;
        };
        if (!SlotIsActive(idx))
        {
            int f = -1;
            for (int i = 0; i < SLOTS; i++) if (SlotIsActive(i)) { f = i; break; }
            if (f >= 0) idx = f;
        }
        bool decided = false;
        PlayerAction selected = null;
        while (!decided)
        {
            if (Input.GetKeyDown(KeyCode.UpArrow))
            {
                int r = idx / COLS;
                int c = idx % COLS;
                int nr = Mathf.Max(0, r - 1);
                int cand = nr * COLS + c;
                while (cand >= 0 && !SlotIsActive(cand))
                {
                    nr--;
                    if (nr < 0) break;
                    cand = nr * COLS + c;
                }
                if (cand >= 0 && SlotIsActive(cand)) idx = cand;
            }
            else if (Input.GetKeyDown(KeyCode.DownArrow))
            {
                int r = idx / COLS;
                int c = idx % COLS;
                int nr = Mathf.Min(ROWS - 1, r + 1);
                int cand = nr * COLS + c;
                while (cand < ROWS * COLS && !SlotIsActive(cand))
                {
                    nr++;
                    if (nr >= ROWS) break;
                    cand = nr * COLS + c;
                }
                if (cand < ROWS * COLS && SlotIsActive(cand)) idx = cand;
            }
            else if (Input.GetKeyDown(KeyCode.LeftArrow))
            {
                int startRow = (idx / COLS) * COLS;
                int cand = idx - 1;
                while (cand >= startRow && !SlotIsActive(cand)) cand--;
                if (cand >= startRow && SlotIsActive(cand)) idx = cand;
            }
            else if (Input.GetKeyDown(KeyCode.RightArrow))
            {
                int startRow = (idx / COLS) * COLS;
                int endRow = startRow + (COLS - 1);
                int cand = idx + 1;
                while (cand <= endRow && cand < ROWS * COLS && !SlotIsActive(cand)) cand++;
                if (cand <= endRow && cand < ROWS * COLS && SlotIsActive(cand)) idx = cand;
            }
            else if (Input.GetKeyDown(KeyCode.Space))
            {
                if (!SlotIsActive(idx)) { /* nothing */ }
                else
                {
                    int slotIndex = idx;
                    var slot = (slotIndex < invCount) ? inv.slots[slotIndex] : null;
                    if (slot == null || slot.item == null || slot.count <= 0)
                    {
                    }
                    else
                    {
                        var item = slot.item;
                        yield return null;
                        itemMenuPanel.SetActive(false);
                        var pa = new PlayerAction() { type = ActionType.Item, item = item };
                        PlayerAction targetResult = null;
                        yield return StartCoroutine(ShowTargetMenuForActor(actor, pa, true, (res) => { targetResult = res; }));
                        if (targetResult == null)
                        {
                            itemMenuPanel.SetActive(true);
                            if (!SlotIsActive(idx))
                            {
                                int f = -1;
                                for (int i = 0; i < SLOTS; i++) if (SlotIsActive(i)) { f = i; break; }
                                if (f >= 0) idx = f;
                            }
                        }
                        else
                        {
                            if (targetResult.type != ActionType.None)
                            {
                                inv.RemoveItem(item, 1);
                            }
                            selected = targetResult;
                            decided = true;
                        }
                    }
                }
            }
            else if (Input.GetKeyDown(KeyCode.Escape))
            {
                itemMenuPanel.SetActive(false);
                decided = true;
                selected = new PlayerAction() { type = ActionType.None };
            }
            for (int i = 0; i < itemGridContainer.childCount; i++)
            {
                var child = itemGridContainer.GetChild(i);
                var txt = child.GetComponentInChildren<Text>();
                if (txt != null) txt.color = (i == idx && txt.gameObject.activeSelf) ? Color.yellow : Color.white;
                var dot = child.Find("Dot");
                if (dot != null) dot.gameObject.SetActive(i == idx && txt != null && txt.gameObject.activeSelf);
            }
            yield return null;
        }
        onComplete(selected);
    }
    IEnumerator ShowTargetMenuForActor(PlayerStats actor, PlayerAction baseAction, bool includeAlliesOption, System.Action<PlayerAction> onComplete)
    {
        targetMenuPanel.SetActive(true);
        foreach (Transform t in targetListContainer) Destroy(t.gameObject);
        List<object> targets = new List<object>();
        foreach (var e in enemies)
        {
            if (e.hp <= 0) continue;
            var go = Instantiate(menuOptionPrefab, targetListContainer);
            var txt = go.GetComponentInChildren<Text>();
            txt.text = e.displayName;
            targets.Add(e);
        }
        if (includeAlliesOption)
        {
            var go = Instantiate(menuOptionPrefab, targetListContainer);
            go.GetComponentInChildren<Text>().text = "みかた";
            targets.Add("みかた");
        }
        yield return null;
        int idx = 0;
        bool decided = false;
        PlayerAction finalAction = null;
        while (!decided)
        {
            if (Input.GetKeyDown(KeyCode.UpArrow)) idx = Mathf.Max(0, idx - 1);
            else if (Input.GetKeyDown(KeyCode.DownArrow)) idx = Mathf.Min(targets.Count - 1, idx + 1);
            else if (Input.GetKeyDown(KeyCode.Space))
            {
                var sel = targets[idx];
                if (sel is string && (string)sel == "みかた")
                {
                    foreach (Transform t in targetListContainer) Destroy(t.gameObject);
                    List<PlayerStats> aliveAllies = allies.Where(a => a.stats.IsAlive).ToList();
                    for (int i = 0; i < aliveAllies.Count; i++)
                    {
                        var go2 = Instantiate(menuOptionPrefab, targetListContainer);
                        go2.GetComponentInChildren<Text>().text = aliveAllies[i].stats.charName;
                    }
                    yield return null;
                    int aidx = 0;
                    bool adec = false;
                    while (!adec)
                    {
                        if (Input.GetKeyDown(KeyCode.UpArrow)) aidx = Mathf.Max(0, aidx - 1);
                        else if (Input.GetKeyDown(KeyCode.DownArrow)) aidx = Mathf.Min(aliveAllies.Count - 1, aidx + 1);
                        else if (Input.GetKeyDown(KeyCode.Space))
                        {
                            finalAction = baseAction;
                            finalAction.target = aliveAllies[aidx];
                            adec = true;
                            decided = true;
                        }
                        else if (Input.GetKeyDown(KeyCode.Escape))
                        {
        
                            decided = false;
                            break;
                        }
                        for (int i = 0; i < targetListContainer.childCount; i++)
                        {
                            var txt = targetListContainer.GetChild(i).GetComponentInChildren<Text>();
                            txt.color = (i == aidx) ? Color.yellow : Color.white;
                            var dotTf = targetListContainer.GetChild(i).Find("Dot");
                            if (dotTf != null) dotTf.gameObject.SetActive(i == aidx);
                        }
                        yield return null;
                    }
                }
                else if (sel is BattleEnemy)
                {
                    finalAction = baseAction;
                    finalAction.target = sel;
                    decided = true;
                }
            }
            else if (Input.GetKeyDown(KeyCode.Escape))
            {
                targetMenuPanel.SetActive(false);
                onComplete(null);
                yield break;
            }

            for (int i = 0; i < targetListContainer.childCount; i++)
            {
                var txt = targetListContainer.GetChild(i).GetComponentInChildren<Text>();
                if (txt != null) txt.color = (i == idx) ? Color.yellow : Color.white;
                var dotTf = targetListContainer.GetChild(i).Find("Dot");
                if (dotTf != null) dotTf.gameObject.SetActive(i == idx);
            }
            yield return null;
        }
        targetMenuPanel.SetActive(false);
        onComplete(finalAction);
    }
    bool AttemptEscape(PlayerStats actor)
    {
        int enemySpeed = 0;
        int count = 0;
        foreach (var e in enemies) if (e.hp > 0) { enemySpeed += e.data.speed; count++; }
        int avgEnemySpeed = (count > 0) ? Mathf.CeilToInt((float)enemySpeed / count) : 0;
        float chance = (actor.stats.speed >= avgEnemySpeed) ? 0.8f : 0.4f;
        return Random.value <= chance;
    }
    IEnumerator ExecuteAllyAction(PlayerStats actor, PlayerAction act)
    {
        if (act == null || act.type == ActionType.None)
        {
            yield return ShowMessage($"{actor.stats.charName} は何もしなかった。");
            yield break;
        }
        if (act.type == ActionType.Flee)
        {
            fledThisBattle = true;
            yield return ShowMessage($"{actor.stats.charName}は逃げ出した!");
            yield break;
        }
        if (act.type == ActionType.Attack)
        {
            var target = act.target as BattleEnemy;
            if (target == null)
            {
                yield return ShowMessage($"{actor.stats.charName} はターゲットを見失った!");
                yield break;
            }
            int damage = CalculateDamageWithElement(actor.stats.atk, target.data.def, actor.stats.attribute, target.data.attribute, false, actor.stats.atk, target.data.def);
            target.hp = Mathf.Max(0, target.hp - damage);
            yield return ShowMessage($"{actor.stats.charName} の こうげき! {target.displayName}{damage} のダメージ!");
            if (target.hp <= 0)
            {
                yield return StartCoroutine(HandleEnemyDeath(target));
            }
            yield break;
        }
        else if (act.type == ActionType.Magic)
        {
            var magic = act.magic;
            if (magic == null) { yield return ShowMessage("魔法が選択されていない。"); yield break; }
            actor.UseMP(magic.mpCost);
            if (magic.effect == MagicEffect.Damage)
            {
                if (act.target is BattleEnemy btarget)
                {
                    int damage = CalculateMagicDamage(actor, btarget.data, magic);
                    btarget.hp = Mathf.Max(0, btarget.hp - damage);
                    yield return ShowMessage($"{actor.stats.charName}{magic.magicName} をとなえた! {btarget.displayName}{damage} のダメージ!");
                    if (btarget.hp <= 0)
                    {
                        yield return StartCoroutine(HandleEnemyDeath(btarget));
                    }
                }
                else if (magic.target == MagicTarget.AllEnemies)
                {
                    foreach (var e in enemies.Where(x => x.hp > 0))
                    {
                        int damage = CalculateMagicDamage(actor, e.data, magic);
                        e.hp = Mathf.Max(0, e.hp - damage);
                        if (e.hp <= 0)
                        {
                            yield return StartCoroutine(HandleEnemyDeath(e));
                        }
                    }
                    yield return ShowMessage($"{actor.stats.charName}{magic.magicName}! 敵全体にダメージ!");
                }
            }
            else if (magic.effect == MagicEffect.Heal)
            {
                if (act.target is PlayerStats pt)
                {
                    pt.Heal(magic.basePower + actor.stats.matk);
                    yield return ShowMessage($"{actor.stats.charName}{magic.magicName} を使った! {pt.stats.charName} を回復した。");
                }
            }
            yield return null;
        }
        else if (act.type == ActionType.Item)
        {
            var item = act.item;
            if (item == null) { yield return ShowMessage("アイテムが選択されていない。"); yield break; }
            if (act.target is BattleEnemy be)
            {
                be.hp = Mathf.Max(0, be.hp - item.effectValue);
                yield return ShowMessage($"{item.itemName}{be.displayName}{item.effectValue} のダメージ!");
                if (be.hp <= 0)
                {
                    yield return StartCoroutine(HandleEnemyDeath(be));
                }
            }
            else if (act.target is PlayerStats pt)
            {
                if (item.effect == ItemEffect.Heal)
                {
                    pt.Heal(item.effectValue);
                    yield return ShowMessage($"{pt.stats.charName}{item.itemName} を使って回復した。");
                }
            }
            yield return null;
        }
    }
    IEnumerator ExecuteEnemyAction(BattleEnemy enemy)
    {
        var aliveAllies = allies.Where(a => a.stats.IsAlive).ToList();
        if (aliveAllies.Count == 0) yield break;
        bool useMagic = enemy.data.learnedMagic != null && enemy.data.learnedMagic.Count > 0 && Random.value < 0.3f;
        if (useMagic)
        {
            var m = enemy.data.learnedMagic[Random.Range(0, enemy.data.learnedMagic.Count)];
            var target = aliveAllies[Random.Range(0, aliveAllies.Count)];
            int damage = Mathf.Max(1, m.basePower + enemy.data.matk - target.stats.mdef);
            target.TakeDamage(damage);
            yield return ShowMessage($"{enemy.displayName}{m.magicName} を使った! {target.stats.charName}{damage} のダメージ!");
        }
        else
        {
            var target = aliveAllies[Random.Range(0, aliveAllies.Count)];
            int damage = Mathf.Max(1, enemy.data.atk - target.stats.def);
            target.TakeDamage(damage);
            yield return ShowMessage($"{enemy.displayName} の こうげき! {target.stats.charName}{damage} のダメージ!");
        }
    }
    IEnumerator HandleEnemyDeath(BattleEnemy be)
    {
        if (be == null) yield break;
        if (be.visualGO != null)
        {
            be.visualGO.SetActive(false);
        }
        if (enemiesContainer != null)
        {
            LayoutRebuilder.ForceRebuildLayoutImmediate(enemiesContainer);
        }
        be.visualGO = null;
        yield break;
    }


    float ElementalMultiplier(Element atkElem, Element defElem)
    {
        // simple relation:
        // Fire > Ice, Fire < Water
        // Water > Fire, Water < Thunder
        // Ice > Wind, Ice < Fire
        // Wind > Thunder, Wind < Ice
        // Thunder > Water, Thunder < Wind
        // Light strong vs Dark, Dark strong vs Light (just invert)
        if (atkElem == Element.None || defElem == Element.None) return 1f;
        if (atkElem == defElem) return 1f;
        // mapping:
        if (atkElem == Element.Fire && defElem == Element.Ice) return 2f;
        if (atkElem == Element.Fire && defElem == Element.Water) return 0.5f;
        if (atkElem == Element.Water && defElem == Element.Fire) return 2f;
        if (atkElem == Element.Water && defElem == Element.Thunder) return 0.5f;
        if (atkElem == Element.Ice && defElem == Element.Wind) return 2f;
        if (atkElem == Element.Ice && defElem == Element.Fire) return 0.5f;
        if (atkElem == Element.Wind && defElem == Element.Thunder) return 2f;
        if (atkElem == Element.Wind && defElem == Element.Ice) return 0.5f;
        if (atkElem == Element.Thunder && defElem == Element.Water) return 2f;
        if (atkElem == Element.Thunder && defElem == Element.Wind) return 0.5f;
        if (atkElem == Element.Light && defElem == Element.Dark) return 2f;
        if (atkElem == Element.Dark && defElem == Element.Light) return 2f;
        return 1f;
    }
    int CalculateDamageWithElement(int atk, int def, Element atkElem, Element defElem, bool isMagic, int atkStat, int defStat)
    {
        float baseDamage = Mathf.Max(1, atk - def);
        float mult = ElementalMultiplier(atkElem, defElem);
        int final = Mathf.Max(1, Mathf.RoundToInt(baseDamage * mult));
        return final;
    }
    int CalculateMagicDamage(PlayerStats actor, EnemyData target, MagicData magic)
    {
        float mult = ElementalMultiplier(actor.stats.attribute, target.attribute);
        int damage = Mathf.Max(1, magic.basePower + actor.stats.matk - target.mdef);
        damage = Mathf.RoundToInt(damage * mult);
        return Mathf.Max(1, damage);
    }
}

PartyManager.csの作成

新しく「PartyManager」という名前のスクリプトを作成してください。
その後、以下をコピペしてください。

//PartyManager.cs
using System.Collections.Generic;
using UnityEngine;

public class PartyManager : MonoBehaviour
{
    public static PartyManager Instance { get; private set; }
    [Header("Party")]
    public PlayerStats[] partyMembers = new PlayerStats[0]; 
    [Header("Party shared")]
    public int partyMoney = 0;
    [Header("Bag (ふくろ) - up to 100 unique items")]
    public int bagCapacity = 100;
    public List<ItemData> bagItems = new List<ItemData>();
    public List<int> bagCounts = new List<int>();
    private void Awake()
    {
        if (Instance != null && Instance != this) Destroy(this.gameObject);
        else Instance = this;
    }
    public bool AddToBag(ItemData item, int count)
    {
        if (item == null || count <= 0) return false;
        int idx = bagItems.IndexOf(item);
        if (idx >= 0)
        {
            bagCounts[idx] += count;
            return true;
        }
        else
        {
            if (bagItems.Count >= bagCapacity) return false;
            bagItems.Add(item);
            bagCounts.Add(count);
            return true;
        }
    }
    public bool UseBagItem(ItemData item)
    {
        int idx = bagItems.IndexOf(item);
        if (idx < 0) return false;
        if (bagCounts[idx] <= 0) return false;
        bagCounts[idx]--;
        if (bagCounts[idx] == 0)
        {
            bagCounts.RemoveAt(idx);
            bagItems.RemoveAt(idx);
        }
        return true;
    }
    public int GetBagCount(ItemData item)
    {
        int idx = bagItems.IndexOf(item);
        if (idx < 0) return 0;
        return bagCounts[idx];
    }
}

おわり

今回は、スクリプトの作成を行いました。
次回は、UIの作成を行います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?