首页
技术分享
实用工具 发布文章 新浪微博 Github

每个人都喜欢经典游戏。你们还有多少人记得在旧的诺基亚手机中的古老贪吃蛇游戏?我敢肯定你们都记得。这就是为什么这节课我们决定利用HTML5重现它。有一个非常棒的开源游戏框架叫 “Phaser”,我们将会使用这个框架来进行开发。

在下面的教程中,你将了解sprites,游戏状态和如何使用的预加载,创建和更新方法。这是我们正在开发的游戏的最终版本:

enter image description here

安装

下载zip压缩包游戏的文件结构在里面。它包含了游戏所需的所有图像资源,但没有代码。我们接下来将会写这些代码。

现在打开index.html,为页面添加标题并引入所有js文件。以后,玩游戏,只需要在浏览器中打开这个文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Snake</title>
<script src="assets/js/phaser.min.js"></script>
<script src="assets/js/menu.js"></script>
<script src="assets/js/game.js"></script>
<script src="assets/js/game_over.js"></script>
<script src="assets/js/main.js"></script>
</head>
<body>

</body>
</html>

你的目录应该看起来像这样:

enter image description here

游戏是如何组织的

Phaser的游戏是围绕状态组织的。认为状态在Phaser中作为一个游戏的不同部分。下面是我们的游戏状态:

  • The Menu state。它是由menu.js处理,并且只显示起始图像。当它被点击,游戏转换到游戏状态。
  • Game state。它是由game.js处理,这是游戏的实际游戏区域。控制蛇吃苹果,有很多的乐趣。“当你死了” 游戏过渡到Game_Over状态。
  • Game_Over state。它显示gameover.png,并显示你的最后得分。当它被点击时返回游戏状态。

main.js是我们主要的JavaScript文件。在这我们创建一个新的游戏实例,并添加一个菜单状态。

1.加载图片

现在,我们的游戏没有做任何事。让我们来编写Menu state,并使其显示标题画面。

在安装过程中,我们在HTML文件中引入Phaser库。这提供给我们一个Phaser的全局对象。通过它,我们可以访问开发游戏库的方法和功能。

现在,我们将使用全局对象Phaser,创建一个新的游戏实例。它是一个对象,代表了整个游戏的对象。我们将状态添加给它。

main.js

1
2
3
4
5
6
7
8
9
10
var game;

// 创建一个新对象实例 600px 宽 450px 高:
game = new Phaser.Game(600, 450, Phaser.AUTO, '');

// 第一个参数是我们的状态将如何被调用。
// 第二个参数是一个对象,该对象包含需要的状态函数方法
game.state.add('Menu', Menu);

game.state.start('Menu');

现在我们需要初始化Menu state对象。在menu.js定义一个新对象并添加如下功能。当启动状态时,预加载函数将被调用,为我们的游戏加载所有需要的资源。一旦完成预加载,创建方法被调用,初始化游戏场景地和我们希望的一切:

menu.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var Menu = {

preload : function() {
// 加载图片是必需的,这样以后我们可以创建基于他们的sprites。
// 第一个参数是图像的别名
// 第二个是文件的路径
game.load.image('menu', './assets/images/menu.png');
},

create: function () {
// 添加一个精灵到你的游戏场景,这里的精灵将是游戏的标志
// 参数为: 坐标X , 坐标Y ,图像名(见上图)
this.add.sprite(0, 0, 'menu');
}

};

由于浏览器的安全限制,要开始游戏需要在本地运行一个Web服务器。这个Phaser文档页面介绍有所有操作系统的web环境–这里。换句话说,如果你简单地双击index . html它是不会工作。

如果一切都已经正确完成,那么一个开始屏幕的页面应该出现在您的浏览器上了。

enter image description here

2.绘制蛇

正如我们前面提到的,Game state记录着实际运行中的游戏状态,同时我们也在这里写下绘制蛇的方法。就像我们做的Menu state,需要在 main.js 在全局游戏对象中注册 Game state。下面是你的代码应该是什么样子:

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
var game;

// 创建一个新对象实例 600px 宽 450px 高:
game = new Phaser.Game(600, 450, Phaser.AUTO, '');

// 第一个参数是我们的状态将如何被调用。
// 第二个参数是一个对象,该对象包含需要的状态函数方法
game.state.add('Menu', Menu);

// 增加游戏状态.
game.state.add('Game', Game);

game.state.start('Menu');

我们还需要在menu.js中添加一些代码,实现当被点击时开始Game state。为了这个目的,我们将用一个按钮来代替精灵。添加一个按钮和添加一个精灵基本上是相同的,你只需要提供一个函数当它被点击时调用。

menu.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var Menu = {

preload : function() {
// 加载图片是必需的,这样以后我们可以创建基于他们的sprites。
// 第一个参数是图像的别名
// 第二个是文件的路径
game.load.image('menu', './assets/images/menu.png');
},

create: function () {
// 添加一个精灵到你的游戏场景,这里的精灵将是游戏的标志
// 参数为: 坐标X , 坐标Y ,图像名(见上图)
// this.add.sprite(0, 0, 'menu');
this.add.button(0, 0, 'menu', this.startGame, this);
},

startGame: function () {

// 状态更改为实际的游戏
this.state.start('Game');

}

};

现在我们可以继续编写的 Game state和绘制蛇。该结构类似于Menu state。

game.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
var snake, apple, squareSize, score, speed,
updateDelay, direction, new_direction,
addNew, cursors, scoreTextValue, speedTextValue,
textStyle_Key, textStyle_Value;

var Game = {

preload : function() {
// 在这里,我们将加载所有需要资源。
// 在我们的例子中,只有2个正方形-一个是蛇的身体 一个是苹果。
game.load.image('snake', './assets/images/snake.png');
game.load.image('apple', './assets/images/apple.png');
},

create : function() {

// 我们在游戏开始时需要初始化全局创建的参数变量。
// 我们需要参数在全局作用域下,以便下面函数可以改变它们。

snake = []; // 这将作为一个栈,控制蛇身体部门
apple = {}; // 苹果元素对象
squareSize = 15; // 小正方形的边的长宽。图像尺寸是15x15像素
score = 0; // 比赛成绩
speed = 0; // 游戏速度
updateDelay = 0; // 控制更新率的变量
direction = 'right'; // 蛇开始游戏后的运动的方向
new_direction = null; // 一个临时变量用来存储新的方向
addNew = false; // 一个状态用来存储 苹果是否被吃掉.

// 设置键盘输入控制 Phaser.
cursors = game.input.keyboard.createCursorKeys();

game.stage.backgroundColor = '#061f27';

// 生成初始蛇栈。我们的蛇长10个元素。
// 开始位置设置在 x= 150 y= 150,并且每次循环修改位置。
for(var i = 0; i < 10; i++){
snake[i] = game.add.sprite(150+i*squareSize, 150, 'snake'); // 参数是: (X coordinate, Y coordinate, image)
}


// 生成第一个苹果
this.generateApple();


// 添加文本到游戏顶部.
textStyle_Key = { font: "bold 14px sans-serif", fill: "#46c0f9", align: "center" };
textStyle_Value = { font: "bold 18px sans-serif", fill: "#fff", align: "center" };

// 评分.
game.add.text(30, 20, "SCORE", textStyle_Key);
scoreTextValue = game.add.text(90, 18, score.toString(), textStyle_Value);
// 速度.
game.add.text(500, 20, "SPEED", textStyle_Key);
speedTextValue = game.add.text(558, 18, speed.toString(), textStyle_Value);

},



update: function() {
// 更新功能就是不断以很高的速度(大约60fps),
// 不断更新游戏的画布。
},


generateApple: function(){

// 在网格上选择一个随机位置
// X 在 0 和 585 之间 (39*15)
// Y 在 0 和 435 之间 (29*15)

var randomX = Math.floor(Math.random() * 40 ) * squareSize,
randomY = Math.floor(Math.random() * 30 ) * squareSize;

// 生成一个新苹果.
apple = game.add.sprite(randomX, randomY, 'apple');
}

};

下面是蛇和苹果展示:

enter image description here


3. 运动与控制

我们通过game.js文件中的update方法来让蛇移动。

首先,我们需要创建一个事件侦听器,用于通过光标键控制蛇的运动方向。

实际的运动有点复杂,因为update方法会以一个非常快的速度触发,如果我们每一次都调用方法去移动蛇的位置,那么它最终会成为一个速度极快且失控的爬行动物。要改变这一点,我们创建一个 if 条件语句,用一个计数器变量updateDelay 来检查这个是否是连续的第10次update()调用。(就类似 if (updateDelay % 10 == 0 ){ 做些什么 } )。

如果到达连续的第10次调用,我们删除蛇的最后一个方块(栈的第一个元素),根据运动方向创建一个新坐标并把它放在蛇头的前面(栈的顶部)。代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
update: function() {

// 操作光标键时不允许非法方向改变,这会杀死玩家.

if (cursors.right.isDown && direction!='left')
{
new_direction = 'right';
}
else if (cursors.left.isDown && direction!='right')
{
new_direction = 'left';
}
else if (cursors.up.isDown && direction!='down')
{
new_direction = 'up';
}
else if (cursors.down.isDown && direction!='up')
{
new_direction = 'down';
}


// 一个计算游戏速度的公式
// 得分越高,游戏的速度越高,最高的是10
speed = Math.min(10, Math.floor(score/5));
// 更新游戏画面中的速度值
speedTextValue.text = '' + speed;

// 由于Phaser的更新函数具有每秒60帧左右
// 我们需要慢下来,让游戏可玩。

// 每次调用update时增加一个计数器
updateDelay++;

// 仅当计数器可以被(10 - the game speed)整除时候执行游戏操作
// 速度越高,就越频繁,
// 蛇的运行速度就越快.
if (updateDelay % (10 - speed) == 0) {


// 蛇运动

var firstCell = snake[snake.length - 1],
lastCell = snake.shift(),
oldLastCellx = lastCell.x,
oldLastCelly = lastCell.y;

// 如果从键盘上选择了一个新的方向,使它成为蛇的方向.
if(new_direction){
direction = new_direction;
new_direction = null;
}


// 根据方向改变最后一个元素的坐标.

if(direction == 'right'){

lastCell.x = firstCell.x + 15;
lastCell.y = firstCell.y;
}
else if(direction == 'left'){
lastCell.x = firstCell.x - 15;
lastCell.y = firstCell.y;
}
else if(direction == 'up'){
lastCell.x = firstCell.x;
lastCell.y = firstCell.y - 15;
}
else if(direction == 'down'){
lastCell.x = firstCell.x;
lastCell.y = firstCell.y + 15;
}


// 把最后一个单元格放在栈的前面
// 标题它称谓第一个项

snake.push(lastCell);
firstCell = lastCell;

}

}

尝试通过键盘上的方向键控制蛇。

enter image description here

4. 碰撞检测

游戏中蛇是在赛场上可以无拘束的自由移动不是一件有趣的事。我们需要检测何时蛇与墙壁,苹果或自己接触。这就是所谓的碰撞检测。

碰撞检测通常是使用物理引擎来完成,Phaser框架对此支持的很少。像这样一个简单的游戏用物理引擎太复杂了。我们转而通过比较坐标的方式来做我们自己的碰撞检测。

在update方法中,移动蛇的位置代码后,我们调用了一些方法。它们会比较坐标如果发生碰撞会通知我们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

update: function() {

// Snake movement
// ...
// End of snake movement

// 如果吃到苹果增加蛇的长度。
// 在蛇的尾部,在过去的最后一个块的旧位置创建一个块
// (它已经和其他的块一起移动).

if(addNew){
snake.unshift(game.add.sprite(oldLastCellx, oldLastCelly, 'snake'));
addNew = false;
}

// 苹果碰撞检查
this.appleCollision();

// 与自我碰撞检查。参数是蛇的头。
this.selfCollision(firstCell);

// 与墙壁碰撞检查。参数是蛇的头。
this.wallCollision(firstCell);


}

},
appleCollision: function() {

// 检查蛇是否有一部分和苹果重叠。
// 这是必要的如果苹果位置在蛇身上
for(var i = 0; i < snake.length; i++){
if(snake[i].x == apple.x && snake[i].y == apple.y){

// 下一次,蛇的移动,一个新的块将增加它的长度。
addNew = true;

// 销毁苹果
apple.destroy();

// 生成一个新的苹果
this.generateApple();

// 增加分数
score++;

// 刷新记分牌
scoreTextValue.text = score.toString();

}
}

},

selfCollision: function(head) {

// 检查蛇的头部是否与蛇身体部分重叠。
for(var i = 0; i < snake.length - 1; i++){
if(head.x == snake[i].x && head.y == snake[i].y){

// 如果重合跳到游戏结束页面
game.state.start('Game_Over');
}
}

},

wallCollision: function(head) {

// 检查蛇的头部是否在游戏区域的边界内。

if(head.x >= 600 || head.x < 0 || head.y >= 450 || head.y < 0){


// 如果不在,我们就撞到墙上。跳到游戏结束页面
game.state.start('Game_Over');
}

}

当蛇与苹果碰撞,我们增加的分和蛇的长度。但是,当与墙或蛇体发生碰撞时,就要结束游戏。要做到这一点,我们需要在main.js中注册Game_Over state。加入这行代码到文件的底部:

main.js

1
game.state.add('Game_Over', Game_Over);

game_over.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var Game_Over = {

preload : function() {
// 加载所需的图像资源
game.load.image('gameover', './assets/images/gameover.png');
},

create : function() {

// 创建一个按钮 和开始游戏菜单的一样。
this.add.button(0, 0, 'gameover', this.startGame, this);

// 添加游戏的分数等文本信息。
game.add.text(235, 350, "LAST SCORE", { font: "bold 16px sans-serif", fill: "#46c0f9", align: "center"});
game.add.text(350, 348, score.toString(), { font: "bold 20px sans-serif", fill: "#fff", align: "center" });

},

startGame: function () {

// 改变状态返回游戏。
this.state.start('Game');

}

};

enter image description here

就是这样!我们游戏已经准备好了!


延伸阅读

我们希望你喜欢这个简单的小游戏,并通过我们的教程学到很多东西,更多关于Phaser的链接:

Phaser’s official introduction tutorial

Phaser live coding sandbox

Gallery with practical examples

原文链接:Making Your First HTML5 Game With Phaser