425 lines
14 KiB
C#
425 lines
14 KiB
C#
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<float> progressQueue = new Queue<float>(); // 线程安全的进度队列
|
||
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);
|
||
|
||
// 在端口9999上初始化UDP监听
|
||
udpListener = new UdpClient(9999);
|
||
udpThread = new Thread(() => ListenForBroadcast(udpCts.Token));
|
||
udpThread.Start();
|
||
|
||
Debug.Log("触摸端等待显示端广播...");
|
||
|
||
// 显示初始页面
|
||
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 serverIp = Encoding.UTF8.GetString(data);
|
||
|
||
Debug.Log("发现显示端IP: " + serverIp);
|
||
ConnectToServer(serverIp, 8888); // 尝试连接到服务器
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError($"UDP接收错误: {ex.Message}");
|
||
Thread.Sleep(1000); // 避免快速重试
|
||
}
|
||
}
|
||
}
|
||
|
||
// 通过TCP连接到显示端服务器
|
||
private void ConnectToServer(string ip, int port)
|
||
{
|
||
try
|
||
{
|
||
client = new TcpClient();
|
||
client.Connect(ip, port); // 连接到服务器
|
||
stream = client.GetStream();
|
||
isConnected = true;
|
||
udpCts.Cancel(); // 连接成功后停止UDP监听线程
|
||
udpListener?.Close(); // 关闭UDP监听器
|
||
Debug.Log("触摸端已连接到显示端: " + ip);
|
||
|
||
// 启动接收线程以处理服务器消息
|
||
receiveThread = new Thread(() => ReceiveMessages(receiveCts.Token));
|
||
receiveThread.Start();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError($"连接显示端失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 接收服务器发送的消息
|
||
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) 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}");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError($"接收服务器消息错误: {ex.Message}");
|
||
isConnected = false;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 向显示端服务器发送消息
|
||
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; // 标记连接失效
|
||
}
|
||
}
|
||
}
|
||
|
||
// 页面按钮点击时调用
|
||
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;
|
||
Image toggleImage = toggle.graphic as Image; // 使用Toggle的graphic字段
|
||
TextMeshProUGUI toggleText = toggle.GetComponentInChildren<TextMeshProUGUI>();
|
||
|
||
if (toggleImage != null)
|
||
{
|
||
toggleImage.sprite = isTextPanelVisible ? showSprite : hideSprite;
|
||
Debug.Log($"Toggle sprite 切换: {currentPage}, isOn={isTextPanelVisible}, sprite={(isTextPanelVisible ? showSprite : hideSprite).name}");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"Toggle {toggle.name} 未找到graphic Image组件");
|
||
}
|
||
|
||
if (toggleText != null)
|
||
{
|
||
toggleText.text = isTextPanelVisible ? "显示文字" : "隐藏文字";
|
||
Debug.Log($"Toggle 文本更新: {currentPage}, text={toggleText.text}");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"Toggle {toggle.name} 未找到TextMeshProUGUI组件");
|
||
}
|
||
|
||
// 发送显隐消息到显示端
|
||
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<Slider>();
|
||
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<Toggle>();
|
||
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和文本
|
||
Image toggleImage = toggle.graphic as Image; // 使用Toggle的graphic字段
|
||
TextMeshProUGUI toggleText = toggle.GetComponentInChildren<TextMeshProUGUI>();
|
||
if (toggleImage != null)
|
||
{
|
||
toggleImage.sprite = showSprite;
|
||
Debug.Log($"初始化Toggle sprite: {pageName}, sprite={showSprite.name}");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"Toggle {toggle.name} 未找到graphic Image组件");
|
||
}
|
||
if (toggleText != null)
|
||
{
|
||
toggleText.text = "显示文字";
|
||
Debug.Log($"初始化Toggle 文本: {pageName}, text={toggleText.text}");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"Toggle {toggle.name} 未找到TextMeshProUGUI组件");
|
||
}
|
||
// 恢复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客户端
|
||
}
|
||
} |