用JS实现的贪吃蛇游戏
需要用到html、css、javascript 和 DOM 这些知识点就可以了。主要是js,其他只是一些基本的知识。js貌似也不是很难。但是问题就在这里,即使知识点都会了,但是还是无法综合运用把东西做出来
创新互联专业成都做网站、成都网站制作,集网站策划、网站设计、网站制作于一体,网站seo、网站优化、网站营销、软文发稿等专业人才根据搜索规律编程设计,让网站在运行后,在搜索中有好的表现,专业设计制作为您带来效益的网站!让网站建设为您创造效益。
游戏界面
先把整个游戏界面做出来:
贪吃蛇
贪吃蛇
请点击开始
分数:0
一个标题,然后下面是游戏界面。界面分2部分,左边是600*600的正方形,右边宽度200,可以显示一些游戏信息。
这里有一个问题,就是界面宽800,左边宽600,右边宽200。不过中间还有个边框至少还要占1的宽度。所以右边的部分会被挤到下一行。把800的总宽度加一点可以解决,也可以像这里这样,使用 box-sizing 属性。box-sizing:border-box;
:为元素设定的宽度和高度决定了元素的边框盒。就是说,为元素指定的任何内边距和边框都将在已设定的宽度和高度内进行绘制。通过从已设定的宽度和高度分别减去边框和内边距才能得到内容的宽度和高度。
初始化游戏
上面的界面还缺少蛇和食物。这里就是一个一个的小方块。食物是一个方块,蛇初始是连续的3个方块。小方块就是带背景色的div,背景色已经在上面的css里写上了。
另外蛇和食物的位置都要用 position: absolute;
来做定位。已经提前在他们的父元素就是left界面里设置好了 position: relative;
。
初始化食物
直接调用一个初始化的方法 init() ,然后是定义这个初始化方法。下面只是定义了食物的一些属性,最后调用了一个生成食物的方法 food() :
// 初始化
init();
// 初始化方法
function init() {
// 获取地图的跨度和高度
this.map_width = parseInt(getComputedStyle(map).width);
this.map_height = parseInt(getComputedStyle(map).height);
// 食物的高度和宽度
this.food_width = 20;
this.food_height = 20;
// 食物的位置,先定义在左上角看看样子,之后再搞随机位置
this.food_X = 0;
this.food_Y = 0;
food(); // 生成食物
}
生成食物的方法。这里主要是生成一个div,然后把这个div添加到map元素里。位置的话使用相对定位,关键是计算出横坐标和纵坐标:
// 获取变量
var map = document.getElementsByClassName('left')[0];
// 生成食物
function food() {
// 创建食物
var foodBox = document.createElement('div');
foodBox.style.width = this.food_width+"px";
foodBox.style.height = this.food_height+"px";
// 食物的位置
this.food_X = Math.floor(Math.random()*this.map_width/this.food_width); // 0~29之间
this.food_Y = Math.floor(Math.random()*this.map_height/this.food_height);
foodBox.style.position = 'absolute';
foodBox.style.top = this.food_Y*this.food_height+"px"; // 随机数乘以宽度
foodBox.style.left = this.food_X*this.food_width+"px";
// 设置一个类名,然后css给这个类定义样式
foodBox.className = "food";
// 将食物追加到map中
map.appendChild(foodBox);
}
初始化蛇
首先在初始化方法里追加蛇的属性。这里蛇是由连续的小方块组成。那么蛇就是一个数组,数组里的每个元素就是组成蛇的一个一个的小方块。有3个参数:水平位置、垂直位置、是否是蛇头:
// 初始化方法
function init() {
// 省略之前的代码
// 食物的位置,先定义在左上角看看样子,之后再搞随机位置
// this.food_X = 0;
// this.food_Y = 0;
food(); // 生成食物
// 初始化蛇身
this.snake_width = 20;
this.snake_height = 20;
// 下面是一个3段的蛇,第一个参数是水平位置,第二个参数是垂直位置,但三个参数是是否是头部
this.snake_body = [[2, 0, true], [1, 0, false], [0, 0, false]];
snake(); // 生成蛇身
}
上面调用了snake()方法来生成蛇。初始化的时候,蛇始终是生成在左上角。蛇的每一段其实都是独立的,这里用一个for循环,把snake_body的每一段都添加到了map中。这里是根据snake_boy来添加蛇,也就是说之后,任何时候只要修改了snake_body这个数组,然后调用snake()方法,就是生成一个蛇了:
// 生成蛇身
function snake() {
// 用for循环遍历数组,将每一段作为一个div然后添加
for(var i=0; i
小结
上面已经生成了完整的游戏初始化的界面,包括随机生成的一个食物,以及左上角的长度为3段的蛇。完整的js代码如下:
移动蛇
首先来实现点击开始按钮,开始游戏
开始按钮
点击开始按钮,调用start_game()方法,触发游戏运行,并且变成暂停按钮。点击暂停按钮,调用pause_game()方法,暂停游戏,并且再变为开始按钮。这里还获取了一个status变量,可以随时改变它的innerHtml的内容,在页面上显示一些信息,调试的时候一些中间过程也可以实现在这里:
var status = document.getElementsByClassName('status')[0];
// 点击按钮
var btn = document.getElementById('btn');
btn.onclick = function btn() {
if (this.innerHTML==="开始"){
start_game();
this.innerHTML = "暂停";
} else {
pause_game();
this.innerHTML = "开始";
}
};
开始游戏
开始游戏就是启动一个定时器。这里首先修改了status里显示的内容,然后创建了一个定时器:
// 开始游戏
function start_game() {
status.innerHTML = "游戏运行中";
this.snakeMove = setInterval(move, 100);
}
上面的定时器调用了move,下面就来写这个move方法。move方法就是让蛇移动,这里先让蛇一直往左移动。具体的做法就是设置snake_body数组,然后调用snake()方法,生成一个新的蛇。把之前的蛇清除了,然后把新的蛇添加进去,看上去取出蛇在移动的效果了:
// 蛇移动的方法
function move() {
// 从尾端开始,数组里的后一个值用数组的前一个值替代
for(var i=this.snake_body.length-1; i>0; i--){
this.snake_body[i][0] = this.snake_body[i-1][0];
this.snake_body[i][1] = this.snake_body[i-1][1];
}
// 数组最前的那个值,就是蛇头
this.snake_body[0][0] += 1;
// 重新生成蛇,才能在页面上有变化
// 先移除原有的蛇身体
clearBox('snake'); // 这个方法可以复用,只有要清除食物的时候也能用到
// 然后绘制新的蛇身
snake();
}
// 清除Box
function clearBox(class_name) {
var box = document.getElementsByClassName(class_name);
while(box.length){
map.removeChild(box[0]);
}
}
这里移除蛇身体的方法,之后要移除食物的时候也能用。只要传入不同的类名,就能把这个类的元素都清除了。
暂停游戏
暂停游戏就很简单了,就是清除掉计时器就好了。再点开始又会重新生成新的计时器:
// 暂停游戏
function pause_game() {
// 暂停游戏就是清除定时器
status.innerHTML = "游戏暂停...";
clearInterval(this.snakeMove)
}
键盘事件
在开始游戏的时候,为键盘绑定事件:
// 开始游戏
function start_game() {
status.innerHTML = "游戏运行中";
this.snakeMove = setInterval(move, 100);
// 绑定键盘按下的事件
bindKeyDown();
}
// 键盘按下的事件
function bindKeyDown() {
window.onkeydown = function (ev) {
status.innerHTML = ev.keyCode;
}
}
这里先看看keyCode这个属性的值。按下不同的按钮,值是不同的,根据这个值来判断按的是哪个按钮,然后触发响应的方法。
左37、上38、右39、下40。还有空格是32。
根据方向来移动蛇
下面就来为这些事件写上不同的处理方法:
// 键盘按下的事件
function bindKeyDown() {
window.onkeydown = function (ev) {
// status.innerHTML = ev.keyCode;
var code = ev.keyCode; // 获取按键
switch (code){
case 37:
this.direction = 'left';
break;
case 38:
this.direction = 'up';
break;
case 39:
this.direction = 'right';
break;
case 40:
this.direction = 'down';
break;
case 32:
// 我这里还希望开始游戏后,可以用空格控制暂停和开始
btn(); // 前面的按钮事件没写
break;
}
}
}
这里只是修改了this.direction这个属性的值。接下来要修改之前写的move()方法,根据这个属性的值,给出朝不同方法移动的效果。这里有了方向这个概念,在初始化的时候也要把方向属性进行初始化。把下面这句添加到初始化函数init()中去:
this.direction = 'right';
最后来修改move()方法,之前是默认向右移动的,现在要根据this.direction的值来确定向哪里移动:
// 蛇移动的方法
function move() {
// 从尾端开始,数组里的后一个值用数组的前一个值替代
for(var i=this.snake_body.length-1; i>0; i--){
this.snake_body[i][0] = this.snake_body[i-1][0];
this.snake_body[i][1] = this.snake_body[i-1][1];
}
// 根据方法来操作
switch (this.direction){
case 'left':
this.snake_body[0][0] -= 1;
break;
case 'right':
this.snake_body[0][0] += 1;
break;
case 'up':
this.snake_body[0][1] -= 1;
break;
case 'down':
this.snake_body[0][1] += 1;
break;
}
// 数组最前的那个值,就是蛇头
// this.snake_body[0][0] += 1;
// 重新生成蛇,才能在页面上有变化
// 先移除原有的蛇身体
clearBox('snake'); // 这个方法可以复用,只有要清除食物的时候也能用到
// 然后绘制新的蛇身
snake();
}
优化移动方向
现在的蛇是自由移动的,按照游戏规则,蛇不不能直接掉头的。所以要修改一个按键事件的case出的内容,只有在特定的条件下才响应键盘方向的事件:
// 键盘按下的事件
function bindKeyDown() {
window.onkeydown = function (ev) {
// status.innerHTML = ev.keyCode;
var code = ev.keyCode; // 获取按键
switch (code){
case 37:
if (this.direction === 'up' || this.direction === 'down') {
this.direction = 'left';
}
break;
case 38:
if (this.direction === 'left' || this.direction === 'right'){
this.direction = 'up';
}
break;
case 39:
if (this.direction === 'up' || this.direction === 'down'){
this.direction = 'right';
}
break;
case 40:
if (this.direction === 'left' || this.direction === 'right'){
this.direction = 'down';
}
break;
case 32:
// 我这里还希望开始游戏后,可以用空格控制暂停和开始
btn(); // 前面的按钮事件没写
break;
}
}
}
移动方法
之前的move()方法只是让蛇动起来。每次移动后,当要判断一下当前的位置。比如:吃到食物了、撞墙了或者撞到蛇身了。
吃食物
吃食物的逻辑放在蛇移动的move()方法里。在计算好新的蛇身数组后,增加判断蛇头位置的逻辑:
// 蛇移动的方法
function move() {
// 前面是生成新的蛇身数组的代码
// 判断蛇头吃食物
if (this.snake_body[0][0]===this.food_X && this.snake_body[0][1]===this.food_Y){
// status.innerHTML = "吃到食物了"
clearBox("food"); // 移除食物
food(); // 生成新的食物
// 增加分数,先把下面这句加到最上面的获取变量里
// var scoreBox = document.getElementById('score');
// 在去初始化函数init()里,加一条初始化分数变量
// this.score = 0;
this.score += 1;
scoreBox.innerHTML = this.score;
// 增加蛇身长度
// var snake_end = this.snake_body[this.snake_body.length-1]; // 这个是错误的
var snake_end = Array.from(this.snake_body[this.snake_body.length-1]); // 这里需要深copy
this.snake_body.push(snake_end);
}
// 后面是移除原有蛇身,然后绘制新蛇身的方法
}
这里增加蛇蛇身长度的需要注意一下。
可以这么做,把新生成的身体的一段放到一个临时的位置,等移动一次以后,就会变成正常的位置:
var snake_end = [0, 0, false]
this.snake_body.push(snake_end);
上面那么做肯定不好看,最好是追加到生当前身体最后一段的位置上,但是不能简单的向下面这么做:
var snake_end = this.snake_body[this.snake_body.length-1]; // 这个是错误的
this.snake_body.push(snake_end);
由于 this.snake_body[this.snake_body.length-1]
本身也是一个数组,如果直接赋值给snake_end的话,就是一个地址引用(浅copy)。这里要么获取到具体的值,生成snake_end这个数组,比如下面这样:
var snake_last = this.snake_body[this.snake_body.length-1];
var snake_end = [snake_last[0], snake_last[1], false];
this.snake_body.push(snake_end);
或者就是像例子中那样要做一个数组的深copy,把数组里的值赋值给snake_end。而不是直接的赋值操作。
判断撞墙
我先在开是的位置增加一个变量wall,用来开启撞墙判断的功能,这样如果只有不想撞墙的话,还能方便的关掉:
// 设置自定义的游戏参数
var wall = true; // 开启撞墙
继续在move()方法里,在判断吃食物的后面添加判断撞墙的逻辑。撞墙之后,就是
// 蛇移动的方法
function move() {
// 判断蛇头吃食物
// 判断撞墙
wall && (
this.snake_body[0][0]<0 || this.snake_body[0][0]>=this.map_width/this.snake_width ||
this.snake_body[0][1]<0 || this.snake_body[0][1]>=this.map_height/this.snake_height
) && game_over();
// 后面是移除原有蛇身,然后绘制新蛇身的方法
}
// 游戏结束的方法
function game_over() {
status.innerHTML = "Game Over";
clearInterval(this.snakeMove);
alert("游戏结束\n分数:"+this.score);
clearBox('snake');
clearBox('food');
init();
}
上面的move()函数里只复杂判断是否撞墙,如果撞墙就调用game_over()方法。
撞到蛇身
先手动设置一个初始的长蛇身,避免测试撞击蛇身之前要先玩一段时间的尴尬。还是把这些自定义参数放到开头部分:
// 设置自定义的游戏参数
var snake_length = 15; // 正整数,增加额外的蛇身体。0就是最小的3段,1就是额外增加1段。
var wall_snake = true; // 开启撞击蛇身
var wall = true; // 开启撞墙
修改一下初始化方法,在初始化的时候就生成一个很长的蛇身体,完整的初始化init()方法:
// 初始化方法
function init() {
// 获取地图的跨度和高度
this.map_width = parseInt(getComputedStyle(map).width);
this.map_height = parseInt(getComputedStyle(map).height);
// 初始化成绩和界面
this.score = 0;
scoreBox.innerHTML = this.score;
btn.innerHTML = "开始";
// 食物的高度和宽度
this.food_width = 20;
this.food_height = 20;
// 食物的位置,先定义在左上角看看样子,之后再搞随机位置
// this.food_X = 0;
// this.food_Y = 0;
food(); // 生成食物
// 初始化蛇身
this.snake_width = 20;
this.snake_height = 20;
// 下面是一个3段的蛇,第一个参数是水平位置,第二个参数是垂直位置,但三个参数是是否是头部
// 最后发现第三个参数并没有用
this.snake_body = [[2, 0, true], [1, 0, false], [0, 0, false]];
// 追加额外的蛇身
if (typeof snake_length === 'number' && snake_length%1 === 0) {
// 综合上面的2个条件,可以判断snake_length是一个整数
for (var i=0; i
好了,现在不用玩了,直接可以钻出一条大长龙。
然后是撞击蛇身的判断,还是move()函数里,放到之前撞墙的后面
// 蛇移动的方法
function move() {
// 判断蛇头吃食物
// 判断撞墙
wall && (
this.snake_body[0][0]<0 || this.snake_body[0][0]>=this.map_width/this.snake_width ||
this.snake_body[0][1]<0 || this.snake_body[0][1]>=this.map_height/this.snake_height
) && game_over();
// 判断撞到蛇身
if (wall_snake) {
for (var i=1; i
这里没有什么难点。可以去试着撞一下自己试试。
总结
上面也只是有个大概的样子。基本的东西都搞出来了。还有优化空间,另外也还有些小BUG其实。下面跳上最终我自己的完整的代码,有兴趣的话自己修改:
贪吃蛇
贪吃蛇
请点击开始
分数:0
网站栏目:用JS实现的贪吃蛇游戏
网站路径:http://ybzwz.com/article/ghgdec.html