Files
bansonic_beta_main/Assets/scripts/gamePlay_gameplay/SkillTriggerFeedUI.cs
T

279 lines
9.2 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 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".
/// </summary>
public static class SkillTriggerFeedUI
{
public static bool RuntimeEnabled = false;
private const int MaxEntries = 5;
private static readonly List<string> s_entries = new List<string>(MaxEntries);
// Colors
public static Color CharacterColor = new Color(1f, 0.5f, 0f); // Orange
public static Color SkillColor = Color.cyan; // Cyan/Blue
public static Color MissColor = new Color(1f, 0.27f, 0f); // Orange Red
public static Color DeathColor = Color.black; // Black
public static Color EnemyNameColor = Color.red;
public static Color EnemySpawnColor = Color.yellow;
// Per request: use legacy UI.Text only (not TMP).
private static Text s_feedText;
// Allow manual assignment to bypass GameObject.Find
public static void SetTargetText(Text textComponent)
{
s_feedText = textComponent;
// Automatically enable when a target is set manually
RuntimeEnabled = (textComponent != null);
if (RuntimeEnabled)
{
s_feedText.supportRichText = true;
}
UpdateText();
}
private static int s_boundTextInstanceId;
private static bool s_warnedMissingScore;
private static string ColorToHex(Color color)
{
return ColorUtility.ToHtmlStringRGB(color);
}
public static void Push(string idolName, string skillName)
{
if (!RuntimeEnabled)
{
TryHideLegacyFeedRoot();
return;
}
if (string.IsNullOrWhiteSpace(idolName)) idolName = "Unknown";
if (string.IsNullOrWhiteSpace(skillName)) skillName = "UnknownSkill";
if (!HasBoundText()) return;
string entry = $"<color=#{ColorToHex(CharacterColor)}>{idolName}</color> - <color=#{ColorToHex(SkillColor)}>{skillName}</color>";
AddEntry(entry);
}
public static void PushMiss(string idolName)
{
if (!RuntimeEnabled)
{
TryHideLegacyFeedRoot();
return;
}
if (string.IsNullOrWhiteSpace(idolName)) idolName = "Unknown";
if (!HasBoundText()) return;
string entry = $"<color=#{ColorToHex(CharacterColor)}>{idolName}</color> - <color=#{ColorToHex(MissColor)}>Miss</color>";
AddEntry(entry);
}
public static void PushDeath(string idolName)
{
if (!RuntimeEnabled)
{
TryHideLegacyFeedRoot();
return;
}
if (string.IsNullOrWhiteSpace(idolName)) idolName = "Unknown";
if (!HasBoundText()) return;
string entry = $"<color=#{ColorToHex(CharacterColor)}>{idolName}</color> - <color=#{ColorToHex(DeathColor)}>死亡</color>";
AddEntry(entry);
}
public static void PushEnemySpawn(string enemyName)
{
if (!RuntimeEnabled)
{
TryHideLegacyFeedRoot();
return;
}
if (string.IsNullOrWhiteSpace(enemyName)) enemyName = "Unknown Enemy";
if (!HasBoundText()) return;
string entry = $"<color=#{ColorToHex(EnemyNameColor)}>{enemyName}</color> - <color=#{ColorToHex(EnemySpawnColor)}>登场</color>";
AddEntry(entry);
}
public static void PushEnemyDeath(string enemyName)
{
if (!RuntimeEnabled)
{
TryHideLegacyFeedRoot();
return;
}
if (string.IsNullOrWhiteSpace(enemyName)) enemyName = "Unknown Enemy";
if (!HasBoundText()) return;
string entry = $"<color=#{ColorToHex(EnemyNameColor)}>{enemyName}</color> - <color=#{ColorToHex(DeathColor)}>死亡</color>";
AddEntry(entry);
}
private static void AddEntry(string formattedEntry)
{
s_entries.Insert(0, formattedEntry);
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 = SceneObjectLookupCache.Find("score/Skill");
if (skillRoot == null)
{
var score = SceneObjectLookupCache.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<RectTransform>();
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<Text>();
if (s_feedText == null) s_feedText = feed.gameObject.AddComponent<Text>();
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<Text>();
if (headerText != null && headerText.font != null)
s_feedText.font = headerText.font;
if (s_feedText.font == null)
s_feedText.font = Resources.GetBuiltinResource<Font>("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<RectTransform>();
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 = SceneObjectLookupCache.Find("score/Skill");
if (feed != null && feed.activeSelf)
feed.SetActive(false);
}
}