feat(iOS): 新增原生相机对焦插件并适配扫码页面

1. 新增iOS平台原生相机对焦辅助插件,支持开启连续自动对焦和点击单点对焦
2. 适配扫码页面,iOS平台使用原生插件实现对焦功能,修复Unity自带autoFocusPoint无效问题
3. 添加点击屏幕触发单点对焦,1.5秒后自动恢复连续对焦
4. 新增定时维护对焦状态的协程,防止系统重置对焦模式
This commit is contained in:
“虞渠成” 2026-07-23 15:48:01 +08:00
parent 8972a69e86
commit 807f940cfb
3 changed files with 155 additions and 7 deletions

View File

@ -0,0 +1,71 @@
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
/// 获取当前正在使用的后置摄像头设备
static AVCaptureDevice *GetActiveRearCamera(void)
{
// 遍历所有正在运行的 AVCaptureSession找到后置摄像头的输入
if (@available(iOS 10.0, *)) {
AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession
discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera]
mediaType:AVMediaTypeVideo
position:AVCaptureDevicePositionBack];
for (AVCaptureDevice *d in session.devices) {
if (d.deviceType == AVCaptureDeviceTypeBuiltInWideAngleCamera) {
return d;
}
}
}
return [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
}
extern "C" {
/// 开启连续自动对焦
void _EnableContinuousAutoFocus(void)
{
AVCaptureDevice *device = GetActiveRearCamera();
if (!device) return;
NSError *error = nil;
if ([device lockForConfiguration:&error]) {
if ([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
device.focusMode = AVCaptureFocusModeContinuousAutoFocus;
NSLog(@"[CameraFocusHelper] 已开启连续自动对焦");
}
if ([device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
device.exposureMode = AVCaptureExposureModeContinuousAutoExposure;
}
[device unlockForConfiguration];
} else {
NSLog(@"[CameraFocusHelper] 配置失败: %@", error.localizedDescription);
}
}
/// 触发单次自动对焦(点击对焦用)
void _TriggerAutoFocus(float normalizedX, float normalizedY)
{
AVCaptureDevice *device = GetActiveRearCamera();
if (!device) return;
NSError *error = nil;
if ([device lockForConfiguration:&error]) {
if ([device isFocusPointOfInterestSupported]) {
device.focusPointOfInterest = CGPointMake(normalizedX, normalizedY);
device.focusMode = AVCaptureFocusModeAutoFocus;
}
if ([device isExposurePointOfInterestSupported]) {
device.exposurePointOfInterest = CGPointMake(normalizedX, normalizedY);
device.exposureMode = AVCaptureExposureModeAutoExpose;
}
[device unlockForConfiguration];
// 1.5秒后恢复连续对焦
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
_EnableContinuousAutoFocus();
});
}
}
}

View File

@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: bfac996797a54a14fa60abaaaf887145
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices;
using Kill.UI.Components; using Kill.UI.Components;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
@ -41,6 +42,15 @@ namespace Kill.UI.Pages
public bool enableAutoFocus = true; public bool enableAutoFocus = true;
[Tooltip("对焦间隔(秒)")] [Tooltip("对焦间隔(秒)")]
public float focusInterval = 1f; public float focusInterval = 1f;
#if UNITY_IOS && !UNITY_EDITOR
[DllImport("__Internal")]
private static extern void _EnableContinuousAutoFocus();
[DllImport("__Internal")]
private static extern void _TriggerAutoFocus(float normalizedX, float normalizedY);
#endif
// 摄像头相关 // 摄像头相关
private WebCamTexture webCamTexture; private WebCamTexture webCamTexture;
private WebCamDevice currentDevice; private WebCamDevice currentDevice;
@ -60,6 +70,22 @@ namespace Kill.UI.Pages
StopScan(); StopScan();
} }
void Update()
{
#if UNITY_IOS && !UNITY_EDITOR
if (isScanning && Input.touchCount > 0)
{
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
float normX = touch.position.x / Screen.width;
float normY = touch.position.y / Screen.height;
_TriggerAutoFocus(normX, normY);
}
}
#endif
}
/// <summary> /// <summary>
/// 开始扫描 /// 开始扫描
/// </summary> /// </summary>
@ -151,18 +177,22 @@ namespace Kill.UI.Pages
} }
} }
RectTransform cameraPreviewRect=cameraPreview.GetComponent<RectTransform>(); RectTransform cameraPreviewRect=cameraPreview.GetComponent<RectTransform>();
// 使用较低的扫描分辨率以提高性能 // 使用 1080p 分辨率
int targetWidth = 2160; webCamTexture = new WebCamTexture(deviceName, 1920, 1080, 30);
// 创建摄像头纹理
webCamTexture = new WebCamTexture(deviceName, targetWidth, 30);
// 开始播放 // 开始播放
webCamTexture.Play(); webCamTexture.Play();
// 等待摄像头启动 // 等待摄像头启动
yield return new WaitUntil(() => webCamTexture.width > 100); yield return new WaitUntil(() => webCamTexture.width > 100);
webCamTexture.autoFocusPoint=new Vector2(0.5f,0.5f);
#if UNITY_IOS && !UNITY_EDITOR
// iOS: 通过原生插件开启连续自动对焦autoFocusPoint 在 iOS 无效)
_EnableContinuousAutoFocus();
// 定期维护对焦状态,防止被系统重置
StartCoroutine(IOsFocusMaintainCoroutine());
#endif
webCamTexture.autoFocusPoint = new Vector2(0.5f, 0.5f);
// 调整预览画面比例 // 调整预览画面比例
AdjustPreviewAspect(); AdjustPreviewAspect();
@ -215,7 +245,21 @@ namespace Kill.UI.Pages
Debug.Log($"[ScanQRcode] 预览调整 - 相机: {cameraWidth}x{cameraHeight}, 预览: {previewWidth}x{previewHeight}"); Debug.Log($"[ScanQRcode] 预览调整 - 相机: {cameraWidth}x{cameraHeight}, 预览: {previewWidth}x{previewHeight}");
} }
#if UNITY_IOS && !UNITY_EDITOR
/// <summary>
/// iOS 对焦维护协程 — 定期重新设置连续自动对焦,防止被系统重置
/// </summary>
private IEnumerator IOsFocusMaintainCoroutine()
{
while (isScanning)
{
yield return new WaitForSeconds(3f);
_EnableContinuousAutoFocus();
}
}
#endif
private Texture2D previewTexture; private Texture2D previewTexture;
/// <summary> /// <summary>