共计 5122 个字符,预计需要花费 13 分钟才能阅读完成。
贪吃蛇是终多小游戏中的经典游戏了。在以前的非智能手机时代一般的手机都会有这个游戏。本篇教程是通过 javascript 纯 div 形式来实现这个小游戏而非以 html5
的canvas
实现。
实现分析
需要一个网格场景来支持”蛇”的移动。比如我们的网格是一个 15*10
的矩阵 (网格数: 150
)。”蛇”在移动的时候变化的只有”蛇”整个身体的第一格和最后一格( 可以将 "蛇" 定义为一个数组,数组的变化永远只有尾和头
)。”蛇”的死亡是在移动到边界的下一次移动超出边界和移动到自己身体上整个游戏就结束了。”蛇”的移动方向需要捕获用户按下方向键的事件来控制移动方向。
通过分析我们需要一个场景类,snake 类,控制类,point 类,事件处理函数。
场景类
场景类的主要功能就是创建一个场景,在场景内绘制自定义大小的网格。场景关链snake 类
,在创建场景的时候也同时创建了snake
。
var Frame = function(options) {
var defaults = {
width: 900, // 场景过度
height: 500, // 场景高度
grid: 30, // 网格大小
snake: {direct: \\'right\\' // 默认移动方向},
point: {}};
this.options = _.merge(defaults, options);
var wh = this.options.grid,
x = Math.floor(this.options.width / (wh 2)),
y = Math.floor(this.options.height / (wh 2));
this.options.x = x;
this.options.y = y;
this.snake = null;
this.frame = null; // 主场景元素
}
Frame.prototype = {
// 创建场景,同时将网格填充致场景
create: function() {var element = this.frame = document.createElement(\\'div\\');
element.style.margin = "50px auto";
element.style.height = this.options.height "px";
element.style.width = this.options.width "px";
element.style.position = "relative";
var grids = this.createGrid();
for(var i in grids) {element.appendChild(grids[i]);
}
document.body.appendChild(element);
var common = {x: this.options.x,y: this.options.y, size: this.options.grid};
this.snake = new Snake(_.merge(common, this.options.snake),
new Point(_.merge(common, this.options.point))
);
},
// 创建网格矩阵
createGrid: function() {
var x = this.options.x,
y = this.options.y,
wh = this.options.grid,
grids = [];
for (var i = 1; i <= y; i) {for (var j = 1; j <= x; j) {var element = document.createElement(\\'div\\');
element.style.width = wh "px";
element.style.height = wh "px";
// 设置网格 ID,为 1 --- x * y
element.setAttribute(\\'id\\', "grid_" (i * x - x j));
element.style.border = "1px dotted #ddd";
if (i !== 1) {element.style.borderTop = "none";}
if (j !== 1) {element.style.borderLeft = "none";}
element.style.float = "left";
grids.push(element);
}
}
return grids;
}
};
Snake 类
Snake 类的主要功能是 snake 的移动、吃、方向、重绘操作,本类需要关联 Point
类。在移动的时候需要判断有没有吃的操作和游戏结束事件的触发。
var Snake = function(options, point) {
var defaults = {
grid: 30,
color: "green",
x: 0,
y: 0,
direct: \\'left\\',
sourceColor: \\'#FFF\\'
},
length = 1;
this.options = _.merge(defaults, options);
var gridSize = this.options.x * this.options.y,
snakeLocus = [],
endLocus = null,
direct = this.options.direct,
angle = {"left": 90, "right": 270, "up": 0, "down": 180};
var create = function() {
var start = gridSize / 2;
snakeLocus.push(start);
};
this.setDirection = function(value) {if (Math.abs(angle[direct] - angle[value]) === 180 && length > 1) {return ;}
direct = value;
};
this.getDirection = function() {return direct;};
this.getSnakeLocus = function() {return snakeLocus;};
this.push = function(value) {snakeLocus.push(value);
};
this.pop = function() {endLocus = snakeLocus.pop();
};
this.unshift = function(value) {snakeLocus.unshift(value);
};
this.getEndLocus = function() {return endLocus;};
this.getPoint = function() {return point;};
this.length = function() {return length;};
this.incremnt = function() {length ;};
create.call(this);
this.create();};
Snake.prototype = {
// 重绘函数
create: function() {var locus = this.getSnakeLocus(),
endLocus = this.getEndLocus();
point = this.getPoint();
for (var i in locus) {document.getElementById(\\'grid_\\' locus[i]).style.backgroundColor = this.options.color;
}
if (point.getPosition() === locus[0]) {this.eat(endLocus);
} else {if (endLocus) {document.getElementById(\\'grid_\\' endLocus).style.backgroundColor = this.options.sourceColor;
}
}
},
// 移动事件
move: function() {var locus = this.getSnakeLocus(),
direct = this.getDirection(),
first = locus[0],
isOver = false;
switch (direct) {
case \\'left\\':
first -= 1;
if (first % this.options.x === 0) isOver = true;
break;
case \\'up\\':
first -= this.options.x;
if (first < 0) isOver = true;
break;
case \\'right\\':
first = 1;
if (first % this.options.x === 1) isOver = true;
break;
case \\'down\\':
first = this.options.x;
if (first> (this.options.x * this.options.y)) isOver = true;
break;
}
// 吃到自己
if (_.inArray(locus, first)) {isOver = true;}
if (isOver) {Events.trigger(\\'over\\', this);
} else {this.pop();
this.unshift(first);
this.create();}
},
// 吃事件
eat: function(end) {this.push(end);
this.getPoint().random(this.getSnakeLocus());
this.incremnt();}
};
Note:
设置方向的时候,不能设置当前方向相反的方向。在随机 point 的时候不能是当前sanke
所在的网格内。在吃到自己和移动网格之外说明游戏结束。snake 第一个网格的位位置和 point 的 position 位置重合触发吃的事件。
Point 类
Point 类主要实现随机分布一个点到网格中,不可以分布出 snake
内的点。
var Point = function(options) {
var defaults = {
color: "red",
size: 0,
x: 0,
y: 0
};
this.options = _.merge(defaults, options);
var gridSize = this.options.x * this.options.y,
position = 0;
this.setPosition = function (outer) {position = Math.floor(Math.random() * gridSize 1);
if (outer.length> 0) {if (_.inArray(outer, position)) {this.setPosition(outer);
}
}
};
this.getPosition = function() {return position;};
this.random([]);
};
Point.prototype = {random: function(outer) {this.setPosition(outer);
var position = this.getPosition();
var grid = document.getElementById("grid_" position);
grid.style.backgroundColor = this.options.color;
}
}
Control 类
Control 类主要实现事件的绑定,snake
移动定时器,游戏的暂停和开始。
var Control = function(options) {
var defaults = {time: 500},
frame = new Frame(),
clock = null,
space = 1,
_this = this;
this.move = function() {
clock = setInterval(function() {frame.snake.move.call(frame.snake);
},
defaults.time
);
};
Events.on("keyup", function(e) {switch (e.which) {
// 移动的方向绑定
case 37:
frame.snake.setDirection(\\'left\\');
break;
case 38:
frame.snake.setDirection(\\'up\\');
break;
case 39:
frame.snake.setDirection(\\'right\\');
break;
case 40:
frame.snake.setDirection(\\'down\\');
break;
// 暂停
case 19:
case 32:
Events.trigger(\\'stop\\', _this);
break;
}
});
var over = function() {var gameOver = document.createElement(\\'div\\');
gameOver.innerHTML = "Game Over";
gameOver.style.font = "normal bold 100px source code pro,arial,sans-serif";
gameOver.style.color = "red";
gameOver.style.width = "100%";
gameOver.style.textAlign = "center";
gameOver.style.position = "absolute";
gameOver.style.top = "80px";
var restart = document.createElement(\\'a\\');
restart.setAttribute(\\'href\\', "javascript:;");
restart.text = "重新开始";
restart.style.display = "block";
restart.style.fontSize = "50px";
restart.style.color = "#f4645f";
Events.on(\\'click\\', function() {gameOver.remove();
}, restart);
gameOver.appendChild(restart);
frame.frame.appendChild(gameOver);
};
// 游戏结束触发事件
Events.on(\\'over\\', function() {clearInterval(clock);
over();});
// 游戏结束触发事件
Events.on(\\'stop\\', function() {if (space % 2 === 0) {this.move();
} else {clearInterval(clock);
}
space ;
});
frame.create();};
Control.prototype = {start: function() {this.move();
}
};
Note:
space 变量是为了控制暂停和开始的,偶数开始奇数与之相反。
辅助函数
var _ = {merge: function(source, child) {for (var key in child) {source[key] = child[key];
}
return source;
},
extend: function(source, parent, pro) { },
inArray: function(array, position) {if (array.includes) {return array.includes(position);
}
if (array.indexOf(position) >= 0) {return true;}
return false;
}
};
var _listening = [];
var Events = {on: function(type, callback, element) {
var ele = element || window;
var listener = ele.addEventListener || function(type, callback) {element.attachEvent("on" type, callback);
};
listener(type, callback);
_listening.push({\\'event\\': type, \\'element\\': element, \\'callback\\': callback});
},
remove: function(type, element, callback) {
var ele = element || window;
var listener = ele.removeEventListener || ele.detachEvent;
listener(\\'on\\' type, callback);
},
trigger: function(type, object, options) {for (var i in _listening) {if (_listening[i].event === type) {_listening[i].callback.apply(object || window, options || []);
}
}
}
};
完结
以上代码就是整个游戏的源代码,其实还有几个功能没有去实现。实现起来实际是挺简单的,本篇的代码有很多地方可以优化 (比如: 可以从网格数组中拿到单个网格对象,而且需要通过 getElementById 来获取对象)
, 代码组织也不是很好。 代码我们可以慢慢的优化,最主要的是实现思路
。
源码地址
推荐阅读
- https://developer.mozilla.org/en-US/docs/Web/API
- https://developer.mozilla.org/en-US/docs/Web/JavaScript
- https://developer.mozilla.org/en-US/docs/Games