using System.Collections.Generic; using Spine.Unity; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; #if UNITY_EDITOR using UnityEditor; #endif public class eqpmtDesPrefab : MonoBehaviour { [Header("so")] public equipmentSO eSO; [Header("display")] public bool displayBasicAttributes = true; public bool displaySpecialEffects = true; public bool displayTypeSameEffects = true; public bool displayIllusionEffects = true; public bool displayMaxLevelEffects = true; [Header("btm")] public Image eqpmtBtm; public Sprite[] eqpmtBtmSprites; [Header("spine particles")] public GameObject golden_particle; public GameObject red_particle; public GameObject pink_particle; [Header("head")] public Text eqpmtName; public Text eqpmtID; public Text eqpmtLevel; public Image eqpmtIcon; public Image eqpmtPbtm; public Color[] profilebtmColor; public Text eqpmtDescription; [Header("body")] public Text eqpmtType; public Text baTitle; public Text baDetail; public GameObject baSpace; public Text saTitle; public Text saDetail; public GameObject saSpace; public Text taTitle; public Text taDetail; public GameObject taSpace; public Text iaTitle; public Text iaDetail; public GameObject iaSpace; public Text mlTitle; public Text mlDetail; public GameObject eqpSpace; public GameObject whoEquipping; public Image equipperProfile; public Text equipperName; [Header("objs")] public GameObject eqpmtSkillPrefab; public Transform sasParent; public Transform iasParent; public Transform mlsParent; [Header("es description")] public GameObject esDesPrefab; public Vector2 esDesPrefabOffset = new Vector2(20f, 30f); private RectTransform rectTransform; private Canvas rootCanvas; private Camera uiCamera; private bool ignoreCloseUntilClickReleased; private void Awake() { rectTransform = transform as RectTransform; rootCanvas = GetComponentInParent(); if (rootCanvas != null && rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay) { uiCamera = rootCanvas.worldCamera; } } public void Bind(equipmentSO so) { eSO = so; RefreshView(); } public void IgnoreCloseUntilClickReleased() { ignoreCloseUntilClickReleased = true; } private void Update() { if (eSO == null || rectTransform == null) { return; } if (ignoreCloseUntilClickReleased) { if (Input.GetMouseButton(0)) { return; } ignoreCloseUntilClickReleased = false; } if (!Input.GetMouseButtonDown(0)) { return; } if (RectTransformUtility.RectangleContainsScreenPoint(rectTransform, Input.mousePosition, uiCamera)) { return; } if (IsClickInsidePopupHierarchy(Input.mousePosition)) { return; } Destroy(gameObject); } private bool IsClickInsidePopupHierarchy(Vector2 screenPoint) { if (EventSystem.current == null) { return false; } PointerEventData pointer = new PointerEventData(EventSystem.current) { position = screenPoint }; var results = new List(); EventSystem.current.RaycastAll(pointer, results); for (int i = 0; i < results.Count; i++) { GameObject target = results[i].gameObject; if (target != null && target.transform.IsChildOf(transform)) { return true; } } return false; } private void RefreshView() { ClearSkillPrefabs(); if (eSO == null) { ApplySpineParticles(null); ApplyHeader(string.Empty, null, 0, string.Empty); ApplyBasicSection(string.Empty, false); ApplyEffectSection(saTitle, saDetail, saSpace, sasParent, string.Empty, null, false); ApplyEffectSection(taTitle, taDetail, taSpace, null, string.Empty, null, false); ApplyEffectSection(iaTitle, iaDetail, iaSpace, iasParent, string.Empty, null, false); ApplyEffectSection(mlTitle, mlDetail, null, mlsParent, string.Empty, null, false); ApplyEquipperInfo(null); return; } equipmentSO.EquipmentTierPresentation presentation = ResolveTierPresentation(eSO); string displayName = eSO.GetDisplayTierName(); if (string.IsNullOrWhiteSpace(displayName)) { displayName = eSO.name; } Sprite displaySprite = presentation != null ? presentation.equipmentSprite : null; string description = eSO.GetDisplayTierDescription(); ApplySpineParticles(eSO); ApplyHeader(displayName, displaySprite, GetQualityVisualIndex(eSO), description); ApplyEquipperInfo(eSO); string basicText = BuildBasicAttributesText(eSO); ApplyBasicSection(basicText, displayBasicAttributes && !string.IsNullOrWhiteSpace(basicText)); string specialText = BuildEffectText(eSO.specialEffects, out int saSkillId); bool hasSpecialSkill = HasSkillEffect(eSO.specialEffects); ApplyEffectSection( saTitle, saDetail, saSpace, sasParent, specialText, hasSpecialSkill && saSkillId > 0 ? new[] { saSkillId } : null, displaySpecialEffects && HasSectionContent(eSO.specialEffects, saSkillId)); string typeSameText = BuildEffectText(eSO.typeSameEffects, out _); ApplyEffectSection( taTitle, taDetail, taSpace, null, typeSameText, null, displayTypeSameEffects && HasSectionContent(eSO.typeSameEffects, 0)); string illusionText = BuildEffectText(eSO.illusionEffects, out int iaSkillId); bool isTourPreview = IsTourPreviewEquipment(eSO); if (isTourPreview) { illusionText = string.IsNullOrWhiteSpace(illusionText) ? "1个随机巡演特效" : $"{illusionText}\n1个随机巡演特效"; } bool hasIllusionSkill = iaSkillId > 0; ApplyEffectSection( iaTitle, iaDetail, iaSpace, iasParent, illusionText, hasIllusionSkill ? new[] { iaSkillId } : null, displayIllusionEffects && (isTourPreview || HasSectionContent(eSO.illusionEffects, iaSkillId))); string maxLevelText = BuildEffectText(eSO.maxLevelEffects, out _); ApplyEffectSection( mlTitle, mlDetail, null, mlsParent, maxLevelText, null, displayMaxLevelEffects && HasSectionContent(eSO.maxLevelEffects, 0)); } private void ApplySpineParticles(equipmentSO equipment) { bool showGolden = false; bool showRed = false; bool showPink = false; if (equipment != null) { int level = Mathf.Max(0, equipment.level); bool hasFinalDreamEffect = equipment.HasFinalDreamEffect(); if (level >= 20 && hasFinalDreamEffect) { showPink = true; } else if (level >= 15) { showRed = true; } else if (level >= 10) { showGolden = true; } } if (golden_particle != null) { golden_particle.SetActive(showGolden); } if (red_particle != null) { red_particle.SetActive(showRed); } if (pink_particle != null) { pink_particle.SetActive(showPink); } } private static void ConfigureSpineParticleRenderers(GameObject target) { if (target == null) { return; } SkeletonGraphic[] skeletonGraphics = target.GetComponentsInChildren(true); for (int i = 0; i < skeletonGraphics.Length; i++) { SkeletonGraphic skeletonGraphic = skeletonGraphics[i]; if (skeletonGraphic == null || skeletonGraphic.allowMultipleCanvasRenderers) { continue; } skeletonGraphic.allowMultipleCanvasRenderers = true; skeletonGraphic.Initialize(true); skeletonGraphic.SetMaterialDirty(); skeletonGraphic.SetVerticesDirty(); } } private void ApplyEquipperInfo(equipmentSO equipment) { AllyHero_SO equipper = FindEquipper(equipment); bool hasEquipper = equipper != null; if (whoEquipping != null) { whoEquipping.SetActive(hasEquipper); } if (eqpSpace != null) { eqpSpace.SetActive(hasEquipper); } if (equipperProfile != null) { equipperProfile.sprite = hasEquipper ? equipper.ally_hero_squareProfile : null; equipperProfile.color = hasEquipper && equipper.ally_hero_squareProfile != null ? Color.white : new Color(1f, 1f, 1f, 0f); } if (equipperName != null) { equipperName.text = hasEquipper ? equipper.ally_heroName : string.Empty; } } private AllyHero_SO FindEquipper(equipmentSO equipment) { if (equipment == null) { return null; } AllyHero_SO[] heroes = LoadHeroAssets(); for (int i = 0; i < heroes.Length; i++) { AllyHero_SO hero = heroes[i]; if (hero == null) { continue; } hero.LoadEquippedEquipmentFromLocal(); equipmentSO equipped = hero.GetEquippedEquipmentResolved(); if (equipped == equipment || (!string.IsNullOrWhiteSpace(equipped?.name) && equipped.name == equipment.name)) { return hero; } } return null; } private AllyHero_SO[] LoadHeroAssets() { #if UNITY_EDITOR if (!Application.isPlaying) { string[] guids = AssetDatabase.FindAssets("t:AllyHero_SO", new[] { "Assets/Resources/so/ally" }); var heroes = new List(guids.Length); for (int i = 0; i < guids.Length; i++) { string path = AssetDatabase.GUIDToAssetPath(guids[i]); AllyHero_SO hero = AssetDatabase.LoadAssetAtPath(path); if (hero != null) { heroes.Add(hero); } } return heroes.ToArray(); } #endif return RuntimeResourcesCache.LoadAllAllyHeroes(); } private equipmentSO.EquipmentTierPresentation ResolveTierPresentation(equipmentSO equipment) { if (equipment == null) { return null; } if (equipment.tierNameConfig != null && equipment.tierNameConfig.TryGetStagePresentation(equipment.skillType, equipment.GetVisualTierStageIndex(), out equipmentTierNameConfigSO.StagePresentation stage) && stage != null) { return new equipmentSO.EquipmentTierPresentation { tierName = stage.tierName, equipmentSprite = stage.equipmentSprite, tierDescription = stage.tierDescription }; } return equipment.GetVisualTierPresentation(); } private void ApplyHeader(string displayName, Sprite displaySprite, int qualityIndex, string description) { if (eqpmtName != null) { eqpmtName.text = displayName; eqpmtName.color = GetProfileColor(qualityIndex); } if (eqpmtID != null) { eqpmtID.text = BuildDisplayEquipmentId(eSO); } if (eqpmtLevel != null) { eqpmtLevel.text = eSO != null ? Mathf.Max(0, eSO.level).ToString() : string.Empty; } if (eqpmtDescription != null) { eqpmtDescription.text = description; } if (eqpmtType != null) { eqpmtType.text = eSO != null ? eSO.skillType.ToString() : string.Empty; } if (eqpmtIcon != null) { eqpmtIcon.sprite = displaySprite; eqpmtIcon.enabled = displaySprite != null; } if (eqpmtBtm != null) { eqpmtBtm.sprite = GetBottomSprite(qualityIndex); eqpmtBtm.enabled = eqpmtBtm.sprite != null; } if (eqpmtPbtm != null) { eqpmtPbtm.color = GetProfileColor(qualityIndex); } } private void ApplyBasicSection(string text, bool active) { SetGroupActive(baTitle, baDetail, baSpace, null, active); if (baDetail != null) { baDetail.supportRichText = true; baDetail.text = text; baDetail.gameObject.SetActive(active && !string.IsNullOrWhiteSpace(text)); } } private void ApplyEffectSection( Text title, Text detail, GameObject space, Transform skillParent, string text, int[] skillIds, bool active) { bool hasText = !string.IsNullOrWhiteSpace(text); bool hasSkills = skillParent != null && skillIds != null && skillIds.Length > 0; bool showGroup = active && (hasText || hasSkills); SetGroupActive(title, detail, space, null, showGroup); if (detail != null) { detail.supportRichText = true; detail.text = text; detail.gameObject.SetActive(showGroup && hasText); } bool showSkillParent = showGroup && hasSkills; if (skillParent != null) { skillParent.gameObject.SetActive(showSkillParent); } if (!showSkillParent) { return; } for (int i = 0; i < skillIds.Length; i++) { SpawnSkillPrefab(skillParent, skillIds[i]); } } private void SpawnSkillPrefab(Transform parent, int skillId) { if (eqpmtSkillPrefab == null || parent == null || skillId <= 0) { return; } GameObject instance = Instantiate(eqpmtSkillPrefab, parent); instance.name = $"eqpmtSkill_{skillId}"; SkillGroup skillGroup = EquipmentSkillGroupLibrary.ResolveSkillGroup(skillId); string displayName = skillGroup != null && !string.IsNullOrWhiteSpace(skillGroup.groupName) ? skillGroup.groupName : $"Skill {skillId}"; Sprite displayIcon = skillGroup != null ? skillGroup.groupIcon : null; esPrefab legacyItem = instance.GetComponent(); if (legacyItem != null) { legacyItem.BindSkillGroup(skillId, displayName, displayIcon, esDesPrefab, transform as RectTransform, esDesPrefabOffset); return; } eqpmtSkillPrefabItem item = instance.GetComponent(); if (item != null) { item.Bind(skillId); return; } Text[] texts = instance.GetComponentsInChildren(true); for (int i = 0; i < texts.Length; i++) { if (texts[i] != null) { texts[i].text = displayName; break; } } } private void ClearSkillPrefabs() { ClearChildren(sasParent); ClearChildren(iasParent); ClearChildren(mlsParent); } private static void ClearChildren(Transform parent) { if (parent == null) { return; } for (int i = parent.childCount - 1; i >= 0; i--) { Transform child = parent.GetChild(i); if (Application.isPlaying) { Destroy(child.gameObject); } else { DestroyImmediate(child.gameObject); } } } private static void SetGroupActive(Text title, Text detail, GameObject space, Transform skillParent, bool active) { if (title != null) { title.gameObject.SetActive(active); } if (detail != null) { detail.gameObject.SetActive(active); } if (space != null) { space.SetActive(active); } if (skillParent != null) { skillParent.gameObject.SetActive(active); } } private bool HasSectionContent(equipmentSO.EquipmentSpecialEffect[] effects, int skillId) { if (skillId > 0) { return true; } if (effects == null) { return false; } for (int i = 0; i < effects.Length; i++) { if (effects[i] != null && effects[i].effectType != equipmentSO.EquipmentSpecialEffectType.Skill && !Mathf.Approximately(effects[i].value, 0f)) { return true; } } return false; } private static bool HasSkillEffect(equipmentSO.EquipmentSpecialEffect[] effects) { if (effects == null) { return false; } for (int i = 0; i < effects.Length; i++) { if (effects[i] != null && effects[i].effectType == equipmentSO.EquipmentSpecialEffectType.Skill) { return true; } } return false; } private string BuildBasicAttributesText(equipmentSO equipment) { if (equipment == null) { return string.Empty; } var lines = new List(); AppendBasicLine(lines, "最大生命", equipment.maxHp, equipmentSO.EquipmentSpecialEffectType.MaxHp); AppendBasicLine(lines, "攻击力", equipment.attack, equipmentSO.EquipmentSpecialEffectType.Attack); AppendBasicLine(lines, "最大法力", equipment.maxMana, equipmentSO.EquipmentSpecialEffectType.MaxMana); AppendBasicLine(lines, "伤害减免", equipment.damageResistance, equipmentSO.EquipmentSpecialEffectType.DamageResistance); AppendBasicLine(lines, "得分效率", equipment.scoreEfficiency, equipmentSO.EquipmentSpecialEffectType.ScoreEfficiency); return string.Join("\n", lines); } private void AppendBasicLine(List lines, string label, equipmentSO.EquipmentStatTuning tuning, equipmentSO.EquipmentSpecialEffectType effectType) { if (lines == null || tuning == null) { return; } float baseValue = tuning.basicGain; if (Mathf.Approximately(baseValue, 0f)) { return; } float levelGain = eSO != null ? Mathf.Max(0, eSO.level) * tuning.cultivationInterval : 0f; float finalDreamGain = GetMaxLevelBoost(effectType); float total = baseValue + levelGain + finalDreamGain; if (eSO == null || (Mathf.Max(0, eSO.level) <= 0 && Mathf.Approximately(finalDreamGain, 0f))) { lines.Add($"{label}{FormatTotalPercent(baseValue + finalDreamGain)}"); return; } string componentText = $"{FormatComponentPercent(baseValue)}"; if (!Mathf.Approximately(levelGain, 0f)) { componentText += $"+{FormatComponentPercent(levelGain)}"; } if (!Mathf.Approximately(finalDreamGain, 0f)) { componentText += $"+{FormatComponentPercent(finalDreamGain)}"; } lines.Add($"{label}{FormatTotalPercent(total)} ({componentText})"); } private string BuildEffectText(equipmentSO.EquipmentSpecialEffect[] effects, out int skillId) { skillId = 0; if (effects == eSO?.specialEffects) { skillId = eSO != null ? eSO.sa_skillID : 0; } else if (effects == eSO?.illusionEffects) { skillId = eSO != null ? eSO.ia_skillID : 0; } if (effects == null || effects.Length == 0) { return string.Empty; } bool isTypeSameEffectSection = effects == eSO?.typeSameEffects; bool isMaxLevelEffectSection = effects == eSO?.maxLevelEffects; var lines = new List(); for (int i = 0; i < effects.Length; i++) { equipmentSO.EquipmentSpecialEffect effect = effects[i]; if (effect == null) { continue; } if (effect.effectType == equipmentSO.EquipmentSpecialEffectType.Skill) { continue; } if (Mathf.Approximately(effect.value, 0f)) { continue; } string valueText = (isTypeSameEffectSection || isMaxLevelEffectSection) ? FormatSignedPercent(effect.value) : FormatSignedValue(effect.value); lines.Add($"{GetEffectDisplayName(effect.effectType)}{valueText}"); } return string.Join("\n", lines); } private float GetMaxLevelBoost(equipmentSO.EquipmentSpecialEffectType effectType) { if (eSO == null || eSO.maxLevelEffects == null) { return 0f; } for (int i = 0; i < eSO.maxLevelEffects.Length; i++) { equipmentSO.EquipmentSpecialEffect effect = eSO.maxLevelEffects[i]; if (effect == null || effect.effectType != effectType) { continue; } return effect.value; } return 0f; } private Sprite GetBottomSprite(int qualityIndex) { if (eqpmtBtmSprites == null || eqpmtBtmSprites.Length == 0) { return null; } int safeIndex = Mathf.Clamp(qualityIndex, 0, eqpmtBtmSprites.Length - 1); return eqpmtBtmSprites[safeIndex]; } private Color GetProfileColor(int qualityIndex) { if (profilebtmColor == null || profilebtmColor.Length == 0) { return Color.white; } int safeIndex = Mathf.Clamp(qualityIndex, 0, profilebtmColor.Length - 1); return profilebtmColor[safeIndex]; } private static int GetQualityVisualIndex(equipmentSO equipment) { if (equipment == null) { return 0; } return equipment.GetVisualQualityColorIndex(); } private static string GetEffectDisplayName(equipmentSO.EquipmentSpecialEffectType effectType) { switch (effectType) { case equipmentSO.EquipmentSpecialEffectType.MaxHp: return "最大生命"; case equipmentSO.EquipmentSpecialEffectType.Attack: return "攻击力"; case equipmentSO.EquipmentSpecialEffectType.MaxMana: return "最大法力"; case equipmentSO.EquipmentSpecialEffectType.DamageResistance: return "伤害减免"; case equipmentSO.EquipmentSpecialEffectType.ScoreEfficiency: return "得分效率"; case equipmentSO.EquipmentSpecialEffectType.HitManaRecovery: return "hit法力恢复"; case equipmentSO.EquipmentSpecialEffectType.HitDamageMultiplier: return "hit伤害倍率"; case equipmentSO.EquipmentSpecialEffectType.HpLoseBase: return "基本生命损失"; case equipmentSO.EquipmentSpecialEffectType.Skill: return "技能"; default: return effectType.ToString(); } } private static string FormatTotalPercent(float value) { float percent = value * 100f; return $"{(percent >= 0f ? "+" : string.Empty)}{percent:0.0}%"; } private static string FormatComponentPercent(float value) { float percent = value * 100f; return $"{percent:0.0}%"; } private static string FormatSignedValue(float value) { return $"{(value >= 0f ? "+" : string.Empty)}{value:0.####}"; } private static string FormatSignedPercent(float value) { float percent = value * 100f; return $"{(percent >= 0f ? "+" : string.Empty)}{percent:0.##}%"; } private static string BuildDisplayEquipmentId(equipmentSO equipment) { if (equipment == null || string.IsNullOrWhiteSpace(equipment.name)) { return string.Empty; } string raw = equipment.name; bool isPreview = raw.Contains("(Clone)") || raw.Contains("(TourPreview)"); raw = raw.Replace("(Clone)", string.Empty).Replace("(TourPreview)", string.Empty).Trim(); if (raw.StartsWith("type")) { raw = raw.Substring(4); } return isPreview ? $"{raw}(Preview)" : raw; } private static bool IsTourPreviewEquipment(equipmentSO equipment) { return equipment != null && !string.IsNullOrWhiteSpace(equipment.name) && equipment.name.Contains("(TourPreview)"); } }