using UnityEngine; using UnityEngine.UI; using TMPro; using LightGlue.Unity.Config; using LightGlue.Unity.Bridge; using LightGlue.Unity.Game; using LightGlue.Unity.Sdk.Unity; namespace LightGlue.Unity.UI { /// /// 网络配置UI控制器 /// - 提供IP和端口配置界面 /// - 自动加载和保存配置到JSON文件 /// - 将配置应用到相关组件(Bridge、ControlClient等) /// public class NetworkConfigUIController : MonoBehaviour { [Header("网络配置UI组件")] [Tooltip("硬件绑定IP输入框(Unity接收硬件图像的本地IP)")] public TMP_InputField hardwareBindIpField; [Tooltip("硬件端口输入框")] public TMP_InputField hardwarePortField; [Tooltip("硬件控制IP输入框(硬件设备IP,用于发送控制指令)")] public TMP_InputField hardwareControlIpField; [Tooltip("硬件控制端口输入框")] public TMP_InputField hardwareControlPortField; [Tooltip("Python IP输入框(UDP模式使用)")] public TMP_InputField pythonIpField; [Tooltip("Python端口输入框(UDP模式使用)")] public TMP_InputField pythonPortField; [Tooltip("Python启动参数输入框(多行文本,支持 {pythonPort} 占位符)")] public TMP_InputField pythonScriptArgsField; [Tooltip("保存配置按钮")] public Button saveConfigButton; [Tooltip("加载配置按钮(从文件重新加载)")] public Button loadConfigButton; [Tooltip("应用配置按钮(将当前配置应用到运行时组件)")] public Button applyConfigButton; [Tooltip("自动启动Toggle(选中后下次Play时自动启动所有组件)")] public Toggle autoStartToggle; [Tooltip("无窗口运行Toggle(选中时 Python 加 --no_display,不弹绘制窗口,避免在游戏背后卡顿)")] public Toggle noDisplayToggle; [Header("UI面板控制")] [Tooltip("网络配置面板的根节点GameObject(用于显示/隐藏整个面板)")] public GameObject configPanelRoot; [Header("配置应用目标")] [Tooltip("Bridge(统一管理:硬件接收/转发Python/硬件控制/结果接收),应用全部网络配置")] public HardwareToPythonUdpBridge bridge; [Tooltip("LightGlue SDK 新桥接组件(可选,用于对比旧 Bridge 行为)")] public LightGlueBridgeBehaviour sdkBridge; [Tooltip("Python进程控制器(应用Python启动参数)")] public LightGlue.Unity.Python.PythonProcessController pythonProcessController; [Header("状态显示")] [Tooltip("配置状态文本(显示配置路径、保存/加载状态等)")] public Text statusText; private NetworkConfig _currentConfig; private void Start() { // 如果没有在 Inspector 指定目标组件,优先从全局 LightGlueManager 获取 if (bridge == null && LightGlueManager.Instance != null) { bridge = LightGlueManager.Instance.bridge; } // sdkBridge 一般通过 Inspector 单独指定,用于对比测试; // 不从 LightGlueManager 自动获取,避免与旧 Bridge 混用。 if (pythonProcessController == null && LightGlueManager.Instance != null) { pythonProcessController = LightGlueManager.Instance.pythonController; } // 初始化时加载配置 LoadConfigFromFile(); InitializeUI(); SetupEventListeners(); // 检查是否需要自动启动 CheckAutoStart(); } /// /// 检查是否需要自动启动组件 /// private void CheckAutoStart() { if (_currentConfig != null && _currentConfig.autoStartOnPlay) { // 自动启动所有组件 StartAllComponents(); // 隐藏配置面板 HideConfigPanel(); } else { // 不自动启动,显示配置面板 ShowConfigPanel(); } } /// /// 初始化UI组件 /// private void InitializeUI() { // 设置输入框类型 if (hardwarePortField != null) { hardwarePortField.contentType = TMP_InputField.ContentType.IntegerNumber; } if (hardwareControlPortField != null) { hardwareControlPortField.contentType = TMP_InputField.ContentType.IntegerNumber; } if (pythonPortField != null) { pythonPortField.contentType = TMP_InputField.ContentType.IntegerNumber; } // Python启动参数输入框设置为多行 if (pythonScriptArgsField != null) { pythonScriptArgsField.lineType = TMP_InputField.LineType.MultiLineNewline; pythonScriptArgsField.textComponent.enableWordWrapping = true; } // 初始化自动启动Toggle if (autoStartToggle != null) { autoStartToggle.isOn = _currentConfig != null ? _currentConfig.autoStartOnPlay : false; } // 初始化无窗口运行Toggle(优先从配置,否则从 PythonProcessController) if (noDisplayToggle != null) { noDisplayToggle.isOn = _currentConfig != null ? _currentConfig.pythonNoDisplay : (pythonProcessController != null && pythonProcessController.addNoDisplayWhenLaunching); } // 从配置更新UI显示(初始化时只在输入框为空时填入默认值) UpdateUIFromConfig(forceUpdate: false); } /// /// 设置事件监听器 /// private void SetupEventListeners() { if (saveConfigButton != null) { saveConfigButton.onClick.AddListener(OnSaveConfigClicked); } if (loadConfigButton != null) { loadConfigButton.onClick.AddListener(OnLoadConfigClicked); } if (applyConfigButton != null) { applyConfigButton.onClick.AddListener(OnApplyConfigClicked); } // 自动启动Toggle变更事件 if (autoStartToggle != null) { autoStartToggle.onValueChanged.AddListener(OnAutoStartToggleChanged); } if (noDisplayToggle != null) { noDisplayToggle.onValueChanged.AddListener(OnNoDisplayToggleChanged); } } /// /// 从文件加载配置 /// public void LoadConfigFromFile() { _currentConfig = NetworkConfigManager.LoadConfig(); UpdateUIFromConfig(forceUpdate: true); // 加载配置时强制更新所有输入框 UpdateStatusText($"配置已从文件加载: {NetworkConfigManager.GetConfigPath()}"); } /// /// 将“硬件绑定IP”设为当前获取的本机地址(可绑定到按钮“获取本机IP”等)。 /// 会更新输入框和内存中的配置,不会自动保存或应用,需用户点击“保存/应用”生效。 /// public void FillHardwareBindIpFromLocal() { string localIp = NetworkConfig.GetLocalBindIp(); if (hardwareBindIpField != null) hardwareBindIpField.text = localIp; if (_currentConfig != null) _currentConfig.hardwareBindIp = localIp; UpdateStatusText($"硬件绑定IP已设为本机地址: {localIp}"); } /// /// 从UI读取配置到内存(如果输入框为空则使用默认值) /// private NetworkConfig ReadConfigFromUI() { NetworkConfig config = NetworkConfig.CreateDefault(); // 使用默认值作为基础 if (hardwareBindIpField != null && !string.IsNullOrWhiteSpace(hardwareBindIpField.text)) { config.hardwareBindIp = hardwareBindIpField.text.Trim(); } if (hardwarePortField != null && int.TryParse(hardwarePortField.text, out int hwPort) && hwPort > 0) { config.hardwarePort = hwPort; } if (hardwareControlIpField != null && !string.IsNullOrWhiteSpace(hardwareControlIpField.text)) { config.hardwareControlIp = hardwareControlIpField.text.Trim(); } if (hardwareControlPortField != null && int.TryParse(hardwareControlPortField.text, out int ctrlPort) && ctrlPort > 0) { config.hardwareControlPort = ctrlPort; } if (pythonIpField != null && !string.IsNullOrWhiteSpace(pythonIpField.text)) { config.pythonIp = pythonIpField.text.Trim(); } if (pythonPortField != null && int.TryParse(pythonPortField.text, out int pyPort) && pyPort > 0) { config.pythonPort = pyPort; } if (pythonScriptArgsField != null && !string.IsNullOrWhiteSpace(pythonScriptArgsField.text)) { config.pythonScriptArgs = pythonScriptArgsField.text.Trim(); } // 读取自动启动选项 if (autoStartToggle != null) { config.autoStartOnPlay = autoStartToggle.isOn; } return config; } /// /// 将配置更新到UI显示(如果配置为空或输入框为空则使用默认值) /// /// 是否强制更新(true=覆盖输入框已有值,false=只在输入框为空时填入) private void UpdateUIFromConfig(bool forceUpdate = false) { // 如果配置为空,创建默认配置 if (_currentConfig == null) { _currentConfig = NetworkConfig.CreateDefault(); } // 获取默认配置值(用于填充空输入框) NetworkConfig defaultConfig = NetworkConfig.CreateDefault(); if (hardwareBindIpField != null) { // 如果强制更新或输入框为空,则填入配置值或默认值 if (forceUpdate || string.IsNullOrWhiteSpace(hardwareBindIpField.text)) { string value = string.IsNullOrWhiteSpace(_currentConfig.hardwareBindIp) ? defaultConfig.hardwareBindIp : _currentConfig.hardwareBindIp; hardwareBindIpField.text = value; } } if (hardwarePortField != null) { // 如果强制更新或输入框为空,则填入配置值或默认值 if (forceUpdate || string.IsNullOrWhiteSpace(hardwarePortField.text)) { int value = _currentConfig.hardwarePort > 0 ? _currentConfig.hardwarePort : defaultConfig.hardwarePort; hardwarePortField.text = value.ToString(); } } if (hardwareControlIpField != null) { // 如果强制更新或输入框为空,则填入配置值或默认值 if (forceUpdate || string.IsNullOrWhiteSpace(hardwareControlIpField.text)) { string value = string.IsNullOrWhiteSpace(_currentConfig.hardwareControlIp) ? defaultConfig.hardwareControlIp : _currentConfig.hardwareControlIp; hardwareControlIpField.text = value; } } if (hardwareControlPortField != null) { // 如果强制更新或输入框为空,则填入配置值或默认值 if (forceUpdate || string.IsNullOrWhiteSpace(hardwareControlPortField.text)) { int value = _currentConfig.hardwareControlPort > 0 ? _currentConfig.hardwareControlPort : defaultConfig.hardwareControlPort; hardwareControlPortField.text = value.ToString(); } } if (pythonIpField != null) { // 如果强制更新或输入框为空,则填入配置值或默认值 if (forceUpdate || string.IsNullOrWhiteSpace(pythonIpField.text)) { string value = string.IsNullOrWhiteSpace(_currentConfig.pythonIp) ? defaultConfig.pythonIp : _currentConfig.pythonIp; pythonIpField.text = value; } } if (pythonPortField != null) { // 如果强制更新或输入框为空,则填入配置值或默认值 if (forceUpdate || string.IsNullOrWhiteSpace(pythonPortField.text)) { int value = _currentConfig.pythonPort > 0 ? _currentConfig.pythonPort : defaultConfig.pythonPort; pythonPortField.text = value.ToString(); } } if (pythonScriptArgsField != null) { // 如果配置中的参数为空,使用默认值 if (string.IsNullOrWhiteSpace(_currentConfig.pythonScriptArgs)) { _currentConfig.pythonScriptArgs = defaultConfig.pythonScriptArgs; } // 如果强制更新或输入框为空,填入配置值或默认值 if (forceUpdate || string.IsNullOrWhiteSpace(pythonScriptArgsField.text)) { pythonScriptArgsField.text = _currentConfig.pythonScriptArgs; } } // 更新自动启动Toggle if (autoStartToggle != null) { autoStartToggle.isOn = _currentConfig.autoStartOnPlay; } if (noDisplayToggle != null) { noDisplayToggle.isOn = _currentConfig.pythonNoDisplay; } if (pythonProcessController != null && noDisplayToggle != null) { pythonProcessController.addNoDisplayWhenLaunching = noDisplayToggle.isOn; } } /// /// 保存配置按钮点击处理 /// private void OnSaveConfigClicked() { NetworkConfig config = ReadConfigFromUI(); if (!config.Validate()) { UpdateStatusText("配置验证失败,请检查IP和端口格式"); return; } // 保存自动启动与无窗口选项 if (autoStartToggle != null) config.autoStartOnPlay = autoStartToggle.isOn; if (noDisplayToggle != null) config.pythonNoDisplay = noDisplayToggle.isOn; if (NetworkConfigManager.SaveConfig(config)) { _currentConfig = config; UpdateStatusText($"配置已保存到: {NetworkConfigManager.GetConfigPath()} (自动启动: {config.autoStartOnPlay})"); } else { UpdateStatusText("配置保存失败,请查看控制台日志"); } } /// /// 加载配置按钮点击处理 /// private void OnLoadConfigClicked() { LoadConfigFromFile(); } /// /// 自动启动Toggle变更处理 /// private void OnAutoStartToggleChanged(bool value) { if (_currentConfig != null) { _currentConfig.autoStartOnPlay = value; Debug.Log($"[NetworkConfigUI] 自动启动选项已更改为: {value}"); if (NetworkConfigManager.SaveConfig(_currentConfig)) Debug.Log($"[NetworkConfigUI] 自动启动选项已保存到配置文件"); } } private void OnNoDisplayToggleChanged(bool value) { if (pythonProcessController != null) { pythonProcessController.addNoDisplayWhenLaunching = value; Debug.Log($"[NetworkConfigUI] 无窗口运行已更改为: " + value); } if (_currentConfig != null) { _currentConfig.pythonNoDisplay = value; if (NetworkConfigManager.SaveConfig(_currentConfig)) Debug.Log($"[NetworkConfigUI] 无窗口运行选项已保存到配置文件"); } } /// /// 应用配置按钮点击处理(将配置应用到运行时组件) /// private void OnApplyConfigClicked() { NetworkConfig config = ReadConfigFromUI(); if (!config.Validate()) { UpdateStatusText("配置验证失败,请检查IP和端口格式"); return; } _currentConfig = config; if (autoStartToggle != null) config.autoStartOnPlay = autoStartToggle.isOn; if (noDisplayToggle != null) config.pythonNoDisplay = noDisplayToggle.isOn; // 应用到 PythonProcessController(无窗口选项立即生效,下次启动 Python 时使用) if (pythonProcessController != null && noDisplayToggle != null) { pythonProcessController.addNoDisplayWhenLaunching = noDisplayToggle.isOn; } // 应用到 Bridge(含硬件接收、Python 发送、硬件控制、结果接收;统一由下方全局重启生效) if (bridge != null) { bridge.hardwareBindIp = config.hardwareBindIp; bridge.hardwarePort = config.hardwarePort; bridge.hardwareTimeoutSeconds = config.hardwareTimeoutSeconds; bridge.pythonIp = config.pythonIp; bridge.pythonPort = config.pythonPort; bridge.hardwareControlIp = config.hardwareControlIp; bridge.hardwareControlPort = config.hardwareControlPort; Debug.Log("[NetworkConfigUI] 已应用配置到 Bridge(硬件/Python/硬件控制)"); } // 应用到 SDK Bridge(仅更新字段,实际生效依赖其自身生命周期与 NetworkConfigManager) if (sdkBridge != null) { sdkBridge.hardwareBindIp = config.hardwareBindIp; sdkBridge.hardwarePort = config.hardwarePort; sdkBridge.hardwareTimeoutSeconds = config.hardwareTimeoutSeconds; sdkBridge.pythonIp = config.pythonIp; sdkBridge.pythonPort = config.pythonPort; sdkBridge.hardwareControlIp = config.hardwareControlIp; sdkBridge.hardwareControlPort = config.hardwareControlPort; Debug.Log("[NetworkConfigUI] 已应用配置到 SDK Bridge(字段已更新)"); } // 应用到 Python 进程控制器 if (pythonProcessController != null) { // 获取处理后的参数(替换占位符) string processedArgs = config.GetPythonScriptArgs(); pythonProcessController.scriptArgs = processedArgs; Debug.Log($"[NetworkConfigUI] 已应用Python启动参数: {processedArgs}"); } // 保存配置到文件(包括自动启动选项) if (NetworkConfigManager.SaveConfig(config)) { _currentConfig = config; Debug.Log($"[NetworkConfigUI] 配置已保存到文件(包括自动启动选项: {config.autoStartOnPlay})"); } else { Debug.LogWarning("[NetworkConfigUI] 配置保存失败,但已应用到运行时组件"); } // 仅通过全局重启使新配置生效(关闭旧端口、重启监听与 Python) RestartAllComponents(); } /// /// 启动所有相关组件(类似 Play 时的自动启动) /// public void StartAllComponents() { Debug.Log("[NetworkConfigUI] 开始启动所有相关组件..."); // 1. 启动 Python 进程(若为「首帧后再启动」则仅启用组件,由 Bridge 收到首帧后启动) if (pythonProcessController != null) { if (!pythonProcessController.IsRunning) { if (!pythonProcessController.enabled) pythonProcessController.enabled = true; if (!pythonProcessController.gameObject.activeInHierarchy) pythonProcessController.gameObject.SetActive(true); bool deferToFirstFrame = bridge != null && bridge.startPythonOnFirstImageReceived && pythonProcessController.startOnFirstImageReceived; if (!deferToFirstFrame) { pythonProcessController.StartPython(); Debug.Log("[NetworkConfigUI] PythonProcessController 已启动"); } else Debug.Log("[NetworkConfigUI] 将等首帧图像到达后由 Bridge 启动 Python"); } else Debug.Log("[NetworkConfigUI] PythonProcessController 已运行"); } // 2. 启动 Bridge(含硬件接收、硬件控制、结果接收;最后启动,依赖 Python) if (bridge != null) { if (!bridge.enabled) { bridge.enabled = true; } else if (!bridge.gameObject.activeInHierarchy) { bridge.gameObject.SetActive(true); } Debug.Log("[NetworkConfigUI] HardwareToPythonUdpBridge 已启动"); // 重启后重新向硬件下发 0x40 图传参数,否则硬件可能停止发图导致画面无更新 if (bridge.transmissionConfig != null) { bridge.ApplyConfig(bridge.transmissionConfig); Debug.Log("[NetworkConfigUI] 已重新下发 0x40 图传参数到硬件,摄像头图像应恢复"); } } // 3. 启动 SDK Bridge(如果有),用于与旧 Bridge 同场对比 if (sdkBridge != null) { if (!sdkBridge.enabled) { sdkBridge.enabled = true; } else if (!sdkBridge.gameObject.activeInHierarchy) { sdkBridge.gameObject.SetActive(true); } Debug.Log("[NetworkConfigUI] LightGlueBridgeBehaviour (SDK) 已启动"); } UpdateStatusText("所有相关组件已启动"); HideConfigPanel(); } /// /// 停止所有相关组件 /// public void StopAllComponents() { Debug.Log("[NetworkConfigUI] 开始停止所有相关组件..."); // 1. 停止Bridge(先停止,避免继续发送数据) if (bridge != null && bridge.enabled) { bridge.enabled = false; Debug.Log("[NetworkConfigUI] HardwareToPythonUdpBridge 已停止"); } // 1b. 停止 SDK Bridge if (sdkBridge != null && sdkBridge.enabled) { sdkBridge.enabled = false; Debug.Log("[NetworkConfigUI] LightGlueBridgeBehaviour (SDK) 已停止"); } // 2. 停止 Python 进程 if (pythonProcessController != null && pythonProcessController.IsRunning) { pythonProcessController.StopPython(); Debug.Log("[NetworkConfigUI] PythonProcessController 已停止"); } // 3. 停止硬件控制由 Bridge 统一管理,Bridge 禁用时一并关闭 UpdateStatusText("所有相关组件已停止"); } /// /// 重启所有相关组件(先停止再启动,用于应用新配置) /// public void RestartAllComponents() { Debug.Log("[NetworkConfigUI] 开始重启所有相关组件以应用新配置..."); // 先停止所有组件 StopAllComponents(); // 等待一帧,确保组件完全停止 StartCoroutine(RestartComponentsCoroutine()); } /// /// 重启组件的协程:先停止所有组件并等待端口释放,再按顺序重新启动(全局唯一重启入口) /// private System.Collections.IEnumerator RestartComponentsCoroutine() { // 等待 Python 进程退出及 UDP 端口释放后再启动(过短会导致 IsRunning 仍为 true 而不执行 StartPython) yield return new WaitForSeconds(0.5f); StartAllComponents(); UpdateStatusText("所有相关组件已重启,新配置已生效"); } /// /// 更新状态文本 /// private void UpdateStatusText(string message) { if (statusText != null) { statusText.text = message; } Debug.Log($"[NetworkConfigUI] {message}"); } /// /// 获取当前配置(供外部访问) /// public NetworkConfig GetConfig() { if (_currentConfig == null) _currentConfig = NetworkConfigManager.LoadConfig(); return _currentConfig; } /// /// 显示配置面板 /// public void ShowConfigPanel() { if (configPanelRoot != null) { configPanelRoot.SetActive(true); Debug.Log("[NetworkConfigUI] 配置面板已显示"); } } /// /// 隐藏配置面板 /// public void HideConfigPanel() { if (configPanelRoot != null) { configPanelRoot.SetActive(false); Debug.Log("[NetworkConfigUI] 配置面板已隐藏"); } } /// /// 切换配置面板显示/隐藏状态 /// public void ToggleConfigPanel() { if (configPanelRoot != null) { bool newState = !configPanelRoot.activeSelf; configPanelRoot.SetActive(newState); Debug.Log($"[NetworkConfigUI] 配置面板已{(newState ? "显示" : "隐藏")}"); } } private void OnDestroy() { // 如果没有全局 LightGlueManager(单场景老用法),仍由本 UI 负责停止组件; // 如有 LightGlueManager,则由其统一管理 Python / Bridge 生命周期(硬件控制已合并到 Bridge), // 场景切换时不再在这里主动 StopAllComponents,避免切场景就把 Python 关掉。 if (LightGlueManager.Instance == null) { StopAllComponents(); } // 清理事件监听器 if (saveConfigButton != null) { saveConfigButton.onClick.RemoveListener(OnSaveConfigClicked); } if (loadConfigButton != null) { loadConfigButton.onClick.RemoveListener(OnLoadConfigClicked); } if (applyConfigButton != null) { applyConfigButton.onClick.RemoveListener(OnApplyConfigClicked); } if (autoStartToggle != null) autoStartToggle.onValueChanged.RemoveListener(OnAutoStartToggleChanged); if (noDisplayToggle != null) noDisplayToggle.onValueChanged.RemoveListener(OnNoDisplayToggleChanged); } private void OnApplicationQuit() { // 如果没有全局 LightGlueManager(单场景老用法),仍然由本UI负责停止组件; // 有全局管理器时,由 LightGlueManager 统一在应用退出时关闭 Python / UDP 组件。 if (LightGlueManager.Instance == null) { StopAllComponents(); } } } }