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)优化效果。
相关推荐
失落的多巴胺22 分钟前
使用deepseek制作“喝什么奶茶”随机抽签小网页
javascript·css·css3·html5
DataGear26 分钟前
如何在DataGear 5.4.1 中快速制作SQL服务端分页的数据表格看板
javascript·数据库·sql·信息可视化·数据分析·echarts·数据可视化
影子信息27 分钟前
vue 前端动态导入文件 import.meta.glob
前端·javascript·vue.js
样子201833 分钟前
Vue3 之dialog弹框简单制作
前端·javascript·vue.js·前端框架·ecmascript
kevin_水滴石穿34 分钟前
Vue 中报错 TypeError: crypto$2.getRandomValues is not a function
前端·javascript·vue.js
翻滚吧键盘34 分钟前
vue文本插值
javascript·vue.js·ecmascript
海的诗篇_2 小时前
前端开发面试题总结-原生小程序部分
前端·javascript·面试·小程序·vue·html
黄瓜沾糖吃4 小时前
大佬们指点一下倒计时有什么问题吗?
前端·javascript
温轻舟4 小时前
3D词云图
前端·javascript·3d·交互·词云图·温轻舟
浩龙不eMo4 小时前
✅ Lodash 常用函数精选(按用途分类)
前端·javascript