You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter ARCore中如何获取相机位置并检测与障碍物的碰撞?

解决方案

获取相机位置

要获取实时相机位置,需启用ARCore的帧更新监听,具体调整如下:

  1. 在 ArCoreView 配置中开启 enableUpdateListener: true
  2. 通过 ArCoreController 的 onFrame 回调获取每一帧的相机姿态,从中提取位置信息

相机与障碍物碰撞检测

ARCore没有直接提供相机碰撞检测功能,我们可以通过空间距离计算实现:

  • 维护一个活跃障碍物节点的列表,方便后续遍历检测
  • 将相机近似为一个小立方体(半径取0.1米),障碍物的碰撞边界取其尺寸的半长轴
  • 实时计算相机中心与障碍物中心的欧氏距离,若距离小于两者半径之和则判定为碰撞

修改后的完整代码

import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:arcore_flutter_plugin/arcore_flutter_plugin.dart';

class ArScreen extends StatefulWidget {
  @override
  _ARScreenState createState() => _ARScreenState();
}

class _ARScreenState extends State<ArScreen> {
  late ArCoreCube cubeRenderable;
  late ArCoreController arCoreController;
  int count = 0;
  Vector3? cameraPosition; // 存储实时相机位置
  final List<ArCoreNode> activeObstacles = []; // 活跃障碍物列表
  Timer? obstacleTimer; // 单独管理障碍物生成定时器

  @override
  void initState() {
    super.initState();
    initializeModels();
  }

  void initializeModels() {
    cubeRenderable = ArCoreCube(
      materials: [
        ArCoreMaterial(
          color: Color.fromARGB(255, 255, 0, 0),
        ),
      ],
      size: Vector3(50, 50, 50),
    );
  }

  bool isARViewReady = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AR App'),
      ),
      body: Stack(
        children: [
          ArCoreView(
            onArCoreViewCreated: _onArCoreViewCreated,
            enablePlaneRenderer: true,
            enableTapRecognizer: true,
            enableUpdateListener: true, // 开启帧更新监听
          ),
          if (!isARViewReady)
            Center(
              child: CircularProgressIndicator(),
            ),
          // 显示实时相机位置
          if (cameraPosition != null)
            Positioned(
              top: 20,
              left: 20,
              child: Text(
                "相机位置: ${cameraPosition!.x.toStringAsFixed(2)}, ${cameraPosition!.y.toStringAsFixed(2)}, ${cameraPosition!.z.toStringAsFixed(2)}",
                style: TextStyle(color: Colors.white, fontSize: 12),
              ),
            ),
        ],
      ),
    );
  }

  void _onArCoreViewCreated(ArCoreController controller) {
    print("onArCoreViewCreated (plane)");
    arCoreController = controller;
    _setupTapGesture();
    _setupPlaneDetection();
    _setupFrameListener(); // 注册帧监听获取相机位置
    setState(() {
      isARViewReady = true;
    });
  }

  void _setupFrameListener() {
    arCoreController.onFrame = (ArCoreFrame frame) {
      // 从帧中获取相机姿态,提取位置
      final cameraPose = frame.cameraPose;
      if (cameraPose != null) {
        setState(() {
          cameraPosition = cameraPose.translation;
        });
        // 每帧执行碰撞检测
        _checkCollisions();
      }
    };
  }

  void _checkCollisions() {
    if (cameraPosition == null || activeObstacles.isEmpty) return;

    const double cameraRadius = 0.1; // 相机近似半径(米)

    for (var obstacle in activeObstacles) {
      final obstaclePosition = obstacle.pose?.translation;
      final obstacleSize = (obstacle.shape as ArCoreCube).size;
      if (obstaclePosition == null) continue;

      // 计算障碍物的半长轴(取最大维度的一半)
      final obstacleHalfSize = max(max(obstacleSize.x, obstacleSize.y), obstacleSize.z) / 2;
      // 计算欧氏距离
      double distance = sqrt(
        pow(cameraPosition!.x - obstaclePosition.x, 2) +
        pow(cameraPosition!.y - obstaclePosition.y, 2) +
        pow(cameraPosition!.z - obstaclePosition.z, 2),
      );

      // 判断碰撞
      if (distance < cameraRadius + obstacleHalfSize) {
        print("碰撞发生!障碍物: ${obstacle.name}");
        // 这里可以添加碰撞后的逻辑(比如游戏结束、扣分等)
        _handleCollision();
        break;
      }
    }
  }

  void _handleCollision() {
    // 示例:停止障碍物生成,弹窗提示
    obstacleTimer?.cancel();
    showDialog(
      context: context,
      builder: (ctx) => AlertDialog(
        title: Text("碰撞!"),
        content: Text("你撞到障碍物了!"),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.pop(ctx);
              // 可选:添加重置游戏逻辑
            },
            child: Text("确定"),
          ),
        ],
      ),
    );
  }

  void _setupTapGesture() {
    arCoreController.onPlaneTap = _handleOnPlaneTap;
    print("onPlaneTap set");
  }

  void _setupPlaneDetection() {
    arCoreController.onPlaneDetected = _handleOnPlaneDetected;
    print("onPlaneDetected set");
  }

  void _handleOnPlaneDetected(ArCorePlane plane) {
    print("Plane detected: ${plane.type}");
  }

  void _handleOnPlaneTap(List<ArCoreHitTestResult> hits) {
    print("tapped on plane");
    final hit = hits.first;
    final cube = ArCoreCube(
      size: Vector3(0.1, 0.1, 0.1),
      materials: [ArCoreMaterial(color: Colors.red)],
    );

    // 避免重复创建定时器
    obstacleTimer?.cancel();
    obstacleTimer = Timer.periodic(Duration(milliseconds: 100), (timer) {
      final node = ArCoreNode(
        shape: cube,
        position: hit.pose.translation +
            Vector3(
              0,
              0,
              -timer.tick * 0.01,
            ),
        rotation: hit.pose.rotation,
        name: "cube$count",
      );
      arCoreController.addArCoreNode(node);
      activeObstacles.add(node); // 添加到活跃列表
      print("${node.name} added");
      count++;

      // 延迟删除障碍物并从列表移除
      Future.delayed(Duration(milliseconds: 1000), () {
        arCoreController.removeNode(nodeName: node.name);
        activeObstacles.remove(node);
        print("${node.name} removed");
      });
    });
  }

  @override
  void dispose() {
    obstacleTimer?.cancel();
    arCoreController.dispose();
    super.dispose();
  }
}

关键修改说明

  1. 相机位置获取:开启 enableUpdateListener,通过 onFrame 回调实时获取相机姿态的位置参数
  2. 碰撞检测逻辑:
    • 维护 activeObstacles 列表跟踪所有未被删除的障碍物
    • 每帧计算相机与障碍物的空间距离,对比碰撞阈值判断是否碰撞
  3. 定时器优化:用单独的 obstacleTimer 管理障碍物生成,避免重复触发
  4. 碰撞处理:添加了碰撞后的弹窗提示,可扩展为游戏逻辑(如结束、重置)

内容的提问来源于stack exchange,提问作者SaraSpears

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 05:07:29