Babylon.js学习之路《七、用户交互:鼠标点击、拖拽与射线检测》

文章目录

  • [1. 引言:用户交互的核心作用](#1. 引言:用户交互的核心作用)
    • [1.1 材质与纹理的核心作用](#1.1 材质与纹理的核心作用)
  • [2. 基础交互:鼠标与触摸事件](#2. 基础交互:鼠标与触摸事件)
    • [2.1 绑定鼠标点击事件](#2.1 绑定鼠标点击事件)
    • [2.2 触摸事件适配](#2.2 触摸事件适配)
  • [3. 射线检测(Ray Casting)](#3. 射线检测(Ray Casting))
    • [3.1 射线检测的原理](#3.1 射线检测的原理)
    • [3.2 高级射线检测技巧](#3.2 高级射线检测技巧)
  • [4. 拖拽物体的实现](#4. 拖拽物体的实现)
    • [4.1 拖拽基础:平面拖拽](#4.1 拖拽基础:平面拖拽)
    • [4.2 3D 空间自由拖拽](#4.2 3D 空间自由拖拽)
    • [4.3 拖拽限制(轴向锁定、范围限制)](#4.3 拖拽限制(轴向锁定、范围限制))
  • [5. 高级交互技巧](#5. 高级交互技巧)
    • [5.1 组合交互:拖拽 + 旋转/缩放](#5.1 组合交互:拖拽 + 旋转/缩放)
    • [5.2 交互反馈设计](#5.2 交互反馈设计)
    • [5.3 性能优化](#5.3 性能优化)
  • [6. 实战任务](#6. 实战任务)
    • [任务 1:实现可拖拽的拼图游戏](#任务 1:实现可拖拽的拼图游戏)
    • [任务 2:射线检测射击游戏](#任务 2:射线检测射击游戏)
  • [7. 常见问题与解决方案](#7. 常见问题与解决方案)
    • [7.1 射线检测不准确](#7.1 射线检测不准确)
    • [7.2 拖拽时物体穿透地面](#7.2 拖拽时物体穿透地面)
  • [8. 总结与下一章预告](#8. 总结与下一章预告)
    • [8.1 关键知识点回顾](#8.1 关键知识点回顾)
    • [8.2 下一章预告](#8.2 下一章预告)

1. 引言:用户交互的核心作用

  • 上一章详解灯光与阴影,材质与纹理的相关知识。
  • 这一章详细介绍一下Babylon中,用户交互:鼠标点击、拖拽与射线检测。

1.1 材质与纹理的核心作用

  • 核心作用
    • 让用户与3D场景中的物体动态互动(如点击按钮、拖拽物体、触发事件)。
    • 提升沉浸感:交互是游戏、数据可视化、虚拟展厅的核心功能。
  • 案例对比
    • 无交互:静态场景,用户只能被动观察。
    • 有交互:点击物体弹出信息、拖拽旋转模型、射线检测选中目标。

2. 基础交互:鼠标与触摸事件

2.1 绑定鼠标点击事件

  • Babylon.js 的 ActionManager

    通过事件管理器简化交互逻辑,支持点击、悬停、拖拽等事件。

  • 代码示例:点击球体变色

    javascript 复制代码
    // 创建球
    var sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {diameter: 2, segments: 32}, scene);
    sphere.position = new BABYLON.Vector3(-1.5, 1, 0);
    // 创建PBR材质,金属度0,粗糙度0.7,反射颜色白色
    var sphereMat = new BABYLON.PBRMaterial("sphereMat", scene)
    sphereMat.metallic = 0;
    sphereMat.roughness = 0.7;
    sphereMat.albedoColor = BABYLON.Color3.White();
    sphere.material = sphereMat;
    // 创建点击事件
    sphere.actionManager = new BABYLON.ActionManager(scene);
    // 绑定点击事件
    sphere.actionManager.registerAction(
        new BABYLON.ExecuteCodeAction(
            BABYLON.ActionManager.OnPickTrigger, // 触发条件:点击
            () => {
                sphere.material.albedoColor = BABYLON.Color3.Random(); // 随机颜色
            }
        )
    );

2.2 触摸事件适配

  • 移动端兼容性

    Babylon.js 自动处理触摸事件,无需额外代码。

  • 示例:双指缩放与旋转

    javascript 复制代码
      camera.attachControl(canvas, true); // 启用触控支持
      camera.inputs.add(new BABYLON.FreeCameraTouchInput()); // 添加触控输入

3. 射线检测(Ray Casting)

3.1 射线检测的原理

  • 核心机制

    从屏幕点击位置向3D场景发射一条射线,检测与物体的碰撞点。

  • 代码实现:点击选中物体

    javascript 复制代码
    // 定义场景点击事件
    scene.onPointerDown = (evt, pickResult) => {
        // 判断是否点击到模型
        if (pickResult.hit) {
            const hitMesh = pickResult.pickedMesh;
            // 高亮选中物体
            hitMesh.material.emissiveColor = BABYLON.Color3.Yellow();
        }
    };

3.2 高级射线检测技巧

  • 筛选检测目标

    仅检测特定类型的物体(如可交互的按钮)。

    javascript 复制代码
      const ray = new BABYLON.Ray(
        camera.position, 
        scene.pointerX, scene.pointerY // 从屏幕坐标生成射线方向
      );
      const predicate = (mesh) => mesh.name.startsWith("interactive_"); // 仅检测名称前缀为 interactive_ 的物体
      const hit = scene.pickWithRay(ray, predicate);
  • 长按检测与连续触发

    javascript 复制代码
      let isHolding = false;
      scene.onPointerObservable.add((pointerInfo) => {
        if (pointerInfo.type === BABYLON.PointerEventTypes.POINTERDOWN) {
          isHolding = true;
          // 开始长按检测
          const interval = setInterval(() => {
            if (!isHolding) clearInterval(interval);
            // 持续触发逻辑(如充能效果)
          }, 100);
        } else if (pointerInfo.type === BABYLON.PointerEventTypes.POINTERUP) {
          isHolding = false;
        }
      });

4. 拖拽物体的实现

4.1 拖拽基础:平面拖拽

  • 步骤分解

    1. 按下时 :检测是否选中物体,记录初始位置。

    2. 移动时 :根据鼠标位移更新物体位置。

    3. 释放时:结束拖拽。

  • 代码示例

    javascript 复制代码
      let draggedMesh = null;
    let startPosition = null;
    
    scene.onPointerDown = (evt, pickResult) => {
    if (pickResult.hit) {
      draggedMesh = pickResult.pickedMesh;
      startPosition = draggedMesh.position.clone();
    }
    };
    
    scene.onPointerMove = () => {
    if (draggedMesh) {
      const ray = scene.createPickingRay(scene.pointerX, scene.pointerY);
      const hit = scene.pickWithRay(ray);
      if (hit.pickedPoint) {
        // 在平面上拖拽(Y轴固定)
        draggedMesh.position.x = hit.pickedPoint.x;
        draggedMesh.position.z = hit.pickedPoint.z;
      }
    }
    };
    
    scene.onPointerUp = () => {
    draggedMesh = null;
    };

4.2 3D 空间自由拖拽

  • 基于射线与碰撞点

    javascript 复制代码
      scene.onPointerMove = () => {
        if (draggedMesh) {
          const ray = scene.createPickingRay(scene.pointerX, scene.pointerY);
          const hit = scene.pickWithRay(ray);
          if (hit.pickedPoint) {
            draggedMesh.position = hit.pickedPoint; // 直接移动到碰撞点
          }
        }
      };

4.3 拖拽限制(轴向锁定、范围限制)

  • 限制拖拽方向

    javascript 复制代码
      // 只允许沿 X 轴拖拽
      draggedMesh.position.x = hit.pickedPoint.x;
      draggedMesh.position.y = startPosition.y; // 固定 Y 轴
      draggedMesh.position.z = startPosition.z; // 固定 Z 轴
  • 限制拖拽范围

    javascript 复制代码
      draggedMesh.position.x = Math.min(10, Math.max(-10, hit.pickedPoint.x)); // X 轴范围 [-10, 10]

5. 高级交互技巧

5.1 组合交互:拖拽 + 旋转/缩放

  • 拽时旋转物体
javascript 复制代码
  let time = 0;
  scene.registerBeforeRender(() => {
    material.diffuseColor = new BABYLON.Color3(
      Math.sin(time) * 0.5 + 0.5, // R通道
      Math.cos(time) * 0.5 + 0.5, // G通道
      0.5                         // B通道
    );
    time += 0.02;
  });

5.2 交互反馈设计

  • 悬停高亮

    javascript 复制代码
      mesh.actionManager.registerAction(
        new BABYLON.SetValueAction(
          BABYLON.ActionManager.OnPointerOverTrigger,
          mesh.material,
          "emissiveColor",
          BABYLON.Color3.White() // 悬停时发光
        )
      );
      mesh.actionManager.registerAction(
        new BABYLON.SetValueAction(
          BABYLON.ActionManager.OnPointerOutTrigger,
          mesh.material,
          "emissiveColor",
          BABYLON.Color3.Black() // 恢复原色
        )
      );

5.3 性能优化

  • 减少射线检测频率

    javascript 复制代码
      let lastCheck = 0;
      scene.onPointerMove = () => {
        if (Date.now() - lastCheck > 100) { // 每 100ms 检测一次
          const hit = scene.pick(scene.pointerX, scene.pointerY);
          lastCheck = Date.now();
        }
      };
  • 使用 Octree 加速检测

    javascript 复制代码
      scene.createOrUpdateSelectionOctree(); // 创建空间索引
      const hit = scene.pickWithRay(ray, (mesh) => true, true); // 启用 Octree 优化

6. 实战任务

任务 1:实现可拖拽的拼图游戏

  • 目标:拖拽碎片到正确位置后自动吸附。

  • 代码要点

    javascript 复制代码
      if (distanceBetween(draggedMesh.position, targetPosition) < 0.5) {
        draggedMesh.position = targetPosition.clone(); // 自动吸附
        showSuccessEffect();
      }

任务 2:射线检测射击游戏

  • 目标:点击屏幕发射子弹,击中目标后爆炸。

  • 代码要点

    javascript 复制代码
      scene.onPointerDown = (evt, pickResult) => {
        if (pickResult.hit) {
          const explosion = BABYLON.MeshBuilder.CreateSphere("explosion", { diameter: 2 }, scene);
          explosion.position = pickResult.pickedPoint;
          explosion.material = new BABYLON.StandardMaterial("explodeMat", scene);
          explosion.material.emissiveColor = BABYLON.Color3.Red();
          setTimeout(() => explosion.dispose(), 500); // 0.5秒后消失
        }
      };

7. 常见问题与解决方案

7.1 射线检测不准确

  • 原因:模型碰撞边界(Bounding Box)与视觉不匹配。

  • 解决

    • 为复杂模型设置精确碰撞体
    javascript 复制代码
      mesh.checkCollisions = true;
      mesh.ellipsoid = new BABYLON.Vector3(1, 2, 1); // 自定义碰撞范围

7.2 拖拽时物体穿透地面

  • 解决

    • 启用物理引擎并添加碰撞约束:
    javascript 复制代码
        new BABYLON.PhysicsImpostor(mesh, BABYLON.PhysicsImpostor.BoxImpostor, {
          mass: 1,
          friction: 0.5
        }, scene);

8. 总结与下一章预告

8.1 关键知识点回顾

  • 件绑定、射线检测、拖拽逻辑、交互反馈设计。
  • 扩展方向:
    • 手势识别:捏合缩放、滑动旋转。
    • VR 交互:通过 WebXR 实现手柄射线交互。
    • 多人协作:通过 WebSocket 同步交互状态。

8.2 下一章预告

  • 《动画基础:关键帧动画与缓动效果》:创建简单动画,使用动画曲线(Easing Functions)优化效果。
相关推荐
会飞的鱼先生23 分钟前
vue3自定义指令来实现 v-copy 功能
前端·javascript·vue.js
牙牙要健康2 小时前
【三维重建】【3DGS系列】【深度学习】3DGS的理论基础知识之如何形成高斯椭球
人工智能·深度学习·3d
谢尔登2 小时前
【React Native】快速入门
javascript·react native·react.js
进取星辰2 小时前
32、跨平台咒语—— React Native初探
javascript·react native·react.js
油丶酸萝卜别吃3 小时前
highCharts生成3D饼图
3d·信息可视化
CaseyWei4 小时前
JS实现直接下载PDF文件
前端·javascript
pianmian14 小时前
3dczml时间动态图型场景
前端·javascript·数据库
iamtsfw4 小时前
从头实现react native expo本地生成APK
javascript·react native·react.js
coding随想4 小时前
JavaScript的三大核心组成:ECMAScript、DOM与BOM
开发语言·javascript·ecmascript