using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; /// /// Runtime UI feed for skill triggers. /// Writes to "score/Skill" in the active scene: /// - newest entry at the top /// - keeps last 5 entries /// If "score/Skill" does not exist, it will be created under "score". /// public static class SkillTriggerFeedUI { public static bool RuntimeEnabled = false; private const int MaxEntries = 5; private static readonly List s_entries = new List(MaxEntries); // Per request: use legacy UI.Text only (not TMP). private static Text s_feedText; private static int s_boundTextInstanceId; private static bool s_warnedMissingScore; public static void Push(string idolName, string skillName) { if (!RuntimeEnabled) { TryHideLegacyFeedRoot(); return; } if (string.IsNullOrWhiteSpace(idolName)) idolName = "Unknown"; if (string.IsNullOrWhiteSpace(skillName)) skillName = "UnknownSkill"; EnsureBound(); if (!HasBoundText()) return; s_entries.Insert(0, $"{idolName} - {skillName}"); if (s_entries.Count > MaxEntries) { s_entries.RemoveRange(MaxEntries, s_entries.Count - MaxEntries); } UpdateText(); } public static void Clear() { if (!RuntimeEnabled) { TryHideLegacyFeedRoot(); return; } s_entries.Clear(); UpdateText(); } private static bool HasBoundText() { if (s_feedText != null && !s_feedText) s_feedText = null; return s_feedText != null; } private static void EnsureBound() { if (HasBoundText()) return; GameObject skillRoot = GameObject.Find("score/Skill"); if (skillRoot == null) { var score = GameObject.Find("score"); if (score == null) { if (!s_warnedMissingScore) { Debug.LogWarning("[SkillTriggerFeedUI] Cannot find GameObject 'score'. Skill trigger feed UI is disabled in this scene."); s_warnedMissingScore = true; } return; } // Create a minimal "score/Skill" container if missing. skillRoot = new GameObject("Skill", typeof(RectTransform)); skillRoot.transform.SetParent(score.transform, false); var rt = skillRoot.GetComponent(); rt.anchorMin = new Vector2(0.5f, 0.5f); rt.anchorMax = new Vector2(0.5f, 0.5f); rt.pivot = new Vector2(0.5f, 0.5f); rt.anchoredPosition = new Vector2(776f, 277f); rt.sizeDelta = new Vector2(160f, 30f); } // Prefer using the existing "sumScoreText" node as an anchor (it is already positioned correctly in the scene), // but render with legacy UI.Text (Chinese glyphs require the same font as the UI, not builtin Arial). Transform feed = skillRoot.transform.Find("SkillFeedText"); RectTransform referenceRect = null; var sumScore = skillRoot.transform.Find("sumScoreText"); if (sumScore != null) referenceRect = sumScore as RectTransform; if (feed == null && sumScore != null) feed = sumScore; if (feed == null) { var go = new GameObject("SkillFeedText", typeof(RectTransform)); go.transform.SetParent(skillRoot.transform, false); feed = go.transform; } s_feedText = feed.GetComponent(); if (s_feedText == null) s_feedText = feed.gameObject.AddComponent(); if (s_feedText != null) s_feedText.enabled = true; if (feed != null && feed.gameObject != null && !feed.gameObject.activeSelf) feed.gameObject.SetActive(true); // If this node used to be TMP (e.g. "sumScoreText"), ensure TMP renderer is disabled so we only use legacy Text. try { var tmp = feed.GetComponent("TMP_Text") as Behaviour; if (tmp != null) tmp.enabled = false; } catch { } // Configure for multi-line feed (legacy Text) s_feedText.raycastTarget = false; // Use the same font as the header under score/Skill so Chinese characters can render. var headerText = skillRoot.GetComponent(); if (headerText != null && headerText.font != null) s_feedText.font = headerText.font; if (s_feedText.font == null) s_feedText.font = Resources.GetBuiltinResource("LegacyRuntime.ttf"); s_feedText.fontSize = 20; s_feedText.color = Color.black; s_feedText.alignment = TextAnchor.UpperLeft; s_feedText.horizontalOverflow = HorizontalWrapMode.Overflow; s_feedText.verticalOverflow = VerticalWrapMode.Overflow; s_feedText.text = string.Empty; var frt = feed.GetComponent(); if (frt != null) { if (referenceRect != null) { frt.anchorMin = referenceRect.anchorMin; frt.anchorMax = referenceRect.anchorMax; frt.pivot = referenceRect.pivot; frt.anchoredPosition = referenceRect.anchoredPosition; frt.sizeDelta = referenceRect.sizeDelta; } else { frt.anchorMin = new Vector2(0.5f, 0.5f); frt.anchorMax = new Vector2(0.5f, 0.5f); frt.pivot = new Vector2(0.5f, 0.5f); frt.anchoredPosition = new Vector2(49.8f, 0f); frt.sizeDelta = new Vector2(200f, 50f); } } int newId = 0; if (s_feedText != null) newId = s_feedText.GetInstanceID(); // If we re-bound (new scene/play session), clear old entries to avoid leaking previous runs. if (newId != 0 && newId != s_boundTextInstanceId) { s_boundTextInstanceId = newId; s_entries.Clear(); UpdateText(); } } private static void UpdateText() { if (!HasBoundText()) return; // Newest first. s_feedText.text = string.Join("\n", s_entries); } private static void TryHideLegacyFeedRoot() { GameObject feed = GameObject.Find("score/Skill"); if (feed != null && feed.activeSelf) feed.SetActive(false); } }