写点什么

使用 HTML5 开发体感游戏——VeloMaze 的开发简介

  • 2013-03-19
  • 本文字数:4637 字

    阅读完需:约 15 分钟

HTML5 现在越来越像一个游戏开发平台。但有时候,游戏领域对于如何应用 HTML5 的特性设置了相当多的限制条件,尤其是对那些访问硬件设备的接口更是如此。

在 2012 年 11 月初,我加入了 copypastel 小组,并决定分享我在第三届年度 NodeKO 竞赛中开发游戏的经历。尽管由于时间限制无法详解全项目,我相信结果仍旧值得与爱好游戏相关技术的读者们分享。接下来,我打算公开该游戏的技术背景,及其如何在多种网络技术基础之上构建整个项目。应用在该游戏中的技术有: Node.js express (静态内容服务), Socket.io (处理客户端和服务器端关于小球往复运动的通讯), Sylvester.js (物理引擎的矢量库)和 jQuery

那什么是 VeloMaze 呢?VeloMaze 是被许多点状恐龙(迅猛龙)占据的迷宫。迅猛龙希望小球能一直在迷宫中移动。由于迷宫的连续性,它可以说是没有终点的。但是每当你通过一级关卡,就会给你之后的玩家造成更多麻烦,因为他(她)会获得另一个小球!是不是很有趣?这就是迷宫中的生活。

这个游戏非常适合那些在同一个地方,而且每个人都有手机的团队。这在当今是很常见的。这里还有一段解说游戏系统要求的视频

系统运行最重要的条件就是加速计。加速计是测量加速度的设备。带有加速计的设备通常返回重力的角度或者重力的矢量数据。这在某些浏览器中有可能做到,比如在下列网贴中所提及的:

从描述系统要求的视频中可以注意到,某些笔记本电脑中也配有加速计。相当多新式的 MacBook Pro 笔记本为防止跌落时造成硬件损伤也安装了加速度计(我那台 2009 年买的笔记本中就安装着一个)。我觉得以笔记本旋转为基础的游戏开发领域目前还是少有人涉足的地带!下面的图表演示了应用程序架构在上层是如何搭建的。

游戏本身的开发相当容易,但全面支持所有的浏览器和加速度计组合需要做更多的工作,而我们的小组只拥有 48 小时的时间。因此,有些测试我们是没有做的,比如对最新版 Android 系统的测试;但是我惊喜的发现,我们的游戏在其中却运行的非常好!然而运气只是成功的一部分。在下面的篇幅中,我打算解析游戏玩法的编写,并解释究竟怎样使该游戏具有可玩性。

读取加速度计数据非常简单,不过标准的缺失使得该过程比预想的更加难以实现。首先,我们快速调查了小组内现有的各种不同的平台和浏览器组合,为适应各种组合方式,编写了如下代码:

复制代码
/* 这里检查游览器是否支持 DeviceOrientationEvent 事件(链接到 W3C)。*/
if (window.DeviceOrientationEvent) {
window.addEventListener('deviceorientation', function(e) {
// 我们从事件“e”中获取角度值并转化成弧度值。
leftRightAngle = e.gamma /90.0*Math.PI/2;
frontBackAngle = e.beta /90.0*Math.PI/2;
}, false);
} else if (window.OrientationEvent) { // 另一个选项是 Mozilla 版本同样的东西
window.addEventListener('MozOrientation', function(e) {
// 在这里将长度值当做一个单位,并转换成角度值,看起来运行的不错。
leftRightAngle = e.x * Math.PI/2;
frontBackAngle = e.y * Math.PI/2;
}, false);
} else {
// 自然地,没有浏览器支持的大多数人会获取这个。
setStatus('Your device does not support orientation reading. Please use Android 4.0 or later, iOS (MBP laptop
is fine) or similar platform.');
}

结果是,代码可以在版本较新的 Chrome 中正常运行,也有人反馈说说它也可以运行在较新版本的 iOS 上的 Safari 浏览器当中(但是我手头上的 Safari 并不支持)。我决定不再试图寻找那种能读取所有可能用的浏览器中加速度计数据的普适性解决方案,因为现实是我们在 Node 淘汰赛的编码环节中个只有 48 小时的时间,而当时游戏的架构还没有完成。

我决定使用 Sylvester,它是一个碰撞检测的向量和矩阵数学库。其实我也可以使用 Box2D JS 来节省时间,但是由于有过 Sylvester 的使用经验,并且所需的碰撞检测比较简单,我还是决定使用 Sylvester。检查小球是否落到洞里去的代码如下所示:

复制代码
function checkBallHole(ball, hole, dropped) {
// 用 Sylvester 定义洞和求的位置为矢量对象
var holeVector = $V([hole.x, hole.y]);
var ballVector = $V([ball.x, ball.y]);
// 在 Sylvester 中用向量简单的计算距离
if (ballVector.distanceFrom(holeVector) < hole.r) {
// 用球的位置作为变量执行回调函数
dropped(ballVector);
}
}

所以事实上这里没有什么复杂的:如果你的小球的中心位于洞内,那么就会触发“dropped”的函数。这段代码在每帧运行一次,那么以前开发过游戏的朋友都知道,这种实现方式可能会造成小球在这一帧内飞跃洞穴而没有掉进去。然而,在日常生活中我们知道,如果你用足够快的速度将小球推向洞穴,它是可以滑过而不掉落的,所以这不是个问题。

这个游戏中也有墙体,所以碰撞检测也是必须要做的。Sylvester 提供了一种目标与计算线状对象的放发,我用的就是这个。简单的代码如下:

复制代码
// 计算球和墙壁碰撞时的冲击矢量数据
function impactBallByWall(ball, wall) {
var ballVector = $V([ball.x, ball.y]);
// 定义墙体为线段 (x1,y1) (x2,y2)
var wallSegment = Line.Segment.create(
              $V([wall.sx, wall.sy]),
              $V([wall.dx, wall.dy]));
// 计算墙与球的最近点(几乎就要撞上的那个位置)
var collisionPoint = wallSegment.pointClosestTo(ballVector)
              .to2D(); // needed by sylvester to convert 3D to 2D vector
//sylvester 将矢量数据从 3D 转化成 2D 所需的变量,然后看这个距离在当前框架内为多少(并不是在两个框架之间差距多少)
var dist = collisionPoint.distanceFrom(ballVector);
// 天真的假设碰撞只发生在球和墙的距离小于球的半径的情况下
if (dist < ball.r) {
// 调整到一个合适的值。较大的逆质量值意味着更大的影响(和较小的质量)
var inverseMassSum = 1/100.0;
// 从球心到碰撞点的向量
var differenceVector = collisionPoint.subtract(ballVector);
var collisionNormal = differenceVector.multiply(1.0/dist);
// 球陷下去的部分相当于在墙内
var penetrationDistance = ball.r-dist;
// 碰撞时球的速率
var collisionVelocity = $V([ball.vx, ball.vy]);
// 从点属性中我们获得冲击速度
var impactSpeed = collisionVelocity.dot(collisionNormal);
if (impactSpeed >= 0) {
    // 计算冲击量。运动能量在每次碰撞是以 2-1-0.4=0.6 的倍率递减
    var impulse = collisionNormal.multiply(
               (-1.4)*impactSpeed/(inverseMassSum));
    // 冲击只会作用在球上,因为墙被设计为固定的
    var newBallVelocity = $V([ball.vx, ball.vy]).add(
               impulse.multiply(inverseMassSum));
    // 把值传回原来的对象
    ball.vx = newBallVelocity.e(1);
    ball.vy = newBallVelocity.e(2);
}
}
}

在实现小球和墙体的碰撞过程时我做了许多并非真实的假设(但是跟现实足够接近)。首先,墙体的厚度为零(而不是实际上的 5 像素),而且,我没有计算两帧之间发生了什么。很明显,这会导致游戏中球体有能力穿越墙体。通过创建球体在不同帧之间的运动线段并找出球体三角与墙体之间是否有交叉,就很可以容易的测试到是否会发生碰撞。那么我们就必须要计算小球和墙体发生碰撞的位置。在上文的代码段中,这个位置数据就存在变量“collisionPoint”内(见下图)。

我很喜欢 Ganvas 和 WebGL,但是我们计划使用 DOM 和 jQuery 来做渲染,因为我们除了制作球体滚动之外,不需要任何 Ganvas 和 WebGL 的特效(如果这样实现,其实是很优雅的,真可惜)。使用 DOM 渲染的场景在缩放时有点生硬,但它很容易实现。我写了下面的函数用于绘制游戏中的子画面。

复制代码
// 设置 DOM 元素属性以反映 sprite 对象
setElementPosition: function(element, sprite) {
// 同步 sprite 维数
sprite.width = (maze.getSquareWidth() * sprite.r * 2);
sprite.height = (maze.getSquareHeigth() * sprite.r * 2);
var x = sprite.x;
var y = sprite.y;
/* 在绝对定位中计算样式属性 left 和 top 的值
* 从而确保点(x,y)在 sprire 的中心位置(使距离计算更加简单)
*/
var newLeft = (x * maze.getSquareWidth()  - element.width() / 2.0);
var newTop = (y * maze.getSquareHeigth()  - element.height() / 2.0);
// 避免 sprite 因为受到传感器持续输入的影响而产生的颤抖
// 通过一个阈值判断是否显示球在屏幕上的移动。
// 这是一个相当大的阈值,对于某些设备来说应该选择较小的值。
if (thresholded(element.css('left') - newLeft, 5) !== 0) {
   // 设置 DOM 元素的 x 坐标位置
    element.css('left', parseInt(newLeft) + 'px');
}
if (thresholded(element.css('top') - newTop, 5) !== 0) {
    // 设置 DOM 元素的 y 坐标位置
    element.css('top', parseInt(newTop) + 'px');
}
// 设置 DOM 元素的大小。
element.css('width', sprite.width + 'px');
element.css('height', sprite.height + 'px');
// 球状 DOM 元素包含许多层(所有的 div),所以重置所有层。
element.find('div').each(function () {
$(this).css('width', sprite.width + 'px');
$(this).css('height', sprite.height + 'px');
});
// sprite 位置的调试信息。通过点击‘enter’显示调试信息。
element.find('.location').html('('+parseInt(sprite.x*10)/10.0+','+parseInt(sprite.y*10)/10.0+')');
},

我做了一个根据视角实时缩放的功能,因此在每个框架中的宽度和高度都是计算得到的。很不幸在游戏中没有体现出这点,因为我们尝试编程控制浏览器旋转失败了(没有用于此项功能的接口,所以这还需要破解)。所以我们最后决定,通知用户关闭手机浏览器的旋转功能,如下图所示:

所有的加速度计数据的读取,物理引擎的运行和 DOM 渲染都被归拢到一个主循环中了。我将所有的主循环的代码放置到函数“update”中并且每 100 毫秒运行一次(我知道这不够频繁,但是它在我的设备上运行的很好,所以就暂时忽略这个设定值吧),像这样:

复制代码
window.setInterval(function() { update(); }, 100);

客户端的所有源代码可以点击这里获取。

顺便提一句,我对于新式的视网膜MacBook Pros 非常失望,它没有加速计(就像我们某位玩家提到的),因为它们的SSD 驱动器没有可以移动的部件!所以也许以笔记本旋转为基础的游戏看起来要到此为止了。- @raimo_t

关于作者:

Raimo Tuisku 是 UserVoice(一种和用户交流的产品)的 API/ 集成系统开发者。他在去年完成了他的计算机科学硕士学位,是位有 10 年开发经验的资深开发人员。在前往旧金山湾区之前,他为三家芬兰软件公司开发产品和安全接口。除 Web 和集成之外,Raimo 还喜欢开发 3D 游戏,设计软件架构,开发社交移动版游戏,和环游世界会见新的朋友。你可以在
Twitter 上 @raimo_t 来获知他在 APIs、WebGL、HTML5 Canvas 和移动版 HTML5 游戏方面正在进行的工作。

查看英文原文: Developing Motoric Games with HTML5 - The Making of VeloMaze

2013-03-19 17:296896
用户头像

发布了 21 篇内容, 共 75987 次阅读, 收获喜欢 1 次。

关注

评论

发布
暂无评论
发现更多内容

详解GuassDB数据库权限命令:GRANT和REVOKE

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟

DataGrip for Mac(数据库管理工具) 2023.2.3+注册码

展初云

Mac datagrip 数据库管理

大模型与Prompt模板全面升级

百度开发者中心

深度学习 大模型 LLM

智能问答技术在百度搜索中的应用

百度Geek说

百度搜索 智能问答 企业号10月PK榜

OmniGraffle Pro for Mac 图表绘制工具 附 注册机

彩云

OmniGraffle Pro

携程商旅发布“全球影响力计划”

携程商旅

Mac版本的Java开发工具 IntelliJ IDEA 2023+注册码

展初云

Java Mac IntelliJ IDEA

数字孪生:让ECU在数字环境中“栩栩如生”

DevOps和数字孪生

数字孪生 虚拟ECU 建模仿真

专业强大的CAD绘图软件 CADintosh X 最新激活版

胖墩儿不胖y

Mac软件 CAD绘图 cad工具

对话在行人|中裕能源:基于多业态特点,融合创新数智化应用场景

用友BIP

2023全球商业创新大会 对话在行人

AIGC加速迭代,云栖大会视频云「媒体服务」专场与你共话云智深度融合

阿里云CloudImagine

云计算 视频云 云栖大会

LeetCode题解:剑指 Offer 39. 数组中出现次数超过一半的数字,摩尔投票,JavaScript,详细注释

Lee Chen

JavaScript LeetCode

VMware Workstation 17安装教程之轻松构建虚拟机

小齐写代码

焕新升级!新一代云原生可观测平台

华为云开发者联盟

云原生 后端 华为云 华为云开发者联盟 华为云CCE容器服

火山引擎DataLeap背后的支持者 - 工作流编排调度系统FlowX

字节跳动数据平台

大数据 数据中台 数据安全 数据研发 企业号10月PK榜

Tampermonkey for Mac(油猴Safari浏览器插件)

展初云

Mac 油猴 Tampermonkey

深度学习预训练模型的突破与发展

百度开发者中心

模型训练 大模型 深度学习‘’ LLM

KaiwuDB 荣获“2023 IDC 中国可持续发展先锋案例”

KaiwuDB

从图像生成到分类的深度学习策略

百度开发者中心

人工智能 深度学习 大模型 图像生成

敏捷专题:下一代的飞机交付

DevOps和数字孪生

数字孪生 航空航天 仿真模拟

新一代云原生可观测平台之CCE服务监控篇

华为云原生团队

云计算 容器 微服务 云原生

个头小却很能“打”!合合信息扫描全能王推出A4便携式打印机

合合技术团队

识别 文字 图像 合合信息 扫描全能王

硬件、数据、模型优化与分布式训练的实践

百度开发者中心

大模型 LLM LLMOps

厦门理工计算机与信息工程学院获“火焰杯”软件测试赛全国奖项

测试人

软件测试

使用HTML5开发体感游戏——VeloMaze的开发简介_JavaScript_Raimo Tuisku_InfoQ精选文章