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 相关文章推荐
js模拟类继承小例子
Jul 17 Javascript
巧用replace将文字表情替换为图片
Apr 17 Javascript
jQuery提交多个表单的小技巧
Jul 27 Javascript
html文档中的location对象属性理解及常见的用法
Aug 13 Javascript
JS实现的不规则TAB选项卡效果代码
Sep 18 Javascript
jQuery语法小结(超实用)
Dec 31 Javascript
jQuery实现元素拖拽并cookie保存顺序的方法
Feb 20 Javascript
JavaScript sort数组排序方法和自我实现排序方法小结
Jun 06 Javascript
jQuery源码分析之sizzle选择器详解
Feb 13 Javascript
jQuery实现table表格信息的展开和缩小功能示例
Jul 21 jQuery
vue中实现回车键登录功能
Feb 19 Javascript
vue+iview框架实现左侧动态菜单功能的示例代码
Jul 23 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实现比较两个字符串日期大小的方法
2015/05/12 PHP
详解WordPress中分类函数wp_list_categories的使用
2016/01/04 PHP
PHP动态地创建属性和方法, 对象的复制, 对象的比较,加载指定的文件,自动加载类文件,命名空间
2016/05/06 PHP
PHP实现的简单排列组合算法应用示例
2017/06/20 PHP
PHP快速导出百万级数据到CSV或者EXCEL文件
2020/11/27 PHP
Javascript Global对象
2009/08/13 Javascript
nodejs中的fiber(纤程)库详解
2015/03/24 NodeJs
jQuery中设置form表单中action值的实现方法
2016/05/25 Javascript
js无提示关闭浏览器窗口的两种方法分析
2016/11/06 Javascript
vue利用better-scroll实现轮播图与页面滚动详解
2017/10/20 Javascript
axios对请求各种异常情况处理的封装方法
2018/09/25 Javascript
vue如何根据网站路由判断页面主题色详解
2018/11/02 Javascript
three.js实现炫酷的全景3D重力感应
2018/12/30 Javascript
在layui.use 中自定义 function 的正确方法
2019/09/16 Javascript
详解vue中多个有顺序要求的异步操作处理
2019/10/29 Javascript
vue实现分页加载效果
2019/12/24 Javascript
原生JS实现萤火虫效果
2020/03/07 Javascript
[15:23]教你分分钟做大人:虚空假面
2014/10/30 DOTA
Python subprocess模块学习总结
2014/03/13 Python
python+mongodb数据抓取详细介绍
2017/10/25 Python
Python使用gRPC传输协议教程
2018/10/16 Python
python实现列表中最大最小值输出的示例
2019/07/09 Python
Python使用random模块生成随机数操作实例详解
2019/09/17 Python
Python下使用Trackbar实现绘图板
2020/10/27 Python
宝拉珍选官方旗舰店:2%水杨酸精华液,收缩毛孔粗大和祛痘
2018/07/01 全球购物
最畅销的视频游戏享受高达90%的折扣:CDKeys
2020/02/10 全球购物
会计实习期自我鉴定
2013/10/06 职场文书
学子宴答谢词
2014/01/25 职场文书
优秀乡村医生先进事迹材料
2014/08/23 职场文书
个人四风对照检查材料
2014/09/26 职场文书
失职检讨书大全
2015/01/26 职场文书
2015秋季开学典礼致辞
2015/07/16 职场文书
大学生军训心得体会5篇
2019/08/15 职场文书
JavaScript原始值与包装对象的详细介绍
2021/05/11 Javascript
Spring Cloud Gateway去掉url前缀
2021/07/15 Java/Android
日本十大血腥动漫,那些被禁播的动漫盘点
2022/03/21 日漫