前言

贪吃蛇(也叫做贪食蛇)游戏是一款休闲益智类游戏,有PC和手机等多平台版本,既简单又耐玩。该游戏通过控制蛇头方向吃东西,从而使得蛇变得越来越长,直到最后占满全部空格结束。贪吃蛇游戏最初为单机模式,后续又陆续推出团战模式、赏金模式、挑战模式等多种玩法,其中出名的贪吃蛇大作战最为广泛流传。

今天就给大家带来一款比较简单的贪吃蛇,也是最基础的贪吃蛇玩法。其中所使用的技术栈vue。

下面就是一个基础贪吃蛇的完整玩法,图片还是有点长的,可耐心观看。

android贪吃蛇蛇的实现 贪吃蛇蛇游戏_javascript

一、游戏分析

  1. 首先游戏中主角肯定是蛇(snake),其次是食物(food)。我们还需要一个规定蛇运动的范围地图(map)。
  2. 游戏中蛇只能上下左右移动,所以我们需要根据WASD、方向键来控制蛇的方向。
  3. 食物的随机生成,每吃完一个,就会随机在空地中生成一个新的食物。
  4. 蛇运动的过程中,不能撞到周围的墙体和自己的身体。

二、基本游戏布局

游戏背景我采用了一个比较有氛围感的图,以及蛇所运动的棋盘网格。
蛇的头、尾、身体以及蛇身拐弯处都用了图片,为了让蛇更为逼真。

首先先看一下效果图:

android贪吃蛇蛇的实现 贪吃蛇蛇游戏_前端_02


(1)渲染元素

首先看一下渲染部分,我是利用坐标向地图中判断渲染的。

<div v-for="(item1, index1) in map" :key="index1" class="mapRow">
	<div v-for="(item2, index2) in item1" :key="index2" ref="zml" class="mapCol">
		<!-- 砖块 -->
		<div class="brick" v-if="item2 == 1"></div>
		<!-- 苹果 -->
		<img class="food" src="img/food.png" alt="" v-show="item2 == 2">
		<!-- 蛇头 -->
		<img class="food" src="img/head.png" alt="" v-show="item2 == 3" :style="{'transform': 'rotate('+ headRotate +'deg)'}">
		<!-- 蛇身 -->
		<img class="food" src="img/body.png" alt="" v-show="item2 == 4" :style="{'transform': 'rotate('+ headRotate +'deg)'}">
		<!-- 尾巴 -->
		<img class="food" src="img/5-1.png" alt="" v-show="item2 == '5-1'">
		<img class="food" src="img/5-2.png" alt="" v-show="item2 == '5-2'">
		<img class="food" src="img/5-3.png" alt="" v-show="item2 == '5-3'">
		<img class="food" src="img/5-4.png" alt="" v-show="item2 == '5-4'">
		<!-- 拐弯处 -->
		<img class="food" src="img/6-1.png" alt="" v-show="item2 == '6-1'">
		<img class="food" src="img/6-2.png" alt="" v-show="item2 == '6-2'">
		<img class="food" src="img/6-3.png" alt="" v-show="item2 == '6-3'">
		<img class="food" src="img/6-4.png" alt="" v-show="item2 == '6-4'">
	</div>
</div>

(2)我们再看一下游戏区上面的得分部分

这里我是利用css的text-shadow让字体有凹进去的效果,具体如下

.score{
	line-height: 1;
	color: rgb(220,197,117);
  	font-size: 40px;
	font-weight: bold;
	position: absolute;
	top: 110px;
	left: 50%;
	transform: rotate(6deg) translateX(-50%);
	text-shadow: -2px -2px 2px rgba(0,0,0,.8), 2px 2px 2px rgba(255,255,255,.6);
	background-color: transparent;
}

三、游戏实现

(1)游戏参数

map: [],				// 地图  0=空地  1=墙  2=食物  3=蛇头  4=蛇身  5 蛇尾  6 蛇拐弯处
row: 18,				// 行
col: 14,				// 列
score: 0, 				// 分数
snake: [], 				// 蛇
dir: 'right', 			// 移动方向 默认向右
speed: 100,				// 速度
isStart: false, 		// 是否开始
isStop: true,			// 是否暂停
isOver: false, 			// 判断游戏是否结束
movetimer: null, 		// 移动定时器

(2)绑定键盘事件

这里我们需要绑定游戏的空格键(游戏开始)、上下左右键(蛇方向)。
蛇前进某个方向,就不能按相反方向的键,比如往右移动,是不能点击左键的,没用。

document.addEventListener('keydown', event => {
	const e = event || window.event || arguments.callee.caller.arguments[0];
	if(this.isOver) { 
		return 
	};
	// 空格
	if(e.keyCode == 32) {
		this.playStart()
	}
	// 这里我用了一个节流函数,防止用户方向键点击过快导致会撞到自己
	throttle(() => {
		// 当蛇向右移动的时候,不能按左,向上的时候,不能按下,其他同理
		if (e.keyCode == 37 && this.dir != 'right' && !this.isStop) {
			this.dir = 'left'; //向左
							
		} else if (e.keyCode == 38 && this.dir != 'down' && !this.isStop) {
			this.dir = 'up'; //向上
							
		} else if (e.keyCode == 39 && this.dir != 'left' && !this.isStop) {
			this.dir = 'right'; //向右
							
		} else if (e.keyCode == 40 && this.dir != 'up' && !this.isStop) {
			this.dir = 'down'; //向下
							
		}
	}, this.speed / 2.5)
})

(3)初始化游戏(createInit)

地图使用的是一个二维数组
蛇默认是四节
墙体可以根据自己的需要进行添加

createInit() {
	// 地图
	for(let i = 0; i < this.row; i++) {
		this.map[i] = [];
		for (let j = 0; j < this.col; j++) {
			this.map[i][j] = 0;
		}
	}
	// 蛇
	for (let i = 4; i > 0; i--) {
		this.snake = [...this.snake, [1, i]];
	}
	// 墙体
	let wall = [];
	if(wall.length) {
		for(let i = 0; i < wall.length; i++) {
			let [x, y] = wall[i];
			this.map[x][y] = 1;
		}
	}
	// 渲染蛇和食物
	this.snakeStyle();
	this.foodStyle();
},

上面所说的墙体可以自定义,就是自己定义哪块有墙,可自行添加。
比如我想让地图的中间一行有强,并且中间留两个空隙走路。则可以像这样添加:

let wall = [
	[9, 0], [9, 1], [9, 2], [9, 3], [9, 4], [9, 5],
	[9, 8], [9, 9], [9, 10], [9, 11],[9, 12], [9, 13]
];

则渲染出来的效果是这样的:

android贪吃蛇蛇的实现 贪吃蛇蛇游戏_前端_03

(4)渲染蛇(snakeStyle)

  1. 我们渲染蛇的时候要注意的是,要想蛇变得跟逼真,则需要注重的是蛇尾、蛇身、蛇头、拐弯处的图片处理
  2. 每种都有四种图片处理,而其中蛇头和直行的蛇身我就直接用了css旋转来调整,蛇尾和蛇身弯处利用了图片处理,为了让蛇看起来更丝滑。
// 蛇头(3)  蛇身(4)  蛇尾(5-1 5-2 5-3 5-4)  蛇拐弯处(6-1 6-2 6-3 6-4)
snakeStyle() {
	// 蛇头
	this.map[this.snake[0][0]][this.snake[0][1]] = 3
	// 尾巴
	// 我们需要比较最后两个数据,来断定尾巴的朝向
	let [x1, y1] = this.snake[this.snake.length - 1];
	let [x2, y2] = this.snake[this.snake.length - 2];
	if(x1 == x2 && y1 < y2) { this.map[x1][y1] = '5-1' }
	if(x1 == x2 && y1 > y2) { this.map[x1][y1] = '5-2' }
	if(x1 > x2 && y1 == y2) { this.map[x1][y1] = '5-3' }
	if(x1 < x2 && y1 == y2) { this.map[x1][y1] = '5-4' }
	// 身体 
	// 先把身体都用普通身体代替
	for (let i = 1; i < this.snake.length - 1; i++) {
		let [x, y] = this.snake[i];
		this.map[x][y] = 4;
	}
	// 再去判断拐弯处
	// 我们只要判断某一段身体的前一个和后一个就行了
	for (let i = 1; i < this.snake.length - 1; i++) {
		let [a, b] = this.snake[i];
		let [a1, b1] = this.snake[i - 1];
		let [a2, b2] = this.snake[i + 1];
		if(((a1 < a2 && b1 > b2) || (a1 > a2 && b1 < b2)) && ((a < a1 && b == b1) || (a == a1 && b < b1))) { this.map[a][b] = '6-1' }
		if(((a1 > a2 && b1 > b2) || (a1 < a2 && b1 < b2)) && ((a > a1 && b == b1) || (a == a1 && b < b1))) { this.map[a][b] = '6-2' }
		if(((a1 > a2 && b1 > b2) || (a1 < a2 && b1 < b2)) && ((a < a1 && b == b1) || (a == a1 && b > b1))) { this.map[a][b] = '6-3' }
		if(((a1 < a2 && b1 > b2) || (a1 > a2 && b1 < b2)) && ((a > a1 && b == b1) || (a == a1 && b > b1))) { this.map[a][b] = '6-4' }
	}
	
	
	this.$forceUpdate();
},

在代码中我们可以看到如何判断尾巴的朝向和何时是拐弯处。

  • 第一点尾巴,我们只需要去看蛇的最后两部分就行,只要判断蛇尾跟倒数第二个部分的坐标值,判断在同一方向上面,如果是左右,则x相等,比较y大小。如果是上下,则y相等,比较x大小。
  • 第二点拐弯处,我们只需要判断每段蛇身的前一块和后一块即可。一共四个方位,八个判断。
  • 包括向右移动向上拐弯、向右移动向下拐弯、向左移动向上拐弯、向左移动向下拐弯、向上移动向左拐弯、向上移动向右拐弯、向下移动向左拐弯、向下移动向右拐弯。

android贪吃蛇蛇的实现 贪吃蛇蛇游戏_android贪吃蛇蛇的实现_04


android贪吃蛇蛇的实现 贪吃蛇蛇游戏_javascript_05

(5)渲染食物(foodStyle)

随机产生食物,并且不能随机到蛇身上、墙上

foodStyle() {
	// 获取所有空格
	let arr = [];
	this.map.forEach((item1, index1) => {
		item1.forEach((item2, index2) => {
			if(item2 == 0) {
				arr.push({ x: index1, y: index2 })
			}
		})
	})
	let randomNum = Math.floor(Math.random() * arr.length)
	let {x, y} = arr[randomNum];
	// 给食物添加样式
	this.map[x][y] = 2;
	this.$forceUpdate();
}

(6)游戏开始(playStart)

我们点击空格的时候,关闭遮罩,开始蛇移动定时器

playStart() {
	if (this.isOver) {
		this.isStart = false;
		alert("游戏结束!请重新开始!");
		return;
	}
	if(!this.isStart) {
		this.isStart = true;
		this.isStop = false;
		this.snakeMove()
		return;
	}
	
	if(this.isStop) {
		this.isStop = false;
		this.snakeMove()
	} else {
		this.isStop = true;
		clearInterval(this.movetimer);
	}
}

(7)蛇移动(snakeMove)

注意,关于游戏的实现都在这里面处理的
基本需要解释的,都在代码里注释了,都很简单

snakeMove() {
	if(this.isOver) { return; }
	this.movetimer = setInterval(() => {
		// 首先定义一个行x 和 列y 坐标,存放下一步将要走的格子
		let x = null;
		let y = null;
		// 判断蛇的方向
		switch (this.dir) {
			case 'right':
				x = this.snake[0][0]; 		// 如果往右移动,则行不变,列++
				y = this.snake[0][1] + 1;
				break;
			case 'down':
				x = this.snake[0][0] + 1;
				y = this.snake[0][1];
				break;
			case 'left':
				x = this.snake[0][0];
				y = this.snake[0][1] - 1;
				break;
			case 'up':
				x = this.snake[0][0] - 1;
				y = this.snake[0][1];
				break;
		}
		// 撞到墙
		// 判断是否超过row、col临界值
		if(x < 0 || x > this.row - 1 || y < 0 || y > this.col - 1 || this.map[x][y] == 1) {
			console.log('撞到墙了')
			this.isOver = true;
			clearInterval(this.movetimer);
			return;
		}
		// 撞到自己了
		// 判断头部是否与身体其他部位重合
		let arrX = this.snake.map(item => item[0]);
		let arrY = this.snake.map(item => item[1]);
		let bodys = [4,'5-1','5-2','5-3','5-4','6-1','6-2','6-3','6-4'];
		if(arrX.includes(x) && arrY.includes(y) && bodys.includes(this.map[x][y])) {
			console.log('撞到自己了')
			this.isOver = true;
			clearInterval(this.movetimer);
			return;
		}
		
		// 如果什么都没撞到,则需要判断是否吃到苹果了
		// 首先无论有没有吃到苹果,都在蛇数组开头加一节
		this.snake.unshift([x, y]);
		// 如果没吃到,则就会把最后一个尾巴清掉
		if (this.map[x][y] != 2) {
			const [a, b] = this.snake.pop();
			this.map[a][b] = 0;
		} else {
			// 如果吃到了,那就不用减去最后一个,相当于直接把苹果变成了蛇头
			this.score += 1;
			this.foodStyle();
		}
		// 重新渲染一下蛇,也就是更新一下蛇最新的位置
		this.snakeStyle();		
	}, this.speed);
}