| | |
| | | using UnityEngine; |
| | | using UnityEngine.UI; |
| | | |
| | | /// <summary> |
| | | /// 文本组件类型枚举 |
| | | /// </summary> |
| | | /// <summary>文本组件类型枚举</summary> |
| | | public enum TextComponentType |
| | | { |
| | | None = 0, |
| | |
| | | GradientText = 3 |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 多语言排版配置项,存储单个语言的布局和适配数据 |
| | | /// </summary> |
| | | /// <summary>单个语言的排版和适配数据节点</summary> |
| | | [Serializable] |
| | | public class LanguageConfigItem |
| | | { |
| | | // ============ RectTransform 配置 ============ |
| | | [Header("RectTransform 配置")] |
| | | public Vector2 anchoredPosition = Vector2.zero; |
| | | public Vector2 sizeDelta = new Vector2(100f, 30f); |
| | |
| | | public Vector3 localScale = Vector3.one; |
| | | public Vector3 localRotation = Vector3.zero; |
| | | |
| | | // ============ Text 排版与适配配置 ============ |
| | | [Header("Text 排版与适配配置")] |
| | | public Font font; |
| | | public FontStyle fontStyle = FontStyle.Normal; |
| | | public int fontSize = 14; |
| | | public float lineSpacing = 1f; |
| | | |
| | | public HorizontalWrapMode horizontalOverflow = HorizontalWrapMode.Wrap; |
| | | public VerticalWrapMode verticalOverflow = VerticalWrapMode.Truncate; |
| | | |
| | | public bool resizeTextForBestFit = false; |
| | | public int resizeTextMinSize = 10; |
| | | public int resizeTextMaxSize = 40; |
| | | |
| | | public TextAnchor alignment = TextAnchor.UpperLeft; |
| | | public bool alignByGeometry = false; |
| | | |
| | | /// <summary> 将配置应用到 RectTransform </summary> |
| | | public void ApplyToRectTransform(RectTransform rectTransform) |
| | | public void ApplyToRectTransform(RectTransform rt) |
| | | { |
| | | if (rectTransform == null) return; |
| | | rectTransform.anchorMin = anchorMin; |
| | | rectTransform.anchorMax = anchorMax; |
| | | rectTransform.pivot = pivot; |
| | | rectTransform.anchoredPosition = anchoredPosition; |
| | | rectTransform.sizeDelta = sizeDelta; |
| | | rectTransform.localScale = localScale; |
| | | rectTransform.localRotation = Quaternion.Euler(localRotation); |
| | | if (rt == null) return; |
| | | rt.anchorMin = anchorMin; |
| | | rt.anchorMax = anchorMax; |
| | | rt.pivot = pivot; |
| | | rt.anchoredPosition = anchoredPosition; |
| | | rt.sizeDelta = sizeDelta; |
| | | rt.localScale = localScale; |
| | | rt.localRotation = Quaternion.Euler(localRotation); |
| | | } |
| | | |
| | | /// <summary> 从 RectTransform 读取配置 </summary> |
| | | public void ReadFromRectTransform(RectTransform rectTransform) |
| | | public void ReadFromRectTransform(RectTransform rt) |
| | | { |
| | | if (rectTransform == null) return; |
| | | anchorMin = rectTransform.anchorMin; |
| | | anchorMax = rectTransform.anchorMax; |
| | | pivot = rectTransform.pivot; |
| | | anchoredPosition = rectTransform.anchoredPosition; |
| | | sizeDelta = rectTransform.sizeDelta; |
| | | localScale = rectTransform.localScale; |
| | | localRotation = rectTransform.localRotation.eulerAngles; |
| | | if (rt == null) return; |
| | | anchorMin = rt.anchorMin; |
| | | anchorMax = rt.anchorMax; |
| | | pivot = rt.pivot; |
| | | anchoredPosition = rt.anchoredPosition; |
| | | sizeDelta = rt.sizeDelta; |
| | | localScale = rt.localScale; |
| | | localRotation = rt.localRotation.eulerAngles; |
| | | } |
| | | |
| | | /// <summary> 将配置应用到 Text </summary> |
| | | public void ApplyToText(Text textComponent) |
| | | public void ApplyToText(Text txt) |
| | | { |
| | | if (textComponent == null) return; |
| | | if (font != null) textComponent.font = font; |
| | | if (txt == null) return; |
| | | if (font != null) txt.font = font; |
| | | |
| | | textComponent.fontStyle = fontStyle; |
| | | textComponent.fontSize = fontSize; |
| | | textComponent.lineSpacing = lineSpacing; |
| | | textComponent.horizontalOverflow = horizontalOverflow; |
| | | textComponent.verticalOverflow = verticalOverflow; |
| | | textComponent.resizeTextForBestFit = resizeTextForBestFit; |
| | | |
| | | txt.fontStyle = fontStyle; |
| | | txt.fontSize = fontSize; |
| | | txt.lineSpacing = lineSpacing; |
| | | txt.horizontalOverflow = horizontalOverflow; |
| | | txt.verticalOverflow = verticalOverflow; |
| | | txt.resizeTextForBestFit = resizeTextForBestFit; |
| | | if (resizeTextForBestFit) |
| | | { |
| | | textComponent.resizeTextMinSize = resizeTextMinSize; |
| | | textComponent.resizeTextMaxSize = resizeTextMaxSize; |
| | | txt.resizeTextMinSize = resizeTextMinSize; |
| | | txt.resizeTextMaxSize = resizeTextMaxSize; |
| | | } |
| | | txt.alignment = alignment; |
| | | txt.alignByGeometry = alignByGeometry; |
| | | } |
| | | |
| | | textComponent.alignment = alignment; |
| | | textComponent.alignByGeometry = alignByGeometry; |
| | | } |
| | | |
| | | /// <summary> 从 Text 读取配置 </summary> |
| | | public void ReadFromText(Text textComponent) |
| | | public void ReadFromText(Text txt) |
| | | { |
| | | if (textComponent == null) return; |
| | | font = textComponent.font; |
| | | fontStyle = textComponent.fontStyle; |
| | | fontSize = textComponent.fontSize; |
| | | lineSpacing = textComponent.lineSpacing; |
| | | horizontalOverflow = textComponent.horizontalOverflow; |
| | | verticalOverflow = textComponent.verticalOverflow; |
| | | resizeTextForBestFit = textComponent.resizeTextForBestFit; |
| | | resizeTextMinSize = textComponent.resizeTextMinSize; |
| | | resizeTextMaxSize = textComponent.resizeTextMaxSize; |
| | | alignment = textComponent.alignment; |
| | | alignByGeometry = textComponent.alignByGeometry; |
| | | if (txt == null) return; |
| | | font = txt.font; |
| | | fontStyle = txt.fontStyle; |
| | | fontSize = txt.fontSize; |
| | | lineSpacing = txt.lineSpacing; |
| | | horizontalOverflow = txt.horizontalOverflow; |
| | | verticalOverflow = txt.verticalOverflow; |
| | | resizeTextForBestFit = txt.resizeTextForBestFit; |
| | | resizeTextMinSize = txt.resizeTextMinSize; |
| | | resizeTextMaxSize = txt.resizeTextMaxSize; |
| | | alignment = txt.alignment; |
| | | alignByGeometry = txt.alignByGeometry; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 快速克隆配置 |
| | | /// 由于内部均为值类型或需浅拷贝的引用(Font),可以直接使用 MemberwiseClone |
| | | /// </summary> |
| | | public LanguageConfigItem Clone() |
| | | { |
| | | return (LanguageConfigItem)this.MemberwiseClone(); |
| | | } |
| | | public LanguageConfigItem Clone() => (LanguageConfigItem)MemberwiseClone(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 语言配置字典(Unity 内置序列化支持的双 List 结构) |
| | | /// </summary> |
| | | /// <summary>支持 Unity 序列化的字典</summary> |
| | | [Serializable] |
| | | public class LanguageConfigDictionary |
| | | { |
| | |
| | | { |
| | | if (string.IsNullOrEmpty(key)) return null; |
| | | int index = keys.IndexOf(key); |
| | | return (index >= 0 && index < values.Count) ? values[index] : null; |
| | | return index >= 0 ? values[index] : null; |
| | | } |
| | | |
| | | public void Set(string key, LanguageConfigItem value) |
| | | { |
| | | int index = keys.IndexOf(key); |
| | | if (index >= 0 && index < values.Count) |
| | | { |
| | | values[index] = value; |
| | | } |
| | | if (index >= 0) values[index] = value; |
| | | else |
| | | { |
| | | keys.Add(key); |
| | |
| | | if (index >= 0) |
| | | { |
| | | keys.RemoveAt(index); |
| | | if (index < values.Count) values.RemoveAt(index); |
| | | values.RemoveAt(index); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 文本多语言排版适配组件 |
| | | /// </summary> |
| | | /// <summary>多语言排版适配器</summary> |
| | | [RequireComponent(typeof(RectTransform))] |
| | | public class TextLanguageAdapter : MonoBehaviour |
| | | { |
| | | public const string DefaultLangId = "default"; // 提取常量避免硬编码错误 |
| | | public const string DefaultLangId = "default"; |
| | | |
| | | [SerializeField] |
| | | [Tooltip("每种语言的配置,使用语言ID作为key(如zh、en、ft),default为默认配置")] |
| | | [SerializeField, Tooltip("语言配置字典")] |
| | | private LanguageConfigDictionary m_LanguageConfigs = new LanguageConfigDictionary(); |
| | | |
| | | [SerializeField] |
| | | [Tooltip("自动检测到的文本组件类型")] |
| | | [SerializeField, Tooltip("目标文本组件类型")] |
| | | private TextComponentType m_TargetTextType = TextComponentType.None; |
| | | |
| | | [SerializeField] |
| | | [Tooltip("关联的文本组件引用")] |
| | | [SerializeField, Tooltip("关联的文本组件引用")] |
| | | private Component m_TargetTextComponent; |
| | | |
| | | private bool m_IsApplied = false; |
| | | |
| | | #region 公共属性 |
| | | public TextComponentType TargetTextType => m_TargetTextType; |
| | | public Component TargetTextComponent => m_TargetTextComponent; // 开放给 Editor 使用,避免反射 |
| | | public Component TargetTextComponent => m_TargetTextComponent; |
| | | public LanguageConfigDictionary LanguageConfigs => m_LanguageConfigs; |
| | | #endregion |
| | | |
| | | #region Unity生命周期 |
| | | protected void Awake() |
| | | { |
| | | DetectTargetComponent(); |
| | | } |
| | | private void Awake() => DetectTargetComponent(); |
| | | |
| | | protected void OnEnable() |
| | | private void OnEnable() |
| | | { |
| | | // 保证仅应用一次排版以节省性能,除非在编辑器非运行模式下需要强刷 |
| | | if (!Application.isPlaying || !m_IsApplied) |
| | | { |
| | | string langId = Application.isPlaying ? Language.Id : DefaultLangId; |
| | |
| | | } |
| | | |
| | | #if UNITY_EDITOR |
| | | protected void Reset() |
| | | private void Reset() |
| | | { |
| | | DetectTargetComponent(); |
| | | if (!HasConfig(DefaultLangId)) |
| | | { |
| | | ReadCurrentToConfig(DefaultLangId); |
| | | } |
| | | if (!HasConfig(DefaultLangId)) ReadCurrentToConfig(DefaultLangId); |
| | | } |
| | | #endif |
| | | #endregion |
| | | |
| | | #region 公共配置方法 |
| | | public LanguageConfigItem GetConfig(string languageId) |
| | | { |
| | | // 1. 尝试获取目标语言配置 |
| | | LanguageConfigItem config = m_LanguageConfigs.Get(languageId); |
| | | if (config != null) return config; |
| | | |
| | | // 2. 找不到则回退到默认配置 |
| | | config = m_LanguageConfigs.Get(DefaultLangId); |
| | | if (config != null) return config; |
| | | |
| | | Debug.LogError($"[TextLanguageAdapter] 找不到语言 {languageId} 的配置,且不存在 default 默认配置!"); |
| | | return null; |
| | | } |
| | | |
| | | public LanguageConfigItem GetConfig(string languageId) => m_LanguageConfigs.Get(languageId); |
| | | public void SetConfig(string languageId, LanguageConfigItem config) => m_LanguageConfigs.Set(languageId, config); |
| | | public void RemoveConfig(string languageId) => m_LanguageConfigs.Remove(languageId); |
| | | public bool HasConfig(string languageId) => m_LanguageConfigs.ContainsKey(languageId); |
| | |
| | | |
| | | public void ApplyConfig(string languageId) |
| | | { |
| | | LanguageConfigItem config = GetConfig(languageId); |
| | | var config = GetConfig(languageId); |
| | | if (config == null) return; |
| | | |
| | | if (TryGetComponent(out RectTransform rectTransform)) |
| | | config.ApplyToRectTransform(rectTransform); |
| | | |
| | | if (m_TargetTextComponent is Text textComponent) |
| | | config.ApplyToText(textComponent); |
| | | config.ApplyToRectTransform(GetComponent<RectTransform>()); |
| | | if (m_TargetTextComponent is Text txt) config.ApplyToText(txt); |
| | | |
| | | m_IsApplied = true; |
| | | } |
| | | |
| | | public void ReadCurrentToConfig(string languageId) |
| | | { |
| | | LanguageConfigItem config = new LanguageConfigItem(); |
| | | |
| | | if (TryGetComponent(out RectTransform rectTransform)) |
| | | config.ReadFromRectTransform(rectTransform); |
| | | |
| | | if (m_TargetTextComponent is Text textComponent) |
| | | config.ReadFromText(textComponent); |
| | | var config = new LanguageConfigItem(); |
| | | config.ReadFromRectTransform(GetComponent<RectTransform>()); |
| | | if (m_TargetTextComponent is Text txt) config.ReadFromText(txt); |
| | | |
| | | SetConfig(languageId, config); |
| | | } |
| | |
| | | public static string GetLanguageShowName(string languageId) |
| | | { |
| | | if (Language.languageShowDict != null && Language.languageShowDict.TryGetValue(languageId, out string showName)) |
| | | { |
| | | return showName; |
| | | } |
| | | return languageId; |
| | | } |
| | | #endregion |
| | | |
| | | #region 私有组件检测 |
| | | private void DetectTargetComponent() |
| | | { |
| | | if (m_TargetTextComponent != null) |
| | |
| | | return; |
| | | } |
| | | |
| | | // 按优先级查找组件 |
| | | m_TargetTextComponent = FindComponent<GradientText>() |
| | | ?? FindComponent<TextEx>() |
| | | ?? (Component)FindComponent<Text>(); |
| | | m_TargetTextComponent = GetComponent<GradientText>() ?? GetComponentInChildren<GradientText>(true) as Component |
| | | ?? GetComponent<TextEx>() ?? GetComponentInChildren<TextEx>(true) as Component |
| | | ?? GetComponent<Text>() ?? GetComponentInChildren<Text>(true) as Component; |
| | | |
| | | DetermineTextType(m_TargetTextComponent); |
| | | } |
| | | |
| | | /// <summary> 辅助方法:在自身和子物体中查找组件 </summary> |
| | | private T FindComponent<T>() where T : Component |
| | | { |
| | | T comp = GetComponent<T>(); |
| | | return comp != null ? comp : GetComponentInChildren<T>(true); |
| | | } |
| | | |
| | | private void DetermineTextType(Component component) |
| | |
| | | _ => TextComponentType.None |
| | | }; |
| | | } |
| | | #endregion |
| | | |
| | | #if UNITY_EDITOR |
| | | [ContextMenu("刷新组件检测")] |
| | |
| | | } |
| | | |
| | | [ContextMenu("应用默认配置")] |
| | | public void Editor_ApplyDefaultConfig() |
| | | { |
| | | ApplyConfig(DefaultLangId); |
| | | } |
| | | public void Editor_ApplyDefaultConfig() => ApplyConfig(DefaultLangId); |
| | | #endif |
| | | } |