-By Daniel X. Moore,来自Pixel Relations, STRd6。
2010年2月1日
本文适用的浏览器为 Google Chrome/Mozilla Firefox/Apple Safari/Opera。
介绍
你想用HTML5的新特性Canvas来制作一个游戏么?那相信这篇教程会给你带来一定的帮助。
不过丑话说前面,你得有至少中级的Javascript应用能力才行。
你可以先看看这个游戏成品,或者是直接来进入文章并查看游戏的源码。
创建canvas区域
为了让内容显示在屏幕上,我们需要一个canvas区域。由于本文重点是游戏制作,那么在这里我们使用jQuery来创建这一区域。
var CANVAS_WIDTH = 480; var CANVAS_HEIGHT = 320; var canvasElement = $(""); var canvas = canvasElement.get(0).getContext("2d"); canvasElement.appendTo('body');
游戏更新
为了让游戏画面更流畅,我们需要将更新画面的频率设置的快一些。快多少呢?只要比人的眼睛和大脑的反应速度稍快就行啦!
var FPS = 30; setInterval(function() { update(); draw(); }, 1000/FPS);
那么就这样,我们就可以更新画面或是绘制空白方法了。不过千万要记得定时调用这个叫做setIntercal()的函数。
function update() { ... } function draw() { ... }
Hello World
现在我们有了一个游戏循环更新的函数,那么接下来就可以在屏幕上绘制东西了。首先从最简单的绘制文字开始。
function draw() { canvas.fillStyle = "#000"; // Set color to black canvas.fillText("Hello World!", 50, 50); }
注意:确保在修改后你的程序还能正常运行。只有分段检查,在出问题之后才更容易确定出问题的地方——查几行代码总比查全部工程要强吧?
如果你想让静态的字体更酷一些的话(比如动起来),那就使用我们已经设定好的游戏循环更新函数吧。通过它,我们可以让文字移动时候更容易一些。
var textX = 50; var textY = 50; function update() { textX += 1; textY += 1; } function draw() { canvas.fillStyle = "#000"; canvas.fillText("Hello World!", textX, textY); }
现在来让文字回旋起来吧。如果按照上面的代码,那么文字应该已经动起来了。不过在它移动后留在屏幕上的痕迹并没有被清除。先停一下,思考思考为啥会这样?其实答案很简单,因为我们在绘制新的图案之前并没有清除屏幕。所以在绘屏之前让我们先执行清屏操作。
function draw() { canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); canvas.fillStyle = "#000"; canvas.fillText("Hello World!", textX, textY); }
嗯,这样我们就让一段文字在屏幕上移动了,于是这样的话,在制作游戏的道路上我们也就成功了一半了……也就是说,除了增加控制、提高游戏体验,美化游戏界面……呃,或许还有6/7的路我们就可以创造一个真正的游戏了呢。别紧张,接下来的部分,也有教程陪着你。
创建游戏控制器
游戏玩家是用于操控游戏信息,并负责绘制诸如图形之类数据的一个对象。那么接下来我们就创建一个游戏控制器,并用简单的字符串变量来储存全部信息。
var player = { color: "#00A", x: 220, y: 270, width: 32, height: 32, draw: function() { canvas.fillStyle = this.color; canvas.fillRect(this.x, this.y, this.width, this.height); } };
为方便起见,让我们用一个简单的上色矩形来代表当前的游戏玩家。当我们绘制游戏时,我们将canvas清空,并绘制游戏玩家对象。
function draw() { canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); player.draw(); }
键盘控制
使用jQuery Hotkeys
jQuery Hotkeys Plugin让浏览器响应键盘按键变得更容易了。通过定义的keyCode和charCode,来将事件如下一样绑定
$(document).bind("keydown", "left", function() { ... });
这个函数的一大长处是不需要知道每个键的ASCII码,我们需要做的就是命令它“当玩家按某个键的时候,去做什么事儿”,jQuery Hotkeys就可以很好的完成我们想要的事儿。
游戏对象移动
JavaScript接管键盘事件的方法是通过事件驱动,所以这也就意味着我们没法检测按下的是哪个键。所以,那让我们使用自己的方法吧。
你可能会问,为什么我们不自己建立一个事件驱动么?好吧,因为键盘是通过系统的IO组件来反映操作的,而并不通过我们的游戏更新循环,所以我们并不能及时获得按键信息。那么为了用户体验的一致性,我们需要在游戏更新循环中加入自己的按键检测。
好消息是,我已经引用了一个16行的JS文件——key_status.js来让检测按键成为了可能。你只要来在looping函数中检查keydown.left或是对应的其他键的事件即可。
现在我们能检测按键了,那就用下面这个update函数来移动游戏对象。
function update() { if (keydown.left) { player.x -= 2; } if (keydown.right) { player.x += 2; } }
那接下来,让它旋转起来吧。
你可能已经注意到了,那块东西(好吧,游戏对象)会被移出可见范围之外,那让我们锁定它的范围。还有,就是移动速度有点慢,那就加速,呵呵。
function update() { if (keydown.left) { player.x -= 5; } if (keydown.right) { player.x += 5; } player.x = player.x.clamp(0, CANVAS_WIDTH - player.width); }
增加更多的输入同样简单。那让我们发射炮弹吧!
function update() { if (keydown.space) { player.shoot(); } if (keydown.left) { player.x -= 5; } if (keydown.right) { player.x += 5; } player.x = player.x.clamp(0, CANVAS_WIDTH - player.width); } player.shoot = function() { console.log("Pew pew"); // :) 好吧,至少增加按键绑定这事很简单。。。就是这样~ };
增加更多的游戏元素
子弹
让我们增加一些子弹吧。首先,需要一个数组来储存它们的信息。
var playerBullets = [];
然后我们需要一个函数来创建这些子弹实例。
function Bullet(I) { I.active = true; I.xVelocity = 0; I.yVelocity = -I.speed; I.width = 3; I.height = 3; I.color = "#000"; I.inBounds = function() { return I.x >= 0 && I.x <= CANVAS_WIDTH && I.y >= 0 && I.y <= CANVAS_HEIGHT; }; I.draw = function() { canvas.fillStyle = this.color; canvas.fillRect(this.x, this.y, this.width, this.height); }; I.update = function() { I.x += I.xVelocity; I.y += I.yVelocity; I.active = I.active && I.inBounds(); }; return I; }
当玩家按下发射键时,我们需要马上创建一个子弹实例,并放置到之前储存信息的数组中以便显示。
player.shoot = function() { var bulletPosition = this.midpoint(); playerBullets.push(Bullet({ speed: 5, x: bulletPosition.x, y: bulletPosition.y })); }; player.midpoint = function() { return { x: this.x + this.width/2, y: this.y + this.height/2 }; };
那么现在我们需要将上面的代码加入到游戏循环更新的函数中了。为了防止集合中已经攻击过敌人或是飞出窗体的子弹(我们称之为“无效子弹”)仍旧滞留在数组中,我们来把整个一系列子弹过滤到只剩活动的子弹。这样的话我们移除无效子弹的方法也简单多了。
function update() { ... playerBullets.forEach(function(bullet) { bullet.update(); }); playerBullets = playerBullets.filter(function(bullet) { return bullet.active; }); }
最后一步是绘制子弹。
function draw() { ... playerBullets.forEach(function(bullet) { bullet.draw(); }); }
敌人
下面让我们来像增加子弹一样增加一些敌人吧。
enemies = []; function Enemy(I) { I = I || {}; I.active = true; I.age = Math.floor(Math.random() * 128); I.color = "#A2B"; I.x = CANVAS_WIDTH / 4 + Math.random() * CANVAS_WIDTH / 2; I.y = 0; I.xVelocity = 0 I.yVelocity = 2; I.width = 32; I.height = 32; I.inBounds = function() { return I.x >= 0 && I.x <= CANVAS_WIDTH && I.y >= 0 && I.y <= CANVAS_HEIGHT; }; I.draw = function() { canvas.fillStyle = this.color; canvas.fillRect(this.x, this.y, this.width, this.height); }; I.update = function() { I.x += I.xVelocity; I.y += I.yVelocity; I.xVelocity = 3 * Math.sin(I.age * Math.PI / 64); I.age++; I.active = I.active && I.inBounds(); }; return I; }; function update() { ... enemies.forEach(function(enemy) { enemy.update(); }); enemies = enemies.filter(function(enemy) { return enemy.active; }); if(Math.random() < 0.1) { enemies.push(Enemy()); } }; function draw() { ... enemies.forEach(function(enemy) { enemy.draw(); }); }
读取并绘制图片
图片移动显然比色块移动看上去帅。在canvas中读取并绘制图片是很痛苦的,为了避免那些,让我们使用一个简单的公用模块。
player.sprite = Sprite("player"); player.draw = function() { this.sprite.draw(canvas, this.x, this.y); }; function Enemy(I) { ... I.sprite = Sprite("enemy"); I.draw = function() { this.sprite.draw(canvas, this.x, this.y); }; ... }
碰撞检测
像机器人比赛?其实是,我们将这些元素绘制完后,它们相撞却不会产生事件。为了让他们能在合适的时候爆炸,那让我们增加一些判断吧。
让我们使用一个简单的矩形碰撞检测算法:
function collides(a, b) { return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y; }
请注意,我们有两个需要检测的东西。
·1、玩家的子弹>=敌方飞船
·2、玩家>=敌方飞船
完成后,让我们做一个钩子来挂载到update函数中,以便游戏更新循环可以更新我们需要的碰撞检测。
function handleCollisions() { playerBullets.forEach(function(bullet) { enemies.forEach(function(enemy) { if (collides(bullet, enemy)) { enemy.explode(); bullet.active = false; } }); }); enemies.forEach(function(enemy) { if (collides(enemy, player)) { enemy.explode(); player.explode(); } }); } function update() { ... handleCollisions(); }
完成了检测,让我们加点爆炸效果吧。这个函数标记了被碰撞的对象,同时移除并播放爆炸效果。
function Enemy(I) { ... I.explode = function() { this.active = false; // 可以增加一点爆炸的图片 }; return I; }; player.explode = function() { this.active = false; // 增加爆炸的图片并结束游戏。 };
音效
声音也是个增加用户体验的不错方法。虽然说在HTML5中直接嵌入声音和图片还是挺那啥的,不过多亏我们有一个额外的js——sound.js,插入声音也变得非常简单了。
player.shoot = function() { Sound.play("shoot"); ... } function Enemy(I) { ... I.explode = function() { Sound.play("explode"); ... } }
需要你注意的是,引入声音可能会带来一系列稳定性问题,诸如选项卡崩溃或是声音卡顿之类的。所以要有心理准备。
结束了
还是那句话,这是这个游戏成品,而且这是整套程序的zip源码包。
看完教程,我希望你已经明白了用JS和html5做游戏的最基础的一些东西了。经过一些适当的抽象编程,我们可以对api更灵活地掌握,同时也学会了一些灵活应对未来问题的方法。
博主 您好 你的这个翻译真的不错
不知道有没有建立群什麽的 大家交流。。
新手学习开发html5 希望能做一些新东西 不只是游戏。。。
邮件交流哈。
啊哈。还真没有呢。