#import #import @interface NativeCameraController : NSObject @property (nonatomic, strong) AVCaptureSession *session; @property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer; @property (nonatomic, copy) NSString *gameObjectName; @property (nonatomic, assign) BOOL hasScanned; @end @implementation NativeCameraController - (void)startWithName:(NSString *)goName { self.gameObjectName = goName; self.hasScanned = NO; // 1. 创建 session self.session = [[AVCaptureSession alloc] init]; self.session.sessionPreset = AVCaptureSessionPresetHigh; // 2. 获取后置摄像头 AVCaptureDevice *device = [self findRearCamera]; if (!device) { [self sendToUnity:@"ERROR:NO_CAMERA"]; return; } NSLog(@"[NativeCamera] 使用摄像头: %@", device.localizedName); // 3. 配置连续自动对焦 NSError *error = nil; if ([device lockForConfiguration:&error]) { if ([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) { device.focusMode = AVCaptureFocusModeContinuousAutoFocus; } if ([device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) { device.exposureMode = AVCaptureExposureModeContinuousAutoExposure; } if ([device respondsToSelector:@selector(isAutoFocusRangeRestrictionSupported)] && [device isAutoFocusRangeRestrictionSupported]) { device.autoFocusRangeRestriction = AVCaptureAutoFocusRangeRestrictionNear; } [device unlockForConfiguration]; NSLog(@"[NativeCamera] 已启用连续自动对焦"); } else { NSLog(@"[NativeCamera] 配置摄像头失败: %@", error.localizedDescription); } // 4. 输入 AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; if (!input) { [self sendToUnity:[NSString stringWithFormat:@"ERROR:INPUT:%@", error.localizedDescription]]; return; } [self.session addInput:input]; // 5. 元数据输出(二维码检测) AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init]; [self.session addOutput:metadataOutput]; [metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; metadataOutput.metadataObjectTypes = @[AVMetadataObjectTypeQRCode]; // 6. 预览层 — 插入到 Unity 视图底层 UIViewController *rootVC = UnityGetGLViewController(); self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.session]; self.previewLayer.frame = rootVC.view.bounds; self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; // 插入到最底层,Unity UI 正常渲染在上层 [rootVC.view.layer insertSublayer:self.previewLayer atIndex:0]; // 7. 启动 session dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self.session startRunning]; dispatch_async(dispatch_get_main_queue(), ^{ [self sendToUnity:@"CAMERA_READY"]; }); }); } /// 选择后置主摄(排除超广角/长焦) - (AVCaptureDevice *)findRearCamera { if (@available(iOS 10.0, *)) { AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeBuiltInTelephotoCamera, AVCaptureDeviceTypeBuiltInUltraWideCamera] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack]; // 优先宽角(主摄) for (AVCaptureDevice *d in session.devices) { if (d.deviceType == AVCaptureDeviceTypeBuiltInWideAngleCamera) { return d; } } // 兜底:返回第一个 return session.devices.firstObject; } return [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; } - (void)stop { self.hasScanned = YES; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self.session stopRunning]; }); [self.previewLayer removeFromSuperlayer]; self.previewLayer = nil; self.session = nil; } - (void)sendToUnity:(NSString *)msg { if (self.gameObjectName) { UnitySendMessage([self.gameObjectName UTF8String], "OnNativeScanResult", [msg UTF8String]); } } #pragma mark - AVCaptureMetadataOutputObjectsDelegate - (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection { if (self.hasScanned) return; if (metadataObjects.count == 0) return; AVMetadataMachineReadableCodeObject *code = metadataObjects.firstObject; if (![code isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) return; if (!code.stringValue) return; self.hasScanned = YES; NSLog(@"[NativeCamera] 扫描到二维码: %@", code.stringValue); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self.session stopRunning]; }); [self sendToUnity:code.stringValue]; } @end // ──── 单例管理 ──── static NativeCameraController *g_cameraController = nil; extern "C" { void _StartNativeCamera(const char *gameObjectName) { if (g_cameraController) { [g_cameraController stop]; g_cameraController = nil; } // 权限请求 AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; if (status == AVAuthorizationStatusNotDetermined) { [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { if (granted) { g_cameraController = [[NativeCameraController alloc] init]; NSString *goName = [NSString stringWithUTF8String:gameObjectName]; [g_cameraController startWithName:goName]; } else { UnitySendMessage((char *)gameObjectName, "OnNativeScanResult", "ERROR:PERMISSION_DENIED"); } }]; return; } if (status != AVAuthorizationStatusAuthorized) { UnitySendMessage((char *)gameObjectName, "OnNativeScanResult", "ERROR:PERMISSION_DENIED"); return; } g_cameraController = [[NativeCameraController alloc] init]; NSString *goName = [NSString stringWithUTF8String:gameObjectName]; [g_cameraController startWithName:goName]; } void _StopNativeCamera() { if (g_cameraController) { [g_cameraController stop]; g_cameraController = nil; } } }