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();
}
}
}
}