如何实现canvas绘制推箱子功能?
实现效果
推箱子是一款风靡已久的益智小游戏,如何在网页实现推箱子功能?这就不得不说我们html5新增的canvas功能,让我们能对图片等资源可以更好的绘制。
设计思路
素材
首先我们应该拥有设计素材,博主木有嗷~所以在网上找了一些合适的素材。分别代表墙、草地、应该被推入的位置标志、箱子和我们的人物,如下图。
实现思路
- 生成背景图
对于页面的显示,我们使用像素化的表示方式。如上图,一个正方块为假设为一个单元,我们的实现图就是一个10 * 7 的地图,我们可以用二维数组来表示它:0表示无,1表示隔离,2表示空地,3表示目标,4表示箱子,5表示当前位置。1
2
3
4
5
6
7
8// 我们的初始地图可以表示为:
[[0,0,0,1,1,1,1,1,1,0],
[0,1,1,1,2,2,2,2,1,0],
[1,1,3,2,4,1,1,2,1,1],
[1,3,3,4,2,4,2,2,5,1],
[1,3,3,2,4,2,4,2,1,1],
[1,1,1,1,1,1,2,2,1,0],
[0,0,0,0,0,1,1,1,1,0]];
我们生成一个Sokoban对象,用来记录我们的推箱子一个地图的所有内容:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var Sokoban = function(boxContext,width,height) {
this.context = boxContext; //存储canvas的 ctx 对象
this.width = width;
this.height = height;
this.curLoc = {x:8,y:3}; //是箱子初始的位置
this.targetLoc = [{x:1,y:3},{x:1,y:4},{x:2,y:2},{x:2,y:3},{x:2,y:4}]; //是应该把箱子推入的位置
this.imgArray = [new Image(),new Image(),new Image(),new Image(),new Image()]; //我们填充地图的图片资源,现在没有给src
//0表示无,1表示隔离,2表示空地,3表示目标,4表示箱子,5表示当前位置
this.cellArray = [[0,0,0,1,1,1,1,1,1,0],
[0,1,1,1,2,2,2,2,1,0],
[1,1,3,2,4,1,1,2,1,1],
[1,3,3,4,2,4,2,2,5,1],
[1,3,3,2,4,2,4,2,1,1],
[1,1,1,1,1,1,2,2,1,0],
[0,0,0,0,0,1,1,1,1,0]];
}
- 页面打开时,根据地图把二维数组用图片进行背景的填充:
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//给sokoban对象附加原型方法
paintCells : function(){
//0表示无,1表示隔离,2表示空地,3表示目标,4表示箱子,5表示当前位置
for(var i=0,w=this.cellArray[0].length,h=this.cellArray.length;i<h;i++){
for(var j=0;j<w;j++){
this.fillCell(this.cellArray[i][j],j,i);
}
}
},
fillCell : function(type,x,y){
var cellWidth = this.width/this.cellArray[0].length; //一个单元的宽度
var cellHeight = this.height/this.cellArray.length; //一个单元的高度
switch(type){
case 0: this.context.save();
this.context.clearRect(cellWidth*x,cellHeight*y,cellWidth,cellHeight);
this.context.fillStyle = '#000';
this.context.fillRect(cellWidth*x,cellHeight*y,cellWidth,cellHeight);
this.context.restore();
break;
case 1:
this.context.save();
this.context.clearRect(cellWidth*x,cellHeight*y,cellWidth,cellHeight);
var ptn = this.context.createPattern(this.imgArray[0],"repeat");
this.context.fillStyle = ptn;
this.context.fillRect(cellWidth*x,cellHeight*y,cellWidth,cellHeight);
this.context.restore();
break;
case 2: this.context.save();
this.context.clearRect(cellWidth*x,cellHeight*y,cellWidth,cellHeight);
this.context.fillStyle = this.context.createPattern(this.imgArray[1],"repeat");
this.context.fillRect(cellWidth*x,cellHeight*y,cellWidth,cellHeight);
this.context.restore();
break;
case 3: this.context.save();
this.context.clearRect(cellWidth*x,cellHeight*y,cellWidth,cellHeight);
this.context.fillStyle = this.context.createPattern(this.imgArray[1],"repeat");
this.context.fillRect(cellWidth*x,cellHeight*y,cellWidth,cellHeight);
this.context.drawImage(this.imgArray[2],cellWidth*x,cellHeight*y,cellWidth,cellHeight);
this.context.restore();
break;
case 4: this.context.save();
this.context.clearRect(cellWidth*x,cellHeight*y,cellWidth,cellHeight);
this.context.fillStyle = this.context.createPattern(this.imgArray[1],"repeat");
this.context.fillRect(cellWidth*x,cellHeight*y,cellWidth,cellHeight);
this.context.drawImage(this.imgArray[3],cellWidth*x,cellHeight*y,cellWidth,cellHeight);
this.context.restore();
break;
case 5: this.context.save();
this.context.clearRect(cellWidth*x,cellHeight*y,cellWidth,cellHeight);
this.context.fillStyle = this.context.createPattern(this.imgArray[1],"repeat");
this.context.fillRect(cellWidth*x,cellHeight*y,cellWidth,cellHeight);
this.context.drawImage(this.imgArray[4],cellWidth*x,cellHeight*y,cellWidth,cellHeight);
this.context.restore();
break;
}
},
3、监听按键事件window.onkeydown ,其中我们的重点是上下左右按键,分别对应的unicode编码为:
←:37
↑:38
→:39
↓:401
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
81window.onkeydown = function(e){
e.preventDefault();
that.action(e.keyCode); //处理按键值
};
//处理上下左右的逻辑
action : function(direction){
var nextLoc = {};
switch(direction){
case 38:
nextLoc.type = this.cellArray[this.curLoc.y-1][this.curLoc.x];
nextLoc.x = this.curLoc.x;
nextLoc.y = this.curLoc.y-1;
if( nextLoc.type == 2 || nextLoc.type == 3){
this.move(nextLoc); //更新移动后的位置
this.paintCells(); //重新绘制地图
}else if( nextLoc.type == 4 && this.cellArray[this.curLoc.y-2][this.curLoc.x] == 2 || this.cellArray[this.curLoc.y-2][this.curLoc.x] == 3){
this.pushBox(nextLoc.x,nextLoc.y-1);
this.move(nextLoc);
this.paintCells();
}
this.checkResult();
break;
case 40:
nextLoc.type = this.cellArray[this.curLoc.y+1][this.curLoc.x];
nextLoc.x = this.curLoc.x;
nextLoc.y = this.curLoc.y+1;
if( nextLoc.type == 2 || nextLoc.type == 3){
this.move(nextLoc);
this.paintCells();
}else if( nextLoc.type == 4 && this.cellArray[this.curLoc.y+2][this.curLoc.x] == 2 || this.cellArray[this.curLoc.y+2][this.curLoc.x] == 3){
this.pushBox(nextLoc.x,nextLoc.y+1);
this.move(nextLoc);
this.paintCells();
}
this.checkResult();
break;
case 37:
nextLoc.type = this.cellArray[this.curLoc.y][this.curLoc.x-1];
nextLoc.x = this.curLoc.x-1;
nextLoc.y = this.curLoc.y;
if( nextLoc.type == 2 || nextLoc.type == 3){
this.move(nextLoc);
this.paintCells();
}else if( nextLoc.type == 4 && this.cellArray[this.curLoc.y][this.curLoc.x-2] == 2 || this.cellArray[this.curLoc.y][this.curLoc.x-2] == 3){
this.pushBox(nextLoc.x-1,nextLoc.y);
this.move(nextLoc);
this.paintCells();
}
this.checkResult();
break;
case 39:
nextLoc.type = this.cellArray[this.curLoc.y][this.curLoc.x+1];
nextLoc.x = this.curLoc.x+1;
nextLoc.y = this.curLoc.y;
if( nextLoc.type == 2 || nextLoc.type == 3){
this.move(nextLoc);
this.paintCells();
}else if( nextLoc.type == 4 && this.cellArray[this.curLoc.y][this.curLoc.x+2] == 2 || this.cellArray[this.curLoc.y][this.curLoc.x+2] == 3){
this.pushBox(nextLoc.x+1,nextLoc.y);
this.move(nextLoc);
this.paintCells();
}
this.checkResult();
break;
default: break;
}
},
pushBox : function(boxToX,boxToY){
this.cellArray[boxToY][boxToX] = 4;
},
move : function(nextLoc){
this.cellArray[nextLoc.y][nextLoc.x] = 5;
this.cellArray[this.curLoc.y][this.curLoc.x] = 2;
for(var i = this.targetLoc.length -1; i>=0; i--){
if(this.targetLoc[i].x == this.curLoc.x && this.targetLoc[i].y == this.curLoc.y){
this.cellArray[this.curLoc.y][this.curLoc.x] = 3;
}
}
this.curLoc.y = nextLoc.y;
this.curLoc.x = nextLoc.x;
},
检测我们的通关条件,提示“通关”!
所有的应该被添位置,都有箱子了,就通关啦1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23checkResult : function(){
var done = 0, total = this.targetLoc.length;
for(var i = total -1; i>=0; i--){
if(this.cellArray[this.targetLoc[i].y][this.targetLoc[i].x] == 4){
done++;
}
}
if( done == total ){
window.onkeydown = null;
this.context.save();
this.context.font="40px Verdana";
this.context.textAlign = "center";
// 创建渐变
var gradient=this.context.createLinearGradient(0,0,this.width,this.height);
gradient.addColorStop("0.3","red");
gradient.addColorStop("0.5","blue");
gradient.addColorStop("0.7","yellow");
// 用渐变填色
this.context.fillStyle = gradient;
this.context.fillText("Congratulations!!",this.width/2,this.height/2);
this.context.restore();
}
},重置地图
如果用户发现走到了死胡同,这时候需要重置地图。1
2
3
4
5
6
7
8
9
10
11
12restart : function(){
this.curLoc = {x:8,y:3};
//0表示无,1表示隔离,2表示空地,3表示目标,4表示箱子,5表示当前位置
this.cellArray = [[0,0,0,1,1,1,1,1,1,0],
[0,1,1,1,2,2,2,2,1,0],
[1,1,3,2,4,1,1,2,1,1],
[1,3,3,4,2,4,2,2,5,1],
[1,3,3,2,4,2,4,2,1,1],
[1,1,1,1,1,1,2,2,1,0],
[0,0,0,0,0,1,1,1,1,0]];
sokoban.init();
}
整体实现
以下,献上完整代码:
1 | <!DOCTYPE html> |
你学会了吗?