“虞渠成” a30c334bb5 设备绑定
2026-04-28 16:35:51 +08:00

320 lines
9.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections;
using System.Collections.Generic;
using Kill.UI.Components;
using UnityEngine;
using UnityEngine.UI;
namespace Kill.UI.Pages
{
/// <summary>
/// 二维码扫描器 - 调用摄像头扫描二维码
/// </summary>
public class ScanQRcode : MonoBehaviour
{
[Header("UI组件")]
public RawImage cameraPreview; // 摄像头预览
[Header("扫描设置")]
public float scanInterval = 0.2f; // 扫描间隔(秒)
// 摄像头相关
private WebCamTexture webCamTexture;
private bool isScanning = false;
// 扫描结果回调
public event Action<string> OnQRCodeScanned;
void OnEnable()
{
StartScan();
}
void OnDisable()
{
StopScan();
}
/// <summary>
/// 开始扫描
/// </summary>
public void StartScan()
{
if (isScanning) return;
StartCoroutine(InitializeCamera());
}
/// <summary>
/// 停止扫描
/// </summary>
public void StopScan()
{
isScanning = false;
StopAllCoroutines();
if (webCamTexture != null && webCamTexture.isPlaying)
{
webCamTexture.Stop();
webCamTexture = null;
}
}
/// <summary>
/// 关闭扫码界面
/// </summary>
public void Close()
{
StopScan();
gameObject.SetActive(false);
}
/// <summary>
/// 初始化摄像头
/// </summary>
private IEnumerator InitializeCamera()
{
// Android 使用 Permission API 请求权限
#if UNITY_ANDROID && !UNITY_EDITOR
if (!UnityEngine.Android.Permission.HasUserAuthorizedPermission(UnityEngine.Android.Permission.Camera))
{
UnityEngine.Android.Permission.RequestUserPermission(UnityEngine.Android.Permission.Camera);
// 等待权限请求结果
yield return new WaitUntil(() => UnityEngine.Android.Permission.HasUserAuthorizedPermission(UnityEngine.Android.Permission.Camera));
}
if (!UnityEngine.Android.Permission.HasUserAuthorizedPermission(UnityEngine.Android.Permission.Camera))
{
UpdateStatus("100085");
yield break;
}
#else
// 其他平台使用传统方式
yield return Application.RequestUserAuthorization(UserAuthorization.WebCam);
if (!Application.HasUserAuthorization(UserAuthorization.WebCam))
{
UpdateStatus("100085");
yield break;
}
#endif
// 获取后置摄像头
WebCamDevice[] devices = WebCamTexture.devices;
if (devices.Length == 0)
{
UpdateStatus("100086");
yield break;
}
// 优先使用后置摄像头
string deviceName = devices[0].name;
for (int i = 0; i < devices.Length; i++)
{
if (!devices[i].isFrontFacing)
{
deviceName = devices[i].name;
break;
}
}
// 创建摄像头纹理
webCamTexture = new WebCamTexture(deviceName, 1280, 720, 30);
cameraPreview.texture = webCamTexture;
// 开始播放
webCamTexture.Play();
// 等待摄像头启动
yield return new WaitUntil(() => webCamTexture.width > 100);
// 调整预览画面比例
AdjustPreviewAspect();
isScanning = true;
// 开始扫描
StartCoroutine(ScanCoroutine());
}
/// <summary>
/// 调整预览画面比例
/// </summary>
private void AdjustPreviewAspect()
{
if (webCamTexture == null) return;
float videoRatio = (float)webCamTexture.width / webCamTexture.height;
float screenRatio = (float)Screen.width / Screen.height;
// 根据摄像头方向调整
int rotation = webCamTexture.videoRotationAngle;
cameraPreview.rectTransform.localEulerAngles = new Vector3(0, 0, -rotation);
// 调整缩放以适应屏幕
if (rotation == 90 || rotation == 270)
{
videoRatio = 1f / videoRatio;
}
// 保持比例填充
if (videoRatio > screenRatio)
{
cameraPreview.rectTransform.localScale = new Vector3(screenRatio / videoRatio, 1, 1);
}
else
{
cameraPreview.rectTransform.localScale = new Vector3(1, videoRatio / screenRatio, 1);
}
}
/// <summary>
/// 扫描协程
/// </summary>
private IEnumerator ScanCoroutine()
{
// 等待一帧确保摄像头已准备好
yield return null;
while (isScanning && webCamTexture != null && webCamTexture.isPlaying)
{
// 获取当前帧
Texture2D snapshot = GetSnapshot();
if (snapshot != null)
{
// 解析二维码
string result = DecodeQRCode(snapshot);
Destroy(snapshot);
if (!string.IsNullOrEmpty(result))
{
// 从结果中提取 MAC 地址
string macAddress = ExtractMacAddress(result);
if (!string.IsNullOrEmpty(macAddress))
{
// 扫描成功,仅回传 MAC 地址
OnQRCodeScanned?.Invoke(macAddress);
yield break;
}
else
{
// 二维码不符合要求,显示提示
UpdateStatus("100084");
}
}
}
yield return new WaitForSeconds(scanInterval);
}
}
/// <summary>
/// 获取摄像头当前帧
/// </summary>
private Texture2D GetSnapshot()
{
if (webCamTexture == null || !webCamTexture.isPlaying) return null;
try
{
Texture2D snapshot = new Texture2D(webCamTexture.width, webCamTexture.height);
snapshot.SetPixels(webCamTexture.GetPixels());
snapshot.Apply();
return snapshot;
}
catch
{
return null;
}
}
/// <summary>
/// 解析二维码
/// </summary>
private string DecodeQRCode(Texture2D texture)
{
try
{
int width = texture.width;
int height = texture.height;
// 获取像素数据
Color32[] pixels = texture.GetPixels32();
// 转换为灰度图
byte[] grayscale = new byte[width * height];
for (int i = 0; i < pixels.Length; i++)
{
// 使用标准亮度公式: Y = 0.299R + 0.587G + 0.114B
grayscale[i] = (byte)(0.299f * pixels[i].r + 0.587f * pixels[i].g + 0.114f * pixels[i].b);
}
// 创建 LuminanceSource
var luminanceSource = new ZXing.PlanarYUVLuminanceSource(
grayscale,
width,
height,
0,
0,
width,
height,
false
);
// 创建 BinaryBitmap
var binarizer = new ZXing.Common.HybridBinarizer(luminanceSource);
var binaryBitmap = new ZXing.BinaryBitmap(binarizer);
// 解码
var reader = new ZXing.QrCode.QRCodeReader();
var hints = new Dictionary<ZXing.DecodeHintType, object>
{
{ ZXing.DecodeHintType.TRY_HARDER, true }
};
var result = reader.decode(binaryBitmap, hints);
return result?.Text;
}
catch (Exception ex)
{
Debug.LogError($"二维码解析失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 从字符串中提取 MAC 地址(格式如 98:EA:A0:02:4E:06
/// </summary>
private string ExtractMacAddress(string input)
{
if (string.IsNullOrEmpty(input))
return null;
// MAC 地址正则表达式:匹配 XX:XX:XX:XX:XX:XX 格式X 为十六进制字符)
System.Text.RegularExpressions.Regex macRegex =
new System.Text.RegularExpressions.Regex(@"[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}");
System.Text.RegularExpressions.Match match = macRegex.Match(input);
if (match.Success)
{
// 返回大写的 MAC 地址
return match.Value.ToUpper();
}
return null;
}
/// <summary>
/// 更新状态文字
/// </summary>
private void UpdateStatus(string code)
{
ToastUI.Show(code);
}
void OnDestroy()
{
StopScan();
gameObject.SetActive(false);
}
}
}