526 lines
16 KiB
C#
526 lines
16 KiB
C#
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<slots_heroSlots> createdSlots = new List<slots_heroSlots>();
|
|
private static AllyHero_SO[] cachedHeroes;
|
|
private static Dictionary<int, AllyHero_SO> cachedHeroesById;
|
|
private Coroutine refreshSkillsRoutine;
|
|
private Coroutine startupSkillValidationRoutine;
|
|
|
|
void Awake()
|
|
{
|
|
if (Instance == null)
|
|
{
|
|
Instance = this;
|
|
EnsureHeroCache();
|
|
}
|
|
else
|
|
{
|
|
Destroy(gameObject);
|
|
}
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
RefreshTeamShareCodeDisplay();
|
|
if (instantiateOnStart)
|
|
{
|
|
CreateSlots();
|
|
}
|
|
|
|
if (startupSkillValidationRoutine != null)
|
|
{
|
|
StopCoroutine(startupSkillValidationRoutine);
|
|
}
|
|
|
|
startupSkillValidationRoutine = StartCoroutine(StartupLoadRoutine());
|
|
}
|
|
|
|
private IEnumerator StartupLoadRoutine()
|
|
{
|
|
yield return null;
|
|
yield return AllyHeroSkillSelectionValidator.ValidateAllHeroesAsync();
|
|
LoadSelectedHeroes();
|
|
startupSkillValidationRoutine = null;
|
|
}
|
|
|
|
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<slots_heroSlots>();
|
|
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<Image>(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<slots_heroSlots> 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<CharacterView>(), -1);
|
|
}
|
|
|
|
if (currentTeam.teamIdList == null)
|
|
{
|
|
currentTeam.teamIdList = new List<CharacterView>();
|
|
}
|
|
|
|
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<CharacterView> 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 = RuntimeResourcesCache.LoadAllAllyHeroes();
|
|
|
|
cachedHeroesById = new Dictionary<int, AllyHero_SO>();
|
|
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)
|
|
{
|
|
return so != null ? so.GetDisplayLevelRatingKey() : "C";
|
|
}
|
|
}
|