2026-04-28 16:35:51 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-18 08:42:33 +08:00
|
|
|
|
Vector2 size=GetComponent<RectTransform>().sizeDelta;
|
2026-04-28 16:35:51 +08:00
|
|
|
|
// 创建摄像头纹理
|
2026-05-18 08:42:33 +08:00
|
|
|
|
webCamTexture = new WebCamTexture(deviceName, (int)size.x, (int)size.y, 30);
|
2026-04-28 16:35:51 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|