案例研究:World Wide Maze

发布于 2022-04-30 20:49:09 字数 22894 浏览 1109 评论 0

World Wide Maze 是一款游戏,您可以在其中使用智能手机导航滚球通过网站创建的 3D 迷宫,以尝试达到目标点。

游戏功能丰富,使用HTML5功能。 例如, DeviceOrientation 事件从智能手机检索倾斜数据,然后通过 WebSocket 将其发送到 PC,玩家在其中通过 WebGL Web Workers

在本文中,我将准确解释这些特性的使用方式、整体开发过程以及优化的关键点。

设备方向

DeviceOrientation 事件( 示例 )用于从智能手机检索倾斜数据。 什么时候 addEventListenerDeviceOrientation事件,一个回调 DeviceOrientationEvent对象作为参数定期调用。 间隔本身因使用的设备而异。 例如,在 iOS + Chrome 和 iOS + Safari 中,回调大约每 1/20 秒调用一次,而在 Android 4 + Chrome 中,它大约每 1/10 秒调用一次。

window.addEventListener('deviceorientation', function (e) {
  // do something here..
});

DeviceOrientationEvent对象包含每个倾斜数据 X, Y, 和 Z以度为单位的坐标轴(不是弧度)( 阅读更多关于 HTML5Rocks 的内容 )。 但是,返回值也会因所使用的设备和浏览器的组合而异。 实际返回值的范围如下表所示:

顶部以蓝色突出显示的值是 W3C 规范中定义的值。 以绿色突出显示的那些与这些规格相匹配,而以红色突出显示的那些则不同。 令人惊讶的是,只有 Android-Firefox 组合返回了符合规范的值。 尽管如此,在实现方面,容纳经常出现的值更有意义。 World Wide Maze 因此使用 iOS 返回值作为标准,并针对 Android 设备进行相应调整。

if android and event.gamma > 180 then event.gamma -= 360

但是,这仍然不支持 Nexus 10。 尽管 Nexus 10 返回的值范围与其他 Android 设备相同,但有一个错误会颠倒 beta 和 gamma 值。 这正在单独解决。 (也许它默认为横向?)

如此证明,即使涉及物理设备的 API 已设置规范,也不能保证返回的值与这些规范相匹配。 因此,在所有预期设备上测试它们至关重要。 这也意味着可能会输入意外的值,这需要创建变通方法。 World Wide Maze 提示初次使用的玩家校准他们的设备作为其教程的第 1 步,但如果收到意外的倾斜值,它将无法正确校准到零位。 因此,它有一个内部时间限制,如果在该时间限制内无法校准,它会提示玩家切换到键盘控制。

网络套接字

在 World Wide Maze 中,您的智能手机和 PC 通过 WebSocket 连接。 更准确地说,它们通过它们之间的中继服务器连接,即智能手机到服务器再到 PC。 这是因为 WebSocket 缺乏将浏览器直接相互连接的能力。 (使用 WebRTC 数据通道允许点对点连接并消除对中继服务器的需要,但在实施时,这种方法 只能与 Chrome Canary 和 Firefox Nightly 一起使用 。)

我选择使用一个名为 Socket.IO (v0.9.11) 的库来实现,它包括在连接超时或断开连接时重新连接的功能。 我将它与 NodeJS 一起使用,因为这个 NodeJS + Socket.IO 组合在几个 WebSocket 实现测试中显示出最好的服务器端性能。

按数字配对

  1. 您的 PC 连接到服务器。
  2. 服务器给你的电脑一个随机生成的号码,并记住号码和电脑的组合。
  3. 在您的移动设备上,指定一个号码并连接到服务器。
  4. 如果指定的号码与来自连接的 PC 的号码相同,则您的移动设备已与该 PC 配对。
  5. 如果没有指定的 PC,则会发生错误。
  6. 当数据从您的移动设备传入时,它会被发送到与之配对的 PC,反之亦然。

您也可以改为从移动设备进行初始连接。 在这种情况下,设备只是简单地颠倒过来。

标签同步

Chrome 特有的 Tab Sync 功能使配对过程更加轻松。 有了它,在 PC 上打开的页面可以在移动设备上轻松打开(反之亦然)。 PC 获取服务器发出的连接号,并将其附加到页面的 URL 中 history.replaceState

history.replaceState(null, null, '/maze/' + connectionNumber)

如果启用选项卡同步,则 URL 会在几秒钟后同步,并且可以在移动设备上打开相同的页面。 移动设备检查打开页面的 URL,如果附加了一个数字,它会立即开始连接。 这消除了手动输入数字或用相机扫描二维码的需要。

潜伏

由于中继服务器位于美国,因此从日本访问它会导致智能手机的倾斜数据到达 PC 之前大约有 200 毫秒的延迟。 与开发期间使用的本地环境相比,响应时间显然很慢,但插入低通滤波器(我使用 EMA )之类的东西可以将其改善到不显眼的水平。 (实际上,出于演示目的,还需要一个低通滤波器;来自倾斜传感器的返回值包含大量噪声,并且将这些值应用于屏幕会导致大量抖动。)这没有无法使用跳跃,这显然是缓慢的,但无法解决此问题。

由于我从一开始就预计会出现延迟问题,因此我考虑在世界各地设置中继服务器,以便客户端可以连接到最近的可用服务器(从而最大限度地减少延迟)。 但是,我最终使用 Google Compute Engine (GCE) ,所以这是不可能的。

纳格尔算法问题

通过 Nagle 算法 在 TCP 级别进行缓冲来集成到操作系统中以实现高效通信,但我发现在启用此算法时我无法实时发送数据。 (特别是与 TCP 延迟确认 。即使没有延迟 ACK,如果出现同样的问题 ACK由于服务器位于海外等因素,在一定程度上有所延迟。)

在 Chrome for Android 中,WebSocket 没有出现 Nagle 延迟问题,其中包括 TCP_NODELAY用于禁用 Nagle 的选项,但它确实发生在 Chrome for iOS 中使用的 WebKit WebSocket 中,它没有启用此选项。 (使用相同 WebKit 的 Safari 也有此问题。 该问题已通过 Google 向 Apple 报告,显然已在 WebKit 的开发版本中得到解决

发生此问题时,每 100 毫秒发送一次的倾斜数据会组合成每 500 毫秒才到达 PC 的块。 游戏无法在这些条件下运行,因此它通过让服务器端以较短的间隔(每 50 毫秒左右)发送数据来避免这种延迟。 我相信收到 ACK在短时间内使 Nagle 算法误以为可以发送数据。

上图显示了接收到的实际数据的间隔。 它表示数据包之间的时间间隔; 绿色代表输出间隔,红色代表输入间隔。 最小54ms,最大158ms,中间接近100ms。 在这里,我使用了带有位于日本的中继服务器的 iPhone。 输出和输入都在100ms左右,运行流畅。

相比之下,此图显示了在美国使用服务器的结果。 当绿色输出间隔稳定在 100ms 时,输入间隔在 0ms 的低点和 500ms 的高点之间波动,表明 PC 正在接收数据块。

最后,该图显示了通过让服务器发送虚拟数据来避免延迟的结果。 虽然它的性能不如使用日本服务器好,但很明显输入间隔保持在 100 毫秒左右相对稳定。

一个错误?

尽管 Android 4 (ICS) 中的默认浏览器具有 WebSocket API,但它无法连接,从而导致 Socket.IO connect_failed 事件。 在内部超时,服务器端也无法验证连接。 (我没有单独用 WebSocket 测试过,所以可能是 Socket.IO 问题。)

扩展中继服务器

由于中继服务器的作用并不复杂,只要确保同一台 PC 和移动设备始终连接到同一台服务器,扩展和增加服务器数量应该不会很困难。

物理

游戏中的球运动(滚下山坡、与地面碰撞、与墙壁碰撞、收集物品等)均由 3D 物理模拟器完成。 我使用 了 Ammo.js—— 将广泛使用的 Bullet 物理引擎移植到 JavaScript 中 Emscripten ——与 Physijs 一起将其用作“Web Worker”。

网络工作者

Web Workers 是一种用于在单独的线程中运行 JavaScript 的 API。 作为 Web Worker 启动的 JavaScript 作为与最初调用它的线程分开的线程运行,因此可以在保持页面响应的同时执行繁重的任务。 Physijs 有效地使用 Web Workers 来帮助通常密集的 3D 物理引擎平稳运行。 World Wide Maze 以完全不同的帧速率处理物理引擎和 WebGL 图像渲染,因此即使在低规格机器上由于 WebGL 渲染负载过重而导致帧速率下降,物理引擎本身也将或多或少保持 60 fps 并且不会阻碍游戏控制。

此图显示了 联想 G570 。 上面的方框显示了 WebGL(图像渲染)的帧速率,下面的方框显示了物理引擎的帧速率。 GPU 是集成的 Intel HD Graphics 3000 芯片,因此图像渲染帧速率没有达到预期的 60 fps。 但是,由于物理引擎达到了预期的帧速率,因此游戏玩法与高规格机器上的性能并没有太大区别。

由于具有活动 Web Worker 的线程没有控制台对象,因此必须通过 postMessage 将数据发送到主线程以生成调试日志。 使用 console4Worker 在 Worker 中创建一个控制台对象的等价物,使调试过程变得更加容易。

最近版本的 Chrome 允许您在启动 Web Workers 时设置断点,这对于调试也很有用。 这可以在开发者工具的“Workers”面板中找到。

表现

具有高多边形数的阶段有时会超过 100,000 个多边形,但即使它们完全生成为 Physijs.ConcaveMesh ( btBvhTriangleMeshShape在子弹)。

最初,帧速率随着需要碰撞检测的对象数量的增加而下降,但在 Physijs 中消除不必要的处理提高了性能。 这项改进是 分支 对原始 Physijs 的

鬼物

具有碰撞检测但对碰撞没有影响从而对其他对象没有影响的对象在 Bullet 中称为“幽灵对象”。 虽然 Physijs 不正式支持幽灵对象,但可以通过在生成一个 Physijs.Mesh. World Wide Maze 使用幽灵对象来检测物品和目标点的碰撞。

hit = new Physijs.SphereMesh(geometry, material, 0)
hit._physijs.collision_flags = 1 | 4
scene.add(hit)

为了 collision_flags, 1 是 CF_STATIC_OBJECT, 4 是 CF_NO_CONTACT_RESPONSE. 尝试搜索 Bullet 论坛 Stack Overflow Bullet 文档 以获取更多信息。 由于 Physijs 是 Ammo.js 的包装器,而 Ammo.js 与 Bullet 基本相同,因此可以在 Bullet 中完成的大多数事情也可以在 Physijs 中完成。

Firefox 18 问题

Firefox 从版本 17 更新到 18 改变了 Web Workers 交换数据的方式,结果 Physijs 停止工作。 该 问题 已在 GitHub 上报告并在几天后得到解决。 虽然这种开源效率给我留下了深刻的印象,但这一事件也让我想起了 World Wide Maze 是如何由几个不同的开源框架组成的。 我正在写这篇文章,希望能提供一些反馈。

asm.js

虽然这与 World Wide Maze 没有直接关系,但 Ammo.js 已经支持 Mozilla 最近发布的 asm.js (这并不奇怪,因为 asm.js 基本上是为了加速 Emscripten 生成的 JavaScript 而创建的,而 Emscripten 的创建者也是弹药.js)。 如果 Chrome 也支持 asm.js,物理引擎的计算负载应该会大大减少。 使用 Firefox Nightly 进行测试时,速度明显加快。 也许最好用 C/C++ 编写需要更高速度的部分,然后使用 Emscripten 将它们移植到 JavaScript?

WebGL

对于 WebGL 实现,我使用了最活跃的开发库, three.js (r53)。 虽然 57 版本已经在开发后期发布,但 API 已经发生了重大变化,所以我还是坚持原来的版本发布。

发光效果

添加到球的核心和项目的发光效果是使用所谓的“ Kawase 方法 MGF ”的简单版本实现的。 然而,当 Kawase 方法使所有明亮区域都绽放时,World Wide Maze 会为需要发光的区域创建单独的渲染目标。 这是因为网站截图必须用于舞台纹理,并且简单地提取所有明亮区域会导致整个网站发光,例如,如果它有白色背景。 我也考虑过在 HDR 中处理所有内容,但这次我决定反对它,因为实施会变得相当复杂。

左上角显示了第一遍,其中发光区域被单独渲染,然后应用了模糊。 右下角显示了第二遍,其中图像尺寸减小了 50%,然后应用了模糊。 右上角显示了第三遍,图像再次缩小了 50%,然后变得模糊。 然后将三者叠加以创建左下角显示的最终合成图像。 对于我使用的模糊 VerticalBlurShaderHorizontalBlurShader,包含在three.js中,所以还有进一步优化的空间。

反光球

球上的反射基于三个.js 中的 示例 。 所有方向都从球的位置渲染并用作环境贴图。 每次球移动时都需要更新环境贴图,但由于以 60 fps 的速度更新非常密集,因此它们每三帧更新一次。 结果并不像更新每一帧那样平滑,但除非指出,否则差异实际上是难以察觉的。

着色器,着色器,着色器……

WebGL 需要着色器(顶点着色器、片段着色器)进行所有渲染。 虽然 three.js 中包含的着色器已经支持广泛的效果,但为了更精细的着色和优化,编写自己的着色器是不可避免的。 由于 World Wide Maze 让 CPU 忙于其物理引擎,因此我尝试通过尽可能多地使用着色语言 (GLSL) 编写来利用 GPU,即使 CPU 处理(通过 JavaScript)会更容易。 海浪效果自然依赖于着色器,目标点的烟花和球出现时使用的网格效果也是如此。

以上是小球出现时的网状效果测试。 左边的那个是游戏中使用的那个,由 320 个多边形组成。 中间的那个使用了大约 5,000 个多边形,右边的那个使用了大约 300,000 个多边形。 即使有这么多多边形,使用着色器进行处理也可以保持 30 fps 的稳定帧速率。

散布在整个舞台上的小物品都集成到一个网格中,单独的移动依赖于着色器移动每个多边形尖端。 这是一项测试,以查看存在大量对象时性能是否会受到影响。 这里布置了大约 5,000 个对象,由大约 20,000 个多边形组成。 性能完全没有受到影响。

poly2tri

阶段基于从服务器接收的轮廓信息形成,然后由 JavaScript 多边形化。 三角剖分是这个过程的关键部分,three.js 实现得很差,而且通常会失败。 因此,我决定自己集成一个名为 poly2tri 。 事实证明,three.js 过去显然曾尝试过同样的事情,所以我只需将其中的一部分注释掉就可以了。 结果,错误显着减少,允许更多可玩的阶段。 偶尔的错误仍然存​​在,出于某种原因,poly2tri 通过发出警报来处理错误,因此我对其进行了修改以引发异常。

上面显示了蓝色轮廓是如何被三角剖分和红色多边形生成的。

各向异性过滤

由于标准各向同性 MIP 映射会缩小水平和垂直轴上的图像尺寸,因此从倾斜角度查看多边形会使 World Wide Maze 舞台远端的纹理看起来像水平拉长的低分辨率纹理。 的右上角图像就是 此 Wikipedia 页面 一个很好的例子。 在实践中,需要更多的水平分辨率,WebGL (OpenGL) 通过使用一种称为各向异性过滤的方法来解决。 在three.js中,为 THREE.Texture.anisotropy启用各向异性过滤。 但是,此功能是一项扩展,可能并非所有 GPU 都支持。

优化

正如这篇 WebGL 最佳实践 文章还提到的,提高 WebGL (OpenGL) 性能的最关键方法是最大限度地减少绘制调用。 在 World Wide Maze 的最初开发过程中,游戏中的所有岛屿、桥梁和护栏都是独立的对象。 这有时会导致超过 2,000 个绘图调用,使复杂的阶段变得笨拙。 但是,一旦我将相同类型的对象全部打包到一个网格中,绘制调用就会下降到大约 50 个,从而显着提高了性能。

我使用 Chrome 跟踪功能进行进一步优化。 Chrome 的开发者工具中包含的分析器可以在一定程度上确定整个方法的处理时间,但跟踪可以准确地告诉您每个部分需要多长时间,低至 1/1000 秒。 查看 本文 有关如何使用跟踪的详细信息

以上是为球的反射创建环境贴图的跟踪结果。 插入 console.timeconsole.timeEnd进入three.js中看似相关的位置给我们一个看起来像这样的图表。 时间从左向右流动,每一层都像是一个调用栈。 将 console.time 嵌套在一个 console.time允许进一步测量。 上图是优化前,下图是优化后。 如上图所示, updateMatrix(尽管该词被截断)在预优化期间为每个渲染 0-5 调用。 但是,我对其进行了修改,使其仅调用一次,因为仅当对象更改位置或方向时才需要此过程。

跟踪过程本身自然会占用资源,因此插入 console.time过度可能会导致与实际性能的显着偏差,从而难以确定需要优化的区域。

性能调节器

由于 Internet 的性质,该游戏可能会在具有广泛不同规格的系统上进行。 Find Your Way to Oz 于 2 月初发布,使用了一个名为 IFLAutomaticPerformanceAdjust根据帧率的波动缩小效果,有助于确保流畅的播放。 World Wide Maze 建立在相同的基础上 IFLAutomaticPerformanceAdjust类并按以下顺序缩小效果,以使游戏尽可能流畅:

  1. 如果帧速率低于 45 fps,环境贴图将停止更新。
  2. 如果仍低于 40 fps,则渲染分辨率将降低至 70%(表面比率的 50%)。
  3. 如果仍低于 40 fps,则消除 FXAA(抗锯齿)。
  4. 如果它仍然低于 30 fps,则会消除辉光效果。

内存泄漏

使用three.js 巧妙地消除对象有点麻烦。 但是不理会它们显然会导致内存泄漏,所以我设计了下面的方法。 @rendererTHREE.WebGLRenderer。three.js 的最新版本使用了稍微不同的释放方法,所以这可能无法按原样使用。

destructObjects: (object) =>
  switch true
    when object instanceof THREE.Object3D
      @destructObjects(child) for child in object.children
      object.parent?.remove(object)
      object.deallocate()
      object.geometry?.deallocate()
      @renderer.deallocateObject(object)
      object.destruct?(this)

    when object instanceof THREE.Material
      object.deallocate()
      @renderer.deallocateMaterial(object)

    when object instanceof THREE.Texture
      object.deallocate()
      @renderer.deallocateTexture(object)

    when object instanceof THREE.EffectComposer
      @destructObjects(object.copyPass.material)
      object.passes.forEach (pass) =>
        @destructObjects(pass.material) if pass.material
        @renderer.deallocateRenderTarget(pass.renderTarget) if pass.renderTarget
        @renderer.deallocateRenderTarget(pass.renderTarget1) if pass.renderTarget1
        @renderer.deallocateRenderTarget(pass.renderTarget2) if pass.renderTarget2

HTML

就个人而言,我认为 WebGL 应用程序最好的一点是能够用 HTML 设计页面布局。 在 Flash 或 openFrameworks (OpenGL) 中构建 2D 界面(例如乐谱或文本显示)是一种痛苦。 Flash 至少有一个 IDE,但是如果你不习惯 openFrameworks,它就很难了(使用 Cocos2D 之类的东西可能会更容易)。 另一方面,HTML 允许使用 CSS 精确控制所有前端设计方面,就像在构建网站时一样。 虽然像粒子凝聚成徽标这样的复杂效果是不可能的,但 CSS 变换功能内的一些 3D 效果是可能的。 World Wide Maze 的“GOAL”和“TIME IS UP”文本效果使用 CSS Transition 中的比例进行动画处理(使用 Transit )。 (显然背景渐变使用了 WebGL。)

游戏中的每个页面(标题、RESULT、RANKING 等)都有自己的 HTML 文件,一旦将它们作为模板加载, $(document.body).append()在适当的时间用适当的值调用。 一个小问题是在追加之前无法设置鼠标和键盘事件,因此尝试 el.click (e) -> console.log(e)在附加之前不起作用。

国际化 (i18n)

在 HTML 中工作也便于创建英文版本。 我选择使用 i18next ,一个 web i18n 库,以满足我的国际化需求,我无需修改就可以使用它。

游戏内文本的编辑和翻译是在 Google Docs 电子表格中完成的。 由于 i18next 需要 JSON 文件 ,我将电子表格导出到 TSV,然后使用自定义转换器对其进行转换。 我在发布之前进行了很多更新,因此从 Google Docs 电子表格自动导出过程会使事情变得更容易。

Chrome 的 自动翻译功能 也可以正常运行。 但是,它有时无法正确检测语言,而是将其误认为是完全不同的语言(例如越南语),因此该功能目前已禁用。 ( 可以使用元标记禁用它 。)

RequireJS

我选择 RequireJS 作为我的 JavaScript 模块系统。 游戏的10000行源代码被分成大约60个类(= coffee file)并编译成单独的js文件。 RequireJS 根据依赖关系以适当的顺序加载这些单独的文件。

define ->
  class Hoge
    hogeMethod: ->

上面定义的类(hoge.coffee)可以按如下方式使用:

define ['hoge'], (Hoge) ->
  class Moge
    constructor: ->
      @hoge = new Hoge()
      @hoge.hogeMethod()

为了工作,hoge.js 必须在 moge.js 之前加载,并且由于“hoge”被指定为“define”的第一个参数,所以 hoge.js 总是首先加载(一旦 hoge.js 完成加载就会回调)。 这种机制称为 AMD ,任何第三方库都可以用于相同类型的回调,只要它支持 AMD 即可。 只要 事先指定了依赖

这与导入 AS3 类似,因此看起来应该不会那么奇怪。 如果您最终得到更多依赖文件, 是一个可能的解决方案。

r.js

RequireJS 包含一个名为 r.js 。 这会将主 js 与所有依赖的 js 文件捆绑为一个,然后使用 UglifyJS(或 Closure 编译器)将其缩小。 这减少了浏览器需要加载的文件数量和数据总量。 World Wide Maze 的 JavaScript 文件总大小约为 2 MB,并且可以通过 r.js 优化减少到约 1 MB。 如果可以使用 gzip 分发游戏,这将进一步减少到 250 KB。 (GAE 存在一个问题,即不允许传输 1 MB 或更大的 gzip 文件,因此游戏目前以 1 MB 纯文本未压缩的形式分发。)

舞台建设者

阶段数据生成如下,完全在美国的 GCE 服务器上执行:

  1. 将要转换为舞台的网站的 URL 是通过 WebSocket 发送的。
  2. PhantomJS 截取屏幕截图,检索 div 和 img 标签位置,并以 JSON 格式输出。
  3. 根据步骤 2 的屏幕截图和 HTML 元素的定位数据,自定义 C++(OpenCV、Boost)程序删除不必要的区域、生成岛屿、将岛屿与桥梁连接、计算护栏和物品位置、设置目标点等。结果以 JSON 格式输出并返回给浏览器。

幻影JS

PhantomJS 是一个不需要屏幕的浏览器。 它可以在不打开窗口的情况下加载网页,因此可以用于自动化测试或在服务器端捕获屏幕截图。 它的浏览器引擎是 WebKit,和 Chrome 和 Safari 一样,所以它的布局和 JavaScript 执行结果也和标准浏览器差不多。

使用 PhantomJS,JavaScript 或 CoffeeScript 用于编写您想要执行的流程。 捕获屏幕截图非常简单,如 本示例 。 我在 Linux 服务器(CentOS)上工作,所以我需要安装字体来显示日语( M+ FONTS )。 即便如此,字体渲染的处理方式也与 Windows 或 Mac OS 不同,因此相同的字体在其他机器上看起来可能会有所不同(尽管差异很小)。

检索 img 和 div 标签位置的处理方式与标准页面的处理方式基本相同。 jQuery 也可以毫无问题地使用。

stage_builder

我最初考虑使用更多基于 DOM 的方法来生成阶段(类似于 Firefox 3D Inspector )并尝试在 PhantomJS 中进行 DOM 分析。 不过,最后,我选择了一种图像处理方法。 为此,我编写了一个使用 OpenCV 和 Boost 的 C++ 程序,名为“stage_builder”。 它执行以下操作:

  1. 加载屏幕截图和 JSON 文件。
  2. 将图像和文本转换为“孤岛”。
  3. 创建连接岛屿的桥梁。
  4. 消除不必要的桥梁以创建迷宫。
  5. 放置大件物品。
  6. 放置小物件。
  7. 放置护栏。
  8. 以 JSON 格式输出定位数据。

下面详细介绍每个步骤。

加载屏幕截图和 JSON 文件

通常 cv::imread用于加载屏幕截图。 我为 JSON 文件测试了几个库,但 picojson 似乎最容易使用。

将图像和文本转换为“孤岛”

以上是 aid-dcc.com (点击查看实际大小)。 图像和文本元素必须转换为孤岛。 为了隔离这些部分,我们应该删除白色背景颜色——换句话说,屏幕截图中最流行的颜色。 这是完成后的样子:

白色部分是潜在的岛屿。

文字太细太锐利,所以我们将它加粗 cv::dilate, cv::GaussianBlur, 和 cv::threshold. 图像内容也丢失了,因此我们将根据 PhantomJS 输出的 img 标签数据用白色填充这些区域。 生成的图像如下所示:

文本现在形成了合适的块,每个图像都是一个合适的岛。

创建连接岛屿的桥梁

一旦岛屿准备就绪,它们就会与桥梁相连。 每个岛屿都在左、右、上、下寻找相邻的岛屿,然后将一座桥连接到最近岛屿的最近点,结果如下:

消除不必要的桥梁以创建迷宫

保留所有桥梁会使舞台太容易导航,因此必须消除一些桥梁以创建迷宫。 选择一个岛(例如,左上角的那个)作为起点,除了连接到该岛的一座桥(随机选择)之外,所有的桥都被删除。 然后对由剩下的桥连接的下一个岛做同样的事情。 一旦路径到达死胡同或返回先前访问过的岛屿,它就会回溯到允许进入新岛屿的点。 一旦所有岛屿都以这种方式处理,迷宫就完成了。

放置大件物品

根据岛屿的尺寸,从离岛屿边缘最远的点中选择一件或多件大型物品。 虽然不是很清楚,但这些点在下面用红色表示:

从所有这些可能的点中,左上角的一个被设置为起点(红色圆圈),右下角的一个被设置为目标(绿色圆圈),剩下的最多六个被选为大物品放置(紫色圆圈)。

放置小物件

适当数量的小物品沿着距岛边缘设定距离的线放置。 上图(不是来自aid-dcc.com)显示了投影的放置线,灰色、偏移并从岛的边缘定期放置。 红点表示小物品的放置位置。 由于此图像来自中期开发版本,因此项目以直线排列,但最终版本将项目更不规则地分散到灰线的两侧。

放置护栏

护栏基本上沿着岛屿的外边界放置,但必须在桥梁处切断以允许进入。 ,Boost Geometry 库 对此很有用,它简化了几何计算,例如确定岛屿边界数据与桥梁两侧的线在何处相交。

勾勒出岛屿的绿线是护栏。 在这张图片中可能很难看到,但桥梁所在的位置没有绿线。 这是用于调试的最终图像,其中包含所有需要输出到 JSON 的对象。 浅蓝色的点是小物品,灰色的点是建议的重启点。 当球落入海中时,比赛从最近的重新开始点重新开始。 重新开始点的排列方式或多或少与小物品的排列方式相同,在距岛边缘一定距离处以固定间隔排列。

以 JSON 格式输出定位数据

我也使用 picojson 进行输出。 它将数据写入标准输出,然后由调用者 (Node.js) 接收。

在 Mac 上创建要在 Linux 中运行的 C++ 程序

游戏是在 Mac 上开发,在 Linux 上部署的,但由于 OpenCV 和 Boost 两种操作系统都存在,所以一旦编译环境建立起来,开发本身并不困难。 我使用 Xcode 中的命令行工具在 Mac 上调试构建,然后使用 automake/autoconf 创建配置文件,以便可以在 Linux 中编译构建。 然后我只需要在 Linux 中使用“configure && make”来创建可执行文件。 由于编译器版本差异,我遇到了一些特定于 Linux 的错误,但使用 gdb 能够相对轻松地解决它们。

结论

可以使用 Flash 或 Unity 创建这样的游戏,这将带来许多优势。 然而,这个版本不需要插件,HTML5 + CSS3 的布局特性被证明是非常强大的。 为每项任务配备合适的工具绝对很重要。 我个人对这款完全用 HTML5 制作的游戏的表现感到惊讶,尽管它在许多方面仍然存在不足,但我期待看到它在未来的发展。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

坏尐絯

暂无简介

0 文章
0 评论
605 人气
更多

推荐作者

醉城メ夜风

文章 0 评论 0

远昼

文章 0 评论 0

平生欢

文章 0 评论 0

微凉

文章 0 评论 0

Honwey

文章 0 评论 0

qq_ikhFfg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文