闲逛github发现一个javascript原生实现的小游戏,源码写的很清晰,适合想提高水平的同学观摩学习。读通源码后,我决定写一系列的博客来分析源码,从整体架构到具体实现细节来帮助一些想提高水平的朋友。源码地址为:https://github.com/keenwon/flappy-pig

需要提醒大家的是,我的分析模式是,先给出源码,加上注释让大家通读一遍,然后分解源码逐步分析。

下载了作者的源码后先看一下目录结构:

其中作者使用了Grunt进行了打包,会使用的grunt的小伙伴一看这个目录肯定一目了然,如果你从来没有使用过任何前端构建工具,别担心,你只用关注src文件夹就可以了,打开src文件夹,内容如下:

这就是游戏的全部组成部分了,我们只关心具体的控制逻辑,所以直接进入js文件夹:

其中game.js是游戏的主程序,其他的分别承担一定的职责,比如pig.js负责跳动小猪的行为和属性、pillar.js负责柱子的移动、util.js包含一些工具方法等。这里我们要学习的是一种分模块的思想,让一个模块负责一程序的一个部分,提供接口供其它程序使用,这样接触了耦合,同时让程序便于维护。
我们第一篇分析就从gama.js下手,初步的分解一下作者的思路。
让我们先看看主程序的全部代码,其中我会用绿色的字体表示注释,让大家看得更清楚:

var flappy = (function (self) {
'use strict';//开启严格模式,新手可以暂时忽视 var controller = self.controller,//获取控制者对象,之后详细介绍
option = self.option,//获取配置值,之后详细介绍
pig = self.pig,//获取小猪模块,之后详细介绍
pillar = self.pillar,//获取柱子模块,之后详细介绍
pos = self.position,//获取位置模块,之后详细介绍
util = self.util,//获取工具模块,之后详细介绍
$ = self.util.$;//从util.js中我们可以看出,$方法可以通过id获取DOM元素 //主程序
self.game = {//给self对象添加game属性,该属性指向一个对象
init: function () {//game对象的init方法
var t = this;//this指向函数调用者,非特殊情况下(不使用call、apply或者直接调用),一般我们就先认为指向了game对象,因为从后面我们也可以看到,作者也是通过flappy.game.init()调用的。 t._isStart = false;//game有一个isStart属性,用于标识游戏是否开始,初始值为false
t._isEnd = false;//game有一个isEnd属性,用于表示游戏是否结束,初始值为false
t._timer = null;//game对象有一个定时器,初始化为null pig.init(t.fall, t);//调用pig模块的init方法,将game.fall方法和game对象传递过去
pillar.init();//调用pillar模块的init方法
pos.init(t.hit, t);//调用pos模块的init方法,将game.hit方法和game对象传递过去 t.addKeyListener();//将this指向game,给game对象添加键盘监听
},
addKeyListener: function () {//监听键盘事件
var t = this;//this指向函数调用者,从上面可以看到调用者是game
document.onkeydown = function (e) {//监听键盘按下事件
e = e || window.event;//获取事件对象,兼容IE
var currKey = e.keyCode || e.which || e.charCode;//获取按了哪一个按键,兼容各家浏览器
if (currKey == 32) {//如果按下了空格
if (!t._isEnd) {//如果游戏没有结束
t.jump();//那么调用game.jump()方法
} else {
window.location.reload();//如果游戏已经结束,按空格后刷新页面,重新开始
}
util.preventDefaultEvent(e);//阻止事件的默认行为,具体细节在util.js中,我会在相应章节分析
}
};
},
jump: function () {//这里就是game.jump()方法
var t = this;//指向game对象
if (!t._isStart) {//如果游戏没有开始
$('start').style.display = 'none';//将游戏开始界面隐藏
t._createTimer(function () {//调用game._createTimer方法,创建定时器,每二十毫秒执行一次
pig.start();//调用pig模块的start方法,让小猪开始移动,具体细节在pig.js中,我会在相应章节分析
pillar.move();//,调用pillar模块的move方法,让柱子移动,具体细节在pillar.js中,我会在相应章节分析
pos.judge();//调用pos模块的judge方法,判断位置,具体细节在position.js中,我会在相应章节分析
$('score').innerHTML = pillar.currentId + 1;//设置记分板分数
});
t._isStart = true;//设置游戏状态为已开始
} else {
pig.jump();//如果游戏已经开始,那么直接调用pig.jump方法
}
},
hit: function () {//game对象的hit方法
var t = this; t.over();//调用game.over方法,游戏结束
pig.hit();//调用pig模块的hit方法,具体细节在pig.js中,我会在相应章节分析
},
fall: function () {//game对象的fall方法
var t = this; t.over();//调用game.over方法,游戏结束
pig.fall();//调用pig模块的fall方法,具体细节在pig.js中,我会在相应章节分析
},
over: function () {//game对象的over方法,负责结束游戏
var t = this;//获取game对象
clearInterval(t._timer);//取消计时器
t._isEnd = true;//将标识游戏是否结束的变量设置为true
$('end').style.display = 'block';//游戏结束的提示显示出来
},
_createTimer: function (fn) {//game对象的_createTimer方法,创建定时器
var t = this;//获取game对象 t._timer = setInterval(fn, option.frequency);//实现定时器,频率为配置项中的frequency属性,具体细节在option.js中,我会在相应章节介绍
}
}; flappy.init = function () {//暴露接口
self.game.init();//game.init()函数中this指向game
}; return self;//返回self对象,实际上也就是给原本的flappy对象加了点东西 })(flappy || {});

以上就是game.js的全部代码和我的注释,可能看完注释后读者仍然有一种雾里看花的感觉,不要担心,我不会用单单用注释来糊弄你们,只是你们可以通过注释大致明白作者每句话的意思。
好,我们来真正的开始解剖它吧。
首先要看看作者的这个用法,相信这也是新手迷茫的地方:
var flappy  = (function(self){
  //其他实现细节
})(flappy || {});
如果你通读了作者的源码后,你可以很容易的理解到这里作者的意思是,给传进来的flappy对象增加相应的模块,因为我们的程序分成了很多不同的模块,但是作为一个有机的整体,程序需要有机的结合在一起,如果还是不太懂,没事我们写一个小小的测试:
这段代码的执行结果是这样的:
这里我们需要学习的有以下几个知识点:
第一,预声明
javascript程序在执行前在执行前会有一次预声明,处理funtion声明和var声明,其中var的变量会被赋值为undefined,详细的过程这里展开,不懂的同学自行谷歌或百度。
第二,匿名函数自执行
var fn = (function(){})();
这里是javascripter编程中经常使用的技巧,它作用在方方面面——实现私有属性、避免污染全局、各种设计模式中或多或少也会使用到。
在了解以上两点的基础上我们来一步一步分析执行过程,首先预编译,flappy的值被设置为undefined;然后代码正式开始执行,执行一个匿名函数,参数中有一个逻辑或判断,如果flappy不是undefined或者null则使用flappy作为参数,如果flappy是undefined或者null则使用空对象作为参数,显然我们的flappy此时的值是undefined,所以这里空对象{}被作为参数传入,也就是说self指向一个空对象,
给这个空对象添加了一个name属性,然后返回这个对象的引用给flappy,也就是说现在flappy的值指向一个含有name属性的对象了;再然后,故伎重演,执行匿名函数,不过此时参数中flappy既不是undefined或者null,而是一个含有name属性的对象,匿名函数执行的结果是给这个对象添加了一个age属性并返回该对象的引用个flappy,所以结果我们看到个对象又多了一个age属性。
这里搞懂了,就不难理解作者在主程序干了些啥了,我们接下来用伪代码来描述一下主程序:
他们在形式上同属于一个对象,然而在逻辑上他们的关系是这样的:
也就是说使用者只需要调用flappy.init方法,其他的实现细节对于使用者来说是透明的。flappy方法也只有一个直接下级——flappy.game对象,其他的模块对于flappy.init来说是透明的。flappy.game对象就很忙了,他负责做具体的事情,所有的细节都由它来调度。
我们形象的形容一下这个过程,市长(也就是上图中的使用者)通过电话(也就是上图中国的flappy.init())对当地公安局局长(图中的flappy.game)说:“我们要整顿市容”,于是公安局中赶紧联系了城管部门(负责对小摊小贩的清理)、清洁部门(负责把墙上贴的小广告清除掉)、消防部门(负责检查城市内的不合格消防设施)等等,具体怎么做就由各部分自己去完成。
这就是主程序的骨架,从中我们并没有看到任何实现细节,我们需要的是建立起一个整体的架构,特别是对于很多渴望进阶的朋友,总觉得自己虽然掌握了基础但是不知道运用在哪里,总有雾里看花的感觉。实际上,时刻记住,程序的出现时为了解决现实中存在的一些问题,所以抽象出现实中的关系才是写程序的核心,学会了这一点,才算上走上了programmer的路子,否则就还是个coder。
好了,这一节的内容是一个概览,理清主程序的框架和思路,下一节我会给大家来分析pig.js的实现细节(也可以想象成带领大家实地观摩城管是怎么对付小贩)。
 

flappy pig小游戏源码分析(1)——主程序初探的更多相关文章

  1. flappy pig小游戏源码分析(4)——核心pig模块(未完待续)

    热身之后,我们要动点真格的了,游戏叫flappy pig,我们的pig终于要出场了. 老规矩,看看目录结构,读者对着目录结构好好回想我们已经讲解的几个模块: 其中game.js是游戏主程序,optio ...

  2. flappy pig小游戏源码分析(3)——解剖util

    这一节我们继续高歌猛进,如果对源码中有无论无何都理解不通的问题,欢迎和我交流,让我也学习一下,我的qq是372402487. 还是按照惯例看看我们的目录结构. 我们在前两节中已经分析了game.js, ...

  3. flappy pig小游戏源码分析(2)——解剖option

    今天继续分析flappy bird的源码.重温一下源码的目录结构. 在本系列第一篇中我们分析了game.js文件,也就是整个程序的架构.这一篇我们来看看option.js文件,这个文件的内容很简单,主 ...

  4. xss小游戏源码分析

    配置 下载地址:https://files.cnblogs.com/files/Lmg66/xssgame-master.zip 使用:下载解压,放到www目录下(phpstudy),http服务下都 ...

  5. HTML5小游戏源码收藏

    html5魅族创意的贪食蛇游戏源码下载 html5网页版打砖块小游戏源码下载 html5 3D立体魔方小游戏源码下载 html5网页版飞机躲避游戏源码下载 html5三国人物连连看游戏源码下载 js ...

  6. Creator仿超级玛丽小游戏源码分享

    Creator仿超级玛丽小游戏源码分享 之前用Cocos Creator 做的一款仿超级玛丽的游戏,使用的版本为14.2 ,可以直接打包为APK,现在毕设已经完成,游戏分享出来,大家一起学习进步.特别 ...

  7. HashMap的小总结 + 源码分析

    一.HashMap的原理 所谓Map,就是关联数组,存的是键值对——key&value. 实现一个简单的Map,你也许会直接用两个LIst,一个存key,一个存value.然后做查询或者get ...

  8. h5小球走迷宫小游戏源码

    无意中找到的一个挺有意思的小游戏,关键是用h5写的,下面就分享给大家源码 还是先来看小游戏的截图 可以用键盘的三个键去控制它,然后通关 下面是源代码 <!doctype html> < ...

  9. C\C++ 1A2B小游戏源码

    学了一段时间,心血来潮写了一个1A2B小游戏,很多人应该玩过,是一个挺有意思的益智小游戏,之前用易语言写过,现在又用C++重写了一下. 编译运行无错,整体程序设计思路为:进入循环,初始化游戏,读入一个 ...

随机推荐

  1. jdbc框架 commons-dbutils的使用

    commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序 ...

  2. opencore

    OpenCore是Android的多媒体核心,它是一个基于C++的实现,定义了全功能的操作系统移植层,各种基本的功能均被封装成类的形式,各层次之间的接口多使用继承等方式. OpenCore是一个多媒体 ...

  3. php面向对象(二) $this

    <!doctype html> <html> <head> <meta charset="UTF-8"> <title> ...

  4. Newtonsoft.Json序列化和反序列之javascriptConvert.SerializeObject,DeserializeObject,JsonWriter,JsonReader

    这里下载:http://www.newtonsoft.com/products/json/安装:   1.解压下载文件,得到Newtonsoft.Json.dll   2.在项目中添加引用.. jav ...

  5. 简单理解Hibernate三种状态的概念及互相转化

    本文描述了Hibernate三种状态的概念及互相转化.Java对象的生命周期中有三种状态,而且互相转化.它们分别是临时状态,持久化状态,以及游离状态. AD:WOT2015 互联网运维与开发者大会 热 ...

  6. This project needs to migrate WTP metadata

    in command-line: path> mvn eclipse:clean path> mvn -Dwtpversion=1.5 eclipse:eclipse path> m ...

  7. poj 3694 pku 3694 Network tarjan求割边 lca

    题意:给你一个连通图,然后再给你n个询问,每个询问给一个点u,v表示加上u,v之后又多少个桥.一个最容易想到的办法就是先加边找桥,加边找桥,这样可定超时.那么就可以缩点,因为如果一条边不是桥那么无论怎 ...

  8. .net4.0下 解决asp.net中“从客户端中检测到有潜在危险的Request.Form值”的错误

    asp.net 2.0 通常解决办法 方案一: 将.aspx文件中的page项添加ValidateRequest="false" ,如下: <%@ Page Validate ...

  9. C语言之内存覆盖

    在实现memcpy函数的时候,我们说过要考虑内存覆盖的问题,到底什么是内存覆盖呢,他的出现对程序到底有什么影响呢?我们又要如何去解决这种问题的发生? 首先先看一般人经常实现的memcpy函数: #in ...

  10. 正则化,数据集扩增,Dropout

    正则化方法:防止过拟合,提高泛化能力 在训练数据不够多时,或者overtraining时,常常会导致overfitting(过拟合).其直观的表现如下图所示,随着训练过程的进行,模型复杂度增加,在tr ...