-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 希望能做一些新东西 不只是游戏。。。
邮件交流哈。
啊哈。还真没有呢。