jQuery实现贪吃蛇小游戏(附源码下载)


Posted in Javascript onMarch 04, 2017

前言

相信贪吃蛇的游戏大家都玩过。在那个水果机还没有流行,人手一部诺基亚的时代,贪吃蛇是手机中的必备游戏。笔者闲的无聊的时候就拿出手机来玩上几局,挑战一下自己的记录。

后来上大学了,用c语言做过贪吃蛇的游戏,不过主要是通过函数来控制(PS:现在让我看代码都看不懂(⊙?⊙))。现在学习前端框架之后,通过jQuery来实现一个贪吃蛇的游戏效果,虽然游戏界面比(bu)较(ren)简(zhi)陋(shi),但是主要学习一下游戏中面向对象和由局部到整体的思想。

设计思想

在开始写代码前首先让我们来构思一下整体游戏的实现过程:

jQuery实现贪吃蛇小游戏(附源码下载)

需要的对象

首先既然是贪吃蛇,那么游戏中肯定要涉及到两个对象,一个是蛇的对象,另一个是食物的对象。食物对象肯定要有一个属性就是食物的坐标点,蛇对象有一个属性是一个数组,用来存放蛇身体所有的坐标点。

如何移动

另外全局需要有一个定时器来周期性的移动蛇的身体。由于蛇的身体弯弯曲曲有各种不同的形状,因此我们只处理蛇的头部和尾部,每次移动都根据移动的方向的不同来添加新的头部,再把尾部擦去,看起来就像蛇在向前爬行一样。

方向控制

由于蛇有移动的方向,因此我们也需要在全局定义一个方向对象,对象中有上下左右所代表的值。同时,在蛇对象的属性中我们也需要定义一个方向属性,用来表示当前蛇所移动的方向。

碰撞检测

在蛇向前爬行的过程中,会遇到三种不同的情况,需要进行不同的判断检测。第一种情况是吃到了食物,这时候就需要向蛇的数组中添加食物的坐标点;第二种情况是碰到了自己的身体,第三种是碰到了边界,这两种情况都导致游戏结束;如果不是上面的三种情况,蛇就可以正常的移动。

开始编程

整体构思有了,下面就开始写代码了。

搭建幕布

首先整个游戏需要一个搭建活动的场景,我们通过一个表格布局来作为整个游戏的背景。

<style type="text/css">
#pannel table{
 border-collapse:collapse;
}
#pannel td{
 width: 10px;
 height: 10px;
 border: 1px solid #000;
}
#pannel td.food{
 background: green;
}
#pannel td.body{
 background: #f60;
}
</style>
<div id="pannel">
</div>
<select name="" id="palSize">
 <option value="10">10*10</option>
 <option value="20">20*20</option>
 <option value="40">30*30</option>
</select>
<select name="" id="palSpeed">
 <option value="500">速度-慢</option>
 <option value="250">速度-正常</option>
 <option value="100">速度-快</option>
</select>
<button id="startBtn">开始</button>

pannel就是我们的幕布,我们在这个里面用td标签来画上一个个的“像素点”。我们用两种样式来表现不同的对象,.body表示蛇的身体的样式,.food表示食物的样式。

var settings = {
 // pannel面板的长度
 pannelSize: 10,
 // 贪吃蛇移动的速度
 speed: 500,
 // 贪吃蛇工作线程
 workThread: null,
};
function setPannel(size){
 var content = [];
 content.push('<table>');
 for(let i=0;i<size;i++){
 content.push('<tr>');
 for(let j=0;j<size;j++){
 content.push('<td class="td_'+i+'_'+j+'"></td>');
 }
 content.push('</tr>');
 }
 content.push('</table>');
 $('#pannel').html(content.join(''));
}
setPannel(settings.pannelSize);

我们定义了一个全局的settings用来存放全局性的变量,比如幕布的大小、蛇移动的速度和工作的线程。然后通过一个函数把幕布画了出来,最后的效果就是这样:

jQuery实现贪吃蛇小游戏(附源码下载)

方向和定位

既然我们的“舞台”已经搭建完了,怎么来定义我们“演员”的位置和移动的方向呢。首先定义一个全局的方向变量,对应的数值就是我们的上下左右方向键所代表的keyCode。

var Direction = {
 UP: 38,
 DOWN: 40,
 LEFT: 37,
 RIGHT: 39,
};

我们在上面画幕布的时候通过两次遍历画出了一个类似于中学里学的坐标系,有X轴和Y轴。如果每次都用{x:x,y:y}来表示会很(mei)麻(bi)烦(ge),我们可以定义一个坐标点对象。

function Position(x,y){
 // 距离X轴长度,取值范围0~pannelSize-1
 this.X = x || 0;
 // 距离Y轴长度,取值范围0~pannelSize-1
 this.Y = y || 0;
}

副咖?食物

既然定义好了坐标点对象,那么可以先来看一下简单的对象,就是我们的食物(Food)对象,上面说了,它有一个重要的属性就是它的坐标点。

function Food(){
 this.pos = null;
 // 随机产生Food坐标点,避开蛇身
 this.Create = function(){
 if(this.pos){
 this.handleDot(false, this.pos, 'food');
 }
 let isOk = true;
 while(isOk){
 let x = parseInt(Math.random()*settings.pannelSize),
 y = parseInt(Math.random()*settings.pannelSize);
 if(!$('.td_'+x+'_'+y).hasClass('body')){
 isOk = false;
 let pos = new Position(x, y);
 this.handleDot(true, pos, 'food');
 this.pos = pos;
 }
 }
 };
 // 画点
 this.handleDot = function(flag, dot, className){
 if(flag){
 $('.td_'+dot.X+'_'+dot.Y).addClass(className);
 } else {
 $('.td_'+dot.X+'_'+dot.Y).removeClass(className);
 }
 };
}

既然食物有了坐标点这个属性,那么我们什么时候给他赋值呢?我们知道Food是随机产生的,因此我们定义了一个Create函数用来产生Food的坐标点。但是产生的坐标点又不能在蛇的身体上,所以通过一个while循环来产生坐标点,如果坐标点正确了,就终止循环。此外为了方便我们统一处理坐标点的样式,因此定义了一个handleDot函数。

主咖?蛇

终于到了我们的主咖,蛇。首先定义一下蛇基本的属性,最重要的肯定是蛇的body属性,每次移动时,都需要对这个数组进行一些操作。其次是蛇的方向,我们给它一个默认向下的方向。然后是食物,在蛇的构造函数中我们传入食物对象,在后续移动时需要判断是否吃到食物。

function Snake(myFood){
 // 蛇的身体
 this.body = [];
 // 蛇的方向
 this.dir = Direction.DOWN;
 // 蛇的食物
 this.food = myFood;
 // 创造蛇身
 this.Create = function(){
 let isOk = true;
 while(isOk){
 let x = parseInt(Math.random()*(settings.pannelSize-2))+1,
 y = parseInt(Math.random()*(settings.pannelSize-2))+1;
 console.log(x,y)
 if(!$('.td_'+x+'_'+y).hasClass('food')){
 isOk = false;
 let pos = new Position(x, y);
 this.handleDot(true, pos, 'body')
 this.body.push(pos);
 }
 }
 };
 this.handleDot = function(flag, dot, className){
 if(flag){
 $('.td_'+dot.X+'_'+dot.Y).addClass(className);
 } else {
 $('.td_'+dot.X+'_'+dot.Y).removeClass(className);
 }
 };
}

移动函数处理

下面对蛇移动的过程进行处理,由于我们每次都采用添头去尾的方式移动,因此我们每次只需要关注蛇的头和尾。我们约定数组的第一个元素是头,最后一个元素是尾。

this.Move = function(){
 let oldHead = Object.assign(new Position(), this.body[0]),
 oldTail = Object.assign(new Position(), this.body[this.body.length - 1]),
 newHead = Object.assign(new Position(), oldHead);
 switch(this.dir){
 case Direction.UP:
 newHead.X = newHead.X - 1;
 break;
 case Direction.DOWN:
 newHead.X = newHead.X + 1;
 break;
 case Direction.LEFT:
 newHead.Y = newHead.Y - 1;
 break;
 case Direction.RIGHT:
 newHead.Y = newHead.Y + 1;
 break;
 default:
 break;
 }
 // 数组添头
 this.body.unshift(newHead);
 // 数组去尾
 this.body.pop();
};

检测函数处理

这样我们对蛇身数组就处理完了。但是我们还需要对新的头(newHead)进行一些碰撞检测,判断新头部的位置上是否有其他东西(碰撞检测)。

// 食物检测
this.eatFood = function(){
 let newHead = this.body[0];
 if(newHead.X == this.food.pos.X&&newHead.Y == this.food.pos.Y){
 return true;
 } else {
 return false;
 }
};
// 边界检测
this.konckWall = function(){
 let newHead = this.body[0];
 if(newHead.X == -1 || 
 newHead.Y == -1 || 
 newHead.X == settings.pannelSize || 
 newHead.Y == settings.pannelSize ){
 return true;
 } else {
 return false;
 }
};
// 蛇身检测
this.konckBody = function(){
 let newHead = this.body[0],
 flag = false;
 this.body.map(function(elem, index){
 if(index == 0)
 return;
 if(elem.X == newHead.X && elem.Y == newHead.Y){
 flag = true;
 }
 });
 return flag;
};

重新绘制

因此我们需要对Move函数进行一些扩充:

this.Move = function(){
 // ...数组操作
 if(this.eatFood()){
 this.body.push(oldTail);
 this.food.Create();
 this.rePaint(true, newHead, oldTail);
 } else if(this.konckWall() || this.konckBody()) {
 this.Over();
 } else {
 this.rePaint(false, newHead, oldTail);
 }
};
this.Over = function(){
 clearInterval(settings.workThread);
 console.log('Game Over');
};
this.rePaint = function(isEatFood, newHead, oldTail){
 if(isEatFood){
 // 加头
 this.handleDot(true, newHead, 'body');
 } else {
 // 加头
 this.handleDot(true, newHead, 'body');
 // 去尾
 this.handleDot(false, oldTail, 'body');
 }
};

因为在Move函数处理数组的后我们的蛇身还没有重新绘制,因此我们很巧妙地判断如果是吃到食物的情况,在数组中就把原来的尾部添加上,这样就达到了吃食物的效果。同时我们定义一个rePaint函数进行页面的重绘。

jQuery实现贪吃蛇小游戏(附源码下载)

游戏控制器

我们的“幕布”、“演员”和“动作指导”都已经到位,那么,我们现在就需要一个“摄影机”进行拍摄,让它们都开始“干活”。

function Control(){
 this.snake = null;
 // 按钮的事件绑定
 this.bindClick = function(){
 var that = this;
 $(document).on('keydown', function(e){
 if(!that.snake)
 return;
 var canChangrDir = true;
 switch(e.keyCode){
 case Direction.DOWN:
 if(that.snake.dir == Direction.UP){
 canChangrDir = false;
 }
 break;
 case Direction.UP:
 if(that.snake.dir == Direction.DOWN){
 canChangrDir = false;
 }
 break;
 case Direction.LEFT:
 if(that.snake.dir == Direction.RIGHT){
 canChangrDir = false;
 }
 break;
 case Direction.RIGHT:
 if(that.snake.dir == Direction.LEFT){
 canChangrDir = false;
 }
 break;
 default:
 canChangrDir = false;
 break;
 }
 if(canChangrDir){
 that.snake.dir = e.keyCode;
 }
 });
 $('#palSize').on('change',function(){
 settings.pannelSize = $(this).val();
 setPannel(settings.pannelSize);
 });
 $('#palSpeed').on('change',function(){
 settings.speed = $(this).val();
 });
 $('#startBtn').on('click',function(){
 $('.food').removeClass('food');
 $('.body').removeClass('body');
 that.startGame();
 });
 };
 // 初始化
 this.init = function(){
 this.bindClick();
 setPannel(settings.pannelSize);
 };
 // 开始游戏
 this.startGame = function(){
 var food = new Food();
 food.Create();
 var snake = new Snake(food);
 snake.Create();
 this.snake =snake;
 settings.workThread = setInterval(function(){
 snake.Move();
 },settings.speed);
 }
 this.init();
}

我们给document绑定一个keydown事件,当触发按键时改变蛇的移动方向,但是如果和当前蛇移动方向相反时就直接return。最后的效果如下:

jQuery实现贪吃蛇小游戏(附源码下载)

可以戳这里查看实现效果

点击这里下载源码

总结

实现了贪吃蛇的一些基本功能,比如移动、吃点、控制速度等,页面也比较的简单,就一个table、select和button。后期可以添加一些其他的功能,比如有计分、关卡等,也可以添加多个点,有的点吃完直接GameOver等等。

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
Knockoutjs的环境搭建教程
Nov 26 Javascript
javascript中slice(),splice(),split(),substring(),substr()使用方法
Mar 13 Javascript
超详细的javascript数组方法汇总
Nov 21 Javascript
AngularJS ng-app 指令实例详解
Jul 30 Javascript
jQuery在header中设置请求信息的方法
Mar 06 Javascript
详解Vue监听数据变化原理
Mar 08 Javascript
基于vue实现swipe轮播组件实例代码
May 24 Javascript
vue中实现在外部调用methods的方法(推荐)
Feb 08 Javascript
jquery动态添加带有样式的HTML标签元素方法
Feb 24 jQuery
详解vue.js下引入百度地图jsApi的两种方法
Jul 27 Javascript
js 实现在2d平面上画8的方法
Oct 10 Javascript
Layui数据表格之单元格编辑方式
Oct 26 Javascript
详解vue父子模版嵌套案例
Mar 04 #Javascript
vue指令以及dom操作详解
Mar 04 #Javascript
JS如何判断浏览器类型和详细区分IE各版本浏览器
Mar 04 #Javascript
详解在Vue中通过自定义指令获取dom元素
Mar 04 #Javascript
在javaScript中检测数据类型的几种方式小结
Mar 04 #Javascript
jQuery插件echarts实现的去掉X轴、Y轴和网格线效果示例【附demo源码下载】
Mar 04 #Javascript
jQuery插件echarts实现的循环生成图效果示例【附demo源码下载】
Mar 04 #Javascript
You might like
PHP程序级守护进程的实现与优化的使用概述
2013/05/02 PHP
php不允许用户提交空表单(php空值判断)
2013/11/12 PHP
PHP批量查询WordPress留言者E-mail地址实现方法
2015/02/15 PHP
PHP从FLV文件获取视频预览图的方法
2015/03/12 PHP
php strftime函数的详细用法
2018/06/21 PHP
php链式操作的实现方式分析
2019/08/12 PHP
PHP Web表单生成器案例分析
2020/06/02 PHP
javascript中的prototype属性实例分析说明
2010/08/09 Javascript
Select下拉框模糊查询功能实现代码
2016/07/22 Javascript
JavaScript-html标题滚动效果的简单实现
2016/09/08 Javascript
Angular在一个页面中使用两个ng-app的方法(二)
2017/02/20 Javascript
详解angularjs中如何实现控制器和指令之间交互
2017/05/31 Javascript
vue中实现左右联动的效果
2018/06/22 Javascript
微信小程序仿微信运动步数排行(交互)
2018/07/13 Javascript
js字符串倒序的实例代码
2018/11/30 Javascript
vuex实现购物车的增加减少移除
2020/06/28 Javascript
vue实现给div绑定keyup的enter事件
2020/07/31 Javascript
[46:21]Liquid vs LGD 2018国际邀请赛淘汰赛BO3 第一场 8.23
2018/08/24 DOTA
python多线程扫描端口示例
2014/01/16 Python
python2.7到3.x迁移指南
2018/02/01 Python
Python+selenium 获取一组元素属性值的实例
2018/06/22 Python
对python For 循环的三种遍历方式解析
2019/02/01 Python
详解用python自制微信机器人,定时发送天气预报
2019/03/25 Python
python变量的存储原理详解
2019/07/10 Python
pycharm中import呈现灰色原因的解决方法
2020/03/04 Python
Python操作Word批量生成合同的实现示例
2020/08/28 Python
英国领先的鞋类零售商:Shoe Zone
2018/12/13 全球购物
初二物理教学反思
2014/01/29 职场文书
优秀部门获奖感言
2014/02/14 职场文书
大型会议接待方案
2014/03/01 职场文书
广告文案的撰写技巧(实用干货)
2019/08/23 职场文书
SQL Server——索引+基于单表的数据插入与简单查询【1】
2021/04/05 SQL Server
MySQL 常见的数据表设计误区汇总
2021/06/07 MySQL
vue使用Google Recaptcha验证的实现示例
2021/08/23 Vue.js
MyBatis自定义SQL拦截器示例详解
2021/10/24 Java/Android
手写Spirit防抖函数underscore和节流函数lodash
2022/03/22 Javascript