279 lines
9.2 KiB
C#
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 = 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<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 = GameObject.Find("score/Skill");
|
|
if (feed != null && feed.activeSelf)
|
|
feed.SetActive(false);
|
|
}
|
|
}
|