【2】A-Frame核心设计

一、基于HTML和Primitives的表达

1.HTML - 超文本标记语言

A-Frame 基于 HTML 和 DOM 之上,使用自定义元素的 polyfill。 HTML 是 Web 的构建块,提供了最易于访问的计算语言之一。无需安装或构建步骤,使用 HTML 创建仅涉及 HTML 文件中的文本并在浏览器中打开 HTML 文件。由于大多数 Web 都是基于 HTML 构建的,因此大多数现有工具和库都可以使用 A-Frame,包括 React、Vue.js、Angular、d3.js 和 jQuery。

如果您没有太多 HTML 经验,没问题!它相当容易上手,甚至比 2D HTML 更容易掌握。一旦您掌握了 HTML 的一般结构或语法(开始标记、属性、结束标记),那么您就可以开始了!。

2.Primitives - 原语

虽然 HTML 层看起来很基础,但 HTML 和 DOM 只是 A-Frame 最外层的抽象层。在底层,A-Frame 是一个以声明方式公开的 Three.js 实体组件框架。

(1)什么是原语?

A-Frame 提供了一些称为原语的元素,例如 <a-box><a-sky> ,它们包装了实体-组件模式,以使其对初学者有吸引力。 A-Frame 提供的开箱即用的每个原语。开发人员也可以创建自己的原语。拿A-Frame整体介绍中的例子:

<html>
  <head>
    <script src="https://aframe.io/releases/1.6.0/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
      <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
      <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
      <a-sky color="#ECECEC"></a-sky>
    </a-scene>
  </body>
</html>

原语主要充当新手的便利层(即语法糖)。现在请记住,原语的底层是 <a-entity>

  • 有一个语义名称(例如 <a-box>

  • 拥有一组带有默认值的预设组件

  • 将 HTML 属性映射或代理到组件数据

原语类似于 Unity 中的预置体。一些有关实体-组件-系统模式的文档将它们称为组合。他们将核心实体组件 API 抽象为:

  • 将有用的组件与规定的默认值一起预先组合

  • 充当复杂但常见的实体类型的简写(例如 <a-sky>

  • 为初学者提供熟悉的界面,因为 A-Frame 将 HTML 带到了新的方向

在底层,这个 <a-box> 原语:

<a-box color="red" width="3"></a-box>

表示这个实体组件形式:

<a-entity geometry="primitive: box; width: 3" material="color: red"></a-entity>

<a-box>geometry.primitive 属性默认为 box 。该基元将 HTML width 属性映射到底层 geometry.width 属性,并将 HTML color 属性映射到底层 material.color 属性。

(2)如何将组件附加到原语?

原语只是底层的 <a-entity> 。这意味着原语具有与实体相同的 API,例如位置、旋转、缩放和附加组件。

例子:

让我们将社区物理组件附加到原语上。我们包含 Don McCurdy 的 aframe-physics-system 的源代码,并通过 HTML 属性附加物理组件:

<html>
  <head>
    <script src="https://aframe.io/releases/1.6.0/aframe.min.js"></script>
    <script src="https://unpkg.com/aframe-physics-system@1.6.0/dist/aframe-physics-system.min.js"></script>
  </head>
  <body>
    <a-scene physics>
      <a-box position="-1 4 -3" rotation="0 45 0" color="#4CC3D9" dynamic-body></a-box>
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" static-body></a-plane>
      <a-sky color="#ECECEC"></a-sky>
    </a-scene>
  </body>
</html>

(3)如何注册原语?

我们可以使用 AFRAME.registerPrimitive(name, definition) 注册我们自己的原语(即注册一个元素)。 name 是一个字符串,必须包含破折号(即 'a-foo' )。 definition 是定义以下属性的 JavaScript 对象:

属性

描述

举例

defaultComponents

指定原语默认组件的对象。键是组件的名称,值是组件的默认数据。

{geometry: {primitive: 'box'}}

mappings

指定 HTML 属性名称和组件属性名称之间映射的对象。每当更新 HTML 属性名称时,原语就会更新相应的组件属性。组件属性使用点语法 ${componentName}.${propertyName} 定义。

{depth: 'geometry.depth', height: 'geometry.height', width: 'geometry.width'}

例子:

例如,下面是 A-Frame 对 <a-box> 原语的注册:

var extendDeep = AFRAME.utils.extendDeep;

// The mesh mixin provides common material properties for creating mesh-based primitives.
// This makes the material component a default component and maps all the base material properties.
var meshMixin = AFRAME.primitives.getMeshMixin();

AFRAME.registerPrimitive('a-box', extendDeep({}, meshMixin, {
  // Preset default components. These components and component properties will be attached to the entity out-of-the-box.
  defaultComponents: {
    geometry: {primitive: 'box'}
  },

  // Defined mappings from HTML attributes to component properties (using dots as delimiters).
  // If we set `depth="5"` in HTML, then the primitive will automatically set `geometry="depth: 5"`.
  mappings: {
    depth: 'geometry.depth',
    height: 'geometry.height',
    width: 'geometry.width'
  }
}));

例如,Don McCurdy 的 aframe-extras 包包含一个包装其 ocean 组件的 <a-ocean> 原语。这是 <a-ocean> 的定义。

AFRAME.registerPrimitive('a-ocean', {
  // Attaches the `ocean` component by default.
  // Defaults the ocean to be parallel to the ground.
  defaultComponents: {
    ocean: {},
    rotation: {x: -90, y: 0, z: 0}
  },

  // Maps HTML attributes to the `ocean` component's properties.
  mappings: {
    width: 'ocean.width',
    depth: 'ocean.depth',
    density: 'ocean.density',
    color: 'ocean.color',
    opacity: 'ocean.opacity'
  }
});

注册了 <a-ocean> 原语后,我们就可以使用一行传统的 HTML 创建海洋:

<a-ocean color="aqua" depth="100" width="100"></a-ocean>

二、基于ECS(实体-组件-系统)架构

1.什么是ECS?

ECS 架构是 3D 和游戏开发中常见且理想的模式,遵循组合优于继承和层次结构原则。

(1)ECS优势

  • 通过混合和匹配可重用部件来定义对象时具有更大的灵活性。

  • 消除了具有复杂交织功能的长继承链的问题。

  • 通过解耦、封装、模块化、可重用性促进清洁设计。

  • 就复杂性而言,构建 VR 应用程序的最具可扩展性的方法。

  • 经过验证的 3D 和 VR 开发架构。

  • 允许扩展新功能(可能将它们作为社区组件共享)。

在 2D Web 上,我们在层次结构中布置具有固定行为的元素。 3D和VR是不同的;有无限种可能的对象具有无限的行为。 ECS 提供了一种可管理的模式来构造对象类型。

以下是 ECS 架构的精彩介绍材料。我们建议浏览一下它们以更好地掌握其好处。 ECS 非常适合 VR 开发,而 A-Frame 完全基于此范例:

Unity是实现ECS的著名游戏引擎。尽管跨实体通信存在一些痛点,但我们将看到 A-Frame、DOM 和声明性 HTML 如何真正让 ECS 发光发热。

(2)ECS定义

ECS 的基本定义包括:

  • 实体:是可以附加组件的容器对象。实体是场景中所有对象的基础。如果没有组件,实体既不会执行也不会渲染任何内容,类似于空的 <div>

  • 组件:是可重用的模块或数据容器,可以附加到实体以提供外观、行为和/或功能。组件就像对象的即插即用。所有逻辑都是通过组件来实现的,我们通过混合、匹配和配置组件来定义不同类型的对象。就像炼金术一样!

  • 系统:为组件类别提供全局范围、管理和服务。系统通常是可选的,但我们可以用它们来分离逻辑和数据;系统处理逻辑,组件充当数据容器

(3)举例

通过将不同组件组合在一起构建的不同类型实体的一些抽象示例:

  • Box = Position + Geometry + Material

  • Light Bulb = Position + Light + Geometry + Material + Shadow

  • Sign = Position + Geometry + Material + Text

  • VR Controller = Position + Rotation + Input + Model + Grab + Gestures

  • Ball = Position + Velocity + Physics + Geometry + Material

  • Player = Position + Camera + Input + Avatar + Identity

作为另一个抽象示例,假设我们想要通过组装组件来构建汽车实体:

  • 我们可以附加一个 material 组件,该组件具有影响汽车外观的“颜色”或“光泽度”等属性。

  • 我们可以附加一个 engine 组件,该组件具有影响汽车功能的“马力”或“重量”等属性。

  • 我们可以附加一个 tire 组件,该组件具有影响汽车行为的“轮胎数量”或“转向角”等属性。

因此,我们可以通过改变 materialenginetire 组件的属性来创建不同类型的汽车。 materialenginetire 组件不必相互了解,甚至可以在其他情况下单独使用。我们可以混合搭配它们来创造不同类型的车辆:

  • 要创建船实体:删除 tire 组件。

  • 创建摩托车实体:将 tire 组件的轮胎数量更改为2个,将 engine 组件配置得更小。

  • 要创建飞机实体:附加 wingjet 组件。

与传统的继承相比,如果我们想要扩展一个对象,我们必须创建一个试图处理所有内容或继承链的大类。

2.A-Frame中的ECS

A-Frame 具有代表每个 ECS 的 API:

  • 实体:<a-entity> 元素和原型表示。

  • 组件:<a-entity> 上的 HTML 属性表示。下面,组件是包含架构、生命周期处理程序和方法的对象。组件通过 AFRAME.registerComponent (name, definition) API 注册。

  • 系统:<a-scene> 的 HTML 属性表示。系统在定义上与组件类似。系统通过 AFRAME.registerSystem (name, definition) API 注册。

(1)语法

创建<a-entity>并将组件附加为 HTML 属性。大多数组件都有多个属性,这些属性由类似于HTMLElement.styleCSS 的语法表示。此语法采用以下形式:用冒号 (:) 分隔属性名称和属性值,并用分号 (;) 分隔不同的属性声明:

<a-entity ${componentName}="${propertyName1}: ${propertyValue1}; ${propertyName2}: ${propertyValue2}">

例如,我们有<a-entity>并附加具有各种属性和属性值的几何体、材质、灯光和位置组件:

<a-entity
  geometry="primitive: sphere; radius: 1.5"
  light="type: point; color: white; intensity: 2"
  material="color: white; shader: flat; src: glow.jpg"
  position="0 0 -5">
</a-entity>

(2)组合实体

我们可以附加更多组件来添加额外的外观、行为或功能(例如物理)。或者我们可以更新组件值来配置实体(以声明方式或通过.setAttribute)。

由多个组件组成的一种常见实体类型是 VR 中玩家的手。玩家的手可以有很多组成部分:外观、手势、行为、与其他对象的交互性。

我们将组件插入手实体以提供其行为,就像我们为 VR 附加超能力或增强功能一样!下面的每个组件彼此不了解,但可以组合起来定义一个复杂的实体:

<a-entity
  tracked-controls  <!-- Hook into the Gamepad API for pose. -->
  vive-controls  <!-- Vive button mappings. -->
  oculus-touch-controls  <!-- Oculus button mappings. -->
  hand-controls  <!-- Appearance (model), gestures, and events. -->
  laser-controls <!-- Laser to interact with menus and UI. -->
  sphere-collider  <!-- Listen when hand is in contact with an object. -->
  grab  <!-- Provide ability to grab objects. -->
  throw <!-- Provide ability to throw objects. -->
  event-set="_event: grabstart; visible: false"  <!-- Hide hand when grabbing object. -->
  event-set="_event: grabend; visible: true"  <!-- Show hand when no longer grabbing object. -->
>

(3)基于声明式 DOM 的 ECS

A-Frame 通过使其具有声明性并基于 DOM,将 ECS 提升到了另一个水平。传统上,基于 ECS 的引擎将通过代码创建实体、附加组件、更新组件、删除组件。但 A-Frame 具有 HTML 和 DOM,这使得 ECS 符合人体工程学并解决了它的许多弱点。以下是 DOM 为 ECS 提供的功能:

  1. 使用查询选择器引用其他实体:DOM 提供了强大的查询选择器系统,它允许我们查询场景图并选择与条件匹配的一个或多个实体。我们可以通过 ID、类或数据属性获取对实体的引用。因为 A-Frame 基于 HTML,所以我们可以直接使用查询选择器。 document.querySelector('#player')

  2. 与事件解耦的跨实体通信:DOM 提供侦听和发出事件的能力。这提供了实体之间的发布-订阅通信系统。组件不必相互了解,它们只需发出一个事件(可能会冒泡),其他组件可以监听这些事件而无需相互调用。 ball.emit('collided')

  3. 使用 DOM API 进行生命周期管理的 API:DOM 提供 API 来更新 HTML 元素和树,包括 .setAttribute.removeAttribute.createElement.removeChild

  4. 使用属性选择器进行实体过滤:DOM 提供属性选择器,允许我们查询具有或不具有某些 HTML 属性的一个或多个实体。这意味着我们可以请求具有或不具有特定组件集的实体。 document.querySelector('[enemy]:not([alive])')

  5. 声明性:DOM 提供 HTML。 A-Frame 在 ECS 和 HTML 之间建立了桥梁,使本已干净的模式具有声明性、可读性和可复制粘贴性。

(4)可扩展性

A-Frame组件无所不能。开发人员可以获得无需许可的创新来创建组件来扩展任何功能。组件可以完全访问 JavaScript、 Three.js 和 Web API(例如 WebRTC、语音识别)。

稍后我们将详细介绍如何编写 A-Frame 组件。作为预览,基本组件的结构可能如下所示:

AFRAME.registerComponent('foo', {
  schema: {
    bar: {type: 'number'},
    baz: {type: 'string'}
  },

  init: function () {
    // Do something when component first attached.
  },

  update: function () {
    // Do something when component's data is updated.
  },

  remove: function () {
    // Do something when the component or its entity is detached.
  },

  tick: function (time, timeDelta) {
    // Do something on every scene tick or frame.
  }
});

声明式 ECS 使我们能够编写 JavaScript 模块并通过 HTML 对其进行抽象。注册组件后,我们可以通过 HTML 属性以声明方式将此代码模块插入到实体中。这种从代码到 HTML 的抽象使 ECS 变得强大且易于推理。 foo 是我们刚刚注册的组件名称,数据包含 barbaz 属性:

<a-entity foo="bar: 5; baz: bazValue"></a-entity>

(4)基于组件的开发模式

为了构建 VR 应用程序,我们建议将所有应用程序代码放置在组件(和系统)中。理想的 A-Frame代码库纯粹由模块化、封装和解耦的组件组成。这些组件可以单独进行单元测试,也可以与其他组件一起进行单元测试。

当仅使用组件创建应用程序时,其代码库的所有部分都可以重用!组件可以共享给其他开发人员使用,或者我们可以在其他项目中重用它们。或者可以对组件进行分叉和修改以适应其他用例。

一个简单的 ECS 代码库的结构可能如下:

index.html
components/
  ball.js
  collidable.js
  grabbable.js
  enemy.js
  scoreboard.js
  throwable.js

(5)高阶组件

组件可以在实体上设置其他组件,使它们成为抽象上的更高阶或更高级别的组件。

例如,光标组件设置并构建在光线投射器组件之上。或者手动控制组件设置并构建在 vive-controls 组件和 oculus-touch-controls 组件之上,而 oculus-touch-controls 组件又构建在 tracked-controls 组件之上。

3.社区组件生态

组件可以共享到A-Frame生态系统中供社区使用。 A-Frame ECS 的美妙之处在于可扩展性。经验丰富的开发人员可以开发物理系统或图形着色器组件,而新手开发人员只需放入 <script> 标记即可从 HTML 获取这些组件并在场景中使用它们。我们可以使用强大的已发布组件,而无需接触 JavaScript。

(1)哪里可以找到组件?

市面上有数百个组件。我们尽力让它们被发现。如果您开发了组件,请通过这些渠道提交分享!

1)npm 新项目管理

大多数 A-Frame 组件都发布在 npm 和 GitHub 上。我们可以使用 npm 的搜索来搜索 aframe-components 。 npm 让我们可以按质量、受欢迎程度和维护情况进行排序。这是查找更完整的组件列表的好地方。

2)GitHub 项目

许多 A-Frame 应用程序纯粹是由组件开发的,其中许多 A-Frame 应用程序都是在 GitHub 上开源的。他们的代码库将包含我们可以直接使用、引用或复制的组件。需要关注的项目包括:

3)A-Frame博客

A-Frame 博客档案包含组件发布或更新时的详细信息,并且是查找组件链接的好地方。

4)A-Frame Wiki

A-Frame Wiki 是一项有用的社区驱动计划,用于收集有关可用 A-Frame 组件的信息和提示。鼓励每个人参与。添加和编辑信息非常容易。

(2)使用社区组件

一旦找到想要使用的组件,我们就可以将该组件包含为 <script> 标记并从 HTML 中使用它。

例如,我们使用 IdeaSpaceVR 的粒子系统组件

1)使用unpkg

首先,我们必须获取组件 JS 文件的 CDN 链接。组件的文档通常会有 CDN 链接或使用信息。但获取最新 CDN 链接的一种方法是使用 unpkg.com。

unpkg 是一个 CDN,它自动托管发布到 npm 的所有内容。 unpkg 可以解析语义版本控制并为我们提供我们想要的组件的版本。 URL 采用以下形式:

https://unpkg.com/<npm package name>@<version>/<path to file>

如果我们想要最新版本,我们可以排除 version

https://unpkg.com/<npm package name>/<path to file>

我们可以排除 path to file 来浏览组件包的目录,而不是输入构建的组件 JS 文件的路径。 JS 文件通常位于名为 dist/build/ 的文件夹中,并以 .min.js 结尾。

对于粒子系统组件,我们前往:

https://unpkg.com/aframe-particle-system-component/

请注意结尾斜杠 ( / )。找到我们需要的文件,右键单击,然后点击“复制链接到地址”,将 CDN 链接复制到剪贴板。

2)包含组件 JS 文件

然后转到我们的 HTML。在 <head> 下,A-Frame JS <script> 标记之后, <a-scene> 之前,我们将包含带有 <script> 的 JS 文件标签。

对于粒子系统组件,我们之前(在撰写本文时)发现的 CDN 链接是:

https://unpkg.com/aframe-particle-system-component@1.0.9/dist/aframe-particle-system-component.min.js

现在我们可以将它包含到我们的 HTML 中:

<html>
  <head>
    <script src="https://aframe.io/releases/1.6.0/aframe.min.js"></script>
    <script src="https://unpkg.com/aframe-particle-system-component@1.0.9/dist/aframe-particle-system-component.min.js"></script>
  </head>
  <body>
    <a-scene>
    </a-scene>
  </body>
</html>
3)使用组件

请遵循该组件的文档,了解如何在实现中使用它。但一般来说,使用涉及将组件附加到实体并对其进行配置。对于粒子系统组件:

现在我们可以将它包含到我们的 HTML 中:

<html>
  <head>
    <script src="https://aframe.io/releases/1.6.0/aframe.min.js"></script>
    <script src="https://unpkg.com/aframe-particle-system-component@1.0.9/dist/aframe-particle-system-component.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-entity particle-system="preset: snow" position="0 0 -10"></a-entity>
    </a-scene>
  </body>
</html>

(3)例子

<html>
  <head>
    <title>Community Components Example</title>
    <meta name="description" content="Community Components Example">
    <script src="https://aframe.io/releases/1.6.0/aframe.min.js"></script>  
    <script src="https://cdn.jsdelivr.net/gh/c-frame/aframe-particle-system-component@a5a8449/dist/aframe-particle-system-component.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/aframe-simple-sun-sky@^1.2.2/simple-sun-sky.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/c-frame/aframe-extras@d5f3f8/dist/aframe-extras.min.js"></script>
  </head>
  <body>
    <a-scene> 
      <a-entity id="rain" particle-system="preset: rain; color: #24CAFF; particleCount: 5000"></a-entity>

      <a-entity id="sphere" geometry="primitive: sphere"
                material="color: #EFEFEF; shader: flat"
                position="0 0.15 -5"
                light="type: point; intensity: 5"
                animation="property: position; easing: easeInOutQuad; dir: alternate; dur: 1000; to: 0 -0.10 -5; loop: true"></a-entity>

      <a-entity id="ocean" ocean="density: 20; width: 50; depth: 50; speed: 4"
                material="color: #9CE3F9; opacity: 0.75; metalness: 0; roughness: 1"
                rotation="-90 0 0"></a-entity>
      
      <a-simple-sun-sky sun-position="1 0.4 0"></a-simple-sun-sky>
      
      <a-entity id="light" light="type: ambient; color: #888"></a-entity>
    </a-scene>
  </body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bill Adams

喜欢?打赏一杯阔乐吧!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值