ui基本完毕,修了一大把的bug
This commit is contained in:
+56
-17
@@ -1,4 +1,5 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class character_detailInformation : MonoBehaviour
|
||||
@@ -6,26 +7,32 @@ public class character_detailInformation : MonoBehaviour
|
||||
[Header("Inspector")]
|
||||
public Image character_HDImage;
|
||||
public Image character_HDImage_shadow;
|
||||
|
||||
[Header("so")]
|
||||
public ScriptableObject characterSO;
|
||||
|
||||
[Header("Inspector")]
|
||||
public Text basic_infoText;
|
||||
public List<Text> basic_infoTexts = new List<Text>(7);
|
||||
public Text designation_name_combination;
|
||||
|
||||
[Header("Inspector")]
|
||||
public basicInfoBars infoBars;
|
||||
|
||||
[Header("Inspector")]
|
||||
public levelBar_controller levelBar;
|
||||
|
||||
[Header("Inspector")]
|
||||
public RatioBar ratioBar;
|
||||
|
||||
[Header("shutdown")]
|
||||
public Button shut_button;
|
||||
|
||||
[Header("Inspector")]
|
||||
public Image mainTheme_fadeImage;
|
||||
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
// Documentation text normalized.
|
||||
if (shut_button != null)
|
||||
{
|
||||
shut_button.onClick.AddListener(ShutDown);
|
||||
@@ -33,25 +40,21 @@ public class character_detailInformation : MonoBehaviour
|
||||
|
||||
if (characterSO is AllyHero_SO heroSO)
|
||||
{
|
||||
// Documentation text normalized.
|
||||
if (character_HDImage != null && heroSO.ally_hero_HD_image != null)
|
||||
{
|
||||
character_HDImage.sprite = heroSO.ally_hero_HD_image;
|
||||
}
|
||||
|
||||
// Documentation text normalized.
|
||||
if (character_HDImage_shadow != null && heroSO.ally_hero_HD_image != null)
|
||||
{
|
||||
character_HDImage_shadow.sprite = heroSO.ally_hero_HD_image;
|
||||
}
|
||||
|
||||
// Documentation text normalized.
|
||||
if (designation_name_combination != null)
|
||||
{
|
||||
designation_name_combination.text = $"{heroSO.ally_heroDesignation} {heroSO.ally_heroName}";
|
||||
}
|
||||
|
||||
// Documentation text normalized.
|
||||
var levelInfo = heroSO.GetEffectiveLevelForCurrentEXP();
|
||||
if (levelInfo != null)
|
||||
{
|
||||
@@ -63,13 +66,20 @@ public class character_detailInformation : MonoBehaviour
|
||||
float missHpLoss = levelInfo.missHpLossBase;
|
||||
int skillSlotLimited = levelInfo.skill_slot_limited;
|
||||
|
||||
// Documentation text normalized.
|
||||
if (basic_infoText != null)
|
||||
{
|
||||
basic_infoText.text = $"{attack}\n{maxHP}\n{damageResistance * 100}%\n{maxMana}\n{scoreEfficiency * 100}%\n{skillSlotLimited}\n{missHpLoss}";
|
||||
}
|
||||
|
||||
// Documentation text normalized.
|
||||
AssignBasicInfoTexts(
|
||||
attack.ToString(),
|
||||
maxHP.ToString(),
|
||||
$"{damageResistance * 100}%",
|
||||
maxMana.ToString(),
|
||||
$"{scoreEfficiency * 100}%",
|
||||
skillSlotLimited.ToString(),
|
||||
missHpLoss.ToString());
|
||||
|
||||
if (infoBars != null)
|
||||
{
|
||||
infoBars.SetAttack(attack);
|
||||
@@ -78,41 +88,70 @@ public class character_detailInformation : MonoBehaviour
|
||||
infoBars.SetBasicMana(maxMana);
|
||||
infoBars.SetScoreEfficiency(scoreEfficiency);
|
||||
infoBars.SetSkillAmountLimit(skillSlotLimited);
|
||||
infoBars.SetMissDamage((int)missHpLoss); // Documentation text normalized.
|
||||
infoBars.SetMissDamage((int)missHpLoss);
|
||||
}
|
||||
|
||||
// Documentation text normalized.
|
||||
if (levelBar != null)
|
||||
{
|
||||
levelBar.SetLevelBar(heroSO);
|
||||
}
|
||||
|
||||
// Documentation text normalized.
|
||||
if (ratioBar != null)
|
||||
{
|
||||
ratioBar.SetManaAndDamage(heroSO);
|
||||
}
|
||||
}
|
||||
|
||||
// Documentation text normalized.
|
||||
if (mainTheme_fadeImage != null)
|
||||
{
|
||||
Color themeColor = heroSO.ally_heroThemeColor;
|
||||
themeColor.a = 1f; // Documentation text normalized.
|
||||
themeColor.a = 1f;
|
||||
mainTheme_fadeImage.color = themeColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Documentation text normalized.
|
||||
private void ShutDown()
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
private void AssignBasicInfoTexts(
|
||||
string attackText,
|
||||
string maxHpText,
|
||||
string damageResistanceText,
|
||||
string maxManaText,
|
||||
string scoreEfficiencyText,
|
||||
string skillSlotLimitedText,
|
||||
string missHpLossText)
|
||||
{
|
||||
if (basic_infoTexts == null || basic_infoTexts.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string[] values =
|
||||
{
|
||||
attackText,
|
||||
maxHpText,
|
||||
damageResistanceText,
|
||||
maxManaText,
|
||||
scoreEfficiencyText,
|
||||
skillSlotLimitedText,
|
||||
missHpLossText
|
||||
};
|
||||
|
||||
int count = Mathf.Min(basic_infoTexts.Count, values.Length);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (basic_infoTexts[i] != null)
|
||||
{
|
||||
basic_infoTexts[i].text = values[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 226, y: 40}
|
||||
m_AnchoredPosition: {x: 274.54, y: 0}
|
||||
m_SizeDelta: {x: 550, y: 40}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &2847136257128320079
|
||||
CanvasRenderer:
|
||||
@@ -67,7 +67,7 @@ MonoBehaviour:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: 374deeecafefea14cb9c506944b98b08, type: 3}
|
||||
m_Type: 0
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
@@ -111,7 +111,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 226, y: 40}
|
||||
m_SizeDelta: {x: 36.777, y: 40}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &3566225389816842317
|
||||
MonoBehaviour:
|
||||
@@ -151,7 +151,7 @@ RectTransform:
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4201383612783777168}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
@@ -160,8 +160,8 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: -25.9662, y: 0}
|
||||
m_SizeDelta: {x: 151.9325, y: 38.37}
|
||||
m_AnchoredPosition: {x: -190.98, y: 0}
|
||||
m_SizeDelta: {x: 168.04, y: 38.37}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &2063637685932881627
|
||||
CanvasRenderer:
|
||||
@@ -184,7 +184,7 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
@@ -193,15 +193,15 @@ MonoBehaviour:
|
||||
m_Calls: []
|
||||
m_FontData:
|
||||
m_Font: {fileID: 12800000, guid: cc180dff846d13a4d88ddaed6f77e5cd, type: 3}
|
||||
m_FontSize: 20
|
||||
m_FontSize: 24
|
||||
m_FontStyle: 0
|
||||
m_BestFit: 0
|
||||
m_MinSize: 2
|
||||
m_MaxSize: 40
|
||||
m_Alignment: 3
|
||||
m_Alignment: 4
|
||||
m_AlignByGeometry: 0
|
||||
m_RichText: 1
|
||||
m_HorizontalOverflow: 0
|
||||
m_VerticalOverflow: 0
|
||||
m_LineSpacing: 1
|
||||
m_Text: columnText
|
||||
m_Text: "S\u7EA7\u522B\u8BA1\u5212"
|
||||
|
||||
+1430
-107
File diff suppressed because it is too large
Load Diff
@@ -1,24 +1,26 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class loadSkillsSelect : MonoBehaviour
|
||||
{
|
||||
private const string HiddenSkillName = "未解锁技能";
|
||||
private const string HiddenSkillDescription = "此技能尚未解锁。";
|
||||
private const string HiddenSkillName = "\u672A\u89E3\u9501\u6280\u80FD";
|
||||
private const string HiddenSkillDescription = "\u6B64\u6280\u80FD\u5C1A\u672A\u89E3\u9501\u3002";
|
||||
|
||||
public static loadSkillsSelect Instance { get; private set; }
|
||||
public AllyHero_SO currentHeroSO { get; private set; }
|
||||
|
||||
[Header("Prefab / UI")]
|
||||
public GameObject skillCardPrefab; // prefab must have slots_skillSlots component
|
||||
public RectTransform contentParent; // container where skill cards will be instantiated
|
||||
public GameObject columnHeaderPrefab; // optional prefab for column headers, e.g. Text or UI element with Text component
|
||||
public GameObject skillCardPrefab;
|
||||
public RectTransform contentParent;
|
||||
public GameObject columnHeaderPrefab;
|
||||
|
||||
[Header("Column Layout Settings")]
|
||||
public float columnWidth = 200f; // Documentation text normalized.
|
||||
public float columnPosX = 0f; // Documentation text normalized.
|
||||
public float columnVerticalSpacing = 10f; // Vertical spacing between header and skills in a column
|
||||
public float columnWidth = 200f;
|
||||
public float columnPosX = 0f;
|
||||
public float columnVerticalSpacing = 10f;
|
||||
|
||||
[Header("Skill Group Container Settings")]
|
||||
public Vector2 skillGroupSpacing = new Vector2(10f, 10f);
|
||||
@@ -35,299 +37,448 @@ public class loadSkillsSelect : MonoBehaviour
|
||||
public ContentSizeFitter.FitMode skillGroupVerticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
[Header("Skill Card Layout Settings")]
|
||||
public bool skillCardUseLayoutElement = true; // Whether to add LayoutElement to skill cards
|
||||
public bool skillCardUseLayoutElement = true;
|
||||
public bool skillCardControlPreferredWidth = false;
|
||||
public float skillCardPreferredWidth = -1f; // Preferred width for skill cards (-1 to ignore)
|
||||
public float skillCardPreferredWidth = -1f;
|
||||
public bool skillCardControlPreferredHeight = false;
|
||||
public float skillCardPreferredHeight = -1f; // Preferred height for skill cards (-1 to ignore)
|
||||
public float skillCardPreferredHeight = -1f;
|
||||
public bool skillCardControlFlexibleWidth = false;
|
||||
public float skillCardFlexibleWidth = -1f; // Flexible width for skill cards (-1 to ignore)
|
||||
public float skillCardFlexibleWidth = 0f;
|
||||
public bool skillCardControlFlexibleHeight = false;
|
||||
public float skillCardFlexibleHeight = -1f; // Flexible height for skill cards (-1 to ignore)
|
||||
public bool skillCardControlMinWidth = false;
|
||||
public int skillCardMinWidth = -1; // Minimum width for skill cards (-1 to ignore)
|
||||
public bool skillCardControlMinHeight = false;
|
||||
public int skillCardMinHeight = -1; // Minimum height for skill cards (-1 to ignore)
|
||||
public float skillCardFlexibleHeight = 0f;
|
||||
|
||||
// Documentation text normalized.
|
||||
public AllyHero_SO currentHeroSO { get; private set; }
|
||||
[Header("Locked Skill Display")]
|
||||
public bool revealLockedSkills = true;
|
||||
|
||||
private readonly List<GameObject> spawnedObjects = new List<GameObject>();
|
||||
private readonly List<slots_skillSlots> spawnedSlots = new List<slots_skillSlots>();
|
||||
|
||||
// The skill-name Text uses BestFit, whose final (smaller) font size — and therefore
|
||||
// the card's ContentSizeFitter-driven width — only settles a frame or two AFTER the
|
||||
// objects are built. FlowLayoutGroup measures card widths during that unsettled window
|
||||
// and sees the widest possible size, so it wraps every card onto its own row. On rapid
|
||||
// hero switches the rebuild happens before the previous BestFit pass converged, which is
|
||||
// why the bug only shows up when switching quickly. This coroutine waits for BestFit to
|
||||
// converge, then forces one authoritative bottom-up layout rebuild so wrapping is
|
||||
// recomputed from the final widths. Guarded so a newer switch cancels the pending reflow.
|
||||
private Coroutine reflowCoroutine;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance == null) Instance = this;
|
||||
else Destroy(gameObject);
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Clear existing instantiated skill cards
|
||||
public void Clear()
|
||||
{
|
||||
if (contentParent == null) return;
|
||||
for (int i = contentParent.childCount - 1; i >= 0; i--)
|
||||
for (int i = spawnedObjects.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var c = contentParent.GetChild(i);
|
||||
GameObject go = spawnedObjects[i];
|
||||
if (go == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying) DestroyImmediate(c.gameObject);
|
||||
else Destroy(c.gameObject);
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
DestroyImmediate(go);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(go);
|
||||
}
|
||||
#else
|
||||
Destroy(c.gameObject);
|
||||
Destroy(go);
|
||||
#endif
|
||||
}
|
||||
|
||||
spawnedObjects.Clear();
|
||||
spawnedSlots.Clear();
|
||||
currentHeroSO = null;
|
||||
}
|
||||
|
||||
// Documentation text normalized.
|
||||
private static AllyHero_SO[] _cachedAllyHeroSOs;
|
||||
|
||||
// Load skill groups for a given hero SO id and instantiate skill card prefabs
|
||||
public void LoadSkillsForHero(int heroId)
|
||||
{
|
||||
Debug.Log($"loadSkillsSelect.LoadSkillsForHero called with heroId={heroId}");
|
||||
LoadSkillsForHero(FindHeroById(heroId));
|
||||
}
|
||||
|
||||
public void LoadSkillsForHero(AllyHero_SO hero)
|
||||
{
|
||||
Clear();
|
||||
if (skillCardPrefab == null)
|
||||
currentHeroSO = hero;
|
||||
|
||||
if (hero == null || skillCardPrefab == null || contentParent == null)
|
||||
{
|
||||
Debug.LogWarning("loadSkillsSelect: skillCardPrefab is not assigned in Inspector");
|
||||
return;
|
||||
}
|
||||
if (contentParent == null)
|
||||
{
|
||||
Debug.LogWarning("loadSkillsSelect: contentParent is not assigned in Inspector");
|
||||
return;
|
||||
}
|
||||
if (heroId <= 0)
|
||||
{
|
||||
Debug.LogWarning("loadSkillsSelect: invalid heroId");
|
||||
return;
|
||||
}
|
||||
|
||||
AllyHero_SO found = null;
|
||||
try
|
||||
List<SkillGroup> groupedSkills = hero.skillGroups == null
|
||||
? new List<SkillGroup>()
|
||||
: hero.skillGroups.Where(group => group != null).ToList();
|
||||
|
||||
HashSet<int> equippedIds = new HashSet<int>((hero.equippedSkillGroupIDs ?? new int[0]).Where(id => id != 0));
|
||||
|
||||
// Group skills by columnID and render one column container (with an optional
|
||||
// header) per distinct columnID, in ascending column order.
|
||||
Dictionary<int, List<SkillGroup>> groupsByColumn = new Dictionary<int, List<SkillGroup>>();
|
||||
foreach (SkillGroup group in groupedSkills)
|
||||
{
|
||||
if (_cachedAllyHeroSOs == null || _cachedAllyHeroSOs.Length == 0)
|
||||
if (!groupsByColumn.TryGetValue(group.columnID, out List<SkillGroup> bucket))
|
||||
{
|
||||
_cachedAllyHeroSOs = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
bucket = new List<SkillGroup>();
|
||||
groupsByColumn[group.columnID] = bucket;
|
||||
}
|
||||
foreach (var a in _cachedAllyHeroSOs)
|
||||
{
|
||||
if (a != null && a.ally_heroID == heroId) { found = a; break; }
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogError($"loadSkillsSelect: Resources.LoadAll threw: {ex}");
|
||||
bucket.Add(group);
|
||||
}
|
||||
|
||||
if (found == null)
|
||||
{
|
||||
Debug.LogWarning($"loadSkillsSelect: could not find AllyHero_SO for id={heroId}. Ensure the SO is in a Resources folder.");
|
||||
return;
|
||||
}
|
||||
if (found.skillGroups == null || found.skillGroups.Length == 0)
|
||||
{
|
||||
Debug.LogWarning($"loadSkillsSelect: found SO for id={heroId} but no skillGroups defined");
|
||||
return;
|
||||
}
|
||||
|
||||
found.LoadEquippedSkillsFromLocal();
|
||||
currentHeroSO = found;
|
||||
bool revealLockedSkills = PlayerSkillService.IsRevealLockedSkillsEnabled();
|
||||
var levelInfo = found.GetEffectiveLevelForCurrentEXP();
|
||||
int currentLevelId = levelInfo != null ? levelInfo.levelID : 0;
|
||||
var groupsByColumn = new System.Collections.Generic.Dictionary<int, System.Collections.Generic.List<SkillGroup>>();
|
||||
foreach (var g in found.skillGroups)
|
||||
{
|
||||
if (g == null) continue;
|
||||
if (!groupsByColumn.ContainsKey(g.columnID)) groupsByColumn[g.columnID] = new System.Collections.Generic.List<SkillGroup>();
|
||||
groupsByColumn[g.columnID].Add(g);
|
||||
}
|
||||
|
||||
var sortedColumns = groupsByColumn.Keys.ToList();
|
||||
List<int> sortedColumns = groupsByColumn.Keys.ToList();
|
||||
sortedColumns.Sort();
|
||||
|
||||
int created = 0;
|
||||
foreach (var colId in sortedColumns)
|
||||
foreach (int columnId in sortedColumns)
|
||||
{
|
||||
var columnGroups = groupsByColumn[colId];
|
||||
var columnName = columnGroups.Count > 0 ? columnGroups[0].columnName : $"Column {colId}";
|
||||
List<SkillGroup> columnGroups = groupsByColumn[columnId];
|
||||
string columnName = columnGroups.Count > 0 && !string.IsNullOrEmpty(columnGroups[0].columnName)
|
||||
? columnGroups[0].columnName
|
||||
: $"Column {columnId}";
|
||||
|
||||
string displayColumnName = columnName;
|
||||
if (!string.IsNullOrEmpty(columnName))
|
||||
Transform skillsContainer = BuildColumnContainer(columnId, columnName);
|
||||
|
||||
foreach (SkillGroup group in columnGroups)
|
||||
{
|
||||
char firstChar = columnName[0];
|
||||
bool isNormalStart = char.IsLetterOrDigit(firstChar) || (firstChar >= 0x4e00 && firstChar <= 0x9fa5);
|
||||
if (isNormalStart)
|
||||
{
|
||||
displayColumnName = " " + columnName;
|
||||
}
|
||||
}
|
||||
GameObject card = Instantiate(skillCardPrefab, skillsContainer);
|
||||
|
||||
if (columnHeaderPrefab != null)
|
||||
{
|
||||
var headerGo = Instantiate(columnHeaderPrefab, contentParent.transform);
|
||||
RectTransform headerRect = headerGo.GetComponent<RectTransform>();
|
||||
if (headerRect != null)
|
||||
slots_skillSlots slot = card.GetComponent<slots_skillSlots>();
|
||||
if (slot == null)
|
||||
{
|
||||
headerRect.sizeDelta = new Vector2(columnWidth, headerRect.sizeDelta.y);
|
||||
headerRect.anchoredPosition = new Vector2(columnPosX, headerRect.anchoredPosition.y);
|
||||
}
|
||||
|
||||
var heroSkillColumnText = headerGo.GetComponent<heroSkill_columnText>() ?? headerGo.GetComponentInChildren<heroSkill_columnText>(true);
|
||||
if (heroSkillColumnText != null)
|
||||
{
|
||||
heroSkillColumnText.SetColumnText(displayColumnName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var text = headerGo.GetComponent<Text>() ?? headerGo.GetComponentInChildren<Text>(true);
|
||||
if (text != null)
|
||||
{
|
||||
text.text = displayColumnName;
|
||||
}
|
||||
else
|
||||
{
|
||||
var tmpText = headerGo.GetComponent<TMPro.TextMeshProUGUI>() ?? headerGo.GetComponentInChildren<TMPro.TextMeshProUGUI>(true);
|
||||
if (tmpText != null)
|
||||
{
|
||||
tmpText.text = displayColumnName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var skillGroupContainer = new GameObject("SkillGroupContainer", typeof(RectTransform));
|
||||
skillGroupContainer.transform.SetParent(contentParent.transform, false);
|
||||
RectTransform skillGroupRect = skillGroupContainer.GetComponent<RectTransform>();
|
||||
if (skillGroupRect != null)
|
||||
{
|
||||
skillGroupRect.anchorMin = new Vector2(0f, 1f);
|
||||
skillGroupRect.anchorMax = new Vector2(1f, 1f);
|
||||
skillGroupRect.pivot = new Vector2(0.5f, 1f);
|
||||
skillGroupRect.offsetMin = new Vector2(0f, skillGroupRect.offsetMin.y);
|
||||
skillGroupRect.offsetMax = new Vector2(0f, skillGroupRect.offsetMax.y);
|
||||
skillGroupRect.anchoredPosition = new Vector2(0f, skillGroupRect.anchoredPosition.y - columnVerticalSpacing);
|
||||
}
|
||||
ConfigureSkillGroupContainer(skillGroupContainer);
|
||||
|
||||
foreach (var g in columnGroups)
|
||||
{
|
||||
if (g == null) continue;
|
||||
var go = Instantiate(skillCardPrefab, skillGroupContainer.transform);
|
||||
var s = go.GetComponent<slots_skillSlots>();
|
||||
if (s == null)
|
||||
{
|
||||
Debug.LogWarning("loadSkillsSelect: instantiated prefab missing slots_skillSlots component");
|
||||
continue;
|
||||
}
|
||||
|
||||
bool unlocked = currentLevelId >= Mathf.Clamp(g.thisSkill_levelLimit, 1, 4);
|
||||
spawnedSlots.Add(slot);
|
||||
|
||||
bool unlocked = IsSkillUnlocked(hero, group);
|
||||
bool shouldMaskSkill = !revealLockedSkills && !unlocked;
|
||||
string displayName = shouldMaskSkill ? HiddenSkillName : (g.groupName ?? string.Empty);
|
||||
string displayDescription = shouldMaskSkill ? HiddenSkillDescription : (g.skillDescriptionsText ?? string.Empty);
|
||||
|
||||
if (s.skillCard_skillName != null) s.skillCard_skillName.text = displayName;
|
||||
s.skillCard_skillID = g.skillGroupID;
|
||||
if (s.skillCard_skillIconImage != null)
|
||||
slot.heroId = hero.ally_heroID;
|
||||
slot.skillCard_skillID = group.skillGroupID;
|
||||
slot.skillCard_skillDescription = shouldMaskSkill ? HiddenSkillDescription : (group.skillDescriptionsText ?? string.Empty);
|
||||
slot.isSelected = equippedIds.Contains(group.skillGroupID);
|
||||
slot.isReadOnly = false;
|
||||
|
||||
if (slot.skillCard_skillName != null)
|
||||
{
|
||||
s.skillCard_skillIconImage.sprite = g.groupIcon;
|
||||
s.skillCard_skillIconImage.color = g.groupIcon != null ? new Color(1f,1f,1f,1f) : new Color(1f,1f,1f,0f);
|
||||
}
|
||||
s.skillCard_skillDescription = displayDescription;
|
||||
|
||||
s.heroId = heroId;
|
||||
|
||||
s.isSelected = false;
|
||||
s.ApplyState();
|
||||
|
||||
s.isReadOnly = true;
|
||||
|
||||
if (skillCardUseLayoutElement)
|
||||
{
|
||||
var le = go.GetComponent<UnityEngine.UI.LayoutElement>() ?? go.AddComponent<UnityEngine.UI.LayoutElement>();
|
||||
if (skillCardControlPreferredWidth) le.preferredWidth = skillCardPreferredWidth;
|
||||
if (skillCardControlPreferredHeight) le.preferredHeight = skillCardPreferredHeight;
|
||||
if (skillCardControlFlexibleWidth) le.flexibleWidth = skillCardFlexibleWidth;
|
||||
if (skillCardControlFlexibleHeight) le.flexibleHeight = skillCardFlexibleHeight;
|
||||
if (skillCardControlMinWidth) le.minWidth = skillCardMinWidth;
|
||||
if (skillCardControlMinHeight) le.minHeight = skillCardMinHeight;
|
||||
slot.skillCard_skillName.text = shouldMaskSkill ? HiddenSkillName : (group.groupName ?? string.Empty);
|
||||
}
|
||||
|
||||
if (s.skillCard_button != null)
|
||||
if (slot.skillCard_skillIconImage != null)
|
||||
{
|
||||
s.skillCard_button.onClick.RemoveAllListeners();
|
||||
var localS = s;
|
||||
s.skillCard_button.onClick.AddListener(() => {
|
||||
if (localS == null) return;
|
||||
localS.ToggleSelect();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("loadSkillsSelect: skillCard_button is null on instantiated prefab");
|
||||
slot.skillCard_skillIconImage.sprite = group.groupIcon;
|
||||
slot.skillCard_skillIconImage.color = group.groupIcon != null ? Color.white : new Color(1f, 1f, 1f, 0f);
|
||||
}
|
||||
|
||||
created++;
|
||||
ApplySkillCardLayout(card);
|
||||
slot.ApplyState();
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"loadSkillsSelect: instantiated {created} skill cards for heroId={heroId}");
|
||||
|
||||
UpdateSkillCardSelections();
|
||||
|
||||
// Wait for the skill-name BestFit pass to converge, then force one authoritative
|
||||
// layout rebuild so FlowLayoutGroup wraps against the final (settled) card widths.
|
||||
ScheduleReflow();
|
||||
}
|
||||
|
||||
private void ConfigureSkillGroupContainer(GameObject skillGroupContainer)
|
||||
// Cancels any pending reflow (so the newest hero switch wins) and starts a fresh one.
|
||||
private void ScheduleReflow()
|
||||
{
|
||||
if (skillGroupContainer == null)
|
||||
if (!isActiveAndEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var existingGrid = skillGroupContainer.GetComponent<GridLayoutGroup>();
|
||||
if (existingGrid != null)
|
||||
if (reflowCoroutine != null)
|
||||
{
|
||||
existingGrid.enabled = false;
|
||||
StopCoroutine(reflowCoroutine);
|
||||
reflowCoroutine = null;
|
||||
}
|
||||
|
||||
var flow = skillGroupContainer.GetComponent<FlowLayoutGroup>() ?? skillGroupContainer.AddComponent<FlowLayoutGroup>();
|
||||
flow.childAlignment = skillGroupChildAlignment;
|
||||
flow.padding = new RectOffset(skillGroupPaddingLeft, skillGroupPaddingRight, skillGroupPaddingTop, skillGroupPaddingBottom);
|
||||
reflowCoroutine = StartCoroutine(ReflowAfterBestFitSettles());
|
||||
}
|
||||
|
||||
// BestFit resolves during rendering, so its final font size (and the resulting
|
||||
// ContentSizeFitter-driven card width) is only readable a frame or two after the cards
|
||||
// are built. Wait for that, then rebuild the layout bottom-up from the settled widths.
|
||||
private IEnumerator ReflowAfterBestFitSettles()
|
||||
{
|
||||
// Two frames: one for BestFit to run, one for the CSF chain to propagate widths.
|
||||
yield return null;
|
||||
yield return null;
|
||||
|
||||
if (contentParent != null)
|
||||
{
|
||||
RebuildLayoutBottomUp(contentParent);
|
||||
}
|
||||
|
||||
reflowCoroutine = null;
|
||||
}
|
||||
|
||||
// Force each LayoutGroup to recompute, working from the deepest children up to the
|
||||
// contentParent, so FlowLayoutGroup sees the final child widths when it wraps.
|
||||
private static void RebuildLayoutBottomUp(RectTransform root)
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var groups = root.GetComponentsInChildren<LayoutGroup>(true);
|
||||
// Children come before parents in reverse of the hierarchy walk; rebuild deepest first.
|
||||
for (int i = groups.Length - 1; i >= 0; i--)
|
||||
{
|
||||
LayoutGroup group = groups[i];
|
||||
if (group != null)
|
||||
{
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform)group.transform);
|
||||
}
|
||||
}
|
||||
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(root);
|
||||
}
|
||||
|
||||
// Builds one column container (VerticalLayoutGroup) parented to contentParent,
|
||||
// instantiates the optional column header into it, then returns the horizontal
|
||||
// skills container that skill cards should be parented under.
|
||||
private Transform BuildColumnContainer(int columnId, string columnName)
|
||||
{
|
||||
GameObject columnContainer = new GameObject($"Column_{columnId}", typeof(RectTransform));
|
||||
columnContainer.transform.SetParent(contentParent, false);
|
||||
spawnedObjects.Add(columnContainer);
|
||||
|
||||
VerticalLayoutGroup vlg = columnContainer.AddComponent<VerticalLayoutGroup>();
|
||||
vlg.spacing = columnVerticalSpacing;
|
||||
vlg.childAlignment = TextAnchor.UpperLeft;
|
||||
// Do NOT let the VLG drive/override child widths. The SkillsContainer's width is
|
||||
// set explicitly below so FlowLayoutGroup has a definite, reliable width to wrap
|
||||
// against. If the VLG controlled width, that width would depend on contentParent
|
||||
// having a layout group that honors LayoutElement.preferredWidth — when it does
|
||||
// not, the container collapses to ~0 and every card wraps onto its own row.
|
||||
vlg.childControlWidth = false;
|
||||
vlg.childControlHeight = false;
|
||||
vlg.childForceExpandWidth = false;
|
||||
vlg.childForceExpandHeight = false;
|
||||
|
||||
// Resolve the width the cards get to flow across. Prefer the real available
|
||||
// width of contentParent (its own width minus its layout padding) so cards only
|
||||
// wrap when they genuinely run out of horizontal space. columnWidth is only a
|
||||
// fallback for when contentParent has no definite width yet — using it directly
|
||||
// is what caused every card to wrap onto its own row (it is far narrower than a
|
||||
// single card).
|
||||
float innerWidth = ResolveFlowWidth();
|
||||
|
||||
LayoutElement columnLayout = columnContainer.AddComponent<LayoutElement>();
|
||||
columnLayout.preferredWidth = innerWidth;
|
||||
RectTransform columnRect = (RectTransform)columnContainer.transform;
|
||||
columnRect.anchorMin = new Vector2(0f, 1f);
|
||||
columnRect.anchorMax = new Vector2(0f, 1f);
|
||||
columnRect.pivot = new Vector2(0f, 1f);
|
||||
columnRect.sizeDelta = new Vector2(innerWidth, columnRect.sizeDelta.y);
|
||||
|
||||
if (columnHeaderPrefab != null)
|
||||
{
|
||||
GameObject header = Instantiate(columnHeaderPrefab, columnContainer.transform);
|
||||
ApplyColumnHeaderText(header, columnName);
|
||||
}
|
||||
|
||||
GameObject skillsContainer = new GameObject("SkillsContainer", typeof(RectTransform));
|
||||
skillsContainer.transform.SetParent(columnContainer.transform, false);
|
||||
|
||||
// Give the SkillsContainer the same explicit width as the column so
|
||||
// FlowLayoutGroup reads a definite width directly from its own RectTransform
|
||||
// (not relying on the parent-width fallback). Anchored top-left so the width is
|
||||
// fixed rather than stretched by the parent.
|
||||
RectTransform skillsRect = (RectTransform)skillsContainer.transform;
|
||||
skillsRect.anchorMin = new Vector2(0f, 1f);
|
||||
skillsRect.anchorMax = new Vector2(0f, 1f);
|
||||
skillsRect.pivot = new Vector2(0f, 1f);
|
||||
skillsRect.sizeDelta = new Vector2(innerWidth, skillsRect.sizeDelta.y);
|
||||
|
||||
// FlowLayoutGroup measures each card's width and wraps to a new row when the
|
||||
// current row exceeds the container's available width. innerWidth (above) gives
|
||||
// it a definite width to wrap against.
|
||||
FlowLayoutGroup flow = skillsContainer.AddComponent<FlowLayoutGroup>();
|
||||
flow.SpacingX = skillGroupSpacing.x;
|
||||
flow.SpacingY = skillGroupSpacing.y;
|
||||
flow.childAlignment = skillGroupChildAlignment;
|
||||
flow.ExpandChildHeight = skillGroupExpandChildHeight;
|
||||
flow.ForcedChildHeight = skillGroupForcedChildHeight;
|
||||
flow.padding = new RectOffset(skillGroupPaddingLeft, skillGroupPaddingRight, skillGroupPaddingTop, skillGroupPaddingBottom);
|
||||
|
||||
var fitter = skillGroupContainer.GetComponent<ContentSizeFitter>() ?? skillGroupContainer.AddComponent<ContentSizeFitter>();
|
||||
fitter.horizontalFit = skillGroupHorizontalFit;
|
||||
// Only fit vertically: FlowLayoutGroup reports the wrapped height as its
|
||||
// preferred height. Horizontal must stay unconstrained so the width is driven
|
||||
// by the column (a PreferredSize horizontal fit would defeat wrapping).
|
||||
ContentSizeFitter fitter = skillsContainer.AddComponent<ContentSizeFitter>();
|
||||
fitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
|
||||
fitter.verticalFit = skillGroupVerticalFit;
|
||||
|
||||
return skillsContainer.transform;
|
||||
}
|
||||
|
||||
private static void ApplyColumnHeaderText(GameObject header, string columnName)
|
||||
{
|
||||
if (header == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
heroSkill_columnText columnTextBinder = header.GetComponent<heroSkill_columnText>();
|
||||
if (columnTextBinder != null)
|
||||
{
|
||||
columnTextBinder.SetColumnText(columnName);
|
||||
return;
|
||||
}
|
||||
|
||||
Text uiText = header.GetComponent<Text>();
|
||||
if (uiText == null)
|
||||
{
|
||||
uiText = header.GetComponentInChildren<Text>(true);
|
||||
}
|
||||
|
||||
if (uiText != null)
|
||||
{
|
||||
uiText.text = columnName;
|
||||
return;
|
||||
}
|
||||
|
||||
TMPro.TMP_Text tmpText = header.GetComponent<TMPro.TMP_Text>();
|
||||
if (tmpText == null)
|
||||
{
|
||||
tmpText = header.GetComponentInChildren<TMPro.TMP_Text>(true);
|
||||
}
|
||||
|
||||
if (tmpText != null)
|
||||
{
|
||||
tmpText.text = columnName;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the width that skill cards flow across before wrapping. Uses the real
|
||||
// rendered width of contentParent minus its layout padding, so a card only wraps
|
||||
// when it genuinely runs out of horizontal room. Falls back to columnWidth (then a
|
||||
// hard 200) only when contentParent has no definite width resolved yet.
|
||||
private float ResolveFlowWidth()
|
||||
{
|
||||
RectTransform parentRect = contentParent;
|
||||
if (parentRect != null)
|
||||
{
|
||||
float width = parentRect.rect.width;
|
||||
|
||||
HorizontalOrVerticalLayoutGroup hvlg = parentRect.GetComponent<HorizontalOrVerticalLayoutGroup>();
|
||||
if (hvlg != null)
|
||||
{
|
||||
width -= hvlg.padding.horizontal;
|
||||
}
|
||||
|
||||
if (width > 1f)
|
||||
{
|
||||
return width;
|
||||
}
|
||||
}
|
||||
|
||||
if (columnWidth > 0f)
|
||||
{
|
||||
return columnWidth;
|
||||
}
|
||||
|
||||
return 200f;
|
||||
}
|
||||
|
||||
private void ApplySkillCardLayout(GameObject card)
|
||||
{
|
||||
if (!skillCardUseLayoutElement)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LayoutElement le = card.GetComponent<LayoutElement>() ?? card.AddComponent<LayoutElement>();
|
||||
if (skillCardControlPreferredWidth)
|
||||
{
|
||||
le.preferredWidth = skillCardPreferredWidth;
|
||||
}
|
||||
if (skillCardControlPreferredHeight)
|
||||
{
|
||||
le.preferredHeight = skillCardPreferredHeight;
|
||||
}
|
||||
else if (skillGroupExpandChildHeight && skillGroupForcedChildHeight > 0f)
|
||||
{
|
||||
le.preferredHeight = skillGroupForcedChildHeight;
|
||||
}
|
||||
if (skillCardControlFlexibleWidth)
|
||||
{
|
||||
le.flexibleWidth = skillCardFlexibleWidth;
|
||||
}
|
||||
if (skillCardControlFlexibleHeight)
|
||||
{
|
||||
le.flexibleHeight = skillCardFlexibleHeight;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSkillCardSelections()
|
||||
{
|
||||
if (currentHeroSO == null || contentParent == null) return;
|
||||
|
||||
var equippedIds = currentHeroSO.equippedSkillGroupIDs ?? new int[0];
|
||||
var equipped = new System.Collections.Generic.HashSet<int>(equippedIds);
|
||||
|
||||
UnityEngine.UI.LayoutRebuilder.ForceRebuildLayoutImmediate(contentParent);
|
||||
|
||||
var slots = contentParent.GetComponentsInChildren<slots_skillSlots>(true);
|
||||
if (slots == null || slots.Length == 0) return;
|
||||
|
||||
foreach (var slot in slots)
|
||||
if (currentHeroSO == null)
|
||||
{
|
||||
if (slot == null) continue;
|
||||
slot.isSelected = equipped.Contains(slot.skillCard_skillID);
|
||||
return;
|
||||
}
|
||||
|
||||
HashSet<int> equippedIds = new HashSet<int>((currentHeroSO.equippedSkillGroupIDs ?? new int[0]).Where(id => id != 0));
|
||||
for (int i = 0; i < spawnedSlots.Count; i++)
|
||||
{
|
||||
slots_skillSlots slot = spawnedSlots[i];
|
||||
if (slot == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
slot.heroId = currentHeroSO.ally_heroID;
|
||||
slot.isSelected = equippedIds.Contains(slot.skillCard_skillID);
|
||||
slot.ApplyState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsSkillUnlocked(AllyHero_SO hero, SkillGroup group)
|
||||
{
|
||||
if (hero == null || group == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int currentLevelId = hero.GetEffectiveLevelForCurrentEXP()?.levelID ?? 0;
|
||||
int requiredLevelId = Mathf.Clamp(group.thisSkill_levelLimit, 1, 4);
|
||||
return currentLevelId >= requiredLevelId;
|
||||
}
|
||||
|
||||
private static AllyHero_SO FindHeroById(int heroId)
|
||||
{
|
||||
if (heroId <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
AllyHero_SO[] allHeroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
for (int i = 0; i < allHeroes.Length; i++)
|
||||
{
|
||||
AllyHero_SO hero = allHeroes[i];
|
||||
if (hero != null && hero.ally_heroID == heroId)
|
||||
{
|
||||
return hero;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+86
-71
@@ -1,53 +1,80 @@
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
public class load_skill_detail_prefab_inHeroDetailPrefab : MonoBehaviour
|
||||
{
|
||||
private const string EmptySlotNameDefault = "\u7A7A\u6280\u80FD\u69FD\u4F4D";
|
||||
private const string EmptySlotDescriptionDefault = "\u8BE5\u69FD\u4F4D\u53EF\u7528\uFF0C\u4F46\u5F53\u524D\u672A\u88C5\u914D\u4EFB\u4F55\u6280\u80FD\u3002";
|
||||
private const string LockedSlotNameDefault = "\u672A\u89E3\u9501\u69FD\u4F4D";
|
||||
private const string LockedSlotDescriptionDefault = "\u9700\u8981\u63D0\u5347\u89D2\u8272\u7B49\u7EA7\u540E\u89E3\u9501\u8BE5\u69FD\u4F4D\u3002";
|
||||
|
||||
public static load_skill_detail_prefab_inHeroDetailPrefab Instance { get; private set; }
|
||||
|
||||
[Header("Prefab / UI")]
|
||||
public GameObject skillDetailPrefab; // prefab must have skill_detail_prefab_inHeroDetail component
|
||||
public RectTransform contentParent; // container where skill detail prefabs will be instantiated (e.g., Scroll View Content)
|
||||
public GameObject skillDetailPrefab;
|
||||
public RectTransform contentParent;
|
||||
|
||||
[Header("Fallback Texts")]
|
||||
public string emptySlotName = EmptySlotNameDefault;
|
||||
public string emptySlotDescription = EmptySlotDescriptionDefault;
|
||||
public string lockedSlotName = LockedSlotNameDefault;
|
||||
public string lockedSlotDescription = LockedSlotDescriptionDefault;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance == null) Instance = this;
|
||||
else Destroy(gameObject);
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear existing instantiated skill details
|
||||
public void Clear()
|
||||
{
|
||||
if (contentParent == null) return;
|
||||
if (contentParent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = contentParent.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
var c = contentParent.GetChild(i);
|
||||
Transform child = contentParent.GetChild(i);
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying) DestroyImmediate(c.gameObject);
|
||||
else Destroy(c.gameObject);
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
DestroyImmediate(child.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
#else
|
||||
Destroy(c.gameObject);
|
||||
Destroy(child.gameObject);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Load equipped skill details for a given hero SO id and instantiate skill detail prefabs
|
||||
public void LoadSkillDetails(int heroId)
|
||||
{
|
||||
Debug.Log($"load_skill_detail_prefab_inHeroDetailPrefab.LoadSkillDetails called with heroId={heroId}");
|
||||
Clear();
|
||||
|
||||
if (skillDetailPrefab == null)
|
||||
{
|
||||
Debug.LogWarning("load_skill_detail_prefab_inHeroDetailPrefab: skillDetailPrefab is not assigned in Inspector");
|
||||
return;
|
||||
}
|
||||
|
||||
if (contentParent == null)
|
||||
{
|
||||
Debug.LogWarning("load_skill_detail_prefab_inHeroDetailPrefab: contentParent is not assigned in Inspector");
|
||||
return;
|
||||
}
|
||||
|
||||
if (heroId <= 0)
|
||||
{
|
||||
Debug.LogWarning("load_skill_detail_prefab_inHeroDetailPrefab: invalid heroId");
|
||||
@@ -57,10 +84,14 @@ public class load_skill_detail_prefab_inHeroDetailPrefab : MonoBehaviour
|
||||
AllyHero_SO found = null;
|
||||
try
|
||||
{
|
||||
var arr = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
foreach (var a in arr)
|
||||
var allHeroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
foreach (var hero in allHeroes)
|
||||
{
|
||||
if (a != null && a.ally_heroID == heroId) { found = a; break; }
|
||||
if (hero != null && hero.ally_heroID == heroId)
|
||||
{
|
||||
found = hero;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
@@ -74,18 +105,13 @@ public class load_skill_detail_prefab_inHeroDetailPrefab : MonoBehaviour
|
||||
return;
|
||||
}
|
||||
|
||||
// Get equipped skill group IDs (filter out 0)
|
||||
var equippedIds = (found.equippedSkillGroupIDs ?? new int[0]).Where(id => id != 0).ToList();
|
||||
|
||||
// Get current level info
|
||||
var levelInfo = found.GetEffectiveLevelForCurrentEXP();
|
||||
int currentMaxSlots = levelInfo?.skill_slot_limited ?? 0;
|
||||
|
||||
// Get max slots from highest level
|
||||
int maxSlotsOverall = 0;
|
||||
if (found.levelStats != null && found.levelStats.Count > 0)
|
||||
{
|
||||
// Assume levelStats is sorted by requiredEXP ascending, so last is highest
|
||||
maxSlotsOverall = found.levelStats[found.levelStats.Count - 1].skill_slot_limited;
|
||||
}
|
||||
|
||||
@@ -94,7 +120,6 @@ public class load_skill_detail_prefab_inHeroDetailPrefab : MonoBehaviour
|
||||
{
|
||||
if (i < equippedIds.Count)
|
||||
{
|
||||
// Instantiate equipped skill
|
||||
int skillId = equippedIds[i];
|
||||
SkillGroup group = found.GetSkillGroupByID(skillId);
|
||||
if (group == null)
|
||||
@@ -111,74 +136,64 @@ public class load_skill_detail_prefab_inHeroDetailPrefab : MonoBehaviour
|
||||
continue;
|
||||
}
|
||||
|
||||
// Populate fields for equipped skill
|
||||
if (detail.skill_info_image != null)
|
||||
{
|
||||
detail.skill_info_image.sprite = group.groupIcon;
|
||||
detail.skill_info_image.color = group.groupIcon != null ? new Color(1f, 1f, 1f, 1f) : new Color(1f, 1f, 1f, 0f);
|
||||
detail.skill_info_image.color = group.groupIcon != null ? Color.white : new Color(1f, 1f, 1f, 0f);
|
||||
}
|
||||
|
||||
if (detail.skill_info_name != null)
|
||||
{
|
||||
detail.skill_info_name.text = group.groupName ?? string.Empty;
|
||||
}
|
||||
|
||||
if (detail.skill_info_description != null)
|
||||
{
|
||||
detail.skill_info_description.text = group.skillDescriptionsText ?? string.Empty;
|
||||
}
|
||||
|
||||
created++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
var emptyGo = Instantiate(skillDetailPrefab, contentParent);
|
||||
var detailEmpty = emptyGo.GetComponent<skill_detail_prefab_inHeroDetail>();
|
||||
if (detailEmpty == null)
|
||||
{
|
||||
// Instantiate empty slot
|
||||
var go = Instantiate(skillDetailPrefab, contentParent);
|
||||
var detail = go.GetComponent<skill_detail_prefab_inHeroDetail>();
|
||||
if (detail == null)
|
||||
{
|
||||
Debug.LogWarning("load_skill_detail_prefab_inHeroDetailPrefab: instantiated prefab missing skill_detail_prefab_inHeroDetail component");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Populate fields for empty slot
|
||||
if (i < currentMaxSlots)
|
||||
{
|
||||
// Available slot
|
||||
if (detail.skill_info_image != null)
|
||||
{
|
||||
detail.skill_info_image.sprite = detail.skill_slot_available_image;
|
||||
detail.skill_info_image.color = detail.skill_slot_available_image != null ? Color.white : new Color(1f, 1f, 1f, 0f);
|
||||
}
|
||||
if (detail.skill_info_name != null)
|
||||
{
|
||||
detail.skill_info_name.text = "���ܲ�λ����";
|
||||
}
|
||||
if (detail.skill_info_description != null)
|
||||
{
|
||||
detail.skill_info_description.text = "���ܲ�λ���У��Ƽ�װ��һ������";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Locked slot
|
||||
if (detail.skill_info_image != null)
|
||||
{
|
||||
detail.skill_info_image.sprite = detail.skill_slot_locked_image;
|
||||
detail.skill_info_image.color = detail.skill_slot_locked_image != null ? Color.white : new Color(1f, 1f, 1f, 0f);
|
||||
}
|
||||
if (detail.skill_info_name != null)
|
||||
{
|
||||
detail.skill_info_name.text = "���ܲ�λ����";
|
||||
}
|
||||
if (detail.skill_info_description != null)
|
||||
{
|
||||
detail.skill_info_description.text = "���������Խ����²�λ";
|
||||
}
|
||||
}
|
||||
|
||||
created++;
|
||||
Debug.LogWarning("load_skill_detail_prefab_inHeroDetailPrefab: instantiated prefab missing skill_detail_prefab_inHeroDetail component");
|
||||
continue;
|
||||
}
|
||||
|
||||
bool slotUnlocked = i < currentMaxSlots;
|
||||
if (detailEmpty.skill_info_image != null)
|
||||
{
|
||||
Sprite spriteToUse = slotUnlocked ? detailEmpty.skill_slot_available_image : detailEmpty.skill_slot_locked_image;
|
||||
detailEmpty.skill_info_image.sprite = spriteToUse;
|
||||
detailEmpty.skill_info_image.color = spriteToUse != null ? Color.white : new Color(1f, 1f, 1f, 0f);
|
||||
}
|
||||
|
||||
if (detailEmpty.skill_info_name != null)
|
||||
{
|
||||
detailEmpty.skill_info_name.text = slotUnlocked
|
||||
? ResolveText(emptySlotName, EmptySlotNameDefault)
|
||||
: ResolveText(lockedSlotName, LockedSlotNameDefault);
|
||||
}
|
||||
|
||||
if (detailEmpty.skill_info_description != null)
|
||||
{
|
||||
detailEmpty.skill_info_description.text = slotUnlocked
|
||||
? ResolveText(emptySlotDescription, EmptySlotDescriptionDefault)
|
||||
: ResolveText(lockedSlotDescription, LockedSlotDescriptionDefault);
|
||||
}
|
||||
|
||||
created++;
|
||||
}
|
||||
|
||||
Debug.Log($"load_skill_detail_prefab_inHeroDetailPrefab: instantiated {created} skill detail prefabs for heroId={heroId}");
|
||||
}
|
||||
|
||||
private static string ResolveText(string value, string fallback)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value) ? fallback : value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,7 +395,7 @@ MonoBehaviour:
|
||||
skillColor_available: {r: 1, g: 1, b: 1, a: 1}
|
||||
skillColor_selected: {r: 0.5943396, g: 1, b: 0.5943396, a: 1}
|
||||
skillColor_unavailable: {r: 0.5999999, g: 0.5999999, b: 0.5999999, a: 1}
|
||||
skillColor_locked: {r: 0.81886786, g: 0.5299465, b: 0.5299465, a: 1}
|
||||
skillColor_locked: {r: 1, g: 0.6179246, b: 0.6179246, a: 1}
|
||||
isSelected: 0
|
||||
heroId: 0
|
||||
isReadOnly: 0
|
||||
|
||||
+86
-10
@@ -34,7 +34,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 100.84909, y: -25.2972}
|
||||
m_AnchoredPosition: {x: 100.84909, y: -29.1}
|
||||
m_SizeDelta: {x: 1030.2424, y: 90}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &4124427729845495542
|
||||
@@ -58,7 +58,7 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.9254902, g: 0.9254902, b: 0.9254902, a: 1}
|
||||
m_Color: {r: 0.14117648, g: 0.34117648, b: 0.7764706, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
@@ -66,7 +66,7 @@ MonoBehaviour:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_FontData:
|
||||
m_Font: {fileID: 12800000, guid: 775c674e81662c644b64550d2e8f74e0, type: 3}
|
||||
m_Font: {fileID: 12800000, guid: d254d20d93651ae448ff13d10dff30ab, type: 3}
|
||||
m_FontSize: 30
|
||||
m_FontStyle: 0
|
||||
m_BestFit: 0
|
||||
@@ -114,7 +114,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 175, y: 175}
|
||||
m_SizeDelta: {x: 180, y: 180}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &6787124985996953894
|
||||
CanvasRenderer:
|
||||
@@ -144,8 +144,8 @@ MonoBehaviour:
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: c7c2fe3c72ded9244bf6b5506cc84f41, type: 3}
|
||||
m_Type: 0
|
||||
m_Sprite: {fileID: 21300000, guid: 9d8f69bebb9e2e9489798d3336f5d2b4, type: 3}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
@@ -212,7 +212,7 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.9245283, g: 0.9245283, b: 0.9245283, a: 1}
|
||||
m_Color: {r: 0.011764706, g: 0.5176471, b: 0.99215686, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
@@ -233,6 +233,81 @@ MonoBehaviour:
|
||||
m_VerticalOverflow: 0
|
||||
m_LineSpacing: 1
|
||||
m_Text: "\u6280\u80FD\u540D / \u7A7A\u95F2\u69FD\u4F4D / \u9501\u5B9A\u69FD\u4F4D"
|
||||
--- !u!1 &5252193634107135165
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 8595639840685474323}
|
||||
- component: {fileID: 7900913233867467852}
|
||||
- component: {fileID: 607155179397191471}
|
||||
m_Layer: 0
|
||||
m_Name: btm
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &8595639840685474323
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5252193634107135165}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 7749999690041009414}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: -526.7, y: 0}
|
||||
m_SizeDelta: {x: 150, y: 150}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &7900913233867467852
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5252193634107135165}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &607155179397191471
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5252193634107135165}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.36078432, g: 0.3647059, b: 0.36078432, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &5512514823739788927
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -298,8 +373,8 @@ MonoBehaviour:
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Type: 1
|
||||
m_Sprite: {fileID: 21300000, guid: 44bccbd64c92341409553503e1fa45b4, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 0
|
||||
m_FillMethod: 4
|
||||
@@ -337,10 +412,11 @@ RectTransform:
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 1227634720023912104}
|
||||
- {fileID: 8595639840685474323}
|
||||
- {fileID: 1097740655041088334}
|
||||
- {fileID: 3402633476703254850}
|
||||
- {fileID: 8725241624818700542}
|
||||
- {fileID: 1227634720023912104}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
|
||||
@@ -49,11 +49,49 @@ public class slots_skillSlots : MonoBehaviour, IPointerClickHandler, IPointerEnt
|
||||
// Documentation text normalized.
|
||||
public bool isReadOnly = false;
|
||||
|
||||
private bool buttonListenerBound = false;
|
||||
|
||||
void Start()
|
||||
{
|
||||
BindButtonClickIfNeeded();
|
||||
ApplyState();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
BindButtonClickIfNeeded();
|
||||
}
|
||||
|
||||
private void BindButtonClickIfNeeded()
|
||||
{
|
||||
if (skillCard_button == null || buttonListenerBound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
skillCard_button.onClick.AddListener(HandleSkillButtonClick);
|
||||
buttonListenerBound = true;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (skillCard_button != null && buttonListenerBound)
|
||||
{
|
||||
skillCard_button.onClick.RemoveListener(HandleSkillButtonClick);
|
||||
buttonListenerBound = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSkillButtonClick()
|
||||
{
|
||||
if (isReadOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ToggleSelect();
|
||||
}
|
||||
|
||||
public void ApplyState()
|
||||
{
|
||||
SkillVisualState visualState = GetVisualState();
|
||||
|
||||
Reference in New Issue
Block a user