首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >JSAR 交互式菜单开发实战:打造沉浸式 3D 导航体验

JSAR 交互式菜单开发实战:打造沉浸式 3D 导航体验

原创
作者头像
用户11873665
发布2025-10-15 20:15:30
发布2025-10-15 20:15:30
1300
举报

JSAR 交互式菜单开发实战:打造沉浸式 3D 导航体验

前言

在空间计算时代,传统的 2D 菜单已经无法满足用户对沉浸式体验的需求。作为一名 AR 开发者,我一直在思考如何利用 Rokid 智能眼镜的空间计算能力,创造出更符合 3D 交互习惯的用户界面。本文将带你从零开始,使用 JSAR 框架开发一个功能完整的 3D 交互式菜单系统。

什么是 JSAR?

JSAR(JavaScript Spatial Application Runtime)是 Rokid 推出的开源空间应用运行时环境。它允许开发者使用熟悉的 Web 技术栈(JavaScript + 类 HTML 标记语言 XSML)来开发 AR/XR 应用,大大降低了空间计算开发的门槛。

为什么选择菜单系统作为切入点?

菜单是几乎所有应用的核心组件,掌握 3D 菜单的开发技巧,能让我们更好地理解空间 UI 设计的原则。通过这个项目,你将学会:

  • 3D 按钮的创建与样式设计
  • DynamicTexture 动态纹理的使用技巧
  • 视图切换与场景管理
  • 键盘交互的最佳实践

让我们开始吧!


第一步:创建基础 3D 按钮

在开始构建完整的菜单系统之前,我们需要先掌握 3D 按钮的创建方法。一个好的 3D 按钮应该具有清晰的视觉反馈、易于识别的文本标签,以及流畅的交互动画。

场景初始化

首先创建基本的 3D 场景:

代码语言:javascript
复制
const scene = spatialDocument.scene;

const camera = new BABYLON.ArcRotateCamera(
  'camera',
  Math.PI / 2,
  Math.PI / 2.5,
  15,
  new BABYLON.Vector3(0, 0, 0),
  scene
);
camera.attachControl(true);

const light = new BABYLON.HemisphericLight(
  'light',
  new BABYLON.Vector3(0, 1, 0),
  scene
);
light.intensity = 0.8;

这里使用了 ArcRotateCamera,这是一种围绕目标点旋转的相机,非常适合展示 3D 对象。

创建按钮几何体

代码语言:javascript
复制
const button = BABYLON.MeshBuilder.CreateBox('button', {
  width: 3,
  height: 1,
  depth: 0.2
}, scene);

const material = new BABYLON.StandardMaterial('buttonMat', scene);
material.diffuseColor = new BABYLON.Color3(0.2, 0.6, 1);
material.emissiveColor = new BABYLON.Color3(0.1, 0.3, 0.5);
button.material = material;

我们使用扁平的立方体作为按钮基础,emissiveColor 让按钮具有自发光效果,在深色背景下更加醒目。

添加文字标签

仅有几何体还不够,我们需要在按钮上显示文字。这里使用 JSAR 的 DynamicTexture 功能:

代码语言:javascript
复制
const textPlane = BABYLON.MeshBuilder.CreatePlane('textPlane', {
  width: 2.8,
  height: 0.8
}, scene);
textPlane.position = new BABYLON.Vector3(0, 0, -0.11);
textPlane.parent = button;

const texture = new BABYLON.DynamicTexture('buttonText', 512, scene, true);
const ctx = texture.getContext();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 80px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('点击我', 256, 256);
texture.update();

const textMaterial = new BABYLON.StandardMaterial('textMat', scene);
textMaterial.diffuseTexture = texture;
textMaterial.emissiveColor = new BABYLON.Color3(1, 1, 1);
textPlane.material = textMaterial;

DynamicTexture 就像一个 Canvas,我们可以在上面绘制任何内容,然后将其作为纹理应用到 3D 平面上。

添加旋转动画

静态的按钮缺乏吸引力,让我们添加一个简单的旋转动画:

代码语言:javascript
复制
scene.registerBeforeRender(() => {
  button.rotation.y += 0.01;
});

实现交互反馈

最后,添加键盘交互:

代码语言:javascript
复制
spatialDocument.addEventListener('keydown', (event) => {
  if (event.key === ' ' || event.key === 'Enter') {
    button.material.diffuseColor = new BABYLON.Color3(0.2, 1, 0.3);
    console.log('按钮被点击了!');

    setTimeout(() => {
      button.material.diffuseColor = new BABYLON.Color3(0.2, 0.6, 1);
    }, 300);
  }
});

按下空格或回车键时,按钮会短暂变成绿色,提供清晰的视觉反馈。


第二步:构建多按钮菜单

单个按钮已经完成,现在我们需要创建一个包含多个选项的菜单系统。

定义菜单数据结构

使用数组来管理菜单项:

代码语言:javascript
复制
const menuItems = [
  { id: 1, title: '立方体', color: new BABYLON.Color3(0.2, 0.6, 1) },
  { id: 2, title: '球体', color: new BABYLON.Color3(1, 0.3, 0.3) },
  { id: 3, title: '圆柱体', color: new BABYLON.Color3(0.3, 1, 0.3) },
  { id: 4, title: '八面体', color: new BABYLON.Color3(1, 0.8, 0.2) }
];

每个菜单项都有独特的颜色,便于用户快速识别。

批量创建按钮

将按钮创建逻辑封装成函数:

代码语言:javascript
复制
function createMenuButton(item, index) {
  const button = BABYLON.MeshBuilder.CreateBox('button' + item.id, {
    width: 3,
    height: 1,
    depth: 0.2
  }, scene);

  // 水平排列
  const spacing = 4;
  button.position = new BABYLON.Vector3(
    (index - 1.5) * spacing,
    0,
    0
  );

  const material = new BABYLON.StandardMaterial('buttonMat' + item.id, scene);
  material.diffuseColor = item.color;
  material.emissiveColor = item.color.scale(0.5);
  button.material = material;

  // ... 创建文字标签(省略重复代码)

  return button;
}

menuItems.forEach((item, index) => {
  const btn = createMenuButton(item, index);
  buttons.push(btn);
});

四个按钮以 (index - 1.5) * spacing 的方式水平居中排列。

实现选择高亮

添加选择指示器,让用户知道当前聚焦在哪个选项上:

代码语言:javascript
复制
function highlightButton(index) {
  buttons.forEach((btn, i) => {
    if (i === index) {
      btn.scaling = new BABYLON.Vector3(1.2, 1.2, 1.2);
      btn.material.emissiveColor = menuItems[i].color;
    } else {
      btn.scaling = new BABYLON.Vector3(1, 1, 1);
      btn.material.emissiveColor = menuItems[i].color.scale(0.5);
    }
  });
}

选中的按钮会放大 20% 并增强发光效果。

添加左右导航

代码语言:javascript
复制
let selectedIndex = 0;

spatialDocument.addEventListener('keydown', (event) => {
  if (event.key === 'ArrowLeft' || event.key === 'a') {
    selectedIndex = (selectedIndex - 1 + menuItems.length) % menuItems.length;
    highlightButton(selectedIndex);
  } else if (event.key === 'ArrowRight' || event.key === 'd') {
    selectedIndex = (selectedIndex + 1) % menuItems.length;
    highlightButton(selectedIndex);
  }
});

第三步:实现视图切换机制

菜单的核心功能是导航到不同的内容页面。我们需要实现一个优雅的视图切换系统。

设计状态管理

代码语言:javascript
复制
let currentView = 'menu'; // 'menu' 或 'detail'
let menuObjects = [];
let detailObjects = [];

使用两个数组分别管理菜单视图和详情视图的所有对象,便于切换时清理。

清理视图函数

代码语言:javascript
复制
function clearMenuView() {
  menuObjects.forEach(obj => obj.dispose());
  menuObjects = [];
}

function clearDetailView() {
  detailObjects.forEach(obj => obj.dispose());
  detailObjects = [];
}

dispose() 方法会释放对象的所有资源,包括几何体、材质和纹理,这在频繁切换视图时非常重要。

创建详情视图

代码语言:javascript
复制
function showDetailView(item) {
  currentView = 'detail';
  clearMenuView();

  let displayObject;
  const material = new BABYLON.StandardMaterial('displayMat', scene);
  material.diffuseColor = item.color;
  material.emissiveColor = item.color.scale(0.3);

  if (item.title === '立方体') {
    displayObject = BABYLON.MeshBuilder.CreateBox('display', { size: 4 }, scene);
  } else if (item.title === '球体') {
    displayObject = BABYLON.MeshBuilder.CreateSphere('display', { diameter: 4 }, scene);
  }
  // ... 其他形状

  displayObject.material = material;
  detailObjects.push(displayObject);
}

根据选择的菜单项创建对应的 3D 对象。

添加返回功能

代码语言:javascript
复制
const backText = BABYLON.MeshBuilder.CreatePlane('backText', {
  width: 6,
  height: 1
}, scene);
backText.position = new BABYLON.Vector3(0, -4, 0);

// 绘制"返回"提示文字
const backTexture = new BABYLON.DynamicTexture('backTexture', 512, scene, true);
const ctx = backTexture.getContext();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 50px Arial';
ctx.textAlign = 'center';
ctx.fillText('按 ESC 或 B 返回菜单', 256, 256);
backTexture.update();

现在我们可以选择并点击选项,并且可以退出到菜单。


第四步:添加信息面板

为了让用户更好地理解每个 3D 对象,我们需要添加一个信息展示面板。

扩展数据结构

代码语言:javascript
复制
const menuItems = [
  {
    id: 1,
    title: '立方体',
    subtitle: 'Cube',
    description: '最基础的3D图形,具有6个面、8个顶点和12条边',
    color: new BABYLON.Color3(0.2, 0.6, 1)
  },
  // ... 其他项
];

创建信息面板

这是本项目最复杂的部分,需要在 3D 空间中绘制格式化的文本信息:

代码语言:javascript
复制
function createInfoPanel(item) {
  const panel = BABYLON.MeshBuilder.CreatePlane('infoPanel', {
    width: 10,
    height: 4
  }, scene);
  panel.position = new BABYLON.Vector3(0, 4.5, 0);

  const texture = new BABYLON.DynamicTexture('infoTexture', {
    width: 1024,
    height: 512
  }, scene, true);

  const ctx = texture.getContext();

  // 背景
  ctx.fillStyle = 'rgba(0, 20, 40, 0.9)';
  ctx.fillRect(0, 0, 1024, 512);

  // 边框
  ctx.strokeStyle = '#' + item.color.toHexString();
  ctx.lineWidth = 8;
  ctx.strokeRect(10, 10, 1004, 492);

  // 标题
  ctx.fillStyle = '#' + item.color.toHexString();
  ctx.font = 'bold 80px Arial';
  ctx.textAlign = 'center';
  ctx.fillText(item.title, 512, 100);

  // 副标题
  ctx.fillStyle = '#ffffff';
  ctx.font = '50px Arial';
  ctx.fillText(item.subtitle, 512, 180);

  // 描述(支持长文本换行)
  ctx.fillStyle = '#cccccc';
  ctx.font = '40px Arial';
  // ... 文字换行逻辑

  texture.update();
}

分层渲染

代码语言:javascript
复制
panel.renderingGroupId = 1;

将信息面板设置为渲染组 1,确保它始终显示在其他对象前面。


第五步:优化交互体验

添加数字快捷键

为了提高效率,我们添加数字键直达功能:

代码语言:javascript
复制
spatialDocument.addEventListener('keydown', (event) => {
  if (currentView === 'menu') {
    if (event.key >= '1' && event.key <= '4') {
      const index = parseInt(event.key) - 1;
      showDetailView(menuItems[index]);
    }
  }
});

现在用户可以直接按 1-4 快速跳转到对应页面。

添加使用提示

代码语言:javascript
复制
function createHelpText() {
  const helpText = BABYLON.MeshBuilder.CreatePlane('helpText', {
    width: 12,
    height: 1.5
  }, scene);
  helpText.position = new BABYLON.Vector3(0, -3, 0);

  // 绘制提示文字
  ctx.fillText('按数字键 1-4 直接选择 | 左右键切换 | 回车确认', 512, 128);
}

Rokid 眼镜适配要点

虽然本教程使用键盘进行交互演示,但这个菜单系统可以轻松适配到 Rokid 智能眼镜上:

1. 手势映射

  • 左右滑动 → 切换菜单项
  • 点击确认 → 进入详情
  • 向下滑动 → 返回菜单

2. 视觉优化

  • 使用 emissiveColor 确保在不同光照环境下都清晰可见
  • 按钮间距 4 个单位,在眼镜视野中提供舒适的选择区域
  • 信息面板使用半透明背景,不会完全遮挡背景环境

3. 性能优化

  • 视图切换时及时调用 dispose() 释放资源
  • 使用 renderingGroupId 控制渲染顺序,避免不必要的深度排序

参考资源


本文所有代码均在 Windows 10 + VSCode + JSAR 扩展环境下测试通过

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JSAR 交互式菜单开发实战:打造沉浸式 3D 导航体验
    • 前言
      • 什么是 JSAR?
      • 为什么选择菜单系统作为切入点?
    • 第一步:创建基础 3D 按钮
      • 场景初始化
      • 创建按钮几何体
      • 添加文字标签
      • 添加旋转动画
      • 实现交互反馈
    • 第二步:构建多按钮菜单
      • 定义菜单数据结构
      • 批量创建按钮
      • 实现选择高亮
      • 添加左右导航
    • 第三步:实现视图切换机制
      • 设计状态管理
      • 清理视图函数
      • 创建详情视图
      • 添加返回功能
    • 第四步:添加信息面板
      • 扩展数据结构
      • 创建信息面板
      • 分层渲染
    • 第五步:优化交互体验
      • 添加数字快捷键
      • 添加使用提示
    • Rokid 眼镜适配要点
      • 1. 手势映射
      • 2. 视觉优化
      • 3. 性能优化
    • 参考资源
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档