316 lines
9.7 KiB
C#
316 lines
9.7 KiB
C#
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<Image> glyphImages = new List<Image>();
|
|
private string currentText = string.Empty;
|
|
|
|
// 数字上方用 sprite 拼接的 "COMBO" 标签所用的 glyph 池。
|
|
private readonly List<Image> comboLabelImages = new List<Image>();
|
|
|
|
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>();
|
|
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<Canvas>();
|
|
if (canvas == null)
|
|
{
|
|
canvas = image.gameObject.AddComponent<Canvas>();
|
|
}
|
|
|
|
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>();
|
|
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);
|
|
}
|
|
}
|