报名参加CloudWeGo黑客松,奖金直推双丰收! 了解详情
写点什么

使用 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:296464
用户头像

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

关注

评论

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

宜兴牵手百度智能云共建人工智能应用中心,推动数字经济创新发展

百度大脑

人工智能

☕️【Java技术之旅】带你一起探究String类不可变的特性

码界西柚

string 原理 字符串 6月日更

准备3个月,面试10分钟,Java中高级岗面试为何越来越难?

Java架构师迁哥

Flink + Iceberg 在去哪儿的实时数仓实践

Apache Flink

flink

2021年最新版Java后端最全面试攻略,全面对标BATJ

Java 程序员 架构 面试

如何科学制定和管理项目计划?

万事ONES

项目管理 ONES 项目经理

毕昇JDK:为啥是ARM上超好用的JDK

华为云开发者联盟

Java 华为 jdk Openjdk 毕昇 JDK

用 CloudQuery 管理和操作数据库,更高效更安全

BinTools图尔兹

运维 dba 数据库管理工具

☕️【Java 技术之旅】带你一起攻克String类创建的难点分析

码界西柚

Java string pool string 6月日更

react源码解析9.diff算法

全栈潇晨

react源码

程序员需要了解数据库知识么?

escray

学习 极客时间 朱赟的技术管理课 6月日更

开源之夏来啦,欢迎报名 Apache APISIX 项目!

API7.ai 技术团队

开源 后端 技术人生 API 网关

带你剖析鸿蒙轻内核任务栈的源代码

华为云开发者联盟

鸿蒙 任务栈 任务调度 任务上下文

博云作为专业独立PaaS厂商,入选中国PaaS市场研究报告

BoCloud博云

PaaS

24道几乎必问的JVM面试题,我只会7道,你能答出几道?

北游学Java

Java 面试 JVM

福利时刻 十年黑客大佬的Web安全渗透技术分享

学神来啦

Linux 黑客 安全 运维自动化

建信金科大咖访谈:ISO20000及ISO27001标准体系解读

金科优源汇

Kubernetes学习笔记之Calico CNI Plugin源码解析(二)

360技术

基于传感器的人体生命体征监控技术

不脱发的程序猿

物联网 传感器 智能医疗 人体生命体征监控技术

从零开始学习3D可视化之模型动画

ThingJS数字孪生引擎

可视化 模型 大屏可视化 数字时代 3D可视化

centos7使用

xujiangniao

Linux

超全!阿里首发内部微服务架构笔记,再也不用为“微服务”苦恼了

Java架构师迁哥

创业邦南立新:搭建创新生态,是奔向万亿美金市值的必经之路

创业邦

油管视频下载: 如何下载油管视频到本地

科技猫

分享 教程 经验 油管视频下载 下载油管视频

我人生的里程碑之【作为独立开发者,第一次承接外包项目的心得经历,也许说出你的心声哦!】

码界西柚

程序人生 6月日更

在外包5年,每天读写删改,突然发现跳不出来了

Java架构师迁哥

如何针对美工与设计师的Maya工具进行版本控制

龙智—DevSecOps解决方案

百度灵医智惠明星案例获人民日报点赞:智慧医疗让看病更便捷

百度大脑

人工智能 智慧医疗

面试官:如何给字符串设计索引?

一个优秀的废人

MySQL 索引 字符串 索引优化

阿里云官方出品:全面总结阿里云云原生架构方法论与实践经验

尹文敏

云计算 阿里云 云原生

《原则》(九)

Changing Lin

6月日更

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