using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using TMPro; using UnityEngine; using UnityEngine.UI; public class TouchClient : MonoBehaviour { private TcpClient client; // 用于与显示端通信的TCP客户端 private NetworkStream stream; // 用于发送/接收数据的流 private volatile bool isConnected = false; // 线程安全的连接状态标志 private UdpClient udpListener; // 用于接收广播IP的UDP客户端 private Thread udpThread; // 监听广播的线程 private CancellationTokenSource udpCts = new CancellationTokenSource(); // 控制UDP线程取消 private Thread receiveThread; // 接收服务器消息的线程 private CancellationTokenSource receiveCts = new CancellationTokenSource(); // 控制接收线程取消 public Transform background; // UI页面的父级变换 private string currentPage = "首页"; // 跟踪当前显示的页面 private readonly Queue progressQueue = new Queue(); // 线程安全的进度队列 private readonly object queueLock = new object(); // 进度队列访问锁 private bool isDraggingSlider = false; // 标记是否正在拖动Slider private float lastSentProgress = 0f; // 记录最后发送的进度 [Header("Text Panel Toggle Sprites")] public Sprite showSprite; // 显示状态的Sprite public Sprite hideSprite; // 隐藏状态的Sprite // 脚本启动时调用 void Start() { // 设置触摸控制端分辨率为 1920x1080 Screen.SetResolution(1920, 1080, false); try { // 在端口9999上初始化UDP监听 udpListener = new UdpClient(9999); udpThread = new Thread(() => ListenForBroadcast(udpCts.Token)); udpThread.Start(); Debug.Log("触摸端等待显示端广播,监听端口: 9999"); } catch (Exception ex) { Debug.LogError($"初始化UDP监听失败: {ex.Message}"); } // 显示初始页面 ShowPage("首页"); } // 监听UDP广播以发现显示端IP private void ListenForBroadcast(CancellationToken token) { IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 9999); while (!isConnected && !token.IsCancellationRequested) { try { byte[] data = udpListener.Receive(ref endPoint); string message = Encoding.UTF8.GetString(data); Debug.Log($"收到UDP广播: {message},来源: {endPoint.Address}:{endPoint.Port}"); // 提取IP地址 string serverIp = message.StartsWith("ServerIP:") ? message.Replace("ServerIP:", "").Trim() : message.Trim(); if (IPAddress.TryParse(serverIp, out IPAddress ipAddress)) { Debug.Log($"解析到有效IP: {serverIp}"); ConnectToServer(serverIp, 8888); // 尝试连接到服务器 } else { Debug.LogWarning($"无效IP地址: {serverIp}"); } } catch (Exception ex) { Debug.LogError($"UDP接收错误: {ex.Message}"); Thread.Sleep(2000); // 等待2秒重试 } } } // 通过TCP连接到显示端服务器 private void ConnectToServer(string ip, int port) { int retryCount = 0; const int maxRetries = 5; const int retryDelayMs = 2000; while (!isConnected && retryCount < maxRetries) { try { Debug.Log($"尝试连接显示端: {ip}:{port} (尝试 {retryCount + 1}/{maxRetries})"); client = new TcpClient(); client.Connect(ip, port); // 连接到服务器 stream = client.GetStream(); isConnected = true; udpCts.Cancel(); // 连接成功后停止UDP监听线程 udpListener?.Close(); // 关闭UDP监听器 Debug.Log($"触摸端已连接到显示端: {ip}:{port}"); // 启动接收线程以处理服务器消息 receiveThread = new Thread(() => ReceiveMessages(receiveCts.Token)); receiveThread.Start(); return; } catch (Exception ex) { Debug.LogError($"连接显示端失败: {ex.Message}"); retryCount++; if (retryCount < maxRetries) { Debug.Log($"将在{retryDelayMs / 1000f}秒后重试..."); Thread.Sleep(retryDelayMs); } } } Debug.LogError($"连接显示端失败,已达到最大重试次数 ({maxRetries})"); } // 接收服务器发送的消息 private void ReceiveMessages(CancellationToken token) { byte[] message = new byte[4096]; int bytesRead; while (!token.IsCancellationRequested && isConnected) { try { bytesRead = stream.Read(message, 0, 4096); if (bytesRead == 0) { Debug.Log("服务器断开连接"); break; } string data = Encoding.UTF8.GetString(message, 0, bytesRead); if (data.StartsWith("Progress:") && float.TryParse(data.Replace("Progress:", ""), out float progress)) { lock (queueLock) { progressQueue.Enqueue(progress); // 将进度加入队列 Debug.Log($"收到进度: {progress:F2}"); } } else { Debug.LogWarning($"收到未知消息: {data}"); } } catch (Exception ex) { Debug.LogError($"接收服务器消息错误: {ex.Message}"); isConnected = false; break; } } // 清理连接 stream?.Close(); client?.Close(); isConnected = false; Debug.Log("TCP连接已断开,将重新启动UDP监听"); // 重启UDP监听 RestartUdpListener(); } // 重启UDP监听 private void RestartUdpListener() { try { udpCts = new CancellationTokenSource(); udpListener = new UdpClient(9999); udpThread = new Thread(() => ListenForBroadcast(udpCts.Token)); udpThread.Start(); Debug.Log("重启UDP监听,端口: 9999"); } catch (Exception ex) { Debug.LogError($"重启UDP监听失败: {ex.Message}"); } } // 向显示端服务器发送消息 private void SendMessageToServer(string msg) { if (isConnected && stream != null) { try { byte[] data = Encoding.UTF8.GetBytes(msg); stream.Write(data, 0, data.Length); Debug.Log($"发送指令: {msg}"); } catch (Exception ex) { Debug.LogError($"发送消息失败: {ex.Message}"); isConnected = false; RestartUdpListener(); } } else { Debug.LogWarning($"无法发送消息,未连接到服务器: {msg}"); } } // 页面按钮点击时调用 public void OnButtonClick_Page(string pageName) { ShowPage(pageName); // 切换到指定页面 SendMessageToServer("Page:" + pageName); // 通知服务器 } // 模型播放按钮点击时调用 public void OnButtonClick_PlayModel(string modelName) { // 清空进度队列,防止旧进度干扰 lock (queueLock) { progressQueue.Clear(); } // 重置Slider到0 Slider slider = GetCurrentPageSlider(); if (slider != null) { var onValueChanged = slider.onValueChanged; slider.onValueChanged = new Slider.SliderEvent(); slider.value = 0f; slider.onValueChanged = onValueChanged; lastSentProgress = 0f; // 重置最后发送的进度 Debug.Log($"播放模型 {modelName} 前重置Slider到0"); } SendMessageToServer("Model:" + modelName); // 通知服务器播放模型 } // 滑块值改变时调用 public void OnSliderChanged(Slider slider) { isDraggingSlider = true; // 标记正在拖动 float progress = slider.value / 100f; // 转换为0-1 if (progress > 0.01f) // 忽略接近0的值以避免异常 { SendMessageToServer("Progress:" + progress.ToString("F2")); // 发送进度 lastSentProgress = progress; // 记录最后发送的进度 Debug.Log($"Slider拖动到: {progress:F2}"); } } // 滑块拖动结束时调用(需在Inspector中绑定) public void OnSliderEndDrag() { Slider slider = GetCurrentPageSlider(); if (slider != null) { float progress = slider.value / 100f; if (progress > 0.01f) { SendMessageToServer("Progress:" + progress.ToString("F2")); // 发送最终进度 lastSentProgress = progress; // 更新最后发送的进度 Debug.Log($"Slider拖动结束,发送最终进度: {progress:F2}"); } } isDraggingSlider = false; // 结束拖动 SendMessageToServer("EndDrag"); // 通知服务器拖动结束 Debug.Log("Slider拖动结束"); } // 文字面板Toggle状态改变时调用 public void OnTextPanelToggleChanged(Toggle toggle) { if (toggle == null) return; bool isTextPanelVisible = toggle.isOn; Transform backgroundTransform = toggle.transform.Find("Background"); Image toggleImage = backgroundTransform != null ? backgroundTransform.GetComponent() : null; TextMeshProUGUI toggleText = toggle.GetComponentInChildren(); if (toggleImage != null) { Sprite newSprite = isTextPanelVisible ? showSprite : hideSprite; toggleImage.sprite = newSprite; Debug.Log($"Toggle sprite 切换: {currentPage}, isOn={isTextPanelVisible}, sprite={(newSprite != null ? newSprite.name : "null")}"); } else { Debug.LogWarning($"Toggle {toggle.name} 未找到Background的Image组件"); } if (toggleText != null) { toggleText.text = isTextPanelVisible ? "显示文字" : "隐藏文字"; Debug.Log($"Toggle 文本更新: {currentPage}, text={toggleText.text}"); } else { Debug.LogWarning($"Toggle {toggle.name} 未找到Text(TMP)组件"); } // 验证Toggle的Graphic字段 if (toggle.graphic != null && toggle.graphic is Image graphicImage) { Debug.Log($"Toggle Graphic验证: {currentPage}, graphic sprite={graphicImage.sprite?.name ?? "null"}"); } else { Debug.LogWarning($"Toggle {toggle.name} 的Graphic字段未正确配置或为空"); } // 发送显隐消息到显示端 string message = isTextPanelVisible ? "TextPanel:Show" : "TextPanel:Hide"; SendMessageToServer(message); Debug.Log($"文字面板Toggle切换: {message}"); } // 获取当前页面的Slider组件 private Slider GetCurrentPageSlider() { if (background == null || currentPage == "首页") return null; Transform pageTransform = background.Find(currentPage); if (pageTransform != null) { Slider slider = pageTransform.GetComponentInChildren(); if (slider != null) { if (!slider.interactable) { slider.interactable = true; // 确保Slider可交互 Debug.Log($"启用Slider交互: {currentPage}"); } return slider; } Debug.LogWarning($"未在页面 {currentPage} 找到Slider"); return null; } Debug.LogWarning($"未找到页面 {currentPage}"); return null; } // 获取当前页面的TextPanelToggle组件 private Toggle GetCurrentPageTextPanelToggle() { if (background == null || currentPage == "首页") return null; Transform pageTransform = background.Find(currentPage); if (pageTransform != null) { Toggle toggle = pageTransform.GetComponentInChildren(); if (toggle != null) { if (!toggle.interactable) { toggle.interactable = true; // 确保Toggle可交互 Debug.Log($"启用TextPanelToggle交互: {currentPage}"); } return toggle; } Debug.LogWarning($"未在页面 {currentPage} 找到TextPanelToggle"); return null; } Debug.LogWarning($"未找到页面 {currentPage}"); return null; } // 切换激活的UI页面 private void ShowPage(string pageName) { foreach (Transform child in background) { child.gameObject.SetActive(child.name == pageName); // 仅激活目标页面 } currentPage = pageName; // 清空进度队列以避免旧页面数据干扰 lock (queueLock) { progressQueue.Clear(); } // 初始化当前页面的Slider Slider slider = GetCurrentPageSlider(); if (slider != null) { // 临时禁用OnValueChanged以防止初始化触发 var onValueChanged = slider.onValueChanged; slider.onValueChanged = new Slider.SliderEvent(); slider.value = 0f; // 重置Slider到0 slider.interactable = true; // 确保可交互 // 恢复OnValueChanged并绑定 slider.onValueChanged = onValueChanged; slider.onValueChanged.RemoveAllListeners(); slider.onValueChanged.AddListener((value) => OnSliderChanged(slider)); lastSentProgress = 0f; // 重置最后发送的进度 Debug.Log($"初始化Slider: {pageName}, 值: {slider.value}"); } // 初始化当前页面的TextPanelToggle Toggle toggle = GetCurrentPageTextPanelToggle(); if (toggle != null) { // 临时禁用OnValueChanged以防止初始化触发 var onValueChanged = toggle.onValueChanged; toggle.onValueChanged = new Toggle.ToggleEvent(); toggle.isOn = true; // 默认显示文字面板 toggle.interactable = true; // 确保可交互 // 更新Sprite和文本 Transform backgroundTransform = toggle.transform.Find("Background"); Image toggleImage = backgroundTransform != null ? backgroundTransform.GetComponent() : null; TextMeshProUGUI toggleText = toggle.GetComponentInChildren(); if (toggleImage != null) { toggleImage.sprite = showSprite; Debug.Log($"初始化Toggle sprite: {pageName}, sprite={(showSprite != null ? showSprite.name : "null")}"); } else { Debug.LogWarning($"Toggle {toggle.name} 未找到Background的Image组件"); } if (toggleText != null) { toggleText.text = "显示文字"; Debug.Log($"初始化Toggle 文本: {pageName}, text={toggleText.text}"); } else { Debug.LogWarning($"Toggle {toggle.name} 未找到Text(TMP)组件"); } // 验证Toggle的Graphic字段 if (toggle.graphic != null && toggle.graphic is Image graphicImage) { Debug.Log($"初始化Toggle Graphic验证: {pageName}, graphic sprite={graphicImage.sprite?.name ?? "null"}"); } else { Debug.LogWarning($"Toggle {toggle.name} 的Graphic字段未正确配置或为空"); } // 恢复OnValueChanged并绑定 toggle.onValueChanged = onValueChanged; toggle.onValueChanged.RemoveAllListeners(); toggle.onValueChanged.AddListener((value) => OnTextPanelToggleChanged(toggle)); // 发送初始显隐状态 SendMessageToServer("TextPanel:Show"); Debug.Log($"初始化TextPanelToggle: {pageName}, isOn: {toggle.isOn}"); } Debug.Log($"切换到页面: {pageName}, Slider {(slider != null ? "找到" : "未找到")}, TextPanelToggle {(toggle != null ? "找到" : "未找到")}"); } // 在主线程中更新Slider进度 private void Update() { if (isDraggingSlider) return; // 拖动时忽略服务器进度 float progress = -1f; lock (queueLock) { if (progressQueue.Count > 0) { progress = progressQueue.Dequeue(); // 获取最新进度 } } if (progress >= 0f) { Slider slider = GetCurrentPageSlider(); if (slider != null) { // 仅当进度与当前Slider值差异较大或接近1.0时更新 float currentProgress = slider.value / 100f; if (progress > 0f || Mathf.Abs(progress - 1f) < 0.005f) { // 临时禁用OnValueChanged以防止循环触发 var onValueChanged = slider.onValueChanged; slider.onValueChanged = new Slider.SliderEvent(); slider.value = progress * 100f; // 更新Slider值(0-100) slider.onValueChanged = onValueChanged; // 恢复事件 Debug.Log($"更新Slider进度: {progress:F2}, Slider值: {slider.value}"); } } } } // 应用程序退出时清理资源 private void OnApplicationQuit() { udpCts.Cancel(); // 通知UDP线程停止 receiveCts.Cancel(); // 通知接收线程停止 if (udpThread != null && udpThread.IsAlive) { udpThread.Join(1000); // 等待UDP线程结束 } if (receiveThread != null && receiveThread.IsAlive) { receiveThread.Join(1000); // 等待接收线程结束 } udpListener?.Close(); // 关闭UDP监听器 stream?.Close(); // 关闭TCP流 client?.Close(); // 关闭TCP客户端 Debug.Log("触摸端客户端已停止"); } }