using System.Collections; using System.Collections.Generic; using Ichni.Localization; using Michsky.MUIP; using TMPro; using UnityEngine; using UnityEngine.Localization; namespace Ichni.UI { /// /// Settings 页面使用的下拉控件。 /// /// 本地化枚举项由 在代码中传入 String Table 名称与 /// Entry Key。控件会创建对应的 ,并在 Michsky 生成 Item 后为每个 /// 可见文字绑定 ;切换语言时不重建列表。 /// /// /// 分辨率、语言自称等运行时文本则使用 ,不会附加本地化组件。 /// /// public class Dropdown : SettingsUIElementBase { // 仅保存本次初始化创建的运行时引用,不在 Inspector 中维护本地化选项列表。 private readonly List _localizedOptions = new List(); // 保留原字段以兼容已有的外部读取。静态本地化选项使用稳定的占位名称;实际显示内容由 // 动态生成 Item 上的 LocalizedTMPText 管理。 [HideInInspector] public List options = new List(); public CustomDropdown dropdown; public int selectedIndex; public string selectedOption; private bool _usesLocalizedOptions; private bool _isValueChangeListenerRegistered; private Coroutine _bindGeneratedItemsCoroutine; /// /// 使用代码提供的 String Table 与 Entry Key 初始化静态枚举选项。 /// 的顺序即设置枚举的索引顺序,必须保持稳定。 /// 标题和说明仍由其 TMP 对象上的 独立管理。 /// public void SetUpLocalized(int initialValue, string tableCollection, params string[] entryKeys) { base.SetUp(); ClearLocalizedBindings(); ClearLocalizedOptionDefinitions(); _usesLocalizedOptions = true; foreach (string entryKey in entryKeys) { _localizedOptions.Add(new LocalizedString(tableCollection, entryKey)); } BuildLocalizedOptionPlaceholders(); EnsureValueChangeListener(); SetValue(initialValue); ApplyOptionsToDropdown(); } /// /// 使用运行时生成的纯文本选项初始化下拉框。 /// 仅用于分辨率、语言自称等不应随当前 Locale 再次翻译的内容。 /// public void SetUpRuntime(int initialValue, IReadOnlyList runtimeOptions) { base.SetUp(); ClearLocalizedBindings(); ClearLocalizedOptionDefinitions(); _usesLocalizedOptions = false; options = runtimeOptions == null ? new List() : new List(runtimeOptions); EnsureValueChangeListener(); SetValue(initialValue); ApplyOptionsToDropdown(); } private void OnEnable() { if (_usesLocalizedOptions) { QueueGeneratedItemBinding(); } } private void OnDisable() { StopGeneratedItemBinding(); } private void OnDestroy() { StopGeneratedItemBinding(); ClearLocalizedOptionDefinitions(); } public int GetOptionIndex(string option) { return options.IndexOf(option); } /// /// 只更新当前选择状态,不调用 。 /// 因此语言切换和 Dropdown 列表重建都不会重新应用玩家的设置。 /// public void SetValue(int index) { if (options == null || options.Count == 0) { selectedIndex = 0; selectedOption = string.Empty; return; } selectedIndex = Mathf.Clamp(index, 0, options.Count - 1); selectedOption = options[selectedIndex]; dropdown.selectedItemIndex = selectedIndex; if (_usesLocalizedOptions) { BindSelectedLocalizedText(); } else if (dropdown.selectedText != null) { dropdown.selectedText.text = selectedOption; } } private void EnsureValueChangeListener() { if (_isValueChangeListenerRegistered) { return; } dropdown.onValueChanged.AddListener(OnDropdownValueChanged); _isValueChangeListenerRegistered = true; } private void OnDropdownValueChanged(int index) { SetValue(index); updateValueAction?.Invoke(); } private void BuildLocalizedOptionPlaceholders() { options = new List(_localizedOptions.Count); for (int index = 0; index < _localizedOptions.Count; index++) { // Michsky 需要 itemName 创建 Item;真正可见的内容会在创建完成后被 LocalizedTMPText 覆盖。 options.Add($"LocalizedOption_{index}"); } } private void ApplyOptionsToDropdown() { if (options == null || options.Count == 0) { dropdown.items = new List(); return; } SetValue(selectedIndex); dropdown.items = new List(options.Count); foreach (string option in options) { dropdown.items.Add(new CustomDropdown.Item { itemName = option }); } if (dropdown.gameObject.activeInHierarchy) { if (dropdown.isOn) { dropdown.Animate(); } dropdown.SetupDropdown(); } else { dropdown.UpdateItemLayout(); } if (_usesLocalizedOptions) { QueueGeneratedItemBinding(); } } /// /// 等待一帧,确保 Michsky 已销毁旧 Item 并完成当前批次的实例化,再为每个 Item 绑定引用。 /// 这样不会保留已销毁 Item 的语言监听,也避免把引用绑定到上一批列表对象。 /// private void QueueGeneratedItemBinding() { if (!isActiveAndEnabled || _bindGeneratedItemsCoroutine != null) { return; } _bindGeneratedItemsCoroutine = StartCoroutine(BindGeneratedItemsNextFrame()); } private IEnumerator BindGeneratedItemsNextFrame() { yield return null; _bindGeneratedItemsCoroutine = null; BindSelectedLocalizedText(); if (dropdown.itemParent == null) { yield break; } int itemCount = Mathf.Min(dropdown.itemParent.childCount, _localizedOptions.Count); for (int index = 0; index < itemCount; index++) { TMP_Text itemText = dropdown.itemParent.GetChild(index).GetComponentInChildren(true); BindLocalizedText(itemText, _localizedOptions[index]); } } private void BindSelectedLocalizedText() { if (!_usesLocalizedOptions || _localizedOptions.Count == 0) { return; } int optionIndex = Mathf.Clamp(selectedIndex, 0, _localizedOptions.Count - 1); BindLocalizedText(dropdown.selectedText, _localizedOptions[optionIndex]); } private static void BindLocalizedText(TMP_Text text, LocalizedString reference) { if (text == null) { return; } LocalizedTMPText localizedText = text.GetComponent(); if (localizedText == null) { localizedText = text.gameObject.AddComponent(); } localizedText.SetStringReference(reference); } /// /// 将本 Dropdown 先前动态附加的本地化引用清空。 /// 同一控件改用运行时文本列表时必须执行此步骤,否则旧 Locale 回调会覆盖新的原始文本。 /// private void ClearLocalizedBindings() { ClearLocalizedBinding(dropdown.selectedText); if (dropdown.itemParent == null) { return; } foreach (Transform item in dropdown.itemParent) { ClearLocalizedBinding(item.GetComponentInChildren(true)); } } private static void ClearLocalizedBinding(TMP_Text text) { LocalizedTMPText localizedText = text == null ? null : text.GetComponent(); if (localizedText != null) { localizedText.SetStringReference(null); } } private void ClearLocalizedOptionDefinitions() { // LocalizedString 的订阅由每个 LocalizedTMPText 持有;ClearLocalizedBindings 已经解除它们。 // 当前 Unity Localization 版本没有公开的 LocalizedString.Dispose,因此清空运行时列表即可。 _localizedOptions.Clear(); } private void StopGeneratedItemBinding() { if (_bindGeneratedItemsCoroutine == null) { return; } StopCoroutine(_bindGeneratedItemsCoroutine); _bindGeneratedItemsCoroutine = null; } } }