using UnityEngine; using UnityEngine.UI; using TMPro; using System.Collections.Generic; #if UNITY_EDITOR using UnityEditor; #endif public class teamUIController : MonoBehaviour { public static teamUIController Instance { get; private set; } // --- 新增:队伍槽 ID 列表(从上到下 5 个友军) --- [Header("Runtime ally configuration")] [Tooltip("IDs for the 5 ally slots (top to bottom). These are used by PopulateAllySOsFromIds to resolve SOs into currentAllySOs.")] public List allySlotIds = new List { 0, 0, 0, 0, 0 }; // --- 新增:解析后的当前五个 SO 引用(按顺序) --- private TeamCharacterDataInfo[] currentAllySOs = new TeamCharacterDataInfo[5]; // previous active flags for detecting external changes private bool[] prevAllyActive = new bool[5]; // 两个路径字段:一个用于编辑器查找(可以填写 Assets/Resources/so/ally 或磁盘绝对路径), // 一个用于运行时代码查找(Resources 相对路径,例如 so/ally) [Header("SO folder paths")] [Tooltip("Editor-only: project path or absolute disk path to the folder that contains SOs. Example: Assets/Resources/so/ally")] public string editorSOFolderPath = "Assets/Resources/so/ally"; [Tooltip("Runtime: Resources subfolder path (no 'Resources/' prefix). Example: so/ally")] public string runtimeResourcesFolderPath = "so/ally"; // Optional per-ally object to toggle together with isAllyX_active. If null falls back to objectFather_allyXX. [Header("Ally runtime objects (optional)")] public GameObject ally01_object; public GameObject ally02_object; public GameObject ally03_object; public GameObject ally04_object; public GameObject ally05_object; // 兼容旧字段(可选) [Header("Editor folder load (Editor only)")] [Tooltip("Deprecated: kept for compatibility. Use editorSOFolderPath or runtimeResourcesFolderPath instead.")] public string selectedProjectFolderPath = string.Empty; /// /// 在运行/编辑器中调用:根据 allySlotIds(5 个 int)在指定文件夹中搜索对应的 TeamCharacterDataInfo 资产。 /// Editor: 使用 editorSOFolderPath / selectedProjectFolderPath;Runtime: 使用 runtimeResourcesFolderPath / selectedProjectFolderPath。 /// public void PopulateAllySOsFromIds() { if (allySlotIds == null || allySlotIds.Count < 5) { Debug.LogWarning("allySlotIds 未正确设置,自动填充为 5 个占位 ID"); allySlotIds = new List { 0, 0, 0, 0, 0 }; } #if UNITY_EDITOR // Choose editor folder path preferentially from editorSOFolderPath, fallback to selectedProjectFolderPath string folderToUse = !string.IsNullOrEmpty(editorSOFolderPath) ? editorSOFolderPath : selectedProjectFolderPath; // Ensure folderToUse points inside the project Assets folder when possible string projectRelative = null; if (!string.IsNullOrEmpty(folderToUse)) { string projectPath = folderToUse.Replace("\\", "/"); if (projectPath.StartsWith(Application.dataPath)) { projectRelative = "Assets" + projectPath.Substring(Application.dataPath.Length); } else if (projectPath.StartsWith("Assets/")) { projectRelative = projectPath; } else { Debug.LogWarning("editorSOFolderPath 必须位于项目的 Assets 文件夹下以便进行查询(建议填写 Assets/Resources/so/ally)。回退到全局列表查找。路径也可填写为磁盘绝对路径。"); } } // Collect TeamCharacterDataInfo and AllyHero_SO assets under the folder (or project-wide) List discovered = new List(); if (!string.IsNullOrEmpty(projectRelative)) { string[] guids = AssetDatabase.FindAssets("t:TeamCharacterDataInfo", new[] { projectRelative }); foreach (var g in guids) { string assetPath = AssetDatabase.GUIDToAssetPath(g); var so = AssetDatabase.LoadAssetAtPath(assetPath); if (so != null) discovered.Add(so); } string[] guids2 = AssetDatabase.FindAssets("t:AllyHero_SO", new[] { projectRelative }); foreach (var g in guids2) { string assetPath = AssetDatabase.GUIDToAssetPath(g); var so = AssetDatabase.LoadAssetAtPath(assetPath); if (so != null) discovered.Add(so); } } else { // no valid folder chosen -> search entire project string[] guids = AssetDatabase.FindAssets("t:TeamCharacterDataInfo"); foreach (var g in guids) { string assetPath = AssetDatabase.GUIDToAssetPath(g); var so = AssetDatabase.LoadAssetAtPath(assetPath); if (so != null) discovered.Add(so); } string[] guids2 = AssetDatabase.FindAssets("t:AllyHero_SO"); foreach (var g in guids2) { string assetPath = AssetDatabase.GUIDToAssetPath(g); var so = AssetDatabase.LoadAssetAtPath(assetPath); if (so != null) discovered.Add(so); } } TeamCharacterList globalList = null; if (teamSettingPanel.Instance != null) globalList = teamSettingPanel.Instance.teamCharacterList; for (int i = 0; i < 5; i++) { int id = allySlotIds[i]; TeamCharacterDataInfo found = null; // 如果 slot ID 为 0 -> 关闭对应 ally UI 并跳过 if (id == 0) { currentAllySOs[i] = null; switch (i) { case 0: isAlly01_active = false; break; case 1: isAlly02_active = false; break; case 2: isAlly03_active = false; break; case 3: isAlly04_active = false; break; case 4: isAlly05_active = false; break; } ToggleAllyObject(i, false); Debug.Log($"[teamUIController] slot {i+1} id=0 -> disabled"); continue; } // 只要 asset 的路径或名称包含 id 即可引用(Editor) for (int j = 0; j < discovered.Count; j++) { var obj = discovered[j]; if (obj == null) continue; string path = AssetDatabase.GetAssetPath(obj); if (!string.IsNullOrEmpty(path) && path.Contains(id.ToString())) { // if it's already TeamCharacterDataInfo, use it if (obj is TeamCharacterDataInfo tc) { found = tc; Debug.Log($"[teamUIController] (Editor) matched asset by path: {path} for id={id} slot={i+1}"); break; } // if it's AllyHero_SO, map to a runtime TeamCharacterDataInfo instance if (obj is AllyHero_SO ah) { var mapped = ScriptableObject.CreateInstance(); mapped.CharacterID = ah.ally_heroID; mapped.CharacterName = ah.ally_heroName; mapped.CharacterCardSprite = ah.ally_heroImage; mapped.CharacterTeamSprite = ah.ally_heroProfile; mapped.name = ah.name; found = mapped; Debug.Log($"[teamUIController] (Editor) matched AllyHero_SO by path: {path} for id={id} slot={i+1}"); break; } } } // 回退到全局 teamCharacterList if (found == null && globalList != null && globalList.characters != null) { for (int j = 0; j < globalList.characters.Count; j++) { var so = globalList.characters[j]; if (so == null) continue; // globalList 中也以 name 包含 id 为准 if (!string.IsNullOrEmpty(so.name) && so.name.Contains(id.ToString())) { found = so; break; } } } currentAllySOs[i] = found; if (found == null) { Debug.LogWarning($"[teamUIController] 未找到 id={id} 对应的 SO (slot {i + 1})"); // disable UI slot when not found switch (i) { case 0: isAlly01_active = false; break; case 1: isAlly02_active = false; break; case 2: isAlly03_active = false; break; case 3: isAlly04_active = false; break; case 4: isAlly05_active = false; break; } ToggleAllyObject(i, false); } else { Debug.Log($"[teamUIController] (Editor) slot {i + 1} resolved to SO: {found.name} (id={found.CharacterID})"); // 确保对应 UI slot 被开启 switch (i) { case 0: isAlly01_active = true; break; case 1: isAlly02_active = true; break; case 2: isAlly03_active = true; break; case 3: isAlly04_active = true; break; case 4: isAlly05_active = true; break; } ToggleAllyObject(i, true); DebugCharacterSO(found, i+1, true); // Also attempt to set the teammate character image from an underlying AllyHero_SO if available try { // first try to find AllyHero_SO by the same id var ahList = Resources.LoadAll(""); AllyHero_SO matched = null; foreach (var a in ahList) if (a != null && a.ally_heroID == id) { matched = a; break; } if (matched != null) { var sprite = matched.ally_heroProfile != null ? matched.ally_heroProfile : matched.ally_heroImage; SetTeammateCharacterImage(i, sprite); } else { // fallback: if current TeamCharacterDataInfo has a TeamSprite, use that var tc = found as TeamCharacterDataInfo; if (tc != null && tc.CharacterTeamSprite != null) SetTeammateCharacterImage(i, tc.CharacterTeamSprite); } } catch { } } } #else // Runtime: prefer runtimeResourcesFolderPath, fallback to selectedProjectFolderPath string resourcesPath = ResolveResourcesRelativePath(!string.IsNullOrEmpty(runtimeResourcesFolderPath) ? runtimeResourcesFolderPath : selectedProjectFolderPath); List runtimeDiscovered = new List(); if (!string.IsNullOrEmpty(resourcesPath)) { resourcesPath = resourcesPath.Trim('/'); try { var arr = Resources.LoadAll(resourcesPath); if (arr != null && arr.Length > 0) runtimeDiscovered.AddRange(arr); // If nothing loaded from the specific folder, fallback to loading all and filter by name later if (runtimeDiscovered.Count == 0) { var arrAll = Resources.LoadAll(""); if (arrAll != null && arrAll.Length > 0) { runtimeDiscovered.AddRange(arrAll); Debug.LogWarning($"[teamUIController] (Runtime) Resources.LoadAll returned 0 in '{resourcesPath}', fell back to loading all ScriptableObjects ({arrAll.Length})"); } } Debug.Log($"[teamUIcontroller] (Runtime) Loaded {runtimeDiscovered.Count} objects from Resources/{resourcesPath}"); } catch (System.Exception ex) { Debug.LogWarning($"[teamUIController] (Runtime) Resources.LoadAll failed for '{resourcesPath}': {ex.Message}"); var arrAll = Resources.LoadAll(""); if (arrAll != null && arrAll.Length > 0) runtimeDiscovered.AddRange(arrAll); } } else { var arr = Resources.LoadAll(""); if (arr != null && arr.Length > 0) runtimeDiscovered.AddRange(arr); Debug.Log($"[teamUIController] (Runtime) Loaded {arr?.Length ?? 0} objects from Resources (project-wide)"); } TeamCharacterList globalList = null; if (teamSettingPanel.Instance != null) globalList = teamSettingPanel.Instance.teamCharacterList; for (int i = 0; i < 5; i++) { int id = allySlotIds[i]; TeamCharacterDataInfo found = null; // 如果 slot ID 为 0 -> 关闭对应 ally UI 并跳过 if (id == 0) { currentAllySOs[i] = null; switch (i) { case 0: isAlly01_active = false; break; case 1: isAlly02_active = false; break; case 2: isAlly03_active = false; break; case 3: isAlly04_active = false; break; case 4: isAlly05_active = false; break; } // also toggle associated object ToggleAllyObject(i, false); Debug.Log($"[teamUIController] (Runtime) slot {i+1} id=0 -> disabled"); continue; } // 只要资源名包含 id 即可引用(Runtime)。支持 TeamCharacterDataInfo 与 AllyHero_SO string runtimeSourceInfo = string.Empty; for (int j = 0; j < runtimeDiscovered.Count; j++) { var obj = runtimeDiscovered[j]; if (obj == null) continue; // name match first if (!string.IsNullOrEmpty(obj.name) && obj.name.Contains(id.ToString())) { if (obj is TeamCharacterDataInfo tc) { found = tc; break; } if (obj is AllyHero_SO ah) { var mapped = ScriptableObject.CreateInstance(); mapped.CharacterID = ah.ally_heroID; mapped.CharacterName = ah.ally_heroName; mapped.CharacterCardSprite = ah.ally_heroImage; mapped.CharacterTeamSprite = ah.ally_heroProfile; found = mapped; break; } } // fallback: if object is TeamCharacterDataInfo, check its CharacterID if (obj is TeamCharacterDataInfo tco && tco.CharacterID == id) { found = tco; break; } // fallback: AllyHero_SO CharacterID equivalent if (obj is AllyHero_SO ah2 && ah2.ally_heroID == id) { var mapped2 = ScriptableObject.CreateInstance(); mapped2.CharacterID = ah2.ally_heroID; mapped2.CharacterName = ah2.ally_heroName; mapped2.CharacterCardSprite = ah2.ally_heroImage; mapped2.CharacterTeamSprite = ah2.ally_heroProfile; found = mapped2; break; } } // 回退到全局 teamCharacterList if (found == null && globalList != null && globalList.characters != null) { for (int j = 0; j < globalList.characters.Count; j++) { var so = globalList.characters[j]; if (so == null) continue; if (!string.IsNullOrEmpty(so.name) && so.name.Contains(id.ToString())) { found = so; break; } if (so.CharacterID == id) { found = so; break; } } } // final fallback: try loading by resource path/id if resourcesPath provided if (found == null && !string.IsNullOrEmpty(resourcesPath)) { try { var byId = Resources.Load($"{resourcesPath}/{id}"); if (byId != null) found = byId; if (found != null) runtimeSourceInfo = $"Resources.Load: {resourcesPath}/{id}"; } catch { } } currentAllySOs[i] = found; if (found == null) { Debug.LogWarning($"[teamUIController] (Runtime) 未找到 id={id} 对应的 SO (slot {i + 1})"); switch (i) { case 0: isAlly01_active = false; break; case 1: isAlly02_active = false; break; case 2: isAlly03_active = false; break; case 3: isAlly04_active = false; break; case 4: isAlly05_active = false; break; } ToggleAllyObject(i, false); } else { // try to get a readable source for the matched object if (string.IsNullOrEmpty(runtimeSourceInfo)) { runtimeSourceInfo = found.name + " (mapped)"; } Debug.Log($"[teamUIcontroller] (Runtime) slot {i + 1} resolved to SO: {found.name} (id={found.CharacterID}) Source={runtimeSourceInfo}"); switch (i) { case 0: isAlly01_active = true; break; case 1: isAlly02_active = true; break; case 2: isAlly03_active = true; break; case 3: isAlly04_active = true; break; case 4: isAlly05_active = true; break; } ToggleAllyObject(i, true); DebugCharacterSO(found, i+1, false); // attempt to set teammate image from underlying AllyHero_SO if possible try { var ahList = Resources.LoadAll(""); AllyHero_SO matched = null; foreach (var a in ahList) if (a != null && a.ally_heroID == id) { matched = a; break; } if (matched != null) { var sprite = matched.ally_heroProfile != null ? matched.ally_heroProfile : matched.ally_heroImage; SetTeammateCharacterImage(i, sprite); } else { var tc = found as TeamCharacterDataInfo; if (tc != null && tc.CharacterTeamSprite != null) SetTeammateCharacterImage(i, tc.CharacterTeamSprite); } } catch { } } } #endif } // Resolve various forms of selectedProjectFolderPath into a Resources.Load relative path (no 'Resources/' prefix, no extension) private string ResolveResourcesRelativePath(string selectedPath) { if (string.IsNullOrEmpty(selectedPath)) return string.Empty; string p = selectedPath.Replace("\\", "/"); // If user provided a path starting with "Assets/Resources/", strip to get relative int idx = p.IndexOf("Assets/Resources/"); if (idx >= 0) { return p.Substring(idx + "Assets/Resources/".Length).Trim('/'); } // If user provided an absolute path that contains Application.dataPath and Resources string appData = Application.dataPath.Replace("\\", "/"); idx = p.IndexOf(appData + "/Resources/"); if (idx >= 0) { return p.Substring((appData + "/Resources/").Length).Trim('/'); } // If user already provided a Resources relative path (like "so/ally"), return it trimmed if (p.StartsWith("Resources/")) p = p.Substring("Resources/".Length); return p.Trim('/'); } // Debug helper to print key fields of a TeamCharacterDataInfo private void DebugCharacterSO(TeamCharacterDataInfo so, int slotIndex, bool isEditor) { if (so == null) return; string mode = isEditor ? "Editor" : "Runtime"; string spriteCard = so.CharacterCardSprite != null ? so.CharacterCardSprite.name : "(null)"; string spriteTeam = so.CharacterTeamSprite != null ? so.CharacterTeamSprite.name : "(null)"; Debug.Log($"[teamUIController] ({mode}) Slot {slotIndex} -> CharacterID={so.CharacterID}, Name={so.CharacterName}, Skill={so.CharacterSkillName}, MaxHealth={so.CharacterMaxHealth}, CardSprite={spriteCard}, TeamSprite={spriteTeam}"); } /// /// 返回当前解析到的 SO 列表(数组副本) /// public TeamCharacterDataInfo[] GetCurrentAllySOs() { return (TeamCharacterDataInfo[])currentAllySOs.Clone(); } [Header("基本文本")] [Tooltip("title")] public TextMeshProUGUI songNametitle; public TextMeshProUGUI difficultyName; public TextMeshProUGUI difficultyID; public TextMeshProUGUI constructionName; [Tooltip("连击计数")] public Text comboCounter; [Tooltip("敌人出场数量")] public Text enemyCounter; [SerializeField] private int enemyCounterMax; // 本局游戏敌人最大数 [SerializeField] private int enemyCurrentCount; // 现在的敌人顺序 [Tooltip("当前总分")] public TextMeshProUGUI currentTotalScore; [Header("Combo判定最低要求")] public ComboJudgeType comboJudgeType = ComboJudgeType.Perfect; private int combo = 0; public enum ComboJudgeType { Perfect, Great, Good, // Miss } [Header("teammate01")] [Tooltip("父物体-控制整体显隐")] public GameObject objectFather_ally01; [Tooltip("开关")] public bool isAlly01_active = true; [Tooltip("角色图")] public Image teammate01_characterImage; [Tooltip("血条")] public Image teammate01_healthImage; [Tooltip("虚血条")] public Image teammate01_fadehealthImage; [Tooltip("法力条")] public Image teammate01_manaImage; [Tooltip("虚法力条")] public Image teammate01_fadeManaImage; [Tooltip("名字文本")] public TextMeshProUGUI teammate01_nameText; [Tooltip("当前生命值/最大生命值")] public TextMeshProUGUI teammate01_healthRate; [Tooltip("当前分数/轨道分数上限")] public TextMeshProUGUI teammate01_current_scoreText; // rate : now score / max score [Tooltip("角色唯一识别码")] [SerializeField] private int ally01_id; [Tooltip("受击闪红")] public Image teammate01_hurtRedImage; [Tooltip("当前生命值")] [SerializeField] private int teammate01_currentHP; [Tooltip("最大生命值")] [SerializeField] private int teammate01_maxHP; [Tooltip("当前法力值")] [SerializeField] private int teammate01_currentMana; [Tooltip("最大法力值")] [SerializeField] private int teammate01_maxMana; [Header("teammate02")] [Tooltip("父物体-控制整体显隐")] public GameObject objectFather_ally02; [Tooltip("开关")] public bool isAlly02_active = true; [Tooltip("角色图")] public Image teammate02_characterImage; [Tooltip("血条")] public Image teammate02_healthImage; [Tooltip("虚血条")] public Image teammate02_fadehealthImage; [Tooltip("法力条")] public Image teammate02_manaImage; [Tooltip("虚法力条")] public Image teammate02_fadeManaImage; [Tooltip("名字文本")] public TextMeshProUGUI teammate02_nameText; [Tooltip("当前生命值/最大生命值")] public TextMeshProUGUI teammate02_healthRate; [Tooltip("当前分数/轨道分数上限")] public TextMeshProUGUI teammate02_current_scoreText; // rate : now score / max score [Tooltip("角色唯一识别码")] [SerializeField] private int ally02_id; [Tooltip("受击闪红")] public Image teammate02_hurtRedImage; [Tooltip("当前生命值")] [SerializeField] private int teammate02_currentHP; [Tooltip("最大生命值")] [SerializeField] private int teammate02_maxHP; [Tooltip("当前法力值")] [SerializeField] private int teammate02_currentMana; [Tooltip("最大法力值")] [SerializeField] private int teammate02_maxMana; [Header("teammate03")] [Tooltip("父物体-控制整体显隐")] public GameObject objectFather_ally03; [Tooltip("开关")] public bool isAlly03_active = true; [Tooltip("角色图")] public Image teammate03_characterImage; [Tooltip("血条")] public Image teammate03_healthImage; [Tooltip("虚血条")] public Image teammate03_fadehealthImage; [Tooltip("法力条")] public Image teammate03_manaImage; [Tooltip("虚法力条")] public Image teammate03_fadeManaImage; [Tooltip("名字文本")] public TextMeshProUGUI teammate03_nameText; [Tooltip("当前生命值/最大生命值")] public TextMeshProUGUI teammate03_healthRate; [Tooltip("当前分数/轨道分数上限")] public TextMeshProUGUI teammate03_current_scoreText; // rate : now score / max score [Tooltip("角色唯一识别码")] [SerializeField] private int ally03_id; [Tooltip("受击闪红")] public Image teammate03_hurtRedImage; [Tooltip("当前生命值")] [SerializeField] private int teammate03_currentHP; [Tooltip("最大生命值")] [SerializeField] private int teammate03_maxHP; [Tooltip("当前法力值")] [SerializeField] private int teammate03_currentMana; [Tooltip("最大法力值")] [SerializeField] private int teammate03_maxMana; [Header("teammate04")] [Tooltip("父物体-控制整体显隐")] public GameObject objectFather_ally04; [Tooltip("开关")] public bool isAlly04_active = true; [Tooltip("角色图")] public Image teammate04_characterImage; [Tooltip("血条")] public Image teammate04_healthImage; [Tooltip("虚血条")] public Image teammate04_fadehealthImage; [Tooltip("法力条")] public Image teammate04_manaImage; [Tooltip("虚法力条")] public Image teammate04_fadeManaImage; [Tooltip("名字文本")] public TextMeshProUGUI teammate04_nameText; [Tooltip("当前生命值/最大生命值")] public TextMeshProUGUI teammate04_healthRate; [Tooltip("当前分数/轨道分数上限")] public TextMeshProUGUI teammate04_current_scoreText; // rate : now score / max score [Tooltip("角色唯一识别码")] [SerializeField] private int ally04_id; [Tooltip("受击闪红")] public Image teammate04_hurtRedImage; [Tooltip("当前生命值")] [SerializeField] private int teammate04_currentHP; [Tooltip("最大生命值")] [SerializeField] private int teammate04_maxHP; [Tooltip("当前法力值")] [SerializeField] private int teammate04_currentMana; [Tooltip("最大法力值")] [SerializeField] private int teammate04_maxMana; [Header("teammate05")] [Tooltip("父物体-控制整体显隐")] public GameObject objectFather_ally05; [Tooltip("开关")] public bool isAlly05_active = true; [Tooltip("角色图")] public Image teammate05_characterImage; [Tooltip("血条")] public Image teammate05_healthImage; [Tooltip("虚血条")] public Image teammate05_fadehealthImage; [Tooltip("法力条")] public Image teammate05_manaImage; [Tooltip("虚法力条")] public Image teammate05_fadeManaImage; [Tooltip("名字文本")] public TextMeshProUGUI teammate05_nameText; [Tooltip("当前生命值/最大生命值")] public TextMeshProUGUI teammate05_healthRate; [Tooltip("当前分数/轨道分数上限")] public TextMeshProUGUI teammate05_current_scoreText; // rate : now score / max score [Tooltip("角色唯一识别码")] [SerializeField] private int ally05_id; [Tooltip("受击闪红")] public Image teammate05_hurtRedImage; [Tooltip("当前生命值")] [SerializeField] private int teammate05_currentHP; [Tooltip("最大生命值")] [SerializeField] private int teammate05_maxHP; [Tooltip("当前法力值")] [SerializeField] private int teammate05_currentMana; [Tooltip("最大法力值")] [SerializeField] private int teammate05_maxMana; [Header("currentEnemy")] [Tooltip("父物体-控制整体显隐")] public GameObject objectFather_enemy; [Tooltip("开关")] public bool isEnemy_active = true; [Tooltip("角色图")] public Image currentEnemy_characterImage; [Tooltip("血条")] public Image currentEnemy_healthImage; [Tooltip("虚血条")] public Image currentEnemy_fadehealthImage; [Tooltip("名字文本")] public TextMeshProUGUI currentEnemy_nameText; [Tooltip("当前生命值/最大生命值")] public TextMeshProUGUI currentEnemy_healthRate; [Tooltip("当前分数/轨道分数上限")] public TextMeshProUGUI currentEnemy_current_scoreText; // rate : now score / max score [Tooltip("角色唯一识别码")] [SerializeField] private int enemy_id; [Tooltip("受击闪红")] public Image currentEnemy_hurtRedImage; // Start is called once before the first execution of Update after the MonoBehaviour is created void Start() { PopulateAllySOsFromIds(); // initialize previous flags so Update can detect changes prevAllyActive[0] = isAlly01_active; prevAllyActive[1] = isAlly02_active; prevAllyActive[2] = isAlly03_active; prevAllyActive[3] = isAlly04_active; prevAllyActive[4] = isAlly05_active; // Ensure score text references are resolved so ScoreManager and AllyCombatant can write to them ResolveScoreTextReferences(); } // Update is called once per frame void Update() { // detect external changes to isAllyX_active and toggle objects accordingly bool[] current = new bool[] { isAlly01_active, isAlly02_active, isAlly03_active, isAlly04_active, isAlly05_active }; for (int i = 0; i < 5; i++) { if (prevAllyActive[i] != current[i]) { ToggleAllyObject(i, current[i]); prevAllyActive[i] = current[i]; } } } private void FixedUpdate() { } private void Awake() { if (Instance == null) Instance = this; else Destroy(gameObject); } /// /// 由判定系统调用,传入判定结果(如"Perfect"、"Great"、"Good"、"Miss") /// public void OnJudgeResult(string result) { if (IsCombo(result)) { combo++; } else { combo = 0; } if (comboCounter != null) comboCounter.text = combo.ToString(); } /// /// 判断当前判定结果是否达到combo要求 /// private bool IsCombo(string result) { switch (comboJudgeType) { case ComboJudgeType.Perfect: return result == "Perfect"; case ComboJudgeType.Great: return result == "Perfect" || result == "Great"; case ComboJudgeType.Good: return result == "Perfect" || result == "Great" || result == "Good"; default: return false; } } // helper to toggle ally object by slot index private void ToggleAllyObject(int slotIndex, bool active) { GameObject target = null; switch (slotIndex) { case 0: target = ally01_object ?? objectFather_ally01; break; case 1: target = ally02_object ?? objectFather_ally02; break; case 2: target = ally03_object ?? objectFather_ally03; break; case 3: target = ally04_object ?? objectFather_ally04; break; case 4: target = ally05_object ?? objectFather_ally05; break; } if (target != null) target.SetActive(active); } // Try to ensure the teammateXX_current_scoreText fields are assigned; if null, search the corresponding parent for TMP/Text private void ResolveScoreTextReferences() { void TryResolve(ref TextMeshProUGUI field, GameObject parent, string slotName) { if (field != null) return; if (parent != null) { var tmp = parent.GetComponentInChildren(true); if (tmp != null) { field = tmp; Debug.Log($"[teamUIController] Resolved {slotName} TMP from parent {parent.name} -> {tmp.gameObject.name}"); return; } var legacy = parent.GetComponentInChildren(true); if (legacy != null) { // if only legacy Text exists, try to create a TMP component to avoid type mismatch var go = legacy.gameObject; var created = go.GetComponent() ?? go.AddComponent(); created.text = legacy.text; field = created; Debug.LogWarning($"[teamUIController] Found legacy Text for {slotName} under {parent.name}. Added/used TMP component on {go.name} and copied text."); return; } } Debug.LogWarning($"[teamUIController] Could not resolve {slotName} TMP (parent '{parent?.name}')"); } TryResolve(ref teammate01_current_scoreText, objectFather_ally01, "teammate01_current_scoreText"); TryResolve(ref teammate02_current_scoreText, objectFather_ally02, "teammate02_current_scoreText"); TryResolve(ref teammate03_current_scoreText, objectFather_ally03, "teammate03_current_scoreText"); TryResolve(ref teammate04_current_scoreText, objectFather_ally04, "teammate04_current_scoreText"); TryResolve(ref teammate05_current_scoreText, objectFather_ally05, "teammate05_current_scoreText"); // total score: if assigned field is null, try to find any TMP in scene named "currentTotalScore" or under this object if (currentTotalScore == null) { var tmp = GetComponentInChildren(true); if (tmp != null) { currentTotalScore = tmp; Debug.Log($"[teamUIController] Resolved currentTotalScore from child {tmp.gameObject.name}"); } } } /// /// 返回相邻的友军槽索引(左右),边侧只有一侧 /// public int[] GetAdjacentAllyIndices(int slotIndex) { var list = new List(); if (slotIndex - 1 >= 0) list.Add(slotIndex - 1); if (slotIndex + 1 <= 4) list.Add(slotIndex + 1); return list.ToArray(); } /// /// 返回指定槽位对应的友军 GameObject(优先返回 allyX_object,否则返回 objectFather_allyXX),可能为 null /// public GameObject GetAllyObjectBySlot(int slotIndex) { switch (slotIndex) { case 0: return ally01_object ?? objectFather_ally01; case 1: return ally02_object ?? objectFather_ally02; case 2: return ally03_object ?? objectFather_ally03; case 3: return ally04_object ?? objectFather_ally04; case 4: return ally05_object ?? objectFather_ally05; default: return null; } } // helper to set teammate character image based on slot index private void SetTeammateCharacterImage(int slotIndex, Sprite sprite) { if (sprite == null) return; switch (slotIndex) { case 0: if (teammate01_characterImage != null) teammate01_characterImage.sprite = sprite; break; case 1: if (teammate02_characterImage != null) teammate02_characterImage.sprite = sprite; break; case 2: if (teammate03_characterImage != null) teammate03_characterImage.sprite = sprite; break; case 3: if (teammate04_characterImage != null) teammate04_characterImage.sprite = sprite; break; case 4: if (teammate05_characterImage != null) teammate05_characterImage.sprite = sprite; break; } } }