| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597 |
- using LightGlue.Unity.Game;
- using LightGlue.Unity.Config;
- using System.Collections.Generic;
- using TMPro;
- using UnityEngine;
- using UnityEngine.SceneManagement;
- using UnityEngine.UI;
- namespace LightGlue.Unity.UI
- {
- /// <summary>
- /// LightGlueUI 全局管理器:
- /// - 单例(防止切场景重复生成 UI)
- /// - 提供 Show/Hide/Toggle 控制 UI 显示
- /// - 可选 DontDestroyOnLoad(常驻)或随场景销毁
- /// </summary>
- public sealed class LightGlueUIManager : MonoBehaviour
- {
- public enum UIType
- {
- Demo = 0,
- Plugin = 1,
- }
- public static LightGlueUIManager Instance { get; private set; }
- [Header("Roma 系统预制体")]
- [Tooltip("RomaManager 预制,由 GameManager 在 Awake 时生成(跨场景持久化)。若未配置则不会创建。")]
- [SerializeField]
- private GameObject romaSystemPrefab;
- [Header("BleUI(可选,仅 Demo 使用)")]
- [Tooltip("是否在生成 LightGlueUI 后,自动在 BLEContainer 下挂载 BleUI 预制。")]
- [SerializeField]
- private bool enableBleUi = false;
- [Tooltip("BleUI 预制体(建议放在 Assets/Demo/Prefabs/BleUI.prefab)。未配置则不会创建。")]
- [SerializeField]
- private GameObject bleUiPrefab;
- [Tooltip("LightGlueUI 里用于挂载 BleUI 的容器节点名。")]
- [SerializeField]
- private string bleContainerName = "BLEContainer";
- [Header("Root")]
- [Tooltip("UI 根节点(默认使用当前 GameObject)。隐藏/显示时会对该节点 SetActive。")]
- public GameObject uiRoot;
- [Header("Optional Anchors")]
- [Tooltip("用于挂载 BLE UI 的容器节点(可选)。若不填,外部可按名称查找。")]
- public Transform bleContainer;
- [Header("Type Visibility")]
- [Tooltip("当前 UI 类型(Demo / Plugin)。可由 GameManager 在启动时设置。")]
- public UIType currentUIType = UIType.Demo;
- [Tooltip("仅 Demo 模式显示的节点列表。")]
- public List<GameObject> showInDemo = new List<GameObject>();
- [Tooltip("仅 Plugin 模式显示的节点列表。")]
- public List<GameObject> showInPlugin = new List<GameObject>();
- [Tooltip("切换类型时是否自动隐藏另一类型的节点。")]
- public bool hideOtherTypeNodes = true;
- [Header("Lifecycle")]
- [Tooltip("是否常驻(DontDestroyOnLoad)。关闭则随场景销毁。")]
- public bool dontDestroyOnLoad = false;
- [Tooltip("若已有实例存在,是否自动销毁当前重复实例。建议保持 true,避免叠 UI/重复 EventSystem。")]
- public bool destroyDuplicateInstance = true;
- [Header("Startup")]
- [Tooltip("启动时是否自动显示 UI。")]
- public bool showOnStart = true;
- [Tooltip("Plugin 模式下生成时默认隐藏 UI(即使 showOnStart=true)。插件侧可按需再 ShowUI。")]
- public bool hideUiOnStartInPlugin = true;
- [Header("Scene Auto Hide")]
- [Tooltip("跨场景切换时自动 HideUI(常用于 DontDestroyOnLoad 的 UI,避免切到别的场景仍遮挡)。")]
- public bool autoHideUiOnSceneChanged = true;
- [Tooltip("跨场景切换时同时隐藏 CursorSettings 面板。")]
- public bool autoHideCursorSettingsOnSceneChanged = true;
- [Header("Scene Visibility (Optional)")]
- [Tooltip("UI 显示白名单:切到这些场景时自动 ShowUI;不在名单中则按默认策略隐藏。\n留空=不启用白名单(切场景默认隐藏)。")]
- public List<string> showUiInScenes = new List<string>();
- [Tooltip("受 showUiInScenes 条件控制的节点:命中白名单场景显示,否则隐藏。\n(独立于 uiRoot,可用于只在特定场景显示部分 UI)")]
- public List<GameObject> showInScenesNodes = new List<GameObject>();
- [Tooltip("CursorSettings 面板显示白名单:切到这些场景时允许保持显示(否则隐藏)。\n留空=不启用白名单(切场景默认隐藏)。")]
- public List<string> showCursorSettingsInScenes = new List<string>();
- [Header("Cursor Settings (optional)")]
- [Tooltip("LightGlueCursorSettings 预制,由 LightGlueUIManager 在 Awake 时生成;Button 可通过本类显示/隐藏。")]
- [SerializeField]
- private GameObject lightGlueCursorSettingsPrefab;
- [Tooltip("是否让 CursorSettings 常驻(DontDestroyOnLoad)。通常建议 true。")]
- [SerializeField]
- private bool cursorSettingsDontDestroyOnLoad = true;
- [Header("Scene Loading (optional)")]
- [SerializeField]
- private string sceneToLoad = "GameScene";
- public bool IsVisible => (uiRoot != null ? uiRoot.activeSelf : gameObject.activeSelf);
- private GameObject _cursorSettingsInstance;
- [Header("Reference Resize (统一与 Python --resize 一致)")]
- [Tooltip("参考图像 resize 下拉框。若不绑定,将在运行时尝试克隆一个现有 TMP_Dropdown 作为样式,并生成到 UIRoot 下。")]
- [SerializeField]
- private TMP_Dropdown resizeDropdown;
- [Tooltip("resize 预设列表(格式:\"320x240\")。")]
- [SerializeField]
- private List<string> resizePresets = new List<string> { "320x240", "640x480" };
- private bool _resizeDropdownInitialized;
- #region 生成RomaUI管理插件部分
- public static void Create(UIType uiType = UIType.Plugin, GameObject parentObj = null)
- {
- if (Instance != null) { return;};
-
- // 这里按你项目资源路径调整
- GameObject o = Object.Instantiate(Resources.Load<GameObject>("LightGlueUI"));
- Object.DontDestroyOnLoad(o);
- CanvasScaler canvasScaler = o.GetComponent<CanvasScaler>();
- if (canvasScaler != null)
- {
- Destroy(canvasScaler);
- }
- RectTransform rectTransform = o.GetComponent<RectTransform>();
- rectTransform.anchorMin = Vector2.zero; // 左下角对齐父级
- rectTransform.anchorMax = Vector2.one; // 右上角对齐父级
- rectTransform.offsetMin = Vector2.zero; // 移除左下角偏移
- rectTransform.offsetMax = Vector2.zero; // 移除右上角偏移
- rectTransform.localScale = Vector3.one; // 确保缩放为 1
- Instance = o.GetComponent<LightGlueUIManager>();
- // 挂到你们 ViewMgr 层级(按你实际节点修改)
- if (parentObj != null)
- {
- if (parentObj != null) o.transform.SetParent(parentObj.transform, false);
- }
- Instance.SetUIType(uiType);
- Instance.OnCreated();
- }
- // 初始化运行时
- private void OnCreated()
- {
- // 确保全局 RomaManager 只创建一次(跨场景)
- if (Roma.RomaManager.Instance == null && romaSystemPrefab != null)
- {
- GameObject romaSystemObj = Instantiate(romaSystemPrefab);
- //setViewMgrParent(romaSystemObj,this.gameObject);
- Debug.Log("[GameManager] RomaSystem prefab instantiated.");
- }
- // 可选:在 LightGlueUI 下挂载 BleUI(Demo 专用,插件用户可不配置)
- TryAttachBleUi();
- }
- private void TryAttachBleUi()
- {
- if (currentUIType != LightGlueUIManager.UIType.Demo) return;
- if (!enableBleUi) return;
- if (bleUiPrefab == null) return;
- var uiMgr = LightGlueUIManager.Instance != null ? LightGlueUIManager.Instance : FindObjectOfType<LightGlueUIManager>();
- if (uiMgr == null || uiMgr.uiRoot == null) return;
- Transform container = uiMgr.bleContainer != null ? uiMgr.bleContainer : FindChildByName(uiMgr.uiRoot.transform, bleContainerName);
- if (container == null) return;
- // 防重复:避免直接引用 BLEUI 类型(插件导出时可能不包含 BLEUI.cs)
- // 这里按 BleUI 预制名判断是否已挂载过实例。
- string bleUiName = bleUiPrefab.name;
- if (HasChildNamed(container, bleUiName)) return;
- var inst = Instantiate(bleUiPrefab, container);
- inst.name = bleUiPrefab.name;
- Debug.Log("[GameManager] BleUI prefab attached under BLEContainer.");
- }
- private static Transform FindChildByName(Transform root, string childName)
- {
- if (root == null || string.IsNullOrWhiteSpace(childName)) return null;
- if (root.name == childName) return root;
- for (int i = 0; i < root.childCount; i++)
- {
- Transform t = root.GetChild(i);
- var found = FindChildByName(t, childName);
- if (found != null) return found;
- }
- return null;
- }
- private static bool HasChildNamed(Transform root, string name)
- {
- if (root == null || string.IsNullOrWhiteSpace(name)) return false;
- for (int i = 0; i < root.childCount; i++)
- {
- var t = root.GetChild(i);
- if (t == null) continue;
- if (t.name == name || t.name == $"{name}(Clone)") return true;
- }
- return false;
- }
- #endregion
- private void Awake()
- {
- if (uiRoot == null) uiRoot = gameObject;
- if (Instance != null && Instance != this)
- {
- if (destroyDuplicateInstance)
- {
- Destroy(gameObject);
- return;
- }
- Instance = this;
- }
- else
- {
- Instance = this;
- }
- if (dontDestroyOnLoad)
- DontDestroyOnLoad(gameObject);
- EnsureCursorSettingsInstance();
- }
- private void OnEnable()
- {
- SceneManager.activeSceneChanged += HandleActiveSceneChanged;
- }
- private void OnDisable()
- {
- SceneManager.activeSceneChanged -= HandleActiveSceneChanged;
- }
- private void Start()
- {
- // Plugin 模式:默认隐藏,避免插件启动即遮挡画面
- if (currentUIType == UIType.Plugin && hideUiOnStartInPlugin)
- {
- HideUI();
- if (autoHideCursorSettingsOnSceneChanged) HideCursorSettingsPanel();
- }
- else
- {
- if (showOnStart) ShowUI();
- else HideUI();
- }
- ApplyUITypeVisibility();
- ApplySceneVisibilityNodes(SceneManager.GetActiveScene().name);
- EnsureResizeDropdown();
- }
- private void EnsureResizeDropdown()
- {
- if (_resizeDropdownInitialized) return;
- _resizeDropdownInitialized = true;
- ReferenceImageResizeService.EnsureInitialized();
- if (resizeDropdown == null)
- {
- resizeDropdown = TryCloneAnyTmpDropdown();
- }
- if (resizeDropdown == null)
- {
- Debug.LogWarning("[LightGlueUI] 未找到/未生成 resizeDropdown(TMP_Dropdown)。请在 LightGlueUIManager Inspector 绑定一个 TMP_Dropdown,或确保 UI 中存在可克隆的 TMP_Dropdown。");
- return;
- }
- var options = new List<TMP_Dropdown.OptionData>();
- for (int i = 0; i < resizePresets.Count; i++)
- {
- string s = resizePresets[i];
- if (string.IsNullOrWhiteSpace(s)) continue;
- options.Add(new TMP_Dropdown.OptionData(s.Trim()));
- }
- if (options.Count == 0)
- {
- options.Add(new TMP_Dropdown.OptionData("320x240"));
- }
- resizeDropdown.onValueChanged.RemoveListener(OnResizeDropdownChanged);
- resizeDropdown.ClearOptions();
- resizeDropdown.AddOptions(options);
- int currentIndex = FindPresetIndex(ReferenceImageResizeService.Width, ReferenceImageResizeService.Height, resizePresets);
- if (currentIndex < 0) currentIndex = 0;
- resizeDropdown.SetValueWithoutNotify(currentIndex);
- resizeDropdown.onValueChanged.AddListener(OnResizeDropdownChanged);
- }
- private TMP_Dropdown TryCloneAnyTmpDropdown()
- {
- if (uiRoot == null) uiRoot = gameObject;
- Transform parent = uiRoot.transform;
- TMP_Dropdown sample = null;
- var dropdowns = uiRoot.GetComponentsInChildren<TMP_Dropdown>(true);
- if (dropdowns != null && dropdowns.Length > 0)
- {
- // 优先找名字含 "resolution" 的下拉框(现有UI里通常有)
- for (int i = 0; i < dropdowns.Length; i++)
- {
- var d = dropdowns[i];
- if (d != null && d.name.ToLowerInvariant().Contains("resolution"))
- {
- sample = d;
- break;
- }
- }
- if (sample == null)
- sample = dropdowns[0];
- }
- if (sample == null) return null;
- var inst = Instantiate(sample.gameObject, sample.transform.parent != null ? sample.transform.parent : parent);
- inst.name = "ResizeDropdown";
- // 尽量让它在 sample 旁边,避免遮挡
- var rt = inst.GetComponent<RectTransform>();
- var sampleRt = sample.GetComponent<RectTransform>();
- if (rt != null && sampleRt != null)
- {
- rt.anchorMin = sampleRt.anchorMin;
- rt.anchorMax = sampleRt.anchorMax;
- rt.pivot = sampleRt.pivot;
- rt.sizeDelta = sampleRt.sizeDelta;
- rt.anchoredPosition = sampleRt.anchoredPosition + new Vector2(0, - (sampleRt.sizeDelta.y + 10f));
- rt.localScale = sampleRt.localScale;
- }
- var dd = inst.GetComponent<TMP_Dropdown>();
- if (dd == null) return null;
- // 尝试改一下 label,方便辨识(不保证所有模板都有 captionText)
- if (dd.captionText != null)
- dd.captionText.text = "Resize";
- return dd;
- }
- private static int FindPresetIndex(int w, int h, List<string> presets)
- {
- if (presets == null) return -1;
- string target = $"{w}x{h}";
- for (int i = 0; i < presets.Count; i++)
- {
- string s = presets[i];
- if (string.IsNullOrWhiteSpace(s)) continue;
- if (NormalizePreset(s) == target) return i;
- }
- return -1;
- }
- private static string NormalizePreset(string s)
- {
- s = s.Trim().ToLowerInvariant().Replace(" ", "");
- s = s.Replace("*", "x").Replace("×", "x");
- return s;
- }
- private void OnResizeDropdownChanged(int index)
- {
- if (resizePresets == null || resizePresets.Count == 0) return;
- if (index < 0 || index >= resizePresets.Count) return;
- string preset = resizePresets[index];
- if (string.IsNullOrWhiteSpace(preset)) return;
- preset = NormalizePreset(preset);
- var parts = preset.Split('x');
- if (parts.Length != 2) return;
- if (!int.TryParse(parts[0], out int w)) return;
- if (!int.TryParse(parts[1], out int h)) return;
- ReferenceImageResizeService.Set(w, h, persistToNetworkConfig: true);
- }
- private void OnDestroy()
- {
- SceneManager.activeSceneChanged -= HandleActiveSceneChanged;
- if (Instance == this) Instance = null;
- }
- private void HandleActiveSceneChanged(Scene oldScene, Scene newScene)
- {
- // 默认策略:切场景隐藏(不配置白名单时就是这个行为)
- // 白名单策略:配置 showUiInScenes / showCursorSettingsInScenes 后,命中名单则显示,否则隐藏
- if (autoHideUiOnSceneChanged)
- {
- // Plugin + 默认隐藏:切场景时永远不自动显示(由插件侧自行 ShowUI)
- if (currentUIType == UIType.Plugin && hideUiOnStartInPlugin)
- {
- HideUI();
- ApplySceneVisibilityNodes(newScene.name);
- }
- else if (IsSceneInList(showUiInScenes, newScene.name))
- {
- ShowUI();
- ApplySceneVisibilityNodes(newScene.name);
- }
- else
- {
- HideUI();
- ApplySceneVisibilityNodes(newScene.name);
- }
- }
- else
- {
- // 即使不做 autoHideUiOnSceneChanged,也刷新一次 showInScenesNodes
- ApplySceneVisibilityNodes(newScene.name);
- }
- if (autoHideCursorSettingsOnSceneChanged)
- {
- if (!IsSceneInList(showCursorSettingsInScenes, newScene.name))
- HideCursorSettingsPanel();
- }
- }
- private void ApplySceneVisibilityNodes(string sceneName)
- {
- if (showInScenesNodes == null || showInScenesNodes.Count <= 0) return;
- bool shouldShow = IsSceneInList(showUiInScenes, sceneName);
- for (int i = 0; i < showInScenesNodes.Count; i++)
- {
- var go = showInScenesNodes[i];
- if (go != null) go.SetActive(shouldShow);
- }
- }
- private static bool IsSceneInList(List<string> sceneNames, string sceneName)
- {
- if (sceneNames == null || sceneNames.Count <= 0) return false;
- if (string.IsNullOrWhiteSpace(sceneName)) return false;
- for (int i = 0; i < sceneNames.Count; i++)
- {
- var n = sceneNames[i];
- if (string.IsNullOrWhiteSpace(n)) continue;
- if (n.Trim() == sceneName) return true;
- }
- return false;
- }
- public void ShowUI()
- {
- if (uiRoot != null) uiRoot.SetActive(true);
- }
- public void HideUI()
- {
- if (uiRoot != null) uiRoot.SetActive(false);
- }
- public void ToggleUI()
- {
- if (uiRoot == null) return;
- uiRoot.SetActive(!uiRoot.activeSelf);
- }
- /// <summary>
- /// 运行时切换是否常驻(仅对当前实例生效)。
- /// 注意:Unity 不支持“撤销 DontDestroyOnLoad”,因此关闭常驻仅更新标志位,不会把对象自动移回场景。
- /// </summary>
- public void SetPersistent(bool persistent)
- {
- dontDestroyOnLoad = persistent;
- if (persistent)
- DontDestroyOnLoad(gameObject);
- }
- private void EnsureCursorSettingsInstance()
- {
- if (LightGlueCursorSettings.Instance != null)
- {
- _cursorSettingsInstance = LightGlueCursorSettings.Instance.gameObject;
- return;
- }
- if (lightGlueCursorSettingsPrefab == null)
- return;
- _cursorSettingsInstance = Instantiate(lightGlueCursorSettingsPrefab);
- if (cursorSettingsDontDestroyOnLoad && _cursorSettingsInstance != null)
- DontDestroyOnLoad(_cursorSettingsInstance);
- Debug.Log("[LightGlueUI] LightGlueCursorSettings prefab instantiated.");
- }
- private LightGlueCursorSettings GetCursorSettings()
- {
- if (_cursorSettingsInstance != null)
- {
- var c = _cursorSettingsInstance.GetComponent<LightGlueCursorSettings>();
- if (c != null) return c;
- }
- return LightGlueCursorSettings.Instance;
- }
- /// <summary> 显示光标设置面板(供 Button 调用)。 </summary>
- public void ShowCursorSettingsPanel()
- {
- var settings = GetCursorSettings();
- if (settings != null) settings.ShowPanel();
- }
- /// <summary> 隐藏光标设置面板(供 Button 调用)。 </summary>
- public void HideCursorSettingsPanel()
- {
- var settings = GetCursorSettings();
- if (settings != null) settings.HidePanel();
- }
- /// <summary> 切换光标设置面板显示/隐藏(供 Button 调用)。 </summary>
- public void ToggleCursorSettingsPanel()
- {
- var settings = GetCursorSettings();
- if (settings != null) settings.TogglePanel();
- }
- /// <summary> 当前光标设置面板是否显示。 </summary>
- public bool IsCursorSettingsPanelVisible()
- {
- var settings = GetCursorSettings();
- return settings != null && settings.IsPanelVisible();
- }
- /// <summary> 加载配置的场景(可在 Inspector 设置 sceneToLoad)。 </summary>
- public void EntGame()
- {
- SceneManager.LoadScene(sceneToLoad);
- }
- /// <summary> 加载指定场景名。 </summary>
- public void EntGame(string sceneName)
- {
- SceneManager.LoadScene(sceneName);
- }
- public void SetUIType(UIType uiType)
- {
- currentUIType = uiType;
- ApplyUITypeVisibility();
- }
- private void ApplyUITypeVisibility()
- {
- List<GameObject> showList = currentUIType == UIType.Demo ? showInDemo : showInPlugin;
- List<GameObject> hideList = currentUIType == UIType.Demo ? showInPlugin : showInDemo;
- if (showList != null)
- {
- for (int i = 0; i < showList.Count; i++)
- {
- var go = showList[i];
- if (go != null) go.SetActive(true);
- }
- }
- if (!hideOtherTypeNodes || hideList == null) return;
- for (int i = 0; i < hideList.Count; i++)
- {
- var go = hideList[i];
- if (go != null) go.SetActive(false);
- }
- }
- }
- }
|