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

news2025/5/22 20:26:12

在这里插入图片描述

文章目录

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


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

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

1.1 材质与纹理的核心作用

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

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

2.1 绑定鼠标点击事件

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

  • 代码示例:点击球体变色
    在这里插入图片描述

    // 创建球
    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 自动处理触摸事件,无需额外代码。

  • 示例:双指缩放与旋转

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

3. 射线检测(Ray Casting)

3.1 射线检测的原理

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

  • 代码实现:点击选中物体 在这里插入图片描述

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

3.2 高级射线检测技巧

  • 筛选检测目标
    仅检测特定类型的物体(如可交互的按钮)。

      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);
    
  • 长按检测与连续触发

      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. 释放时:结束拖拽。

  • 代码示例

      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 空间自由拖拽

  • 基于射线与碰撞点
      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 拖拽限制(轴向锁定、范围限制)

  • 限制拖拽方向
      // 只允许沿 X 轴拖拽
      draggedMesh.position.x = hit.pickedPoint.x;
      draggedMesh.position.y = startPosition.y; // 固定 Y 轴
      draggedMesh.position.z = startPosition.z; // 固定 Z 轴
    
  • 限制拖拽范围
      draggedMesh.position.x = Math.min(10, Math.max(-10, hit.pickedPoint.x)); // X 轴范围 [-10, 10]
    

5. 高级交互技巧

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

  • 拽时旋转物体
  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 交互反馈设计

  • 悬停高亮
      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 性能优化

  • 减少射线检测频率
      let lastCheck = 0;
      scene.onPointerMove = () => {
        if (Date.now() - lastCheck > 100) { // 每 100ms 检测一次
          const hit = scene.pick(scene.pointerX, scene.pointerY);
          lastCheck = Date.now();
        }
      };
    
  • 使用 Octree 加速检测
      scene.createOrUpdateSelectionOctree(); // 创建空间索引
      const hit = scene.pickWithRay(ray, (mesh) => true, true); // 启用 Octree 优化
    

6. 实战任务

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

  • 目标:拖拽碎片到正确位置后自动吸附。
  • 代码要点
      if (distanceBetween(draggedMesh.position, targetPosition) < 0.5) {
        draggedMesh.position = targetPosition.clone(); // 自动吸附
        showSuccessEffect();
      }
    

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

  • 目标:点击屏幕发射子弹,击中目标后爆炸。
  • 代码要点
      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)与视觉不匹配。
  • 解决
    • 为复杂模型设置精确碰撞体
      mesh.checkCollisions = true;
      mesh.ellipsoid = new BABYLON.Vector3(1, 2, 1); // 自定义碰撞范围
    

7.2 拖拽时物体穿透地面

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

8. 总结与下一章预告

8.1 关键知识点回顾

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

8.2 下一章预告

  • 《动画基础:关键帧动画与缓动效果》:创建简单动画,使用动画曲线(Easing Functions)优化效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2383364.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

星际争霸小程序:用Java实现策略模式的星际大战

在游戏开发的世界里&#xff0c;策略模式是一种非常实用的设计模式&#xff0c;它允许我们在运行时动态地选择算法或行为。今天&#xff0c;我将带你走进一场星际争霸的奇幻之旅&#xff0c;用Java实现一个简单的星际争霸小程序&#xff0c;通过策略模式来模拟不同种族单位的战…

Python数据可视化高级实战之一——绘制GE矩阵图

目录 一、课程概述 二、GE矩阵? 三、GE 矩阵图的适用范围 五、GE 矩阵的评估方法 (一)市场吸引力的评估要素 二、企业竞争实力的评估要素 三、评估方法与实践应用 1. 定量与定性结合法 2. 数据来源 六、GE矩阵的图形化实现 七、总结:GE 矩阵与 BCG 矩阵的对比分析 (一)GE…

StreamSaver实现大文件下载解决方案

StreamSaver实现大文件下载解决方案 web端 安装 StreamSaver.js npm install streamsaver # 或 yarn add streamsaver在 Vue 组件中导入 import streamSaver from "streamsaver"; // 确保导入名称正确完整代码修正 <!--* projectName: * desc: * author: dua…

CSS【详解】弹性布局 flex

适用场景 一维&#xff08;行或列&#xff09;布局 基本概念 包裹所有被布局元素的父元素为容器 所有被布局的元素为项目 项目的排列方向&#xff08;垂直/水平&#xff09;为主轴 与主轴垂直的方向交交叉轴 容器上启用 flex 布局 将容器的 display 样式设置为 flex 或 i…

自回归图像编辑 EditAR: Unified Conditional Generation with Autoregressive Models

Paperhttps://arxiv.org/pdf/2501.04699 Code (coming soon) 目录 方法 实验 EditAR是一个统一的自回归框架&#xff0c;用于各种条件图像生成任务——图像编辑、深度到图像、边缘到图像、分割到图像。 next-token预测的功效尚未被证明用于图像编辑。 EditAR主要构建在Ll…

React Flow 中 Minimap 与 Controls 组件使用指南:交互式小地图与视口控制定制(含代码示例)

本文为《React Agent&#xff1a;从零开始构建 AI 智能体》专栏系列文章。 专栏地址&#xff1a;https://blog.csdn.net/suiyingy/category_12933485.html。项目地址&#xff1a;https://gitee.com/fgai/react-agent&#xff08;含完整代码示​例与实战源&#xff09;。完整介绍…

STM32之串口通信WIFI上云

一、W模块的原理与应用 基本概念 如果打算让硬件设备可以通过云服务器进行通信&#xff08;数据上报/指令下发&#xff09;&#xff0c;像主流的云服务器有阿里云、腾讯云、华为云&#xff0c;以及其他物联网云平台&#xff1a;巴法云.......&#xff0c;硬件设备需要通过TCP…

PCB智能报价系统——————仙盟创梦IDE

软件署名 代码贡献&#xff1a; 紫金电子科技有限公司 文案正路&#xff1a;cybersnow 正文 对企业的竞争力有着深远影响。传统的 PCB 报价方式往往依赖人工核算&#xff0c;不仅耗时较长&#xff0c;还容易出现误差。随着科技的发展&#xff0c;PCB 自动报价系统应运而生&a…

LeetCode-链表-合并两个有序链表

LeetCode-链表-合并两个有序链表 ✏️ 关于专栏&#xff1a;专栏用于记录 prepare for the coding test。 文章目录 LeetCode-链表-合并两个有序链表&#x1f4dd; 合并两个有序链表&#x1f3af;题目描述&#x1f50d; 输入输出示例&#x1f9e9;题目提示&#x1f9ea;AC递归&…

sqli-labs靶场29-31关(http参数污染)

目录 前言 less29&#xff08;单引号http参数污染&#xff09; less30&#xff08;双引号http参数污染&#xff09; less31(双引号括号http参数污染) 前言 在JSP中&#xff0c;使用request.getParameter("id")获取请求参数时&#xff0c;如果存在多个同名参数&a…

JVM 垃圾回收机制深度解析(含图解)

JVM 垃圾回收机制深度解析&#xff08;含图解&#xff09; 一、垃圾回收整体流程 垃圾回收图解 #mermaid-svg-KPtxlwWntQx8TOj3 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-KPtxlwWntQx8TOj3 .error-icon{fill…

如何利用 Conda 安装 Pytorch 教程 ?

如何利用 Conda 安装 Pytorch 教程 &#xff1f; 总共分为六步走&#xff1a; &#xff08;1&#xff09;第一步&#xff1a;验证conda 环境是否安装好&#xff1f; 1) conda -V2) conda --version&#xff08;2&#xff09;第二步&#xff1a;查看现有环境 conda env list…

uniapp vue 开发微信小程序 分包梳理经验总结

嗨&#xff0c;我是小路。今天主要和大家分享的主题是“uniapp vue 开发微信小程序 分包梳理经验总结”。 在使用 UniAppvue框架开发微信小程序时&#xff0c;当项目比较大的时候&#xff0c;经常需要分包加载。它有助于控制主包的大小&#xff0c;从而提升小程序的启…

什么是VR展示?VR展示的用途

随着科技的迅猛发展&#xff0c;我们步入一个全新的数字时代。在这个时代&#xff0c;虚拟现实&#xff08;VR&#xff09;技术崭露头角&#xff0c;逐步改变我们对世界的认知。全景展示厅作为VR技术与传统展览艺术的完美结合&#xff0c;以独特的全景视角&#xff0c;引领我们…

.NET外挂系列:4. harmony 中补丁参数的有趣玩法(上)

一&#xff1a;背景 1. 讲故事 前面几篇我们说完了 harmony 的几个注入点&#xff0c;这篇我们聚焦注入点可接收的几类参数的解读&#xff0c;非常有意思&#xff0c;在.NET高级调试 视角下也是非常重要的&#xff0c;到底是哪些参数&#xff0c;用一张表格整理如下&#xff…

Go语言中new与make的深度解析

在 Go 语言中&#xff0c;new 和 make 是两个用于内存分配的内置函数&#xff0c;但它们的作用和使用场景有显著区别。 理解它们的核心在于&#xff1a; new(T): 为类型 T 分配内存&#xff0c;并将其初始化为零值&#xff0c;然后返回一个指向该内存的指针 (*T)。make(T, ar…

3、ubantu系统 | 通过vscode远程安装并配置anaconda

1、vscode登录 登录后通过pwd可以发现目前位于wangqinag账号下&#xff0c;左侧为属于该账号的文件夹及文件。 通过cd ..可以回到上一级目录&#xff0c;通过ls可以查看当前目录下的文件夹及文件。 2、安装 2.1、下载anaconda 通过wget和curl下载未成功&#xff0c;使用手动…

【Unity】 HTFramework框架(六十五)ScrollList滚动数据列表

更新日期&#xff1a;2025年5月16日。 Github 仓库&#xff1a;https://github.com/SaiTingHu/HTFramework Gitee 仓库&#xff1a;https://gitee.com/SaiTingHu/HTFramework 索引 一、ScrollList滚动数据列表二、使用ScrollList1.快捷创建ScrollList2.ScrollList的属性3.自定义…

Swagger在java的运用

Swagger 是一个广泛使用的工具&#xff0c;用于设计、构建、记录和使用 RESTful Web 服务。它通过提供交互式的 API 文档、客户端 SDK 生成和 API 发现功能&#xff0c;极大地简化了 API 的开发和使用过程。以下是对 Swagger 的详细介绍&#xff0c;包括它的功能、使用场景、如…

代码随想录算法训练营 Day49 图论Ⅰ 深度优先与广度优先

图论 基础 图的概念 图的概念 概念清单有向图 (a)无向图 (b)有向/无向如图 a 所示每条边有指向如图 b 所示每条边没有箭头指向权值每条边的权值每条边的权值度-有几条边连到该节点 (eg V 2 V_2 V2​ 度为 3)入度/出度出度&#xff1a;从该节点出发的边个数入度&#xff1a;…