免费下载案例集|20+数字化领先企业人才培养实践经验 了解详情
写点什么

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

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

关注

评论

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

如何用好免费的chatGPT

劼哥stone

人工智能 openai ChatGPT

React-Hooks源码深度解读

goClient1992

React

PyTorch深度学习实战 | 搭建卷积神经网络进行图像分类与图像风格迁移

TiAmo

PyTorch 图像处理

A-Ops 数据库场景在线应用性能诊断案例

openEuler

数据库 Linux 操作系统 openEuler 性能测评

毕业设计

张贺

深入探究 Go log 标准库

江湖十年

Go 后端 日志 log

Matlab常用图像处理命令108例(五)

timerring

图像处理

运维训练营第17周作业

好吃不贵

精华推荐 |【深度挖掘RocketMQ底层源码】「生产故障分析系列」深度挖掘RocketMQ底层那些导致消息丢失的汇总盘点透析(TIMEOUT_CLEAN_QUEUE)broker busy

洛神灬殇

RocketMQ 消息队列 3月日更 原理分析 底层源码

文盘Rust -- 安全连接 TiDB/Mysql

京东科技开发者

rust TiDB 京东云 京东技术 企业号 3 月 PK 榜

什么大模型?我是时尚产业“大模王”!

脑极体

AI

React源码分析(一)Fiber

goClient1992

React

细说react源码中的合成事件

flyzz177

React

Amazon Detective 支持 Amazon EKS 上的 Kubernetes 工作负载以进行安全调查

亚马逊云科技 (Amazon Web Services)

前端工程师leetcode算法面试必备-二分搜索算法(上)

js2030code

JavaScript LeetCode

2023算云融合产业大会-行业先驱!算力云服务领航者计划评选结果即将揭晓

中国IDC圈

算力 云算力 云计算,

JRC Flink流作业调优指南

京东科技开发者

Apache 京东云 京东技术 企业号 3 月 PK 榜

提示工程在AI绘画领域中的应用

FinFish

AI绘画 提示工程

微信小程序管理软件助力企业数字化转型,实现智能化升级

FinFish

私有小程序技术 小程序管理平台 小程序技术 小程序管理

mybatis plus怎么使用注解的方式执行原生sql

俊俊哥

mybatis Mybatis-Plus 原生sql

马克·安德森:纯粹产品护城河在硅谷少见,SaaS公司护城河是“分销”

B Impact

深入react源码看setState究竟做了什么?

flyzz177

React

@Transaction注解的失效场景

京东科技开发者

京东云 京东技术 企业号 3 月 PK 榜

Nautilus Chain 现已推出测试网“Triton ”,有哪些潜在的机会?

股市老人

一天吃透分布式事务八股文

程序员大彬

Java 分布式事务

看透react源码之感受react的进化

goClient1992

React

React Context源码是怎么实现的呢

flyzz177

React

前端工程师leetcode算法面试必备-二分搜索算法(下)

js2030code

JavaScript LeetCode

震惊,一行MD5居然让小伙伴都回不了家!!!

京东科技开发者

md5 京东云 京东技术 企业号 3 月 PK 榜

用javascript分类刷leetcode3.动态规划(图文视频讲解)

js2030code

JavaScript LeetCode

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