using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; [DisallowMultipleComponent] public class SpriteNumberDisplay : MonoBehaviour { [Header("Sprites")] public Sprite[] digitSprites = new Sprite[10]; public Sprite decimalPointSprite; public Sprite percentSprite; public Sprite plusSprite; public Sprite minusSprite; [Header("string")] public string en_combo = "COMBO"; [Tooltip("COMBO 标签字形高度(像素)。宽度按各字母 sprite 宽高比自动计算。")] public int en_combo_fontSize = 20; public Color en_combo_color = Color.white; public float en_combo_yOffset= 0f; [Header("COMBO letter sprites")] [Tooltip("字母 C 的 sprite")] public Sprite letterC_sprite; [Tooltip("字母 O 的 sprite")] public Sprite letterO_sprite; [Tooltip("字母 M 的 sprite")] public Sprite letterM_sprite; [Tooltip("字母 B 的 sprite")] public Sprite letterB_sprite; [Header("Layout")] public Vector2 digitSize = new Vector2(28f, 42f); public Vector2 decimalPointSize = new Vector2(12f, 42f); public Vector2 percentSize = new Vector2(28f, 42f); public float spacing = 2f; public bool centerAlign = true; public bool preserveAspect = true; [Tooltip("Keep all visible glyphs at the configured height. Width is recalculated from each sprite aspect ratio.")] public bool forceUniformHeight = true; public Color color = Color.white; [Tooltip("Canvas sorting order used by generated character sprites.")] public int characterSortingOrder = 0; private readonly List glyphImages = new List(); private string currentText = string.Empty; // 数字上方用 sprite 拼接的 "COMBO" 标签所用的 glyph 池。 private readonly List comboLabelImages = new List(); private void OnEnable() { Refresh(); } private void OnValidate() { digitSize.x = Mathf.Max(0f, digitSize.x); digitSize.y = Mathf.Max(0f, digitSize.y); decimalPointSize.x = Mathf.Max(0f, decimalPointSize.x); decimalPointSize.y = Mathf.Max(0f, decimalPointSize.y); percentSize.x = Mathf.Max(0f, percentSize.x); percentSize.y = Mathf.Max(0f, percentSize.y); // 编辑器中改动 combo 标签相关字段时,已生成的字母 glyph 实时更新(不新建,避免编辑期实例化)。 if (comboLabelImages.Count > 0 && isActiveAndEnabled) { RefreshComboLabel(); } } public void SetNumber(int value) { SetText(value.ToString()); } public void SetText(string value) { string next = value ?? string.Empty; if (currentText == next && glyphImages.Count > 0) { return; } currentText = next; Refresh(); } public void Clear() { SetText(string.Empty); } private void Refresh() { EnsureImageCount(currentText.Length); float totalWidth = CalculateTotalWidth(currentText); float x = centerAlign ? -totalWidth * 0.5f : 0f; for (int i = 0; i < glyphImages.Count; i++) { Image image = glyphImages[i]; if (image == null) continue; if (i >= currentText.Length) { image.gameObject.SetActive(false); continue; } char c = currentText[i]; Sprite sprite = ResolveSprite(c); Vector2 size = ResolveSize(c, sprite); if (sprite == null) { image.gameObject.SetActive(false); continue; } RectTransform rect = image.rectTransform; image.gameObject.SetActive(true); image.sprite = sprite; image.color = color; image.preserveAspect = preserveAspect; ApplyCharacterSortingOrder(image); rect.sizeDelta = size; rect.anchorMin = new Vector2(0.5f, 0.5f); rect.anchorMax = new Vector2(0.5f, 0.5f); rect.pivot = new Vector2(0.5f, 0.5f); rect.anchoredPosition = new Vector2(x + size.x * 0.5f, 0f); x += size.x + spacing; } RefreshComboLabel(); } // 用字母 sprite(C/O/M/B)在数字上方拼接 "COMBO" 标签。字形高度、颜色、Y 偏移由 Inspector 字段控制。 private void RefreshComboLabel() { string label = en_combo ?? string.Empty; // 计算总宽度并按需扩容 glyph 池。 float height = Mathf.Max(1f, en_combo_fontSize); float totalWidth = 0f; int visibleCount = 0; for (int i = 0; i < label.Length; i++) { Sprite s = ResolveComboLetterSprite(label[i]); if (s == null) continue; totalWidth += ComboLetterWidth(s, height); visibleCount++; } if (visibleCount > 1) totalWidth += spacing * (visibleCount - 1); EnsureComboImageCount(label.Length); float x = centerAlign ? -totalWidth * 0.5f : 0f; for (int i = 0; i < comboLabelImages.Count; i++) { Image image = comboLabelImages[i]; if (image == null) continue; Sprite sprite = i < label.Length ? ResolveComboLetterSprite(label[i]) : null; if (sprite == null) { image.gameObject.SetActive(false); continue; } float w = ComboLetterWidth(sprite, height); RectTransform rect = image.rectTransform; image.gameObject.SetActive(true); image.sprite = sprite; image.color = en_combo_color; image.preserveAspect = preserveAspect; ApplyCharacterSortingOrder(image); rect.sizeDelta = new Vector2(w, height); rect.anchorMin = new Vector2(0.5f, 0.5f); rect.anchorMax = new Vector2(0.5f, 0.5f); rect.pivot = new Vector2(0.5f, 0.5f); // 上方定位:数字 glyph 以 y=0 居中、高约 digitSize.y,标签放其上半高 + 半个字形高 + 偏移处。 float baseY = digitSize.y * 0.5f + height * 0.5f; rect.anchoredPosition = new Vector2(x + w * 0.5f, baseY + en_combo_yOffset); x += w + spacing; } } private void EnsureComboImageCount(int count) { while (comboLabelImages.Count < count) { GameObject glyph = new GameObject("combo_letter_" + comboLabelImages.Count, typeof(RectTransform), typeof(Canvas), typeof(Image)); RectTransform rect = glyph.transform as RectTransform; rect.SetParent(transform, false); Image image = glyph.GetComponent(); image.raycastTarget = false; comboLabelImages.Add(image); } } private Sprite ResolveComboLetterSprite(char c) { switch (c) { case 'C': case 'c': return letterC_sprite; case 'O': case 'o': return letterO_sprite; case 'M': case 'm': return letterM_sprite; case 'B': case 'b': return letterB_sprite; default: return null; } } private float ComboLetterWidth(Sprite sprite, float height) { if (sprite == null || sprite.rect.height <= 0f) return height; float aspect = sprite.rect.width / sprite.rect.height; return height * aspect; } private void ApplyCharacterSortingOrder(Image image) { if (image == null) { return; } Canvas canvas = image.GetComponent(); if (canvas == null) { canvas = image.gameObject.AddComponent(); } canvas.overrideSorting = true; canvas.sortingOrder = characterSortingOrder; } private void EnsureImageCount(int count) { while (glyphImages.Count < count) { GameObject glyph = new GameObject("digit_" + glyphImages.Count, typeof(RectTransform), typeof(Canvas), typeof(Image)); RectTransform rect = glyph.transform as RectTransform; rect.SetParent(transform, false); Image image = glyph.GetComponent(); image.raycastTarget = false; glyphImages.Add(image); } } private float CalculateTotalWidth(string text) { if (string.IsNullOrEmpty(text)) { return 0f; } float total = 0f; int visibleCount = 0; for (int i = 0; i < text.Length; i++) { if (ResolveSprite(text[i]) == null) { continue; } total += ResolveSize(text[i], ResolveSprite(text[i])).x; visibleCount++; } if (visibleCount > 1) { total += spacing * (visibleCount - 1); } return total; } private Sprite ResolveSprite(char c) { if (c >= '0' && c <= '9') { int index = c - '0'; return digitSprites != null && index < digitSprites.Length ? digitSprites[index] : null; } if (c == '.') return decimalPointSprite; if (c == '%') return percentSprite; if (c == '+') return plusSprite; if (c == '-') return minusSprite; return null; } private Vector2 ResolveSize(char c, Sprite sprite) { Vector2 baseSize = digitSize; if (c == '.') baseSize = decimalPointSize; else if (c == '%') baseSize = percentSize; if (!forceUniformHeight || sprite == null || sprite.rect.height <= 0f) { return baseSize; } float height = Mathf.Max(0f, baseSize.y); float aspect = sprite.rect.width / sprite.rect.height; return new Vector2(height * aspect, height); } }