using System; using System.Collections.Generic; using System.IO; using System.Text; using Aisu.SIM1.Face.Core; using Aisu.SIM1.Face.Models; using Aisu.SIM1.Face.Protocol; using Aisu.SIM1.Face.UI.Common; using UnityEngine; using UnityEngine.UI; namespace Aisu.SIM1.Face.UI.Pages { public sealed class FaceConnectPage : FaceModulePageBase { // Inspector 绑定字段。 [SerializeField] private InputField m_portInput; // Inspector 绑定字段。 [SerializeField] private InputField m_baudRateInput; // Inspector 绑定字段。 [SerializeField] private Toggle m_mockToggle; // Inspector 绑定字段。 [SerializeField] private Text m_connectionText; // Inspector 绑定字段。 [SerializeField] private Text m_readyText; // Inspector 绑定字段。 [SerializeField] private Text m_moduleStatusText; // Inspector 绑定字段。 [SerializeField] private Text m_licenseText; // Inspector 绑定字段。 [SerializeField] private Text m_versionText; // Inspector 绑定字段。 [SerializeField] private Text m_snText; public override void Build(FaceModuleUiBuilder builder) { builder.Text(transform, "连接 / 基础状态", 30); var form = builder.Section(transform, "通信配置"); m_portInput = builder.Input(form.transform, "串口号", "COM3"); m_baudRateInput = builder.Input(form.transform, "波特率", "115200"); m_mockToggle = builder.Toggle(form.transform, "Editor 使用 Mock 模式", IsEditorMockEnabled()); var row = builder.Row(form.transform); builder.Button(row.transform, "打开连接", OpenConnection); builder.Button(row.transform, "关闭连接", CloseConnection); builder.Button(row.transform, "等待 READY", WaitReady); var actions = builder.Section(transform, "基础命令"); var actionRow = builder.Row(actions.transform); builder.Button(actionRow.transform, "RESET", ResetModule); builder.Button(actionRow.transform, "GET STATUS", GetStatus); builder.Button(actionRow.transform, "GET VERSION", GetVersion); builder.Button(actionRow.transform, "GET SN", GetSn); var info = builder.Section(transform, "状态"); m_connectionText = builder.Text(info.transform, "连接状态: 未连接"); m_readyText = builder.Text(info.transform, "READY: 未 READY"); m_moduleStatusText = builder.Text(info.transform, "模组状态: Invalid"); m_licenseText = builder.Text(info.transform, "授权: Mock 模式不需要授权"); m_versionText = builder.Text(info.transform, "版本: -"); m_snText = builder.Text(info.transform, "SN: -"); m_resultText = builder.Text(info.transform, "结果: -"); } /// 打开通信连接。 /// /// 执行 OpenConnection 方法。 /// public void OpenConnection() { RunAsync(async () => { var useMock = m_mockToggle != null && m_mockToggle.isOn; var baud = ReadInt(m_baudRateInput, 115200); if (!useMock && !ValidateLicenseBeforeOpen()) { return; } m_manager.Configure(useMock, ReadText(m_portInput), baud); m_manager.Open(); if (useMock) { await m_manager.WaitReadyAsync(); RefreshState(); SetResult("Mock 模组已连接"); return; } try { await m_manager.WaitReadyAsync(); RefreshState(); SetResult("硬件连接成功,已收到 READY"); } catch { try { var status = await m_manager.ProbeReadyByStatusAsync(); RefreshState(); m_moduleStatusText.text = "模组状态: " + status; SetResult("硬件连接成功,未收到 READY NOTE,但 GET STATUS 探测成功"); } catch { m_manager.Close(); RefreshState(); throw new TimeoutException("未收到硬件 READY,GET STATUS 也无响应,请检查串口、波特率、电源和接线"); } } }); } /// 关闭通信连接。 /// /// 执行 CloseConnection 方法。 /// public void CloseConnection() { RunAsync(async () => { await m_manager.CloseAsync(); RefreshState(); SetResult("连接已关闭"); }); } /// 等待 READY NOTE。 /// /// 执行 WaitReady 方法。 /// public void WaitReady() { RunAsync(async () => { await m_manager.WaitReadyAsync(); RefreshState(); SetResult("模组 READY"); }); } /// 复位模组。 /// /// 执行 ResetModule 方法。 /// public void ResetModule() { RunAsync(async () => { if (RequireConnected()) { await m_manager.Basic.ResetAsync(); SetResult("RESET 成功"); } }); } /// 获取模组状态。 /// /// 执行 GetStatus 方法。 /// public void GetStatus() { RunAsync(async () => { if (!RequireConnected()) return; var status = await m_manager.ProbeReadyByStatusAsync(); RefreshState(); m_moduleStatusText.text = "模组状态: " + status; SetResult("状态获取成功"); }); } /// 获取版本。 /// /// 执行 GetVersion 方法。 /// public void GetVersion() { RunAsync(async () => { if (!RequireConnected()) return; m_versionText.text = "版本: " + await m_manager.Basic.GetVersionAsync(); SetResult("版本获取成功"); }); } /// 获取 SN。 /// /// 执行 GetSn 方法。 /// public void GetSn() { RunAsync(async () => { if (!RequireConnected()) return; m_snText.text = "SN: " + await m_manager.Basic.GetSnAsync(); SetResult("SN 获取成功"); }); } /// /// 执行 RefreshState 方法。 /// private void RefreshState() { var state = m_manager.IsReady ? "硬件已连接" : (m_manager.IsOpen ? "端口已打开,未 READY" : "未连接"); m_connectionText.text = "连接状态: " + state; m_readyText.text = "READY: " + (m_manager.IsReady ? "READY" : "未 READY"); RefreshLicenseState(m_mockToggle != null && m_mockToggle.isOn); } /// /// 真实串口连接前检查授权。 /// private bool ValidateLicenseBeforeOpen() { var path = FaceLicenseManager.GetDefaultLicensePath(); var status = FaceLicenseManager.LoadAndVerify(path); if (status == FaceLicenseStatus.Valid) { RefreshLicenseState(false); return true; } var message = "授权无效,无法打开真实串口。路径: " + path + ",原因: " + FaceLicenseManager.LastError; if (m_licenseText != null) { m_licenseText.text = "授权: " + message; } SetResult(message); return false; } /// /// 刷新授权状态显示。 /// private void RefreshLicenseState(bool useMock) { if (m_licenseText == null) { return; } if (useMock) { m_licenseText.text = "授权: Mock 模式不需要授权"; return; } if (FaceLicenseManager.Status == FaceLicenseStatus.Valid && FaceLicenseManager.Payload != null) { m_licenseText.text = "授权: 有效,剩余 " + FaceLicenseVerifier.GetRemainingDays(FaceLicenseManager.Payload) + " 天"; return; } m_licenseText.text = "授权: 未验证,文件路径 " + FaceLicenseManager.GetDefaultLicensePath(); } private bool IsEditorMockEnabled() { if (m_manager == null) { return true; } return m_manager.Settings != null ? m_manager.Settings.UseMockInEditor : m_manager.Config.UseMockInEditor; } } public sealed class FaceProtocolDebugPage : FaceModulePageBase { // Inspector 绑定字段。 [SerializeField] private InputField m_hexInput; // Inspector 绑定字段。 [SerializeField] private Text m_parseText; private readonly FacePacketParser m_parser = new FacePacketParser(); private FaceModuleManager m_boundManager; // 已订阅事件的 Manager。 public override void Build(FaceModuleUiBuilder builder) { builder.Text(transform, "协议调试", 30); m_hexInput = builder.Input(transform, "EF AA 10 00 00 10", "EF AA 10 00 00 10"); var row = builder.Row(transform); builder.Button(row.transform, "发送 Hex", SendHex); builder.Button(row.transform, "清空输入", () => m_hexInput.text = string.Empty); m_parseText = builder.Text(transform, "解析结果: -", 20, TextAnchor.UpperLeft); m_resultText = builder.Text(transform, "结果: -"); } public override void Init(FaceModuleManager manager, FaceModuleLogPanel logPanel, FaceModuleLoadingView loadingView) { UnbindManagerEvents(); base.Init(manager, logPanel, loadingView); m_boundManager = m_manager; m_manager.OnRawLog += ParseRaw; } private void OnDestroy() { UnbindManagerEvents(); } /// 发送原始 Hex 数据。 /// /// 执行 SendHex 方法。 /// public void SendHex() { try { if (!RequireConnected()) return; var bytes = ParseHex(ReadText(m_hexInput)); m_manager.SendRawBytes(bytes); SetResult("Hex 已发送"); } catch (Exception ex) { SetError(ex); } } /// /// 执行 ParseRaw 方法。 /// private void ParseRaw(string line) { var index = line.IndexOf(' '); if (index < 0) return; try { var packets = m_parser.Push(ParseHex(line.Substring(index + 1))); foreach (var packet in packets) { m_parseText.text = $"MsgID=0x{packet.MsgId:X2}\nSize={packet.Size}\nData={BitConverter.ToString(packet.Data)}\nChecksum=0x{packet.Checksum:X2}\nValid={packet.IsValid}"; } } catch { } } /// /// 执行 ParseHex 方法。 /// private static byte[] ParseHex(string text) { var parts = text.Split(new[] { ' ', '\t', '\r', '\n', '-' }, StringSplitOptions.RemoveEmptyEntries); var bytes = new byte[parts.Length]; for (var i = 0; i < parts.Length; i++) { bytes[i] = Convert.ToByte(parts[i], 16); } return bytes; } private void UnbindManagerEvents() { if (m_boundManager != null) { m_boundManager.OnRawLog -= ParseRaw; m_boundManager = null; } } } public sealed class FaceVerifyPage : FaceModulePageBase { // 内部状态字段。 private InputField m_timeoutInput; // 内部状态字段。 private InputField m_maxTimesInput; // 内部状态字段。 private Toggle m_autoToggle; // 内部状态字段。 private Text m_faceStateText; // 内部状态字段。 private Text m_poseText; // 内部状态字段。 private Text m_boxText; // 内部状态字段。 private Text m_userIdText; // 内部状态字段。 private Text m_userNameText; // 内部状态字段。 private Text m_adminText; // 内部状态字段。 private Text m_unlockText; private FaceModuleManager m_boundManager; // 已订阅事件的 Manager。 public override void Build(FaceModuleUiBuilder builder) { builder.Text(transform, "人脸验证", 30); m_timeoutInput = builder.Input(transform, "超时时间", "10"); m_maxTimesInput = builder.Input(transform, "最大识别次数", "30"); m_autoToggle = builder.Toggle(transform, "自动探测验证", false); var row = builder.Row(transform); builder.Button(row.transform, "开始验证", StartVerify); builder.Button(row.transform, "停止 / RESET", StopVerify); m_faceStateText = builder.Text(transform, "人脸状态: -"); m_poseText = builder.Text(transform, "Yaw/Pitch/Roll: -"); m_boxText = builder.Text(transform, "人脸框: -"); m_userIdText = builder.Text(transform, "UserID: -"); m_userNameText = builder.Text(transform, "UserName: -"); m_adminText = builder.Text(transform, "Admin: -"); m_unlockText = builder.Text(transform, "UnlockStatus: -"); m_resultText = builder.Text(transform, "结果: -"); } public override void Init(FaceModuleManager manager, FaceModuleLogPanel logPanel, FaceModuleLoadingView loadingView) { UnbindManagerEvents(); base.Init(manager, logPanel, loadingView); m_boundManager = m_manager; m_manager.OnFaceStateUpdated += OnFaceState; m_manager.OnAutoVerifyResult += OnAutoVerifyResult; } private void OnDestroy() { UnbindManagerEvents(); } /// 开始验证或自动验证。 /// /// 执行 StartVerify 方法。 /// public void StartVerify() { RunAsync(async () => { if (!RequireReady()) return; if (m_autoToggle.isOn) { await m_manager.StartAutoVerifyAsync(ReadByte(m_timeoutInput, 10)); SetResult("自动验证已开启"); return; } var result = await m_manager.Verify.VerifyAsync(ReadByte(m_timeoutInput, 10), ReadByte(m_maxTimesInput, 30)); ShowResult(result); }); } /// 停止验证。 /// /// 执行 StopVerify 方法。 /// public void StopVerify() { RunAsync(async () => { if (!RequireConnected()) return; await m_manager.StopFaceOperationAsync(); SetResult("已停止验证"); }); } /// /// 执行 OnFaceState 方法。 /// private void OnFaceState(FaceFaceStateNote state) { m_faceStateText.text = "人脸状态: " + FaceProtocolEncoding.FaceStateMessage(state.State); m_poseText.text = $"Yaw/Pitch/Roll: {state.Yaw}/{state.Pitch}/{state.Roll}"; m_boxText.text = $"人脸框: L{state.Left} T{state.Top} R{state.Right} B{state.Bottom}"; } /// /// 执行 ShowResult 方法。 /// private void ShowResult(FaceVerifyResult result) { m_userIdText.text = "UserID: " + result.UserId; m_userNameText.text = "UserName: " + result.UserName; m_adminText.text = "Admin: " + result.IsAdmin; m_unlockText.text = "UnlockStatus: " + result.UnlockStatus; SetResult(result.Success ? "验证成功" : FaceProtocolEncoding.ResultMessage(result.ResultCode)); } private void OnAutoVerifyResult(FaceAutoVerifyResult result) { ShowResult(result.VerifyResult); } private void UnbindManagerEvents() { if (m_boundManager != null) { m_boundManager.OnFaceStateUpdated -= OnFaceState; m_boundManager.OnAutoVerifyResult -= OnAutoVerifyResult; m_boundManager = null; } } } public sealed class FaceEnrollPage : FaceModulePageBase { // 内部状态字段。 private InputField m_nameInput; // 内部状态字段。 private Toggle m_adminToggle; // 内部状态字段。 private Dropdown m_modeDropdown; // 内部状态字段。 private Dropdown m_directionDropdown; // 内部状态字段。 private Dropdown m_enrollTypeDropdown; // 内部状态字段。 private Toggle m_duplicateToggle; // 内部状态字段。 private InputField m_timeoutInput; // 内部状态字段。 private Text m_faceStateText; // 内部状态字段。 private Text m_poseText; // 内部状态字段。 private Text m_directionText; // 内部状态字段。 private Text m_userIdText; private FaceModuleManager m_boundManager; // 已订阅事件的 Manager。 private Action m_faceStateHandler; // 人脸状态事件回调。 public override void Build(FaceModuleUiBuilder builder) { builder.Text(transform, "人脸录入", 30); m_nameInput = builder.Input(transform, "用户姓名", "User"); m_adminToggle = builder.Toggle(transform, "管理员", false); m_modeDropdown = builder.Dropdown(transform, new[] { "交互录入 ENROLL", "单帧录入 ENROLL_SINGLE", "集成录入 ENROLL_ITG" }); m_directionDropdown = builder.Dropdown(transform, new[] { "正脸", "朝上", "朝下", "朝左", "朝右" }); m_enrollTypeDropdown = builder.Dropdown(transform, new[] { "交互", "单帧" }); m_duplicateToggle = builder.Toggle(transform, "允许重复录入", false); m_timeoutInput = builder.Input(transform, "超时时间", "20"); var row = builder.Row(transform); builder.Button(row.transform, "开始录入", StartEnroll); builder.Button(row.transform, "取消录入", CancelEnroll); m_faceStateText = builder.Text(transform, "人脸状态: -"); m_poseText = builder.Text(transform, "姿态: -"); m_directionText = builder.Text(transform, "已录入方向: -"); m_userIdText = builder.Text(transform, "最终 UserID: -"); m_resultText = builder.Text(transform, "ResultCode: -"); } public override void Init(FaceModuleManager manager, FaceModuleLogPanel logPanel, FaceModuleLoadingView loadingView) { UnbindManagerEvents(); base.Init(manager, logPanel, loadingView); m_boundManager = m_manager; m_faceStateHandler = state => { m_faceStateText.text = FaceProtocolEncoding.FaceStateMessage(state.State); m_poseText.text = $"Yaw/Pitch/Roll: {state.Yaw}/{state.Pitch}/{state.Roll}"; }; m_manager.OnFaceStateUpdated += m_faceStateHandler; } private void OnDestroy() { UnbindManagerEvents(); } /// 开始录入。 /// /// 执行 StartEnroll 方法。 /// public void StartEnroll() { RunAsync(async () => { if (!RequireReady()) return; if (FaceProtocolEncoding.GetUserNameByteCount(ReadText(m_nameInput)) > FaceModuleConstants.UserNameSize) { SetResult("用户名 UTF-8 编码超过 32 bytes"); return; } await m_manager.EnsureExclusiveOperationReadyAsync("人脸录入"); FaceEnrollResult result; if (m_modeDropdown.value == 1) { result = await m_manager.Enrollment.EnrollSingleAsync(m_adminToggle.isOn, ReadText(m_nameInput), ReadByte(m_timeoutInput, 20)); } else if (m_modeDropdown.value == 2) { result = await m_manager.Enrollment.EnrollIntegratedAsync(m_adminToggle.isOn, ReadText(m_nameInput), Direction(), ReadByte(m_timeoutInput, 20), m_enrollTypeDropdown.value == 1, !m_duplicateToggle.isOn); } else { result = await m_manager.Enrollment.EnrollAsync(m_adminToggle.isOn, ReadText(m_nameInput), Direction(), ReadByte(m_timeoutInput, 20)); } m_userIdText.text = "最终 UserID: " + result.UserId; m_directionText.text = "已录入方向: " + DirectionText(result.Direction); if (result.Success) { await ConfirmEnrollResult(result.UserId); } else { SetResult(FaceProtocolEncoding.ResultMessage(result.ResultCode)); } }); } /// /// 录入成功后读取用户信息,确认模组已写入用户表。 /// private async System.Threading.Tasks.Task ConfirmEnrollResult(int userId) { try { var info = await m_manager.Users.GetUserInfoAsync(userId); SetResult("录入成功,已确认用户: ID=" + info.UserId + " 姓名=" + info.UserName + " 管理员=" + info.IsAdmin); } catch (Exception ex) { SetResult("录入成功,但查询用户确认失败: " + ex.Message); } } /// 取消录入。 /// /// 执行 CancelEnroll 方法。 /// public void CancelEnroll() { RunAsync(async () => { if (RequireConnected()) { await m_manager.EnsureExclusiveOperationReadyAsync("取消录入"); await m_manager.Basic.FaceResetAsync(); SetResult("已取消录入"); } }); } /// /// 执行 Direction 方法。 /// private FaceDirection Direction() { switch (m_directionDropdown.value) { case 1: return FaceDirection.Up; case 2: return FaceDirection.Down; case 3: return FaceDirection.Left; case 4: return FaceDirection.Right; default: return FaceDirection.Middle; } } /// /// 执行 DirectionText 方法。 /// private string DirectionText(FaceDirection direction) { return direction.ToString(); } private void UnbindManagerEvents() { if (m_boundManager != null && m_faceStateHandler != null) { m_boundManager.OnFaceStateUpdated -= m_faceStateHandler; } m_boundManager = null; m_faceStateHandler = null; } } public sealed class FaceUserManagePage : FaceModulePageBase { // 内部状态字段。 private InputField m_userIdInput; // 内部状态字段。 private Text m_listText; // 内部状态字段。 private Text m_detailText; // 内部状态字段。 private FaceModuleConfirmDialog m_confirmDialog; public override void Build(FaceModuleUiBuilder builder) { builder.Text(transform, "用户管理", 30); m_userIdInput = builder.Input(transform, "UserID", "1"); var row = builder.Row(transform); builder.Button(row.transform, "查询用户", QueryUser); builder.Button(row.transform, "删除用户", DeleteUser); builder.Button(row.transform, "删除全部", DeleteAll); var row2 = builder.Row(transform); builder.Button(row2.transform, "获取所有 ID", QueryAll); builder.Button(row2.transform, "获取所有 ID2", QueryAll2); var scroll = builder.ScrollView(transform, out m_listText); scroll.GetComponent().preferredHeight = 240; m_detailText = builder.Text(transform, "用户详情: -"); m_resultText = builder.Text(transform, "结果: -"); } public override void Init(FaceModuleManager manager, FaceModuleLogPanel logPanel, FaceModuleLoadingView loadingView) { base.Init(manager, logPanel, loadingView); m_confirmDialog = FindObjectOfType(true); } /// /// 执行 QueryUser 方法。 /// public void QueryUser() { RunAsync(async () => { if (!RequireReady()) return; await m_manager.EnsureExclusiveOperationReadyAsync("查询用户"); var info = await m_manager.Users.GetUserInfoAsync(ReadInt(m_userIdInput, 0)); m_detailText.text = $"用户详情: ID={info.UserId} Name={info.UserName} Admin={info.IsAdmin}"; SetResult("查询成功"); }); } /// /// 执行 DeleteUser 方法。 /// public void DeleteUser() { RunAsync(async () => { if (!RequireReady()) return; await m_manager.EnsureExclusiveOperationReadyAsync("删除用户"); await m_manager.Users.DeleteUserAsync(ReadInt(m_userIdInput, 0)); SetResult("删除成功"); await RefreshIds(false); }); } /// /// 执行 DeleteAll 方法。 /// public void DeleteAll() { if (m_confirmDialog != null) { m_confirmDialog.Show("确定删除全部用户?该操作不可撤销。", DeleteAllConfirmed); return; } DeleteAllConfirmed(); } /// /// 执行 DeleteAllConfirmed 方法。 /// private void DeleteAllConfirmed() { RunAsync(async () => { if (!RequireReady()) return; await m_manager.EnsureExclusiveOperationReadyAsync("删除全部用户"); await m_manager.Users.DeleteAllAsync(); SetResult("全部用户已删除"); await RefreshIds(false); }); } /// /// 执行 QueryAll 方法。 /// public void QueryAll() { RunAsync(async () => { if (RequireReady()) await RefreshIds(false); }); } /// /// 执行 QueryAll2 方法。 /// public void QueryAll2() { RunAsync(async () => { if (RequireReady()) await RefreshIds(true); }); } /// /// 执行 RefreshIds 方法。 /// private async System.Threading.Tasks.Task RefreshIds(bool useV2) { await m_manager.EnsureExclusiveOperationReadyAsync("刷新用户列表"); var ids = useV2 ? await m_manager.Users.GetAllUserIds2Async() : await m_manager.Users.GetAllUserIdsAsync(); m_listText.text = string.Join("\n", ids.ConvertAll(id => "UserID: " + id)); SetResult("用户列表已刷新"); } } public sealed class FaceQrCodePage : FaceModulePageBase { // 内部状态字段。 private InputField m_timeoutInput; // 内部状态字段。 private Text m_qrText; private FaceModuleManager m_boundManager; // 已订阅事件的 Manager。 public override void Build(FaceModuleUiBuilder builder) { builder.Text(transform, "二维码", 30); m_timeoutInput = builder.Input(transform, "扫码超时时间", "10"); var row = builder.Row(transform); builder.Button(row.transform, "开始扫码", Scan); builder.Button(row.transform, "自动探测二维码", AutoScan); builder.Button(row.transform, "停止", Stop); builder.Button(row.transform, "复制内容", Copy); m_qrText = builder.Text(transform, "二维码内容: -", 22, TextAnchor.UpperLeft); m_resultText = builder.Text(transform, "扫码状态: -"); } public override void Init(FaceModuleManager manager, FaceModuleLogPanel logPanel, FaceModuleLoadingView loadingView) { UnbindManagerEvents(); base.Init(manager, logPanel, loadingView); m_boundManager = m_manager; m_manager.OnQrCodeReceived += OnQrCodeReceived; } private void OnDestroy() { UnbindManagerEvents(); } /// /// 执行 Scan 方法。 /// public void Scan() { RunAsync(async () => { if (!RequireReady()) return; await m_manager.EnsureExclusiveOperationReadyAsync("扫码"); var qr = await m_manager.QrCode.ScanQrCodeAsync(ReadByte(m_timeoutInput, 10)); m_qrText.text = "二维码内容: " + qr; SetResult(string.IsNullOrEmpty(qr) ? "二维码为空" : "扫码成功"); }); } /// /// 执行 AutoScan 方法。 /// public void AutoScan() { RunAsync(async () => { if (RequireReady()) { await m_manager.StartAutoVerifyAsync(ReadByte(m_timeoutInput, 10)); SetResult("自动探测已启动"); } }); } /// /// 执行 Stop 方法。 /// public void Stop() { RunAsync(async () => { if (!RequireConnected()) return; await m_manager.StopFaceOperationAsync(); SetResult("已停止"); }); } /// /// 执行 Copy 方法。 /// public void Copy() { GUIUtility.systemCopyBuffer = m_qrText.text.Replace("二维码内容: ", string.Empty); SetResult("已复制到剪贴板"); } private void OnQrCodeReceived(string qr) { m_qrText.text = "二维码内容: " + qr; } private void UnbindManagerEvents() { if (m_boundManager != null) { m_boundManager.OnQrCodeReceived -= OnQrCodeReceived; m_boundManager = null; } } } public sealed class FaceImagePage : FaceModulePageBase { // 内部状态字段。 private Dropdown m_dpiDropdown; // 内部状态字段。 private Slider m_progress; // 内部状态字段。 private Text m_sizeText; // 内部状态字段。 private RawImage m_preview; // 内部状态字段。 private Text m_userIdText; // 内部状态字段。 private byte[] m_lastBytes; // 内部状态字段。 private string m_lastType; public override void Build(FaceModuleUiBuilder builder) { builder.Text(transform, "图片抓拍", 30); m_dpiDropdown = builder.Dropdown(transform, new[] { "480x640", "600x800", "1200x1600" }); var row = builder.Row(transform); builder.Button(row.transform, "抓拍普通图片", () => Snap("Normal")); builder.Button(row.transform, "抓拍人脸图片", () => Snap("Face")); builder.Button(row.transform, "抓拍大图", () => Snap("Large")); builder.Button(row.transform, "保存图片", SaveImage); builder.Button(row.transform, "清空图片", ClearImage); m_progress = builder.Slider(transform); m_sizeText = builder.Text(transform, "图片大小: -"); m_userIdText = builder.Text(transform, "本地匹配 UserID: -"); var previewGo = FaceModuleUiBuilder.Create("Preview", transform); previewGo.AddComponent().preferredHeight = 360; previewGo.AddComponent().color = Color.white; m_preview = FaceModuleUiBuilder.Create("RawImage", previewGo.transform).AddComponent(); FaceModuleUiBuilder.Stretch(m_preview.rectTransform); m_resultText = builder.Text(transform, "结果: -"); } /// /// 执行 Snap 方法。 /// public void Snap(string type) { RunAsync(async () => { if (!RequireReady()) return; await m_manager.EnsureExclusiveOperationReadyAsync("图片抓拍"); FaceImageResult result; if (type == "Face") result = await m_manager.Images.SnapUploadFaceImageAsync(); else if (type == "Large") result = await m_manager.Images.SnapUploadLargeImageAsync((ImageDpi)m_dpiDropdown.value); else result = await m_manager.Images.SnapUploadImageAsync(); m_lastBytes = result.Bytes; m_lastType = type; m_preview.texture = result.Texture; m_sizeText.text = "图片大小: " + (m_lastBytes == null ? 0 : m_lastBytes.Length) + " bytes"; m_userIdText.text = "本地匹配 UserID: " + result.UserId; m_progress.value = 1; SetResult(result.Bytes.Length > 0 ? "图片接收完成" : "抓拍命令已完成,未收到图片数据"); }); } /// /// 执行 SaveImage 方法。 /// public void SaveImage() { if (m_lastBytes == null || m_lastBytes.Length == 0) { SetResult("没有可保存的图片"); return; } var dir = Path.Combine(Application.persistentDataPath, "FaceModuleImages"); Directory.CreateDirectory(dir); File.WriteAllBytes(Path.Combine(dir, DateTime.Now.ToString("yyyyMMdd_HHmmss_") + m_lastType + ImageExtension(m_lastBytes)), m_lastBytes); SetResult("图片已保存: " + dir); } /// /// 执行 ClearImage 方法。 /// public void ClearImage() { m_lastBytes = null; m_preview.texture = null; m_sizeText.text = "图片大小: -"; m_userIdText.text = "本地匹配 UserID: -"; m_progress.value = 0; SetResult("已清空图片"); } private static string ImageExtension(byte[] bytes) { if (bytes != null && bytes.Length >= 4 && bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47) { return ".png"; } return ".jpg"; } } }