如何构建实时多人虚拟现实游戏(第 1 部分)
已发表: 2022-03-10在本教程系列中,我们将构建一个基于网络的多人虚拟现实游戏,玩家需要在其中协作来解决难题。 我们将使用 A-Frame 进行 VR 建模,使用 MirrorVR 进行跨设备实时同步,使用 A-Frame Low Poly 进行低多边形美学。 在本教程结束时,您将拥有一个功能齐全的在线演示,任何人都可以玩。
每对玩家都获得一个圆环。 目标是“打开”所有球体,如果球体升高且明亮,则该球体“打开”。 如果球体较低且昏暗,则该球体“关闭”。 然而,某些“主导”球体会影响它们的邻居:如果它切换状态,它的邻居也会切换状态。 只有玩家 2 可以控制主导球体,而只有玩家 1 可以控制非主导球体。 这迫使双方玩家合作解决难题。 在教程的第一部分,我们将为我们的 VR 游戏构建环境并添加设计元素。
本教程中的七个步骤分为三个部分:
- 设置场景(步骤 1-2)
- 创建球体(步骤 3-5)
- 使球体互动(步骤 6-7)
第一部分将以打开和关闭的可点击球体结束(如下图所示)。 您将使用 A-Frame VR 和几个 A-Frame 扩展。
设置场景
1. 让我们来看一个基本场景
首先,让我们看看如何设置一个带地面的简单场景:
下面的前三个说明摘自我之前的文章。 您将首先建立一个具有单个静态 HTML 页面的网站。 这允许您从桌面编写代码并自动部署到 Web。 然后可以将部署的网站加载到您的手机上并放置在 VR 耳机中。 或者,部署的网站可以由独立的 VR 耳机加载。
通过导航到 glitch.com 开始。 然后,执行以下操作:
- 点击右上角的“新建项目”,
- 点击下拉菜单中的“hello-webpage”,
- 接下来,单击左侧边栏中的index.html 。 我们将其称为您的“编辑器”。
您现在应该会看到以下带有默认 HTML 文件的故障屏幕。
与上面的链接教程一样,首先删除当前index.html文件中的所有现有代码。 然后,使用 A-Frame VR 为基本的 webVR 项目键入以下内容。 这将使用 A-Frame 的默认照明和相机创建一个空场景。
<!DOCTYPE html> <html> <head> <title>Lightful</title> <script src="https://aframe.io/releases/0.8.0/aframe.min.js"></script> </head> <body> <a-scene> </a-scene> </body> </html>
将相机升高到站立高度。 根据 A-Frame VR 建议(Github 问题),用新实体包装相机并移动父实体而不是直接移动相机。 在第 8 行和第 9 行的a-scene
标签之间,添加以下内容。
<!-- Camera! --> <a-entity position="0 3 0"> <a-camera wasd-controls look-controls></a-camera> </a-entity>
接下来,使用a-box
添加一个大框来表示地面。 按照前面的说明将其直接放在相机下方。
<!-- Action! --> <a-box shadow width="75" height="0.1" depth="75" position="0 -1 0" color="#222"></a-box>
您的index.html文件现在应该与以下内容完全匹配。 你可以在 Github 上找到完整的源代码。
<html> <head> <title>Lightful</title> <script src="https://aframe.io/releases/0.8.0/aframe.min.js"></script> </head> <body> <a-scene> <!-- Camera! --> <a-entity position="0 3 0"> <a-camera wasd-controls look-controls></a-camera> </a-entity> <!-- Action! --> <a-box shadow width="75" height="0.1" depth="75" position="0 -1 0" color="#222"></a-box> </a-scene> </body> </html>
设置到此结束。 接下来,我们将定制灯光,营造更神秘的氛围。
2.添加气氛
在这一步中,我们将设置雾和自定义照明。
添加雾,它会模糊我们远处的物体。 修改第 8 行的a-scene
标签。在这里,我们将添加一个快速遮盖地面边缘的黑雾,营造出遥远地平线的效果。
<a-scene fog="type: linear; color: #111; near:10; far:15"></a-scene>
深灰色#111
从 10 到 15 的距离线性淡入。所有超过 15 个单位的物体都完全被遮蔽,所有小于 10 个单位的物体都完全可见。 中间的任何物体都被部分遮挡。
添加一个环境光以照亮游戏中的对象,并添加一个单向光以突出您稍后将添加的反射表面。 将其直接放在您在上一条指令中修改的a-scene
标记之后。
<!-- Lights! --> <a-light type="directional" castshadow="true" intensity="0.5" color="#FFF" position="2 5 0"></a-light> <a-light intensity="0.1" type="ambient" position="1 1 1" color="#FFF"></a-light>
在上一个指令中的灯光正下方,添加一个黑暗的天空。 请注意深灰色#111
与远处雾的颜色相匹配。
<a-sky color="#111"></a-sky>
这结束了对情绪的基本修改以及更广泛的场景设置。 检查您的代码是否与 Github 上第 2 步的源代码完全匹配。 接下来,我们将添加一个低多边形球体并开始自定义球体的美学。
创建球体
3. 创建一个低聚球体
在这一步中,我们将创建一个旋转的反射球,如下图所示。 该球体由两个程式化的低多边形球体组成,并带有一些提示反射材料的技巧。
首先在你的head
标签中导入低多边形库。 在第 4 行和第 5 行之间插入以下内容。
<script src="https://cdn.jsdelivr.net/gh/alvinwan/[email protected]/dist/aframe-low-poly.min.js"></script>
创建一个轮播、包装器和球体容器。 carousel
将包含多个球体, wrapper
将允许我们围绕中心轴旋转所有球体,而无需单独旋转每个球体,并且container
(顾名思义)将包含所有球体组件。
<a-entity> <a-entity rotation="0 90 0" class="wrapper" position="0 0 0"> <a-entity class="container" position="8 3 0" scale="1 1 1"> <!-- place orb here --> </a-entity> </a-entity> </a-entity>
在球体容器内,添加球体本身:一个球体略微半透明并偏移,另一个球体完全实心。 这两个组合模拟反射表面。
<a-entity class="orb" data-> <lp-sphere seed="0" shadow max-amplitude="1 1 1" position="-0.5 0 -0.5"></lp-sphere> <lp-sphere seed="0" shadow max-amplitude="1 1 1" rotation="0 45 45" opacity="0.5" position="-0.5 0 -0.5"></lp-sphere> </a-entity>
最后,通过在最后一条指令中的.orb
实体内的lp-sphere
之后立即添加以下a-animation
标签,无限期地旋转球体。
<a-animation attribute="rotation" repeat="indefinite" from="0 0 0" to="0 360 0" dur="5000"></a-animation>
您的 orb 包装器和 orb 本身的源代码应与以下内容完全匹配。
<a-entity> <a-entity rotation="0 90 0" class="wrapper" position="0 0 0"> <a-entity class="container" position="8 3 0" scale="1 1 1"> <a-entity class="orb" data-> <lp-sphere seed="0" shadow max-amplitude="1 1 1" position="-0.5 0 -0.5"></lp-sphere> <lp-sphere seed="0" shadow max-amplitude="1 1 1" rotation="0 45 45" opacity="0.5" position="-0.5 0 -0.5"></lp-sphere> <a-animation attribute="rotation" repeat="indefinite" from="0 0 0" to="0 360 0" dur="5000"></a-animation> </a-entity> </a-entity> </a-entity> </a-entity>
检查您的源代码是否与 Github 上第 3 步的完整源代码匹配。 您的预览现在应该与以下内容匹配。
接下来,我们将为球体添加更多照明以获得金色。
4.点亮球体
在这一步中,我们将添加两盏灯,一盏彩色灯和一盏白色灯。 这会产生以下效果。
首先添加白光以从下方照亮对象。 我们将使用点光源。 直接在#orb0
之前但在#container-orb0
内,添加以下偏移点光源。
<a-entity position="-2 -1 0"> <a-light distance="8" type="point" color="#FFF" intensity="0.8"></a-light> </a-entity>
在您的预览中,您将看到以下内容。
默认情况下,灯光不会随距离衰减。 通过添加distance="8"
,我们确保光以 8 个单位的距离完全衰减,以防止点光照亮整个场景。 接下来,添加金光。 在最后一个灯的正上方添加以下内容。
<a-light class="light-orb" distance="8" type="point" color="#f90" intensity="1"></a-light>
检查您的代码是否与第 4 步的源代码完全匹配。 您的预览现在将与以下内容匹配。
接下来,您将对球体进行最终的美学修改并添加旋转环。
5.添加戒指
在此步骤中,您将生成最终的球体,如下图所示。
在 #orb0 之前直接在#orb0
#container-orb0
中添加一个环。
<a-ring color="#fff" material="side:double" position="0 0.5 0" radius-inner="1.9" radius-outer="2" opacity="0.25"></a-ring>
请注意,环本身不包含颜色,因为颜色将被上一步中的点光充满。 此外, material="side:double"
很重要,因为没有它,戒指的背面就不会被渲染; 这意味着环会在其旋转的一半时间内消失。
但是,仅包含上述代码的预览看起来并没有什么不同。 这是因为环当前垂直于屏幕。 因此,只有环的“侧面”(厚度为 0)是可见的。 将以下动画放置在上一条指令中的a-ring
标签之间。
<a-animation attribute="rotation" easing="linear" repeat="indefinite" from="0 0 0" to="0 360 0" dur="8000"></a-animation>
您的预览现在应该与以下内容匹配:
创建具有不同旋转轴、速度和大小的可变数量的环。 您可以使用以下示例环。 任何新的环都应该放在最后a-ring
下面。
<a-ring color="#fff" material="side:double" position="0 0.5 0" radius-inner="2.4" radius-outer="2.5" opacity="0.25"> <a-animation attribute="rotation" easing="linear" repeat="indefinite" from="0 45 0" to="360 45 0" dur="8000"></a-animation> </a-ring> <a-ring color="#fff" material="side:double" position="0 0.5 0" radius-inner="1.4" radius-outer="1.5" opacity="0.25"> <a-animation attribute="rotation" easing="linear" repeat="indefinite" from="0 -60 0" to="-360 -60 0" dur="3000"></a-animation> </a-ring>
您的预览现在将与以下内容匹配。
检查您的代码是否与 Github 上第 5 步的源代码匹配。 球体的装饰到此结束。 完成球体后,我们接下来将向球体添加交互性。 在下一步中,我们将专门添加一个可见光标,当指向可点击对象时带有点击动画。
使球体互动
6.添加光标
在这一步中,我们将添加一个可以触发可点击对象的白色光标。 光标如下图所示。
在您a-camera
标签中,添加以下实体。 fuse
属性允许该实体触发点击事件。 raycaster
属性决定了检查可点击对象的频率和距离。 objects
属性接受一个选择器来确定哪些实体是可点击的。 在这种情况下,所有clickable
点击类的对象都是可点击的。
<a-entity cursor="fuse: true; fuseTimeout: 250" position="0 0 -1" geometry="primitive: ring; radiusInner: 0.03; radiusOuter: 0.04" material="color: white; shader: flat; opacity: 0.5" scale="0.5 0.5 0.5" raycaster="far: 20; interval: 1000; objects: .clickable"> <!-- Place cursor animation here --> </a-entity>
接下来,为美观添加光标动画和额外的环。 将以下内容放在上面的实体光标对象中。 这将动画添加到光标对象,以便点击可见。
<a-circle radius="0.01" color="#FFF" opacity="0.5" material="shader: flat"></a-circle> <a-animation begin="fusing" easing="ease-in" attribute="scale" fill="backwards" from="1 1 1" to="0.2 0.2 0.2" dur="250"></a-animation>
接下来,将clickable
类添加到#orb0
以匹配以下内容。
<a-entity class="orb clickable" data->
检查您的代码是否与 Github 上第 6 步的源代码匹配。 在您的预览中,将光标从它们上拖到球体上,以查看实际的点击动画。 如下图所示。
请注意,可点击属性已添加到球体本身,而不是球体容器。 这是为了防止环变成可点击的对象。 这样,用户必须单击构成球体本身的球体。
在这部分的最后一步中,您将添加动画来控制球体的开启和关闭状态。
7.添加球体状态
在此步骤中,您将在单击时将球体设置为动画进入和退出关闭状态。 如下图所示。
首先,您将缩小球体并将其降低到地面。 在 #orb0 之后将a-animation
标签添加到#container-orb0
#orb0
。 两个动画都是通过单击触发的,并共享相同的缓动函数ease-elastic
以实现轻微反弹。
<a-animation class="animation-scale" easing="ease-elastic" begin="click" attribute="scale" from="0.5 0.5 0.5" to="1 1 1" direction="alternate" dur="2000"></a-animation> <a-animation class="animation-position" easing="ease-elastic" begin="click" attribute="position" from="8 0.5 0" to="8 3 0" direction="alternate" dur="2000"></a-animation>
为了进一步强调关闭状态,我们将在球体关闭时移除黄金点光源。 但是,球体的灯光放置在球体对象之外。 因此,当点击球体时,点击事件不会传递给灯光。 为了规避这个问题,我们将使用一些轻量级的 Javascript 将点击事件传递给灯光。 将以下动画标签放在#light-orb0
中。 灯光由自定义switch
事件触发。
<a-animation class="animation-intensity" begin="switch" attribute="intensity" from="0" to="1" direction="alternate"></a-animation>
接下来,将以下单击事件侦听器添加到#container-orb0
。 这会将点击中继到球灯。
<a-entity ...>
检查您的代码是否与 Github 上第 7 步的源代码匹配。 最后,拉起您的预览,然后将光标移开和移出球体以在关闭和打开状态之间切换。 如下图所示。
这结束了球体的交互性。 玩家现在可以随意打开和关闭球体,具有不言自明的打开和关闭状态。
结论
在本教程中,您构建了一个具有开启和关闭状态的简单球体,可以通过 VR 耳机友好的光标单击来切换。 通过多种不同的照明技术和动画,您可以区分这两种状态。 球体的虚拟现实设计元素到此结束。 在本教程的下一部分中,我们将动态填充球体,添加游戏机制,并设置一对玩家之间的通信协议。