项目准备

项目展示图

贪吃蛇游戏JavaScript的设计 js贪吃蛇项目_javascript

贪吃蛇游戏JavaScript的设计 js贪吃蛇项目_贪吃蛇游戏JavaScript的设计_02


贪吃蛇游戏JavaScript的设计 js贪吃蛇项目_贪吃蛇游戏JavaScript的设计_03

  1. 建立新文件夹,新建出images CSS JS 三个文件夹,并在根目录下创建出index.html
  2. 将下列素材图片转到images文件中

贪吃蛇游戏JavaScript的设计 js贪吃蛇项目_贪吃蛇游戏JavaScript的设计_04


贪吃蛇游戏JavaScript的设计 js贪吃蛇项目_贪吃蛇游戏JavaScript的设计_05


贪吃蛇游戏JavaScript的设计 js贪吃蛇项目_css_06


贪吃蛇游戏JavaScript的设计 js贪吃蛇项目_游戏_07


接下来可以开始着手操作了

HTML结构

  1. 游戏内容区域 (content)
  2. 游戏开始按钮 (btn startBtn)
  3. 游戏暂停按钮 (btn pauseBtn)
  4. 游戏进行区域 (snakeWrap)

可以构造出如下的结构

<div class="content">
    <div class="btn startBtn"><button></button></div>
    <div class="btn pauseBtn"><button></button></div>
    <div id="snakeWrap"></div>
</div>

在head标签内引入CSS文件夹内的index.css

<link rel="stylesheet" href="CSS/index.css">

在body标签的最末尾,添加script,引入JS文件夹内的index.js

<script src="JS/index.js"></script>

CSS样式

首先,要让游戏内容区域 居中显示

.content {
    width: 640px;
    height: 640px;
    margin: 100px auto;
    position: relative;
}

左右margin为 auto 使其自适应居中显示

给两个按钮以及其蒙层添加属性

.btn {
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    top: 0;
    background-color: rgba(0, 0, 0, 0.3);
    z-index: 2;
}

.btn button {
    background: none;
    border: none;
    background-size: 100% 100%;
    cursor: pointer;
    outline: none;
    position: absolute;
    left: 50%;
    top: 50%;
}

btn 是两个按钮的蒙层属性,完全覆盖了游戏内容区域,而btn 中 的 button 给它设置了一些相应的属性,例如水平垂直居中(在下面获取具体宽高的时候,给予负 的 margin)background-size 平铺背景图片 等属性

.startBtn button {
    width: 200px;
    height: 200px;sda
    background-image: url(../images/startGame.png);
    margin-left: -100px;
    margin-top: -100px;
}

.pauseBtn {
    display: none;
}

.pauseBtn button {
    width: 70px;
    height: 70px;
    background-image: url(../images/pauseGame.png);
    margin-top: -35px;
    margin-left: -35px;
}

给予开始按钮 以及 暂停按钮宽高,并设置背景图片,让其居中,并让一开始,暂停按钮不显示

给游戏进行区域添加样式

#snakeWrap {
    width: 600px;
    height: 600px;
    background-color: #225675;
    border: 20px solid #7dd9ff;
    position: relative;
}

游戏内容区域宽高为640px 游戏进行区域的宽高为600px 加上边框border 刚好就是640px,并且给予其相对定位

预定义蛇的样式

首先,我们的蛇的身体,头部,包括我们的食物苹果,其实都是一个一个的小方块构成,因此,我们可以先预定义 游戏内的div小方块

#snakeWrap div {
    width: 20px;
    height: 20px;
    position: absolute;
}

使得其宽高20,游戏进行区域是宽高600,其实相当于最多存放30 * 30 个 小方块

蛇头样式

.snakeHead {
    background-image: url(../images/snakeHead.png);
    background-size: cover;
    border-top-right-radius: 30%;
    border-bottom-right-radius: 30%;
}

引入蛇头图片,让其图片平铺方块,再将其初始的蛇头方向圆角修饰,让其蛇头朝右

.snakeBody {
    background-color: #9ddbb1;
    border-radius: 50%;
}

.food {
    background-image: url(../images/food.png);
    background-size: cover;
}

蛇的身体其实是一个小圆点,直接设置背景颜色和 border-radius 即可

而食物则是将背景图片引入即可

JS逻辑 && 代码

游戏逻辑

贪吃蛇游戏JavaScript的设计 js贪吃蛇项目_javascript_08


蛇逻辑

贪吃蛇游戏JavaScript的设计 js贪吃蛇项目_贪吃蛇游戏JavaScript的设计_09

食物逻辑

贪吃蛇游戏JavaScript的设计 js贪吃蛇项目_游戏_10

在开始代码之前,先在全局定义几个变量

var sw = 20, // 方块宽度
	sh = 20, // 方块高度
	tr = 30, // 行数
	td = 30; // 列数

var snake = null, //蛇的实例
	food = null, //食物的实例
	game = null; //游戏的实例

接下来开始着手主要的核心代码

上面说到 蛇 和 食物 可以看成是小方块,所以我们可以先写小方块的构造函数

小方块

function Square(x, y, classname) {
	this.x = x * sw;                                   	// x坐标 * 小方块宽度
	this.y = y * sh;									// y坐标 * 小方块高度
	this.classname = classname;							// 赋值classname
	this.viewContent = document.createElement('div');
	this.viewContent.className = classname;				
	this.parent = document.getElementById('snakeWrap');	// 取出父级游戏进行区域
}

接下来,我们在Square的原型链上添加两个方法,创建和移除

Square.prototype.create = function () {
	this.viewContent.style.left = this.x + 'px';
	this.viewContent.style.top = this.y + 'px';			// 定位
	this.parent.appendChild(this.viewContent); 			// 将小方块添加到页面当中
}

Square.prototype.remove = function () {
	this.parent.removeChild(this.viewContent);			// 移除小方块
};

首先,先写出蛇的构造函数

function Snake() {
	this.head = null;									// 蛇头
	this.tail = null;									// 蛇尾
	this.pos = [];										// 蛇在游戏区域中存在的坐标
	this.directionNum = {
		left: {
			x: -1,
			y: 0,
			rotate: 180
		},
		right: {
			x: 1,
			y: 0,
			rotate: 0
		},
		up: {
			x: 0,
			y: -1,
			rotate: -90
		},
		down: {
			x: 0,
			y: 1,
			rotate: 90
		}
	};													// 设置蛇的方向num,方便后序控制操作
}

其中呢,rotate是为了蛇头转向时候,蛇头的朝向设置的

接下来是 蛇的 初始换函数 (最开始的两个蛇身 以及 蛇头)

Snake.prototype.init = function() {
    var snakeHead = new Square(2, 0, 'snakeHead');          
    snakeHead.create();                                 // 在(2,0)处创建蛇头
    this.head = snakeHead;                              // 并给让这个创建的蛇头 赋值给 this.head
    this.pos.push([2, 0]);                              // 将蛇头所在的位置,用数组pos存储起来

    var snakeBody1 = new Square(1, 0, 'snakeBody');
    snakeBody1.create();
    this.pos.push([1, 0]);

    var snakeBody2 = new Square(0, 0, 'snakeBody');
    snakeBody2.create();
    this.pos.push([0, 0]);
    this.tail = snakeBody2;
                                                        // 创建链表关系
    snakeHead.last = null;                              // 蛇头的上一个是null
    snakeHead.next = snakeBody1;                        // 蛇头的下一个是snakeBody1

    snakeBody1.last = snakeHead;                        // snakeBody1的上一个是蛇头
    snakeBody1.next = snakeBody2;                       // snakeBody1的下一个是蛇尾

    snakeBody2.last = snakeBody1;                       // snakeBody2的上一个是snakeBody1
    snakeBody2.next = null;                             // snakeBody2的下一个是null

    this.direction = this.directionNum.right;           // 设置默认的方向为右
};

注释写的挺清楚了 ,也就不做过多的解释,创建链表关系是为了更方便我们写蛇移动的函数

接下来是蛇的下一个点的判断,也就是四块逻辑,撞墙,撞自己,撞食物,未碰撞

Snake.prototype.getNextPos = function () {
	var nextPos = [
		this.head.x / sw + this.direction.x,
		this.head.y / sw + this.direction.y
	];

	// 下一个点是自己,代表撞到了自己,游戏结束
	
	var selfCollied = false; //是否撞到自己
	this.pos.forEach(function (value) {
		// 数组,对象,直接比较,还要比较引用值
		if (value[0] == nextPos[0] && value[1] == nextPos[1]) {
			selfCollied = true; // 表示撞到了自己
		}
	});

	if (selfCollied) {
		// console.log('撞到自己的了');
		this.strategies.die.call(this);
		return;
	}

	// 下一个点是墙,代表撞到了围墙,游戏结束
	if (nextPos[0] < 0 || nextPos[1] < 0 || nextPos[0] > td - 1 || nextPos[1] > tr - 1) {
		// console.log('撞到墙上了');
		this.strategies.die.call(this);
		return;
	}

	//说明蛇头要走的下一个点是食物的那个点
	if (food && food.pos[0] == nextPos[0] && food.pos[1] == nextPos[1]) {
		
		// console.log('eat food');
		this.strategies.eat.call(this);
		return;
	}

	// 下一个点是空,继续走
	this.strategies.move.call(this);
}

书写四种逻辑,每种逻辑内有三种处理情况 游戏结束(die) 蛇吃食物(eat) 蛇移动(move)

接下来就是书写着三种处理情况的逻辑

Snake.prototype.strategies = {
    move: function () {
        
    },

    die: function() {

    },

    eat: function() {

    }
}

首先是 move 函数

蛇移动的思路是如下

  • 蛇朝着对应的方向移动的,首先先在蛇头处创建一个蛇身
  • 再在蛇头下一个移动的坐标上创建蛇头
  • 最后根据是否有吃食物,来进行最后一个蛇身是否消失
move: function (format) {
	// 传递的参数来表示蛇是否吃了食物,吃了食物传true
    // 在蛇头位置创建新的蛇身体
    var newBody = new Square(this.head.x / sw, this.head.y / sh, 'snakeBody');
    // 更新链表关系
    newBody.last = null;
    newBody.next = this.head.next;
    newBody.next.last = newBody;
    this.head.remove;
    newBody.create();

    // 在蛇的下一个方向,创建一个新的蛇头
    var newHead = new Square(this.head.x / sw + this.direction.x, this.head.y / sh + this.direction.y, 'snakeBody');
    // 更新链表关系
    newBody.last = newHead;
    newHead.next = newBody;
    newHead.last = null;
    newHead.create();
    // 改变蛇头的朝向
    newHead.viewContent.style.transform = 'rotate(' + this.direction.rotate + 'deg)';  
    // 更新位置信息
    this.pos.unshift([this.head.x / sw + this.direction.x, this.head.y / sh + this.direction.y]);
    this.head = newHead;

    //如果format 的值为false  表示需要删除(除了吃之外的操作)
    if (!format) { 
        this.tail.remove();
        // 更新链表
        this.tail = this.tail.last;
        this.tail.next = null;
        // 更新蛇身坐标数组
        this.pos.pop();
    }
},

die 这块逻辑,我们用游戏结束来代替,通过game对象的 over 来结束游戏,并打印分数等

die: function() {
    game.over();
},
  • eat 这块逻辑,首先,吃了食物,蛇身体变长,但其实,蛇的身体是变长可以通过给move函数传递参数true来实现。
  • 蛇吃完了食物,就要创建新的食物,这个食物不和蛇的身体重叠,这块逻辑,就交给食物对象来处理。
  • 吃了食物之后,游戏的分数会增加,通过game对象来处理
eat: function () {
	this.strategies.move.call(this, true);
	createFood();
	game.score++;
}

到此,蛇的逻辑基本完成了

食物

function createFood() {
	var x = null,
		y = null;
	var include = true;								// include 用来表示食物是否在蛇的身体内
	while (include) {								
		x = Math.round(Math.random() * (td - 1))	// 随机生成一个 0~29的 x 坐标
		y = Math.round(Math.random() * (tr - 1))    // 随机生成一个 0~29的 y 坐标
		snake.pos.forEach(function (value) {		// 通过forEach遍历蛇位置数组,来判断食物和蛇是否重叠
			if (value[0] != x && value[1] != y) {
				include = false;					// 蛇和食物未重叠
			}
		});
	}

	food = new Square(x, y, 'food');
	food.pos = [x, y];
	// 如果是开局条件,直接创建一个食物对象
	// 如果开局之后再吃的食物,那么直接改变食物的 left 和 top 值即可
	var foodDom = document.querySelector('.food');
	if (foodDom) {
		foodDom.style.left = x * sw + 'px';
		foodDom.style.top = y * sh + 'px';
	} else {
		food.create();
	}
}

game 对象

首先,是Game 的构造函数

function Game() {
	this.timer = null;	// 游戏进行通过计时器进行,所以需要创建个时间戳
	this.score = 0;		// 记录游戏的分数
}

然后是game 的初始化函数

Game.prototype.init = function () {
	snake.init();
	createFood();
	// 通过键盘控制蛇的方向
	document.onkeydown = function (ev) {
		if (ev.which == 37 && snake.direction != snake.directionNum.right) { 		//用户摁下左键时候,这条蛇不能是正在往右走
			snake.direction = snake.directionNum.left;
		} else if (ev.which == 38 && snake.direction != snake.directionNum.down) {
			snake.direction = snake.directionNum.up;
		} else if (ev.which == 39 && snake.direction != snake.directionNum.left) {
			snake.direction = snake.directionNum.right;
		} else if (ev.which == 40 && snake.direction != snake.directionNum.up) {
			snake.direction = snake.directionNum.down;
		}
	}
	this.start();
}

开始游戏

Game.prototype.start = function () { // 开始游戏
	this.timer = setInterval(function () {
		snake.getNextPos();
	}, 200)
}

暂停游戏

Game.prototype.pause = function () {
	clearInterval(this.timer);
}

游戏结束

Game.prototype.over = function () {
	clearInterval(this.timer);
	alert('你的得分为:' + this.score + '分');

	// 游戏回到最初始的状态
	var snakeWrap = document.getElementById('snakeWrap');
	snakeWrap.innerHTML = '';
	snake = new Snake();
	game = new Game();
	var startBtnWrap = document.querySelector('.startBtn');
	startBtnWrap.style.display = 'block';
}
game逻辑内的 两个按钮 (开始游戏,暂停游戏,继续游戏)
var startBtn = document.getElementsByClassName('startBtn')[0];
startBtn.onclick = function () {
	// 开始游戏点击
	startBtn.style.display = 'none';
	game.init();
}

var snakeWrap = document.getElementById('snakeWrap');
var pauseBtn = document.querySelector('.pauseBtn button');
snakeWrap.onclick = function () {
	// 点击屏幕,让游戏暂停
	game.pause();
	pauseBtn.parentNode.style.display = 'block'
}

pauseBtn.onclick = function () {
	// 点击游戏继续
	game.start();
	pauseBtn.parentNode.style.display = 'none';
}

完整代码

html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>贪吃蛇</title>
    <link rel="stylesheet" href="CSS/index.css">
</head>

<body>
    <div class="content">
        <div class="btn startBtn"><button></button></div>
        <div class="btn pauseBtn"><button></button></div>
        <div id="snakeWrap"></div>
    </div>
    <script src="JS/index.js"></script>
</body>

</html>

css

.content {
    width: 640px;
    height: 640px;
    margin: 100px auto;
    /* background-color: #123; */
    position: relative;
}

.btn {
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    top: 0;
    background-color: rgba(0, 0, 0, 0.3);
    z-index: 2;
}

.btn button {
    background: none;
    border: none;
    background-size: 100% 100%;
    cursor: pointer;
    outline: none;
    position: absolute;
    left: 50%;
    top: 50%;
}

.startBtn button {
    width: 200px;
    height: 200px;
    background-image: url(../images/startGame.png);
    margin-left: -100px;
    margin-top: -100px;

}

.pauseBtn {
    display: none;
}

.pauseBtn button {
    width: 70px;
    height: 70px;
    background-image: url(../images/pauseGame.png);
    margin-top: -35px;
    margin-left: -35px;
}

/* snakeWrap */

#snakeWrap {
    width: 600px;
    height: 600px;
    background-color: #225675;
    border: 20px solid #7dd9ff;
    position: relative;
}

#snakeWrap div {
    width: 20px;
    height: 20px;
    position: absolute;
}

.snakeHead {
    background-image: url(../images/snakeHead.png);
    background-size: cover;
    border-top-right-radius: 30%;
    border-bottom-right-radius: 30%;
}

.snakeBody {
    background-color: #9ddbb1;
    border-radius: 50%;
}

.food {
    background-image: url(../images/food.png);
    background-size: cover;
}

JavaScript

var sw = 20, // 方块宽度
	sh = 20, // 方块高度
	tr = 30, // 行数
	td = 30; // 列数

var snake = null, //蛇的实例
	food = null, //食物的实例
	game = null; //游戏的实例

function Square(x, y, classname) {
	this.x = x * sw;
	this.y = y * sh;
	this.class = classname;
this.viewContent = document.createElement('div');
this.viewContent.className = classname;
	this.parent = document.getElementById('snakeWrap');
}

Square.prototype.create = function () {
	this.viewContent.style.left = this.x + 'px';
	this.viewContent.style.top = this.y + 'px';
	this.parent.appendChild(this.viewContent); // 将小方块添加到页面当中
}

Square.prototype.remove = function () {
	this.parent.removeChild(this.viewContent);
};

function Snake() {
	this.head = null;
	this.tail = null;
	this.pos = [];
	this.directionNum = {
		left: {
			x: -1,
			y: 0,
			rotate: 180
		},
		right: {
			x: 1,
			y: 0,
			rotate: 0
		},
		up: {
			x: 0,
			y: -1,
			rotate: -90
		},
		down: {
			x: 0,
			y: 1,
			rotate: 90
		}
	};
}

Snake.prototype.init = function () {
	var snakeHead = new Square(2, 0, 'snakeHead');
	snakeHead.create();                           		
	this.head = snakeHead;
	this.pos.push([2, 0]);

	var snakeBody1 = new Square(1, 0, 'snakeBody');
	snakeBody1.create();
	this.pos.push([1, 0]);

	var snakeBody2 = new Square(0, 0, 'snakeBody');
	snakeBody2.create();
	this.tail = snakeBody2;
	this.pos.push([0, 0]);

	snakeHead.last = null;
	snakeHead.next = snakeBody1;

	snakeBody1.last = snakeHead;
	snakeBody1.next = snakeBody2;

	snakeBody2.last = snakeBody1;
	snakeBody2.next = null;

	this.direction = this.directionNum.right;
}

Snake.prototype.getNextPos = function () {
	var nextPos = [
		this.head.x / sw + this.direction.x,
		this.head.y / sw + this.direction.y
	];

	// 下一个点是自己,代表撞到了自己,游戏结束

	var selfCollied = false; //是否撞到自己
	this.pos.forEach(function (value) {
		// 数组,对象,直接比较,还要比较引用值
		if (value[0] == nextPos[0] && value[1] == nextPos[1]) {
			selfCollied = true; // 表示撞到了自己
		}
	});

	if (selfCollied) {
		// console.log('撞到自己的了');
		this.strategies.die.call(this);
		return;
	}

	// 下一个点是墙,代表撞到了围墙,游戏结束
	if (nextPos[0] < 0 || nextPos[1] < 0 || nextPos[0] > td - 1 || nextPos[1] > tr - 1) {
		// console.log('撞到墙上了');
		this.strategies.die.call(this);
		return;
	}

	//说明蛇头要走的下一个点是食物的那个点
	if (food && food.pos[0] == nextPos[0] && food.pos[1] == nextPos[1]) {
		
		// console.log('eat food');
		this.strategies.eat.call(this);
		return;
	}

	// 下一个点是空,继续走
	this.strategies.move.call(this);
}

Snake.prototype.strategies = {
	move: function (format) {

		var newBody = new Square(this.head.x / sw, this.head.y / sh, 'snakeBody'); 
		newBody.last = null;
		newBody.next = this.head.next;
		newBody.next.last = newBody;

		this.head.remove();
		newBody.create();

		var newHead = new Square(this.head.x / sw + this.direction.x, this.head.y / sh + this.direction.y, 'snakeHead');
		newHead.last = null;
		newHead.next = newBody;
		newBody.last = newHead;
		newHead.create();
		newHead.viewContent.style.transform = 'rotate(' + this.direction.rotate + 'deg)';
		this.pos.unshift([this.head.x / sw + this.direction.x, this.head.y / sh + this.direction.y]);
		// this.pos.splice(0, 0, [this.head.x / sw + this.direction.x, this.head.y / sh + this.direction.y]);
		this.head = newHead;

		if (!format) { //如果format 的值为false  表示需要删除(除了吃之外的操作)
			this.tail.remove();
			// 更新链表
			this.tail = this.tail.last;
			this.tail.next = null;
			// 更新蛇身坐标数组
			this.pos.pop();
		}

	},
	die: function () {
		game.over();
	},
	eat: function () {
		this.strategies.move.call(this, true);
		createFood();
		game.score++;
	}
}

function createFood() {
	var x = null,
		y = null;
	var include = true;								// include 用来表示食物是否在蛇的身体内
	while (include) {								
		x = Math.round(Math.random() * (td - 1))	// 随机生成一个 0~29的 x 坐标
		y = Math.round(Math.random() * (tr - 1))    // 随机生成一个 0~29的 y 坐标
		snake.pos.forEach(function (value) {		// 通过forEach遍历蛇位置数组,来判断食物和蛇是否重叠
			if (value[0] != x && value[1] != y) {
				include = false;					// 蛇和食物未重叠
			}
		});
	}

	food = new Square(x, y, 'food');
	food.pos = [x, y];
	// 如果是开局条件,直接创建一个食物对象
	// 如果开局之后再吃的食物,那么直接改变食物的 left 和 top 值即可
	var foodDom = document.querySelector('.food');
	if (foodDom) {
		foodDom.style.left = x * sw + 'px';
		foodDom.style.top = y * sh + 'px';
	} else {
		food.create();
	}
}

snake = new Snake();

function Game() {
	this.timer = null;
	this.score = 0;
}

Game.prototype.init = function () {
	snake.init();
	createFood();
	document.onkeydown = function (ev) {
		if (ev.which == 37 && snake.direction != snake.directionNum.right) { 		//用户摁下左键时候,这条蛇不能是正在往右走
			snake.direction = snake.directionNum.left;
		} else if (ev.which == 38 && snake.direction != snake.directionNum.down) {
			snake.direction = snake.directionNum.up;
		} else if (ev.which == 39 && snake.direction != snake.directionNum.left) {
			snake.direction = snake.directionNum.right;
		} else if (ev.which == 40 && snake.direction != snake.directionNum.up) {
			snake.direction = snake.directionNum.down;
		}
	}
	this.start();
}

Game.prototype.start = function () { // 开始游戏
	this.timer = setInterval(function () {
		snake.getNextPos();
	}, 200)
}

Game.prototype.pause = function () {
	clearInterval(this.timer);
}

Game.prototype.over = function () {
	clearInterval(this.timer);
	alert('你的得分为:' + this.score + '分');

	// 游戏回到最初始的状态
	var snakeWrap = document.getElementById('snakeWrap');
	snakeWrap.innerHTML = '';
	snake = new Snake();
	game = new Game();
	var startBtnWrap = document.querySelector('.startBtn');
	startBtnWrap.style.display = 'block';
}

game = new Game();

var startBtn = document.getElementsByClassName('startBtn')[0];
startBtn.onclick = function () {
	// 开始游戏点击
	startBtn.style.display = 'none';
	game.init();
}

var snakeWrap = document.getElementById('snakeWrap');
var pauseBtn = document.querySelector('.pauseBtn button');
snakeWrap.onclick = function () {
	// 点击屏幕,让游戏暂停
	game.pause();
	pauseBtn.parentNode.style.display = 'block'
}

pauseBtn.onclick = function () {
	// 点击游戏继续
	game.start();
	pauseBtn.parentNode.style.display = 'none';
}