WebXR 应用开发之 aframe 框架入门

发表于 2年以前  | 总阅读数:558 次

本文主要共包含三大部分:第一部分为 WebXR,包括WebXR 的概念、标准、优点以及主流的开发方式;由 WebXR 开发方式中【使用封装好的第三方库开发】又引出了第二部分—— aframe 框架,其简介、特性及其中应用的 ECS 架构;第三部分为通过一个小游戏 demo,快速掌握 aframe 开发基础。

可以先对小游戏进行一个体验:

游戏体验地址:https://webxr-game.zlxiang.com

源码地址:https://github.com/zh-lx/webxr-game

WebXR

什么是 WebXR?

WebXR 是一组支持将渲染3D场景用来呈现虚拟世界(虚拟现实,也称作VR将图形图像添加到现实世界(增强现实,也称作AR的标准。这个标准实际上是一组 WebXR Device API[1],它们实现了 WebXR 功能集的核心,管理输出设备的选择,以适当的帧速率将3D场景呈现给所选设备,并管理使用输入控制器创建的运动矢量。

WebXR 兼容性设备包括沉浸式3D运动和定位跟踪耳机,通过框架覆盖在真实世界场景之上的眼镜,以及手持移动电话,它们通过用摄像机捕捉世界来增强现实,并通过计算机生成的图像增强场景。

为了完成这些事情,WebXR Device API 提供了以下关键功能:

  • 查找兼容的VR或AR输出设备

  • 以适当的帧率将3D场景渲染到设备

  • (可选)将输出镜像到2D显示器

  • 创建代表输入控件运动的向量

WebXR api 是建立在早期 WebVR api 之上的,如今除了支持 VR 之外,还添加了对 AR 的支持,VR、AR是从感官体验的角度来区分的:

  • VR是用户借助外设输入输出(头戴、手柄、体感、运动感知等软硬件系统)来和纯虚拟场景的交互体验

  • AR也是用户借助外设来体验额外的虚拟内容,区别是虚拟内容是叠加在真实世界上,其方式可以是通过投射或者视频叠加

一个 WebXR 应用的生命周期

一个 WebXR 应用,底层都要经过以下生命周期:

WebXR 开发 VR 应用的优点

WebXR是基于网页的XR应用程序,可以用来支持一些本地XR应用不那么适合的场景,比如一些短小精干时效不长的营销推广页面、在线沉浸式视频、电商、在线小游戏和艺术创作等。

相比于本地 XR 应用,WebXR 具备如下等优点:

  • Web的即时性:我们只需通过链接分享一段内容然后单击,就能立即使用该内容。从用户的角度来看,这是一个优势——无需安装App即可使用内容。从开发者的角度来看,我们可以完全掌控自己的工作,无需征得许可,也无需通过管理或审批流程即可发布应用。

  • Web 标准的稳定性:因为 Web 标准的存在,WebXR api 在现阶段已经发布之后,浏览器几乎就不会删除这些 api 了,因此我们通过 WebXR 建立的应用,可以保持长时间的稳定性,而不像原生应用那般需要不断随系统升级而进行适配。

  • Web 开发拥有大量的从业者:目前 XR 在 Web 开发中尚未流行起来,而 Web 开发者的群体基数十分庞大,一旦 WebXR 开发在 Web 开发从业者中流行,那么势必会得到一个快速发展,促进该项技术的繁荣。

WebXR 应用的开发方式

主流的 WebXR 应用开发方式有三种:

使用封装好的第三方库

对于没有 WebGL 基础的用户,学习和开发成本相对都比较高,因此市面上有一些在 WebGL 基础上封装的库,帮助我们快速上手开发 WebXR,例如 aframe[2]、babylon[3]、three.js[4]

WebGL + WebXR api

使用 WebGL 加 WebXR api 开发的方式,相对来说是比较贴近于底层的,对于底层,特别是渲染模块我们可以做一些优化操作从而提升 XR 的性能和体验。

传统 3d 引擎 + emscripten

传统的 3D 应用开发我们一般都会采用一些比较知名的 3D 引擎例如 unity、unreal 等,借助 emscripten[5],我们可以将 C 和 C++ 代码编译为 WebAssembly,从而实现 web 端的 XR。

aframe 框架

简介

aframe 是一个用来构建虚拟现实(VR)应用的网页开发框架,它基于 HTML 之上,使其上手十分简单。但是 aframe不仅仅是一个3D场景渲染引擎或者一个标记语言,其核心思想是基于 Three.js 来提供一个声明式、可扩展以及组件化的编程结构。

它由WebVR的发起人 Mozilla VR[6] 团队所构建,是当下用来开发WebVR内容主流技术方案,现在由 aframe 在 Supermedium 中的联合创建者维护。作为一个独立的开源项目,aframe 已经成长为最大的 VR 社区之一。

特性

  • 简单的 VR 制作:只需要引入 <script> 标签 和 <a-scene>,aframe 将自动生成3D渲染的样板代码、VR相关设置和缺省的交互控制。不需要安装任何东西也无需编译构建。

  • 声明式 HTML:aframe 通过 html 标签的方式,将大量的 3D 逻辑封装在内,容易阅读,理解和复制粘贴。

  • ECS 架构:aframe 基于强大的 three.js[7] 框架, 同时提供声明式、组件化、可复用的实体组件结构。HTML只是冰山的一角,开发者可以自由的使用 JavaScript、DOM API,Three.js,WebVR,和 WebGL。

  • 高性能:aframe 从底层对 WebVR 做了优化,尽管其使用 DOM,但其元素并不接触浏览器的布局引擎。3D 对象的更新全部在低开销内存中通过单个 requestAnimationFrame 来调用,甚至能够像本地应用一样来运行 (90+ FPS)。

  • 跨平台:A-Frame 能构建能兼容主流头显设备的 VR 应用程序,如HTC Vive, Rift, Daydream, GearVR,Pico, Oculus乃至在普通电脑和手机上运行。

  • 工具无关性:由于是构建在 HTML 之上,所以 A-Frame 和大多数开发库、框架和工具如 react[8], vue[9],angular[10] 等都能够兼容。

  • 可视化的检测工具:aframe 提供一个便捷的内置3D可视化检测工具。打开任意的A-Frame场景,Mac 敲击 <control> + <option> + <i> 或者 windows 敲击 <ctrl> + <alt> + i 组合键,将切换到3D元素检测模式。

  • 丰富的组件:aframe 内置了很丰富的组件,核心组件如几何模型(geometries),材料(materials),光线(lights),动画(animations),模式(models),光线投射(raycasters),阴影(shadows),定位音频(positional audio),文本(text),和 Vive / Touch / Daydream / GearVR / Cardboard 等控制。以及更多社区贡献的组件如:粒子系统(aframe-particle-system-component[11])、物理系统(aframe-physics-system[12]),多人模式(networked-aframe[13])、海洋(oceans[14])、山脉(mountain[15])、语音识别(aframe-speech-command-component[16])、运动捕捉(aframe-motion-capture[17])、瞬移(aframe-teleport-controls[18])、人手(aframe-super-hands-component[19])、以及增强现实(augmented-reality[20]) 等等。

ECS 架构

ECS 全称 Entity-component-system(实体-组件-系统) ,是一种主要在游戏开发领域使用的架构模式。ECS 架构遵循组合模式要好于继承和层次结构的设计原则,具有很大的灵活性。

组成

实体

实体是指存在于游戏中的一个物体,实际上它是一系列组件的集合

aframe 中使用 <a-entity> 元素来表示一个实体,如同在 ECS 架构中定义的那样,实体是一个占位符对象,我们通过插入组件来提供其外观、行为和功能。其中,位置(position), 旋转(rotation)和尺寸(scale)是实体的固有组件。

在代码中,一个实体你可以看做是一个 html 标签:

<!-- 一个空实体,它没有外表、行为或功能 -->
<a-entity />

<!-- 我们可以给实体加上几何模型(geometry)和 材料(material)组件 ,使它具有形状和外观-->
<a-entity geometry= primitive: box  material= color: red  />

组件

组件是一个可重用和模块化的数据块,我们将其插入到一个实体中,以添加外观、行为或功能。aframe 中,组件修改场景中的三维对象实体,我们将组件组合在一起构建复杂对象(实际上其封装了 three.js 和 js 代码逻辑)。

aframe 内置了大量的组件供我们使用:https://aframe.io/docs/1.3.0/components/animation.html

在代码中,一个组件可以看作是 html 标签的一个属性:

<!-- 如下给实体添加了 position 组件,用以改变实体在三维坐标中的位置 -->
<a-entity position= 1 2 3 ></a-entity>

可以通过 AFRAME.registerComponent api 来注册一个组件:

<script>
AFRAME.registerComponent('very-high', {
  init: function () {
    this.el.setAttribute('position', '0 9999 0')
  }
});
</script>

<a-entity very-high></a-entity>

系统

一个系统为组件类提供全局范围,服务和管理 它为组件类提供公共 API (方法和属性) 。一个系统可以通过场景元素来访问,并能帮助组件和全局场景交互。

系统的注册方式和组件类似,通过 AFRAME.registerSystem 进行注册。如下代码,注册了一个 car 系统,它为 car 组件提供服务,car 组件可以通过 this.system 来访问它的同名系统,根据 car 组件的不同 type ,系统为组件对应的实体设置了不同的 speed

AFRAME.registerSystem('car', {
  getSpeed: function (type) {
    if (type === 'tractor') {
      return 40;
    } else if (type === 'sports car') {
      return 300;
    } else {
      return 100;
    }
  }
})

AFRAME.registerComponent('car', {
  schema: {
    type: { default: 'tractor' },
  },
  init: function () {
    this.el.setAttribute('speed', this.system.getSpeed(this.data.type))
  },
})

优势

在 3D 和 VR 游戏开发领域,ECS 架构经久考验,著名的 unity 游戏引擎就是采用 ECS 架构,那么相比于 OOP(面向对象),ECS 有什么优势呢?

与面向对象相比,ECS 架构最大的区别就是面向对象是通过继承的方式来构建复杂的类,而 ECS 通过组合的方式来构建复杂的实体。在 OOP 模式下,当一个新的类型需要多个老类型的不同功能的时候,不能很好的继承出来;而 ECS 把大量的模块进行集成并解耦,用最小的耦合来集成大量分散的系统,更为灵活。

举个例子:

现在我们有一个游戏,里面有玩家、敌人、建筑、树等物体,游戏开发了一段时间后,我们需要增加一类会攻击的建筑。

如果通过面向对象的方式,在一系列冗长的继承链之后,会攻击的建筑无法再直接继承 Building 和 Enemy 类了(Enemy 类继承了 Dynamic 类):

而在 ECS 架构下,由于每个组件都是最小单元且相互解耦的,所以只需要组合 Position、Rotation、Scale、Recover、Attack 组件就可以构建出新的 AttackBuilding 实体。

VR 开发中的重要概念

VR 开发是基于 3D 的,几乎在所有的 3D 开发中,都有以下两个较为重要的概念:相机(camera) 和 三维坐标系,理解这两个概念,是进行 3D 开发的基础。

相机 (camera)

相机定义了用户从哪个角度查看场景,你可以将相机理解为观察者的眼睛,只有相机看到的画面,才会呈现在屏幕画布上。相机通常与允许输入设备移动和旋转相机的控件组件配对。

相机通常分为正交相机(OrthographicCamera)和透视相机(PerspectiveCamera),3D 场景中一般使用透视相机,而正交相机一般用于 2D 渲染。

正交相机

正交相机(OrthographicCamera)所看到的物体都是三维的,但是人的眼睛只能看到正面,不能看到被遮挡的背面,你看到的是一个2D的投影图。空间几何体转化为一个二维图的过程就是投影,不同的投影方式意味着投影尺寸不同的算法。

透视相机

透视相机(PerspectiveCamera)的结果除了与几何体的角度有关,还和距离相关,人的眼睛观察世界就是透视投影,比如你观察一条铁路距离越远你会感到两条轨道之间的宽度越小。

aframe 是基于 three.js 的,无论正投影还是透视投影,three.js都对相关的投影算法进行了封装,大家只需要根据不同的应用场景自行选择不同的投影方式。使用OrthographicCamera相机对象的时候,three.js会按照正投影算法自动计算几何体的投影结果;使用PerspectiveCamera相机对象的时候,three.js会按照透视投影算法自动计算几何体的投影结果。

三维坐标系

aframe 中的 3d 坐标系使用右手坐标系统,默认的摄像机的朝向,就是下图视角:

距离单位

aframe的距离单位是米(meters),因为 WebXR API 以米为单位返回姿势数据。

旋转单位

aframe的旋转单位是角度(degrees),它会在three.js内部转换为弧度。要确定旋转的正方向,需要使用右手法则:把大拇指指向正轴的方向,我们手指绕的方向就是旋转的正方向。

从一个小游戏上手 aframe 开发

了解了 WebXR 及 aframe 的一些基本概念后,我们可以尝试动手制作一个小游戏了。通过这个小游戏的制作过程,你将学习到 a-frame 开发的一些常用 api,并具备上手开发的能力。

构建 aframe 开发环境

首先要引入 aframe 框架,aframe 支持多种引入方式(https://github.com/aframevr/aframe#usage),这里我选择了通过 script 标签的方式直接在 html 中引入:

<!DOCTYPE html>
<html lang= en >
  <head>
    <meta charset= UTF-8  />
    <meta http-equiv= X-UA-Compatible  content= IE=edge  />
    <meta name= viewport  content= width=device-width, initial-scale=1.0  />
    <title>WebXR Game</title>
    <script src= https://aframe.io/releases/1.3.0/aframe.min.js ></script>
  </head>
  <body>
  </body>
</html>

需要注意的是,aframe 中的资源(assets)、纹理贴图(textures) 以及模型 (models) 通常需要远程加载,如果是直接通过本地绝对路径打开 html 页面会因为跨域而无法正常访问资源,所以需要通过 host 或者 localhost 访问 html 文件进行开发:

这里我通过 webpack 起了一个 devServer 去访问本地的 HTML 文件。

添加原语/实体

什么是原语

实体我们前面说过了,aframe 中通过 <a-entity> 创建一个实体,表示 VR 世界中的一个物体。原语(primitives)[21] 同样是 <a-xxx> 形式的一个 html 标签,其内部实际上是 实体-组件 的封装。aframe 内置了大量的原语供我们使用: https://aframe.io/docs/1.3.0/primitives/a-box.html

添加场景

前面引入了 aframe 框架,接下来我们在 body 中添加一个 <a-scene> 原语, <a-scene>是场景容器,用来包含所有实体,我们所有的实体和原语都需要添加在 <a-scene> 里面。<a-scene> 帮我们处理了所有 XR 开发所需要的设置:

  • 设置画布(canvas),渲染器(renderer)以及渲染循环

  • 缺省相机和光照

  • 设置webvr-polyfill, VREffect

  • 添加进入 VR 的界面,来启动WebXR API

<!DOCTYPE html>
<html lang= en >
  <head>
    <meta charset= UTF-8  />
    <meta http-equiv= X-UA-Compatible  content= IE=edge  />
    <meta name= viewport  content= width=device-width, initial-scale=1.0  />
    <title>WebXR Game</title>
    <script src= https://aframe.io/releases/1.3.0/aframe.min.js ></script>
  </head>
  <body>
    <a-scene></a-scene>
  </body>
</html>

添加了 <a-scene> 之后,我们在页面的右下角能看到一个 VR 的图标,表示添加成功了。点击图标我们可以进入到 VR 的页面,来启动 WebXR API:

引入社区资源

对于我们 web 开发者来说,VR 最难的可能是建立合适的 3D 模型,好在社区有许多已经封装好的资源供我们使用,A-Frame Registry[22] 收集并组织这些资源以便开发者发现和复用,在这里面我们可以找到许多供我们开箱即用的资源。

这里我引用了 aframe-environment-component[23],它可以帮助我们快速创建一个美观的场景,引入脚本,然后在 <a-scene 上面添加一个 environment:

<!DOCTYPE html>
<html lang= en >
  <head>
    <meta charset= UTF-8  />
    <meta http-equiv= X-UA-Compatible  content= IE=edge  />
    <meta name= viewport  content= width=device-width, initial-scale=1.0  />
    <title>WebXR Game</title>
    <script src= https://aframe.io/releases/1.3.0/aframe.min.js ></script>
    <script src= https://unpkg.com/aframe-environment-component@1.3.1/dist/aframe-environment-component.min.js ></script>
  </head>
  <body>
    <a-scene environment= preset: forest; ></a-scene>
  </body>
</html>

一个森林的场景就出现在了我们的屏幕中:

添加一面墙

接下来我们要在场景中添加一面墙,用于展示我们游戏的开始、结束、得分以及生命值等信息。使用 <a-box> 原语,在场景中建立一个立方体作为墙:

<a-scene environment= preset: forest; >
  <a-box></a-box>
</a-scene>

前面我们说了原语是对 实体-组件 的封装,上面的代码等价于:

<a-scene environment= preset: forest; >
  <a-entity geometry= primitive: box; ></a-entity>
</a-scene>

添加3D坐标变换

为我们的墙添加 scale= 30 20 4,将其设置为一堵 x 轴方向长 30 米,z 轴方向宽 4 米,y 轴方向高 20 米的墙;并添加 postion= 0 0 -20 ,将其位置设置在 z 轴方向 -20 米的位置:


<a-scene environment= preset: forest; >
  <a-box scale= 30 20 4  position= 0 0 -20 ></a-box>
</a-scene>

应用图片纹理

<a-box> 添加一个 src 属性,指定一个图片地址, aframe 会将图片作为贴图渲染在物体的表面。如下代码,我们给墙壁表面贴上了石头纹理:

<a-scene environment= preset: forest; >
  <a-box
    scale= 30 20 4 
    position= 0 0 -20 
    src= https://image-1300099782.cos.ap-beijing.myqcloud.com/wall.jpeg 
  >
   <a-box position= 0 0 0 ></a-box>
  </a-box>
</a-scene>

使用资源管理系统

上面给墙添加图片纹理的方式有一个缺点:墙体的资源加载不会等待图片加载完再开始,这可能导致场景中先渲染出没有图片纹理的墙,等墙体的图片加载完成后墙面才会渲染图片纹理。

出于性能考虑我们推荐使用资源管理系统(<a-assets>)。资源管理系统使浏览器缓存资源更容易(例如图像,视频,模型),并且A-Frame框架将确保所有的资源都在渲染之前被获取到。

如果我们在资源管理系统里面定义一个 <img> ,three.js就无需在底层再创建一个 <img> 。在 aframe 中自行创建 <img> 也给了我们更多的控制,让我们在多个实体上重用纹理。同时必要时 aframe 还能自动设置 crossOrigin 以及其他一些属性。

要将资源管理系统用于图像纹理:

  • 添加<a-assets>到场景中。

  • 将纹理定义为<a-assets>下面的<img>

  • <img>一个HTML ID (e.g. id= boxTexture)。

  • 以DOM选择器格式使用ID来引用资源(src= #boxTexture

使用资源管理系统来实现上面给墙添加图片纹理效果的代码如下:

<a-scene environment= preset: forest; >
  <a-assets timeout= 30000 >
    <img
      id= wallImg 
      src= https://image-1300099782.cos.ap-beijing.myqcloud.com/wall.jpeg 
    />
  </a-assets>

  <a-box scale= 30 20 4  position= 0 0 -20  src= #wallImg ></a-box>
</a-scene>

添加文字

WebGL 有多种方法来处理文字的渲染,各有优缺点,aframe 采用 SDF 文本[24] 实现方案,使用了three-bmfont-text,简单且性能好。通过添加 <a-text> 原语,实现文本的渲染。

<a-scene environment= preset: forest; >
  <!-- ... -->
  <a-text
    id= start-text 
    value= Start 
    color= #BBB 
    position= -3 6 -18 
    scale= 10 10 10 
    font= mozillavr 
  ></a-text>
</a-scene>

其他的一些文本渲染方案还有:

  • text-geometry[25]:三维文本,渲染代价要高些

  • html-shader[26]:把HTML渲染为一个纹理,好处是容易设置样式,缺点是性能糟糕。

添加光标

在 VR 世界中,我们可以通过 VR 设备的控制器进行交互,考虑到目前许多开发人员没有合适的带控制器的 VR 硬件,我们可以使用内置的 cursor 组件来进行交互。

光标原语 <a-cursor> 既可以用于基于注视的交互,也可以用于基于控制器的交互。默认的外观是一个环形几何图形,它通常作为相机的子对象放置。

如下我们通过 <a-camera> 添加一个自己的相机去代替 <a-scene> 设置的缺省相机,并将 <a-cursor> 作为相机的子对象挂载,这样无论我们的相机如何旋转和移动,我们始终能看到光标。

这里说明一下:aframe 中当一个实体作为另一个实体的子对象挂载后,子对象实体的 3d 坐标属性都是相对于其父对象实体的坐标位置,而不是整个 3d 世界的坐标位置。

<a-scene environment= preset: forest; >
  <a-camera>
    <a-cursor color= #FAFAFA ></a-cursor>
  </a-camera>
</a-scene>

使用 gltf 模型

gltf[27] 是 Khronos 的一个开放项目,它为3D资产提供了一种通用的、可扩展的格式,这种格式既高效又与现代web技术高度可交互。gltf-model 组件使用glTF ( .gltf.glb)文件来加载模型数据,我们此应用中大量的 3d 模型都将使用 gltf 模型。

开放资源

下面是几个开放的 gltf 资源网站:

  • Sketchfab[28]:提供所有可下载模型的自动转换,包括PBR模型以及 gltf 格式

  • Poimandres Market[29]:提供 gltf 格式的 3D 资源下载

  • Poly Haven[30]:提供 CC0 HDRIs、PBR 纹理和 gltf 模型

添加一个武器

引入 gltf 模型来加载一个武器,在资源管理系统中,通过 <a-asset-item> 原语引入一个 gltf 资源,然后在相机下挂载一个实体子对象,将实体的 src 设置为 <a-asset-item> 原语的 id:

<a-scene environment= preset: forest; >
  <a-assets timeout= 30000 >
    <img
      id= wallImg 
      src= https://image-1300099782.cos.ap-beijing.myqcloud.com/wall.jpeg 
    />
    <a-asset-item
      id= weapon 
      src= https://vazxmixjsiawhamofees.supabase.co/storage/v1/object/public/models/blaster/model.gltf 
    ></a-asset-item>
  </a-assets>

  <a-camera>
    <a-cursor color= #FAFAFA ></a-cursor>
    <a-gltf-model
      id= _weapon 
      src= #weapon 
      position= 0.5 -0.5 -0.8 
      scale= 1 1 1 
      rotation= 0 180 0 
    ></a-gltf-model>
  </a-camera>
</a-scene>

在页面上就出现了我们的武器:

使用组件

前面我们提到过组件的注册方式,下面我们要实现当光标聚焦了 start 文字时,文字变大并且变色;光标失焦是还原的效果。

组件有很多生命周期,在本例中我们使用了 init() 方法,它方法在组件生命周期开始时被调用一次,通常被用于设置初始状态和变量 绑定方法 以及 附加事件侦听器

注册组件

下面的代码中我们注册了一个 start-focus 组件,它在 init() 生命周期时给对应的实体注册了 mouseentermouseleave 事件监听,当触发 mouseenter 事件时,start 字体会变大并且变成橙色;当触发 mouseleave 事件时,start 字体会复原:

AFRAME.registerComponent('start-focus', {
  init: function () {
    this.el.addEventListener('mouseenter', function () {
      if (window.startLeaveTimer) {
        clearTimeout(window.startLeaveTimer);
        window.startLeaveTimer = null;
      }
      window.CursorFocusEntity = 'start';
      this.setAttribute('scale', '12 12 12');
      this.setAttribute('color', 'orange');
    });

    this.el.addEventListener('mouseleave', function () {
      window.startLeaveTimer = setTimeout(() => {
        window.CursorFocusEntity = null;
        this.setAttribute('scale', '10 10 10');
        this.setAttribute('color', '#bbb');
      }, 500);
    });
  },
});

挂载组件

我们把刚刚注册的 start-focus 组件挂载到 start 文本原语上:

<a-scene environment= preset: forest; >
  <!-- ... -->
  <a-text
    id= start-text 
    value= Start 
    color= #BBB 
    position= -3 6 -18 
    scale= 10 10 10 
    font= mozillavr 
    start-focus
  ></a-text>
</a-scene>

现在就实现了我们想要的效果:

监听光标点击事件

按照刚刚的方法,故技重施,如法炮制,给光标添加一个点击监听事件:

<script>
AFRAME.registerComponent('cursor-listener', {
  init: function () {
    // 点击进行攻击
    this.el.addEventListener('click', function (evt) {
      console.log('光标点击了')
    });
  }
});
</script>

<a-camera>
  <a-cursor color= #FAFAFA  cursor-listener></a-cursor>
</a-camera>

javaScript 进行交互

因为 aframe 本质上就是HTML,所以我们可以像普通Web开发一样使用 JavaScript 和 DOM[31] API来控制其中的场景和实体。

下面我们要实现一个点击光标时,武器向光标位置发射子弹的效果,使用 JavaScript 的 DOM API 来做一些实体与场景的交互。

获取光标点信息

当光标点击事件触发时,回调函数有一个默认参数 evt,里面包含了光标的相关信息,我们可以打印一下:

AFRAME.registerComponent('cursor-listener', {
  init: function () {
    // 点击进行攻击
    this.el.addEventListener('click', function (evt) {
      console.log(evt)
    });
  }
});

通过打印的结果得知,evt.detail.intersection.point 包含了当前光标所指向的三维坐标位置:

现在我们在光标点击事件的回调函数中,执行一个 createAttack 方法,其接收 evt.detail.intersection.point 作为参数:

AFRAME.registerComponent('cursor-listener', {
  schema: {},

  init: function () {
    // 点击进行攻击
    this.el.addEventListener('click', function (evt) {
      createAttack(evt.detail.intersection.point);
    });
  },
});

创建实体

我们要发射子弹,首先要创建子弹实体,通过 document.createElement api 来创建子弹实体,如下代码创建了一个球体:

function createAttack(point) {
  const attackEntity = document.createElement('a-sphere');
}

给实体设置组件

通过 javascript 给实体设置组件,与 js 给 dom 设置属性一样,也是通过 Node.setAttribute 方式。

如下代码中,给刚刚创建的球体设置了 radiuscolorpositionanimation 等组件:

function createAttack(point) {
  const { newX, newY, newZ } = getPosition(point);
  const attackEntity = document.createElement('a-sphere');
  attackEntity.setAttribute('radius', '0.2');
  attackEntity.setAttribute('color', 'red');
  attackEntity.setAttribute('position', `${newX} ${newY} ${newZ}`);
  attackEntity.setAttribute(
    'animation',
    `property: position; dur: 300; to: ${point.x} ${point.y} ${point.z};`
  );
}

其中 position 的位置,是球体生成的位置,我们需要让子弹从武器的枪口位置处发出,最终通过动画,发射到光标所在的位置。

计算子弹的初始位置

上面的代码中,getPosition 是计算子弹初始位置的函数,此部分设计复杂且枯燥的数学计算,不感兴趣的可以跳过。

前面我们提到过,aframe 中实体的 position 是相对于父对象实体的,我们的子弹最终是要挂载到 <a-scene> 下面,由于当相机转动时,武器在三维世界坐标系中的位置会发生变化,所以我们需要计算出武器枪口所在的初始位置,即子弹的初始位置。

我们先看三维坐标系中的其中一个平面,以 x 轴和 z 轴所在的平面为例:

首先无论相机如何转动,枪口位置和相机指向的位置与相机所在点的连线在 xz 平面所成的夹角是始终不变的。

光标起始点和点击时光标所在点的坐标我们都是已知的,所以可以求出点击时和初始状态在 xz 平面所旋转的弧度 θ,然后根据下面的数学知识,我们能计算出点击时枪口所在的位置。

坐标系中求两条直线之间的夹角:

直线 l1 的斜率 k1 : k1 = (y1 - y) ``/ (x1 - x)

直线 l2 的斜率 k2:k2 = (y2 - y) ``/ (x2 - x)

夹角 θ 的正切值:tanθ = (k2 - k1) ``/ (1 + k1 * k2)

夹角 θ:θ = Math.atan(tanθ)

坐标系求一个点以另一个点为圆心旋转 θ 后的坐标:

x2 = (x1 - x) * cosθ - (y1 - y) * sinθ + x

y2 = (y1 - y) * cosθ + (x1 - x) * sinθ + y

通过上面的数学公式,代入 getPosition 函数中,就可以求出我们子弹的初始位置。

获取实体

我们最终要把创建出的子弹挂载到场景中,所以需要先获取到场景,通过 document.querySelector api 去获取:

const scene = document.querySelector('a-scene');

挂载实体

同样通过 appendChild dom api,我们可以将刚刚创建的子弹实体给挂载到场景中:

function createAttack(point) {
  const { newX, newY, newZ } = getPosition(point);
  const attackEntity = document.createElement('a-sphere');
  attackEntity.setAttribute('radius', '0.2');
  attackEntity.setAttribute('color', 'red');
  attackEntity.setAttribute('position', `${newX} ${newY} ${newZ}`);
  attackEntity.setAttribute(
    'animation',
    `property: position; dur: 300; to: ${point.x} ${point.y} ${point.z};`
  );
  scene.appendChild(attackEntity);
}

销毁实体

当子弹发射到光标所在位置之后,我们不能让其一直停留在场景中,需要将其销毁,即通过 removeChild 将其从场景中移除:

function createAttack(point) {
  const { newX, newY, newZ } = getPosition(point);
  const attackEntity = document.createElement('a-sphere');
  attackEntity.setAttribute('radius', '0.2');
  attackEntity.setAttribute('color', 'red');
  attackEntity.setAttribute('position', `${newX} ${newY} ${newZ}`);
  attackEntity.setAttribute(
    'animation',
    `property: position; dur: 300; to: ${point.x} ${point.y} ${point.z};`
  );
  scene.appendChild(attackEntity);
  const timer = setTimeout(() => {
    scene.removeChild(attackEntity);
    clearTimeout(timer);
  }, 300);
}

至此我们的子弹发射效果就完成了:

添加音频资源

音频对于在虚拟现实中提供沉浸感是很重要的,方法是添加一个<audio>元素到我们的HTML(最好是<a-assets>)中来播放一个音频文件,并在相机下面挂载一个实体,通过 sound 组件来挂载对应的音频:

<a-scene environment= preset: forest; >
      <a-assets timeout= 30000 >
        <audio
          id= shooting-sound 
          src= https://audio-1300099782.cos.ap-beijing.myqcloud.com/shooting.mp3 
          preload= auto 
        ></audio>
      </a-assets>

      <a-camera>
        <a-cursor color= #FAFAFA  cursor-listener></a-cursor>
        <a-gltf-model
          id= _weapon 
          src= #weapon 
          position= 0.5 -0.5 -0.8 
          scale= 1 1 1 
          rotation= 0 180 0 
        ></a-gltf-model>
        <a-entity
          sound= src: #shooting-sound 
          id= shooting_sound_player 
          position= 0.5 -0.5 -0.8 
          poolSize= 10 
        ></a-entity>
      </a-camera>
    </a-scene>

通过 entity.components.sound.playSound() 方法,我们可以播放实体上挂载的音频,所以我们在 createAttack 方法执行时通过如下代码播放射击的音频:

const shootingSoundPlayer = document.querySelector('#shooting_sound_player');

function createAttack(point) {
  shootingSoundPlayer.components.sound.playSound();
  const { newX, newY, newZ } = getPosition(point);
  const attackEntity = document.createElement('a-sphere');
  attackEntity.setAttribute('radius', '0.2');
  attackEntity.setAttribute('color', 'red');
  attackEntity.setAttribute('position', `${newX} ${newY} ${newZ}`);
  attackEntity.setAttribute(
    'animation',
    `property: position; dur: 300; to: ${point.x} ${point.y} ${point.z};`
  );
  scene.appendChild(attackEntity);
}

其余工作

到这里,我们本游戏 demo 所涉及的所有 aframe 的知识点都已经讲完了,你可以根据上面的知识点,结合 javascript,完成剩余的部分工作:

  • 定时生成怪物

  • 怪物间隔一定时间对我们发射攻击,造成伤害

  • 攻击怪物,造成伤害,消灭怪物时获得分数

  • 更新分数和我们剩余的 HP

  • 游戏开始、结束和重新开始

总结

通过本文,你应该收获了有关 WebXR 的概念、标准以及如何通过 aframe 框架开发 WebXR 应用等知识,WebXR 无论在 Web 开发还是 VR 开发中都是目前参与人数较少的一片蓝海,其前景十分的广阔,甚至我们的教育业务如果结合 WebXR 技术,也是一个新的思路。

希望通过本文能够引起大家对 WebXR 的兴趣,总结了 WebXR 的一些学习资料和开发资源,便于感兴趣的同学开发上手:

  • WebXR 标准:https://immersive-web.github.io/webxr/#xrpose-interface

  • aframe

  • Sketchfab[32]

  • Poimandres Market[33]

  • Poly Haven[34]

  • 文档:https://aframe.io/docs/1.3.0/introduction/

  • 资源:https://aframe.io/aframe-registry/

  • gltf 资源

  • WebGL:https://github.com/KhronosGroup/WebGL

  • Three.js:https://github.com/mrdoob/three.js

参考资料

[1]WebXR Device API: https://immersive-web.github.io/webxr/#terminology

[2]aframe: https://github.com/aframevr/aframe

[3]babylon: https://github.com/BabylonJS/Babylon.js

[4]three.js: https://github.com/mrdoob/three.js

[5]emscripten: https://github.com/emscripten-core/emscripten

[6]Mozilla VR: https://mozvr.com/

[7]three.js: https://github.com/mrdoob/three.js

[8]react: https://github.com/aframevr/aframe-react/

[9]vue: https://vuejs.org/

[10]angular: https://angularjs.org/

[11]aframe-particle-system-component: https://github.com/IdeaSpaceVR/aframe-particle-system-component

[12]aframe-physics-system: https://github.com/donmccurdy/aframe-physics-system

[13]networked-aframe: https://github.com/haydenjameslee/networked-aframe

[14]oceans: https://github.com/donmccurdy/aframe-extras/tree/master/src/primitives

[15]mountain: https://github.com/ngokevin/kframe/tree/master/components/mountain/

[16]aframe-speech-command-component: https://github.com/lmalave/aframe-speech-command-component

[17]aframe-motion-capture: https://github.com/dmarcos/aframe-motion-capture

[18]aframe-teleport-controls: https://github.com/fernandojsg/aframe-teleport-controls

[19]aframe-super-hands-component: https://github.com/wmurphyrd/aframe-super-hands-component

[20]augmented-reality: https://github.com/jeromeetienne/AR.js#augmented-reality-for-the-web-in-less-than-10-lines-of-html

[21]原语(primitives): https://aframe.io/docs/1.3.0/introduction/html-and-primitives.html#primitives

[22]A-Frame Registry: https://aframe.io/aframe-registry

[23]aframe-environment-component: https://github.com/supermedium/aframe-environment-component

[24]SDF 文本: https://steamcdn-a.akamaihd.net/apps/valve/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf

[25]text-geometry: https://github.com/supermedium/superframe/tree/master/components/text-geometry/

[26]html-shader: https://github.com/mayognaise/aframe-html-shader

[27]gltf: https://github.com/KhronosGroup/glTF

[28]Sketchfab: https://sketchfab.com/features/gltf

[29]Poimandres Market: https://market.pmnd.rs/

[30]Poly Haven: https://polyhaven.com/

[31]DOM: https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction

[32]Sketchfab: https://sketchfab.com/features/gltf

[33]Poimandres Market: https://market.pmnd.rs/

[34]Poly Haven: https://polyhaven.com/

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/A75jdXCZ45WTMOGo1nNllA

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237271次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8108次阅读
 目录