先看效果:
这里只是实现了贪吃蛇游戏的基础功能,没有修饰蛇、食物、背景,食物随机出现,蛇吃掉食物身体增长一节,蛇不能撞墙不能撞到自己,同时食物不会随机出现在蛇身上。总之比较基础和粗糙。
游戏的地图、食物、蛇的移动以及游戏规则其实相对都比较好理解,自己理一理问题应该就不大了。这个游戏带给我最大的收获是,锁,的应用,比如0.5s内键盘按下多次方向改变蛇该如何移动,蛇撞墙之后蛇头正常会移动出去才会游戏停止,都是通过添加锁来解决的,下面的代码种有详细说明。
完整代码如下:
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>贪吃蛇小游戏</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div id="app"></div>
<script src="map.js"></script>
<script src="food.js"></script>
<script src="game.js"></script>
<script src="snake.js"></script>
<script>
// 地图
let map = new Map(20,20);
// 蛇
let snake = new Snake();
// 食物
let food = new Food(12,6);
// 游戏
let game = new Game(map,food,snake);
</script>
</body>
</html>
css:
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
.box{
width: 600px;
height: 600px;
margin: auto;
}
.row{
overflow: hidden;
}
.col{
width: 30px;
height: 30px;
float: left;
/* border: 1px solid #ccc; */
}
map.js:
/*
Map 地图
*/
function Map(x,y){
this.arr = [];
this.x = x;
this.y = y;
// 创建地图DOM格子
this.dom = document.createElement("div");
}
// 把地图渲染进页面,把地图格子渲染出来
Map.prototype.fill = function(){
for(let i = 0; i < this.x; i++){
// 创建div标签
let dom_div = document.createElement("div");
// 给创建的div标签加类名
dom_div.classList.add("row");
// 创建一个行数组
let row_arr = [];
for(let j = 0; j < this.y; j++){
// 创建span标签
let dom_span = document.createElement("span");
// 给创建的span标签加类名
dom_span.classList.add("col");
// 把创建的span追加到行数组里
row_arr.push(dom_span);
// 把创建的span标签追加到创建的横向div里
dom_div.appendChild(dom_span);
}
// 把外层for循环创建的行数组追加到this.arr里
this.arr.push(row_arr);
// 把创建的横向div追加到this.dom里面
this.dom.appendChild(dom_div);
}
// 给this.dom 加类名
this.dom.classList.add("box");
// 把追加好的this.dom放进#app里
document.querySelector("#app").appendChild(this.dom);
}
// 清屏, 把所有的小方格的颜色都清掉
Map.prototype.clear = function(){
for(let i = 0; i < this.arr.length; i++){
for(let j = 0; j < this.arr[i].length; j++){
this.arr[i][j].style.background = "white";
}
}
}
food.js:
/*
Food 食物
@x 食物的横向坐标
@y 食物的纵向坐标
*/
function Food(x,y){
this.x = x;
this.y = y;
}
// 更新食物坐标的方法
Food.prototype.change = function(x,y){
this.x = x;
this.y = y;
}
snake.js:
/*
Snake 蛇
*/
function Snake(){
// 蛇的数组
this.arr = [
{x: 4, y: 4},
{x: 4, y: 5},
{x: 4, y: 6},
{x: 4, y: 7},
{x: 4, y: 8}
];
// 蛇的移动方向
// 左 37,上38,右39,下40,这是js里的keyCode
this.direction = 39;
// 加锁
this.lock = true;
}
// 蛇的移动
Snake.prototype.move = function(){
// 克隆一个蛇的头部
let new_head = {
x: this.arr[this.arr.length-1].x,
y: this.arr[this.arr.length - 1].y
};
// 判断蛇头的移动方向
if(this.direction === 37){
new_head.y--;
}else if(this.direction === 38){
new_head.x--;
}else if(this.direction === 39){
new_head.y++;
}else if(this.direction === 40){
new_head.x++;
}
// 将新的头部添加到数组
this.arr.push(new_head);
// 移除数组的尾部
this.arr.shift();
this.lock = true;
// 蛇的移动就是蛇头增加一节,蛇尾删除一节
}
/**
* 为什么要加锁呢?
* 这个游戏设置的是每0.5s进行一次渲染,包括蛇、地图、食物等。
* 那如果在0.5s内按了很多次不同的方向键,蛇要怎么移动呢?
* 这个时候给蛇本身设置一个锁的属性为true,
* 当蛇的方向要改变时,如果锁为true,方向正常改变,同时将lock设置为false,如果lock为false,蛇的方向不能改变
* 当蛇移动完,move()函数执行到最后,lock设置为true。
*
*/
// 蛇的移动方向
Snake.prototype.change = function(e){
if(!this.lock){
// 跳出语句,下面后续代码不再执行
return;
}
// 解锁
this.lock = false;
// 当前的方向 - 想要移动的新方向
let num = this.direction - e;
if(num === 0 || num === -2 || num === 2){
// 观察37,38,39,40,键盘输入方向和之前相反或相同时
// 两个方向相减的数值的差为2或0,所以此时return跳出即可。
return;
// return是跳出不再执行的意思
}else{ // 否则,修改方向direction
this.direction = e;
}
}
// 蛇的尾巴增长
Snake.prototype.grow = function(){
// 获取蛇的尾巴位置
let s = this.arr[0];
this.arr.unshift(s);
}
game.js:
/*
Game 游戏类
@map 地图
@food 食物
@snake 蛇
*/
function Game(map,food,snake){
this.map = map;
this.food = food;
this.snake = snake;
// 计时器
this.timer = null;
// 是否开始游戏
this.play = true;
// 初始化
this.init();
}
// 初始化
Game.prototype.init = function(){
// 把地图渲染进页面
this.map.fill();
// 把食物渲染到地图里
this.renderFood();
// 把蛇渲染到地图里
this.renderSnake();
// 开始游戏
this.start();
// 监听键盘事件
this.eventKey();
}
// 渲染食物的方式 render 理解为渲染
Game.prototype.renderFood = function(){
let x = this.food.x;
let y = this.food.y;
// 渲染食物是给指定的格子渲染背景颜色
// this.map.dom.childNodes[x].childNodes[y].style.background = "red";
// 使用数组填充食物的颜色
this.map.arr[x][y].style.background = "red";
// this.map.arr[x][y].style.backgroundSize = "cover";
}
// 渲染蛇
Game.prototype.renderSnake = function(){
for(let i = 0; i < this.snake.arr.length; i++){
// 蛇的身体数组
let x = this.snake.arr[i].x;
let y = this.snake.arr[i].y;
this.map.arr[x][y].style.background = "green";
}
}
// 开始游戏
Game.prototype.start = function(){
// 这里使用了箭头函数和setInterval(),因为箭头函数不绑定this!!
// 使用计时器的箭头函数每隔0.5s执行一次
this.timer = setInterval(() => {
// 让蛇移动
this.snake.move();
// 检测蛇头是否撞到了墙
this.checkWall();
// 检测蛇头是否撞到自己的身体
this.checkSnake();
// 检测蛇头是否吃到食物
this.checkFood();
// 检测游戏是否在运行,这里也加入了锁
if(this.play){
// 其实蛇的数组,蛇头已经移动出去了,
// 但由于没有清屏渲染,所以页面没动而已
// 清屏
this.map.clear();
// 渲染食物
this.renderFood();
// 渲染蛇
this.renderSnake();
}
},500)
}
// 监听方向键
Game.prototype.eventKey = function(){
document.onkeydown = (e) => {
let code = e.keyCode;
// console.log(e);
if(code === 37 || code === 38|| code === 39 || code ===40){
// 如果按了上下左右键则调用snake里面的change,其余键盘按键没用
// console.log(this);
this.snake.change(code);
}
}
}
// 判断蛇头是否吃到食物
Game.prototype.checkFood = function(){
// 食物位置
let food = this.food;
// 蛇头的位置
let head = this.snake.arr[this.snake.arr.length - 1];
// console.log(food);
// console.log(head);
// 判断蛇头是否和食物位置重合
if(food.x === head.x && food.y == head.y){
// console.log("蛇头吃到食物了");
// 蛇的尾巴增长1节
this.snake.grow();
// 食物位置随即更新
this.changeFood();
}
}
// 更新食物的坐标
Game.prototype.changeFood = function(){
// 随机生成食物的坐标
let x = parseInt(Math.round(Math.random() * this.map.x));
let y = parseInt(Math.round(Math.random() * this.map.y));
// 循环蛇的每一节身体
for(let i = 0; i < this.snake.arr.length; i++){
// 获取蛇的单节
let one = this.snake.arr[i];
// 检测生成的食品是否与蛇的身体重合
if(one.x === x && one.y === y){
// 说明随机生成的食物与蛇的身体重合了 -
// 重新更新一遍食物的坐标
// console.log("重合")
this.changeFood();
return;
}
}
// 更新食物的方法
this.food.change(x,y);
}
// 游戏结束
Game.prototype.gameOver = function(){
// play锁
this.play = false;
// 停止计时器
clearInterval(this.timer);
// 告别用户
alert("游戏结束");
}
// 检测蛇头吃到自己的身体
Game.prototype.checkSnake = function(){
// 找到蛇头
let head = this.snake.arr[this.snake.arr.length - 1];
// 循环蛇的每一节身体
for(let i = 0; i < this.snake.arr.length - 1; i++){
// 蛇的每节身体
let snakeBody = this.snake.arr[i];
if(head.x === snakeBody.x && head.y === snakeBody.y){
// 表示蛇头和自己的身体重合,游戏结束
this.gameOver();
}
}
}
// 检测蛇头是否撞墙
Game.prototype.checkWall = function(){
let head = this.snake.arr[this.snake.arr.length - 1];
// console.log(head.x);
// 与墙的四个边缘做对比
if(head.x < 0 || head.y < 0 || head.x >= this.map.x || head.y >= this.map.y){
this.gameOver();
}
}
更多css小动画,js小案例见我的主页: