HTML5游戏系列(2):装B达人 由David发表在天码营
小编的第一款HTML游戏:“你能戳几下?”引起了微信圈不错的反响,这更加激发了“天码营”团队以及小编本人的斗志。此次小编带来了可玩性更强的HTML5游戏:“装B达人”!还没有玩过的可以关注天码营公众号“TMY-EDE”并获取最新文章即可得到游戏链接。也可以直接打开链接: http://tianmaying.com/app/collect-b/
下面便开始详述“装B达人”是怎样炼就的!先上图,一睹为快:
简介
游戏流程
B会不断地从天而降,玩家可以移动篮子来装B,我们会给出装B计数。一旦B已经接触地面在本次装B失败,游戏结束(Game Over~)。
技术框架
本游戏的实现借助于HTML5提供的Canvas标签,Canvas是一个基于位图的矩形绘图区域,该标签最初由Safari提供支持,在2014年10月终于被纳入HTML标准。进入HTML标准意味着主流浏览器都将对此提供支持,Google在Android4.0中便对Canvas提供了硬件加速。这意味着在HTML中复杂的游戏也可以做到很好的性能。
Canvas: a resolution-dependent bitmap canvas which can be used for rendering graphs, game graphics, or other visual images on the fly. -- HTML5, W3C
“装B达人”引入了CreateJS来进行更方便的Canvas操作,以及Sprite素材的载入。
CreateJS: A suite of modular libraries and tools which work together or independently to enable rich interactive content on open web technologies via HTML5.
CreateJS是一个组件库,它提供了四部分的工具:
- EaselJS:提供了更加方便的Canvas操作,这是我们主要使用的一个组件。
- TweenJS:更方便地进行HTML5中元素属性的渐变和动画。
- SoundJS:提供了丰富的音频处理API。
- PreloadJS:用来管理Web资源的载入,我们用它来载入图片素材,并形成一个Sprite对象以供使用。
实现流程
理论上HTML文件只需提供一个canvas
标签,其它的交给JS来处理。最终的实现也是这样的,HTML框架非常简单,只需引入需要的样式的脚本即可。在脚本中完成游戏的初始化过程与交互动画。
首先利用PreloadJS载入需要的图片素材;然后用这些素材来初始化整个场景。此后游戏就可以开始啦!游戏开始后,我们需要在Tick(时钟)事件到达时重新绘图,完成如下几个操作:
- 让B下落。只需增加该元素的
y
属性。 - 检测B与地面的碰撞。如果发生碰撞则游戏结束。
- 检测B与篮子的碰撞。如果发生碰撞则分数+1,从天上再掉下一个B。
为了让玩家能够移动篮子,需要监听mousedown
和pressmove
事件:
- 当
mousedown
时,记录按下的位置。 - 当
pressmove
时,计算与mousedown
时的位置差,并相应地移动篮子。
HTML框架
“装B达人”使用HTML5 Canvas实现,因此HTML框架非常简单。只需定义一个<canvas>
并在HTML头部引入相应的样式和JS即可!
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no"/>
<link rel="stylesheet" href="assets/css/main.css">
<script src="assets/lib/easeljs-NEXT.combined.js"></script>
<script src="assets/lib/preloadjs-NEXT.min.js"></script>
<script src="assets/lib/tweenjs-NEXT.min.js"></script>
<script src="assets/js/main.js"></script>
</head>
<body>
<canvas id="testCanvas"></canvas>
</body>
</html>
其中的easeljs-NEXT
, preloads-NEXT
, tweenjs-NEXT
是CreateJS提供的Canvas工具,这三个文件可以在参考代码中下载;我们的所有编程工作都在main.css
和main.js
中,请手动新建这两个文件!
全局样式
样式很简单啦,只需要样canvas全屏即可:
/* css/main.css */
html, body{
padding: 0;
margin: 0;
}
因为有些浏览器的UA默认样式表会为
html
和body
提供边距,所以我们需要设置它们的内外边距为0.
初始化场景
说道游戏,你首先想到的概念可能是:场景、模型、灯光、动画。游戏引擎们尽可能地封装了这些数据和操作,尤其在JavaScript中这些概念只会更简单!我们看看CreateJS中,如何创建一个初始的场景!
首先你需要从参考代码中把图片素材都解压出来。然后建立一个素材清单:
// file: js/main.js
manifest = [
{src: "sky.png", id: "sky"},
{src: "ground.png", id: "ground"},
{src: "hill1.png", id: "hill"},
{src: "hill2.png", id: "hill2"},
{src: "b.png", id: "b"},
{src: 'basket.png', id: 'basket'}
];
接着,我们需要载入这些素材,并初始化场景。
// file: js/main.js
var score = 0, w, h, bs = []; // 全局变量:得分、画布宽高、B的数组
document.addEventListener('DOMContentLoaded', function(){
$canvas = document.getElementById('testCanvas');
$canvas.setAttribute('width', window.innerWidth);
$canvas.setAttribute('height', window.innerHeight);
stage = new createjs.Stage($canvas);
w = stage.canvas.width;
h = stage.canvas.height;
loader = new createjs.LoadQueue(false);
loader.addEventListener("complete", handleComplete);
loader.loadManifest(manifest, true, "assets/art/");
});
在CreateJS中,出于性能考虑,图片清单是异步加载的。当我们给出的图片清单加载完毕时,handleComplete()
会被调用。在handleComplete
中素材都已下载完毕,我们需要设置这些图片的位置,变换,以及平铺属性:
function handleComplete() {
// 分数文本:放在右上角,红色
scoreText = new createjs.Text("", "bold 28px Impact", "red");
scoreText.x = w - 20;
scoreText.y = 10;
// 天空:横向平铺。地面和山丘也是类似的,请见参考代码
var skyImg = loader.getResult("sky");
sky.graphics.beginBitmapFill(skyImg, 'repeat-x').drawRect(0, 0, w, h);
// 篮筐:为了方便动画,将中心点设为参考点
basketImg = loader.getResult('basket');
basket = new createjs.Bitmap(basketImg);
basket.regX = basketImg.width / 2;
basket.regY = basketImg.height / 2;
basketRange = {
x: basket.regX,
y: basket.regY,
width: w - basketImg.width,
height: h - groundImg.height - basketImg.height
};
// 把上述对象添加到场景中
stage.addChild(skybg, sky, hill, ground, trolley, scoreText);
// 获取B的图片
bImg = loader.getResult("b");
brange = {
width: w - bImg.width,
height: h - groundImg.height
};
// 把它们都添加到场景中
for (var i = 0; i < 5; i++) { // 为什么是5?见下一节
var b = new createjs.Bitmap(bImg);
initB(b); // 初始化B,见下一节
bs.push(b); stage.addChild(b);
}
// 更新这个场景,触发canvas刷新
stage.update();
}
初始化B
游戏中会有大量的B从天而降,为了保证性能我们手动地管理B的缓存。基于我们的观察,在任何一刻的画面中B的数量都是有限的。不妨就此规定B的数量,比如5个。当一个B被收集或者落地时,我们回收它,让它重新从天而降。回收和初始化B都是用initB()
函数:
function initB(b){
b.regX = bImg.width / 2;
b.regY = bImg.height / 2;
// 随机生成一个在当前场景上方的位置。
b.x = Math.random() * (brange.width - bImg.width) + b.regX;
b.y = Math.random() * brange.height - brange.height - bImg.height / 2;
}
掉落动画
随着时间的推移,B应当从天而降。为了逐帧地更新动画,在handleComplete()
函数最后监听时钟:
createjs.Ticker.addEventListener("tick", tick);
其中的tick
函数中,我们需要让B往下落,同时要检测B是否被收集了,以及B是否碰到地面了。
function tick(){
// event.delta是距离上次`tick`的时间,以毫秒计
// 将时间变换为距离,0.15是可调参数,其物理意义是下落速度 v = s/t
var deltaS = event.delta * 0.15;
// 遍历所有的B
bs.forEach(function (b) {
if (b.inCollision) return; // 如果它已经触地,则返回
b.y += deltaS; // 让它下落!
if (collisionX(b, basket)) { // b和basket的碰撞检测,见下一节
b.inCollision = true;
score ++; // 更新分数
scoreText.text = score + 'B';
initB(b); // 初始化B,让它重新从天而降
}
else if (b.y + b.regY >= brange.height) {
b.inCollision = true; // b碰到地面啦,游戏结束
gameOver();
}
});
// 刷新canvas
stage.update(event);
}
function gameOver(){
// 游戏结束!
// 以任何形式告知用户
}
碰撞检测
上述的掉落动画中,我们调用了B和basket的碰撞检测,CreateJS并未提供这样的高阶功能,但矩形的碰撞检测并不困难!
function collisionX(a, b) {
return a.x + a.regX > b.x - b.regX // a的右边超出了b的左边
&& a.x - a.regX < b.x + b.regX // b的右边超出了a的左边
&& a.y + a.regY > b.y - b.regY // a的下边超出了b的上边
&& a.y - a.regY < b.y + b.regY; // b的下边超出了a的上边
}
可执行的、完整的代码见此处参考代码。