using System.Collections.Generic; using UnityEngine; using System.Collections; using UnityEngine.UI; public class newTeamSelector : MonoBehaviour { public static newTeamSelector Instance { get; private set; } [Header("Inspector")] public Sprite levelIcon_sprite_C; public Sprite levelIcon_sprite_B; public Sprite levelIcon_sprite_A; public Sprite levelIcon_sprite_S; [Header("Inspector")] public int selected_heroSlot01_heroID; public int selected_heroSlot02_heroID; public int selected_heroSlot03_heroID; public int selected_heroSlot04_heroID; public int selected_heroSlot05_heroID; [Tooltip("Prefab for a single hero slot. Must contain slots_heroSlots component.")] public GameObject slotPrefab; [Tooltip("Container (GameObject with HorizontalLayoutGroup) where slots will be instantiated")] public RectTransform container; [Tooltip("Number of slots to create (default 5)")] public int slotCount = 5; [Tooltip("Optional override colors used for header images per slot. If not set, prefab's headerImage_colors will be used.")] public Color[] overrideHeaderColors; [Tooltip("Per-slot default images used when the slot is empty. Index 0-4 maps to slot 1-5.")] public Sprite[] slotDefaultImage; [Tooltip("If true, create slots automatically on Start")] public bool instantiateOnStart = true; [Tooltip("Delay skill list rebuild until after slot enter animation to reduce hitching.")] public float delayedSkillRefreshSeconds = 0.75f; [Header("Team Share")] public TeamShareApplyPanel teamShareApplyPanel; private readonly List createdSlots = new List(); private static AllyHero_SO[] cachedHeroes; private static Dictionary cachedHeroesById; private Coroutine refreshSkillsRoutine; void Awake() { if (Instance == null) { Instance = this; EnsureHeroCache(); } else { Destroy(gameObject); } } void Start() { RefreshTeamShareCodeDisplay(); if (instantiateOnStart) CreateSlots(); StartCoroutine(LoadSelectedHeroesNextFrame()); } private IEnumerator LoadSelectedHeroesNextFrame() { yield return null; LoadSelectedHeroes(); } public void CreateSlots() { if (slotPrefab == null || container == null) { Debug.LogError("newTeamSelector: slotPrefab or container is not assigned."); return; } // Clear existing for (int i = container.childCount - 1; i >= 0; i--) { var c = container.GetChild(i); #if UNITY_EDITOR if (!Application.isPlaying) DestroyImmediate(c.gameObject); else Destroy(c.gameObject); #else Destroy(c.gameObject); #endif } createdSlots.Clear(); for (int i = 0; i < slotCount; i++) { var go = Instantiate(slotPrefab, container); go.transform.SetParent(container, false); go.name = $"heroSlot_{i}"; var slotComp = go.GetComponent(); if (slotComp == null) { Debug.LogWarning("newTeamSelector: instantiated prefab does not contain slots_heroSlots component."); continue; } slotComp.heroSlot_slotID = i; // choose color: overrideHeaderColors takes priority, then prefab's headerImage_colors if available Color chosen = Color.white; if (overrideHeaderColors != null && i < overrideHeaderColors.Length) { chosen = overrideHeaderColors[i]; } else if (slotComp.headerImage_colors != null && i < slotComp.headerImage_colors.Length) { chosen = slotComp.headerImage_colors[i]; } // Ensure visible alpha chosen.a = Mathf.Clamp01(chosen.a); if (chosen.a <= 0f) chosen.a = 1f; // Apply immediately if possible if (slotComp.heroSlot_colorImage != null) { slotComp.heroSlot_colorImage.color = chosen; } createdSlots.Add(slotComp); } // Force immediate layout rebuild so HorizontalLayoutGroup arranges children right away LayoutRebuilder.ForceRebuildLayoutImmediate(container); // Re-apply colors next frame to override any Start() logic on instantiated prefabs StartCoroutine(ApplyHeaderColorsNextFrame()); } private IEnumerator ApplyHeaderColorsNextFrame() { yield return null; // wait one frame for (int i = 0; i < createdSlots.Count; i++) { var slot = createdSlots[i]; if (slot == null) continue; Color chosen = Color.white; if (overrideHeaderColors != null && i < overrideHeaderColors.Length) { chosen = overrideHeaderColors[i]; } else if (slot.headerImage_colors != null && i < slot.headerImage_colors.Length) { chosen = slot.headerImage_colors[i]; } chosen.a = 1f; if (slot.heroSlot_colorImage == null) { var img = slot.GetComponentInChildren(true); if (img != null) slot.heroSlot_colorImage = img; } if (slot.heroSlot_colorImage != null) slot.heroSlot_colorImage.color = chosen; } // ensure layout rebuilt after modifications LayoutRebuilder.ForceRebuildLayoutImmediate(container); } public List GetCreatedSlots() => createdSlots; public void UpdateAndSaveSelectedHeroes() { int[] ids = new int[slotCount > 0 ? slotCount : 5]; for (int i = 0; i < ids.Length; i++) { ids[i] = i < createdSlots.Count && createdSlots[i] != null ? Mathf.Max(0, createdSlots[i].heroSlot_heroID) : 0; } ApplySelectedHeroIdsToFields(ids); SyncCurrentTeamFromIds(ids); SaveSelectedHeroIdsToPlayerPrefs(ids); PlayerPrefs.Save(); Debug.Log("已保存当前英雄到 PlayerPrefs"); RefreshTeamShareCodeDisplay(); } public void LoadSelectedHeroes() { if (createdSlots.Count == 0) return; int[] selectedIds; bool loadedFromCurrentTeam = TryGetCurrentTeamHeroIds(out selectedIds); if (!loadedFromCurrentTeam) { selectedIds = ReadSelectedHeroIdsFromPlayerPrefs(); SyncCurrentTeamFromIds(selectedIds); } else { SaveSelectedHeroIdsToPlayerPrefs(selectedIds); } ApplySelectedHeroIdsToFields(selectedIds); // Set the slots if (createdSlots.Count > 0) createdSlots[0].heroSlot_heroID = selected_heroSlot01_heroID; if (createdSlots.Count > 1) createdSlots[1].heroSlot_heroID = selected_heroSlot02_heroID; if (createdSlots.Count > 2) createdSlots[2].heroSlot_heroID = selected_heroSlot03_heroID; if (createdSlots.Count > 3) createdSlots[3].heroSlot_heroID = selected_heroSlot04_heroID; if (createdSlots.Count > 4) createdSlots[4].heroSlot_heroID = selected_heroSlot05_heroID; bool changed = false; // Update each slot's UI for (int i = 0; i < createdSlots.Count; i++) { var slot = createdSlots[i]; if (slot == null) continue; if (slot.heroSlot_heroID != 0) { // Find the hero SO AllyHero_SO hero = FindHeroById(slot.heroSlot_heroID); if (hero != null && hero.isUnlocked) { slot.RefreshSlotVisualsFromCurrentHero(false); } else { slot.heroSlot_heroID = 0; slot.RefreshSlotVisualsFromCurrentHero(false); changed = true; } } else { slot.heroSlot_heroID = 0; slot.RefreshSlotVisualsFromCurrentHero(false); } } if (changed) { UpdateAndSaveSelectedHeroes(); } if (refreshSkillsRoutine != null) StopCoroutine(refreshSkillsRoutine); refreshSkillsRoutine = StartCoroutine(RefreshSkillsGradually()); StartCoroutine(ForceRefreshSlotNamesNextFrame()); RefreshTeamShareCodeDisplay(); } private IEnumerator ForceRefreshSlotNamesNextFrame() { yield return null; for (int i = 0; i < createdSlots.Count; i++) { var slot = createdSlots[i]; if (slot == null) continue; slot.RefreshSlotVisualsFromCurrentHero(false); } } private int[] ReadSelectedHeroIdsFromPlayerPrefs() { int[] ids = new int[Mathf.Max(5, slotCount)]; if (ids.Length > 0) ids[0] = Mathf.Max(0, PlayerPrefs.GetInt("selected_heroSlot01_heroID", 0)); if (ids.Length > 1) ids[1] = Mathf.Max(0, PlayerPrefs.GetInt("selected_heroSlot02_heroID", 0)); if (ids.Length > 2) ids[2] = Mathf.Max(0, PlayerPrefs.GetInt("selected_heroSlot03_heroID", 0)); if (ids.Length > 3) ids[3] = Mathf.Max(0, PlayerPrefs.GetInt("selected_heroSlot04_heroID", 0)); if (ids.Length > 4) ids[4] = Mathf.Max(0, PlayerPrefs.GetInt("selected_heroSlot05_heroID", 0)); return ids; } private void SaveSelectedHeroIdsToPlayerPrefs(int[] ids) { PlayerPrefs.SetInt("selected_heroSlot01_heroID", GetIdAt(ids, 0)); PlayerPrefs.SetInt("selected_heroSlot02_heroID", GetIdAt(ids, 1)); PlayerPrefs.SetInt("selected_heroSlot03_heroID", GetIdAt(ids, 2)); PlayerPrefs.SetInt("selected_heroSlot04_heroID", GetIdAt(ids, 3)); PlayerPrefs.SetInt("selected_heroSlot05_heroID", GetIdAt(ids, 4)); } private void ApplySelectedHeroIdsToFields(int[] ids) { selected_heroSlot01_heroID = GetIdAt(ids, 0); selected_heroSlot02_heroID = GetIdAt(ids, 1); selected_heroSlot03_heroID = GetIdAt(ids, 2); selected_heroSlot04_heroID = GetIdAt(ids, 3); selected_heroSlot05_heroID = GetIdAt(ids, 4); } private bool TryGetCurrentTeamHeroIds(out int[] ids) { ids = new int[Mathf.Max(5, slotCount)]; TeamManager teamManager = TeamManager.Instance; if (teamManager == null) return false; TeamSetting currentTeam = null; try { currentTeam = teamManager.getCurrentSelectedTeam(); } catch { currentTeam = null; } if (currentTeam == null || currentTeam.teamIdList == null) return false; bool hasAnyHero = false; for (int i = 0; i < ids.Length && i < currentTeam.teamIdList.Count; i++) { CharacterView slot = currentTeam.teamIdList[i]; ids[i] = slot != null ? Mathf.Max(0, slot.characterId) : 0; if (ids[i] > 0) hasAnyHero = true; } return hasAnyHero; } private void SyncCurrentTeamFromIds(int[] ids) { TeamManager teamManager = TeamManager.Instance; if (teamManager == null) return; TeamSetting currentTeam = null; try { currentTeam = teamManager.getCurrentSelectedTeam(); } catch { currentTeam = null; } if (currentTeam == null) { currentTeam = new TeamSetting(new List(), -1); } if (currentTeam.teamIdList == null) { currentTeam.teamIdList = new List(); } int targetCount = Mathf.Max(5, ids != null ? ids.Length : 0); while (currentTeam.teamIdList.Count < targetCount) { currentTeam.teamIdList.Add(new CharacterView(0, null)); } for (int i = 0; i < targetCount; i++) { int heroId = GetIdAt(ids, i); string boundaryLevel = ResolveBoundaryLevel(heroId); if (currentTeam.teamIdList[i] == null) { currentTeam.teamIdList[i] = new CharacterView(heroId, boundaryLevel); } else { currentTeam.teamIdList[i].characterId = heroId; currentTeam.teamIdList[i].currentBoundaryLevel = boundaryLevel; } } if (currentTeam.LeaderId <= 0 || !ContainsHeroId(currentTeam.teamIdList, currentTeam.LeaderId)) { currentTeam.LeaderId = FindFirstHeroId(ids); } teamManager.setCurrentSelectedTeam(currentTeam, teamManager.currentSelectedTeam); } private static int GetIdAt(int[] ids, int index) { if (ids == null || index < 0 || index >= ids.Length) return 0; return Mathf.Max(0, ids[index]); } private static bool ContainsHeroId(List teamIdList, int heroId) { if (teamIdList == null || heroId <= 0) return false; for (int i = 0; i < teamIdList.Count; i++) { CharacterView view = teamIdList[i]; if (view != null && view.characterId == heroId) return true; } return false; } private static int FindFirstHeroId(int[] ids) { if (ids == null) return -1; for (int i = 0; i < ids.Length; i++) { if (ids[i] > 0) return ids[i]; } return -1; } private string ResolveBoundaryLevel(int heroId) { if (heroId <= 0) return null; AllyHero_SO hero = FindHeroById(heroId); return hero != null ? GetRatingFromSO(hero) : null; } private IEnumerator RefreshSkillsGradually() { if (delayedSkillRefreshSeconds > 0f) yield return new WaitForSecondsRealtime(delayedSkillRefreshSeconds); else yield return null; for (int i = 0; i < createdSlots.Count; i++) { var slot = createdSlots[i]; if (slot == null) continue; if (slot.heroSlot_heroID != 0) { slot.UpdateSkillList(false); // Spread UI instantiation across frames to reduce hitch. yield return null; } } TeamSelectorAnimController.PlaySkillsFlash(null); refreshSkillsRoutine = null; RefreshTeamShareCodeDisplay(); } public void RefreshTeamShareCodeDisplay() { if (teamShareApplyPanel != null) { teamShareApplyPanel.RefreshShareCodeDisplay(); } } private AllyHero_SO FindHeroById(int id) { EnsureHeroCache(); if (cachedHeroesById != null && cachedHeroesById.TryGetValue(id, out var hero)) return hero; RebuildHeroCache(); if (cachedHeroesById != null && cachedHeroesById.TryGetValue(id, out hero)) return hero; return null; } private static void EnsureHeroCache() { if (cachedHeroesById != null && cachedHeroesById.Count > 0) return; RebuildHeroCache(); } private static void RebuildHeroCache() { cachedHeroes = Resources.LoadAll("so/ally"); if (cachedHeroes == null || cachedHeroes.Length == 0) cachedHeroes = Resources.LoadAll(""); cachedHeroesById = new Dictionary(); if (cachedHeroes == null) return; for (int i = 0; i < cachedHeroes.Length; i++) { AllyHero_SO hero = cachedHeroes[i]; if (hero == null) continue; cachedHeroesById[hero.ally_heroID] = hero; } } private string GetRatingFromSO(AllyHero_SO so) { if (so == null || so.levelStats == null || so.levelStats.Count == 0) return "C"; List sorted = new List(); foreach (var level in so.levelStats) { if (level != null) sorted.Add(level); } sorted.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP)); int currentExp = so.ally_currentEXP; int selectedIndex = 0; for (int i = 0; i < sorted.Count; i++) { if (currentExp >= sorted[i].requiredEXP) selectedIndex = i; else break; } if (selectedIndex <= 0) return "C"; if (selectedIndex == 1) return "B"; if (selectedIndex == 2) return "A"; return "S"; } }