前几天一个朋友问了我要做看你有多色这样的一个游戏需要怎么学,我也就自己写了一下。
我有放在自己的个人网站上:查看游戏
源代码也放在了github上:查看源码
现在开始说明制过程:
项目结构
注:偷懒用了jquery,其实代码不多,完全可以原生js,另外,由于这次我没有使用图片,所以img里面的内容是空的
game
-index.html
-css
-index.css
-js
-index.js
-jqeuery.js
-img
游戏界面
- loading: 主要用于加载游戏资源
- 启动游戏: 可以写些游戏规则,点击开始游戏就开始游戏
- 游戏中:游戏进行的页面
- 游戏暂停: 游戏暂停的页面
- 游戏结束: 游戏结束的页面
每个页面使用一个div表示,进行到哪一步就将哪一个页面div的display变为block,从而显示。
代码展示
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>找找看</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1">
<link href="./css/index.css" rel="stylesheet">
</head>
<body>
<!--加载页面-->
<div class="page loading" style="display: none">
<p>加载中...</p>
</div>
<!--开始游戏页面 -->
<div class="page index" style="display: none">
<h1>找找看</h1>
<p class="tip">
找出所有色块里颜色不同的一个
</p>
<button class="start-game">
开始游戏
</button>
</div>
<!--游戏中-->
<div class="page room" style="display: block;">
<div class="top">
<span class="score">得分:0</span>
<span class="time">60</span>
<span class="btn btn-pause">暂停</span>
</div>
<div id="box"></div>
</div>
<!--暂停-->
<div class="page pause">
<h1>游戏暂停</h1>
<button class="continue">
继续游戏
</button>
</div>
<!--游戏结束-->
<div class="page over">
<h1>游戏结束</h1>
<p class="result">
总分:0
</p>
<button class="restart">
重新开始
</button>
</div>
<script src="./js/jquery-1.7.1.min.js"></script>
<script src="./js/index.js"></script>
</body>
</html>
index.css
/* 重置样式 */
html,body,h1,h2,h3,h4,h5,h6,div,dl,dt,dd,ul,ol,li,p,blockquote,pre,hr,figure,table,caption,th,td,form,fieldset,legend,input,button,textarea,menu{margin:0;padding:0;}
header,footer,section,article,aside,nav,hgroup,address,figure,figcaption,menu,details{display:block;}
table{border-collapse:collapse;border-spacing:0;}
caption,th{text-align:left;font-weight:normal;}
html,body,fieldset,img,iframe,abbr{border:0;}
i,cite,em,var,address,dfn{font-style:normal;}
[hidefocus],summary{outline:0;}
li{list-style:none;}
h1,h2,h3,h4,h5,h6,small{font-size:100%;}
sup,sub{font-size:83%;}
pre,code,kbd,samp{font-family:inherit;}
q:before,q:after{content:none;}
textarea{overflow:auto;resize:none;}
label,summary{cursor:default;}
a,button{cursor:pointer;}
h1,h2,h3,h4,h5,h6,em,strong,b{font-weight:normal;}
del,ins,u,s,a,a:hover{text-decoration:none;}
body,textarea,input,button,select,keygen,legend{font:16px/1 "Microsoft Yahei", Arial, Helvetica, Sans-Serif,5b8b4f53;color:#333;outline:0;}
a,a:hover{color:#444;}
button{padding:0;margin:0;border:0;background-color:#fff;}
html, body { height: 100%; width: 100%; overflow: hidden;}
body { background-color: rgb(18, 149, 73);}
.page { display: none; position: relative; height: 100%; max-width: 600px; margin: 0 auto; color: #fff; text-align: center; }
.loading { display: block;}
.loading p {
position: absolute; top: 50%; left: 50%; width: 200px; height: 30px; margin-left: -100px; margin-top: -15px;
line-height: 30px; font-size: 26px;
}
.index h1 { font-size: 30px; padding: 30px 0; }
.index p { height: 40px; line-height: 40px; font-size: 20px; color: #adffe0; text-align: center;}
.index button, .pause button, .over button{
display: block; position: absolute; bottom: 100px; left: 50%;
height: 50px; width: 220px; margin-left: -110px; border-radius: 7px;
line-height: 50px; box-shadow: 0 5px #da9622;
color: inherit; cursor: pointer; font-weight: 700; font-size: 20px;
background: #fcad26;
}
#box {
position: absolute;
top: 50%;
left: 50%;
width: 500px;
height: 500px;
margin-left: -260px;
margin-top: -260px;
border-radius: 10px;
padding: 10px;
background-color: #ddd;
}
@media only screen and (max- 500px) and (min- 414px) {
#box {
width: 380px;
height: 380px;
margin-left: -200px;
margin-top: -200px;
}
}
@media only screen and (max- 414px) and (min- 375px) {
#box {
width: 340px;
height: 340px;
border-radius: 8px;
margin-left: -180px;
margin-top: -180px;
}
}
@media only screen and (max- 375px) {
#box {
width: 300px;
height: 300px;
border-radius: 5px;
margin-left: -160px;
margin-top: -160px;
}
}
#box span {
display: block;
float: left;
border-radius: 10px;
cursor: pointer;
border: 5px solid #ddd;
position: relative;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
#box.lv2 span {
width: 50%;
height: 50%;
}
#box.lv3 span {
width: 33.3333%;
height: 33.3333%;
}
#box.lv4 span {
width: 25%;
height: 25%;
}
#box.lv5 span {
width: 20%;
height: 20%;
}
#box.lv6 span {
width: 16.666%;
height: 16.666%;
}
#box.lv7 span {
width: 14.285%;
height: 14.285%;
}
.score {
position: absolute;
top: 10px;
left: 5px;
color: #adffe0;
}
.time {
position: absolute;
top: 5px;
left: 50%;
display: block;
color: #fff;
width: 50px;
margin-left: -25px;
height: 24px;
line-height: 24px;
border-radius: 10px;
text-align: center;
font-size: 20px;
font-weight: 700;
background-color: rgb(117, 214, 171);
}
.btn-pause{
display: block; position: absolute; top: 5px; right: 5px;
height: 24px; width: 60px; border-radius: 7px;
line-height: 24px; box-shadow: 0 4px #da9622;
color: inherit; cursor: pointer; font-weight: 500; font-size: 16px;
background: #fcad26;
}
.pause h1, .over h1{
height: 150px;
line-height: 150px;
font-size: 30px;
}
.over p {
font-size: 26px;
}
index.js
var game = {};
//缓存
game.pages = $('.page');
game.startBtn = $('.start-game').eq(0);
game.pauseBtn = $('.btn-pause').eq(0);
game.continueBtn = $('.continue').eq(0);
game.restartBtn = $('.restart').eq(0);
game.blockBox = $('#box');
game.scoreBox = $('.score').eq(0);
game.timeBox = $('.time').eq(0);
game.resultBox = $('.result').eq(0);
game.eventType = document.ontouchstart ? 'touchstart' : 'click';
game.imgUrls = [];
game.time = 60;
game.score = 0;
game.level = 0;
game.levelMap = [2, 3, 3, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7];
game.colorDiff = [115, 100, 100, 85, 85, 85, 70, 70, 70, 70, 55, 55, 55, 55, 55, 40, 40, 40, 40, 40, 40, 25, 25, 25, 25, 25, 25, 25, 10, 10, 10, 10, 10, 10, 10, 10];
game.diffIndex = 0;
//加载游戏
game.loading = function () {
var imgCounts = this.imgUrls.length;
if ( imgCounts == 0) {
this.switchPage(1);
this.initEvent();
} else {
var num = 0,
self = this;
function count () {
num++;
if ( num == imgCounts ) {
self.switchPage(1);
self.initEvent();
}
}
for ( var i = 0; i < imgCounts; i++ ) {
var img = new Image();
img.onload = count;
img.src = this.imgUrls[i];
}
}
};
//初始化事件
game.initEvent = function () {
this.startBtn.on(this.eventType, this.start.bind(this));
this.restartBtn.on(this.eventType, this.start.bind(this));
this.blockBox.on(this.eventType, this.clickBlock.bind(this));
this.pauseBtn.on(this.eventType, this.pause.bind(this));
this.continueBtn.on(this.eventType, this.continue.bind(this));
};
//点击方块事件
game.clickBlock = function ( event ) {
if ( event.target.tagName.toLowerCase() === 'span' ) {
//选对了
if ( $(event.target).index() == this.diffIndex ) {
this.level++;
this.score++;
this.render();
} else {
this.over()
}
}
};
//开始游戏
game.start = function () {
this.switchPage(2);
this.render();
this.timeCount();
};
//游戏界面渲染
game.render = function () {
//获取有n*n
var num = this.levelMap[this.level] ? this.levelMap[this.level] : this.levelMap[this.levelMap.length - 1],
colorDiff = this.colorDiff[this.level] ? this.colorDiff[this.level] : this.colorDiff[this.colorDiff.length - 1],
color = [],
lvColor = [];
//分数
this.scoreBox.html('得分:' + this.score);
//时间
this.timeBox.html(this.time);
//给box添加类名
this.blockBox[0].className = 'lv' + num;
//得到不同块的index
this.diffIndex = Math.floor(Math.random() * num * num);
//获取颜色
color = this.getColor(257 - colorDiff);
lvColor = this.getLvColor(color[0], colorDiff);
//添加block
var str = '';
num *= num;
for ( var i = 0; i < num; i++ ) {
if ( i == this.diffIndex ) {
str += '<span style="background-color: ' + lvColor[1] + ';"></span>';
} else {
str += '<span style="background-color: ' + color[1] + ';"></span>';
}
};
this.blockBox.html(str);
};
//得到随机颜色
game.getColor = function (max) {
var t = [Math.floor(Math.random() * max), Math.floor(Math.random() * max), Math.floor(Math.random() * max)];
return [t, "rgb(" + t.join(",") + ")"];
};
//得到不同的颜色
game.getLvColor = function (color, diff) {
var r = [];
r[0] = color[0] + diff;
r[1] = color[1] + diff;
r[2] = color[2] + diff;
return [r, "rgb(" + r.join(",") + ")"];
};
//游戏结束处理
game.over = function () {
this.switchPage(4);
this.resultBox.html('总分:' + this.score);
clearInterval(this.timer);
this.time = 60;
this.score = 0;
this.level = 0;
};
//游戏暂停处理
game.pause = function () {
clearInterval(this.timer);
this.switchPage(3);
};
//继续游戏
game.continue = function () {
this.switchPage(2);
this.timeCount();
};
//计时器
game.timeCount = function () {
var self = this;
game.timer = setInterval(function(){
self.time--;
self.timeBox.html(self.time);
if ( self.time == 0 ) {
self.over.call(self);
}
}, 1000)
};
//换页
game.switchPage = function ( index ) {
this.pages.css('display', 'none');
this.pages.eq(index).css('display', 'block');
};
//游戏入口,触发加载
game.loading();
大家可以看到js代码,所有的变量和函数都添加到了game={}这个对象上,以防发生全局污染。
游戏加载
有游戏加载这个步骤的主要目的是在游戏加载资源的时候让用户知道具体的加载过程,免得加载过慢时用户离开页面。
代码为:
...
game.imgUrls = [];
...
//加载游戏
game.loading = function () {
var imgCounts = this.imgUrls.length;
if ( imgCounts == 0) {
this.switchPage(1);
this.initEvent();
} else {
var num = 0,
self = this;
function count () {
num++;
if ( num == imgCounts ) {
self.switchPage(1);
self.initEvent();
}
}
for ( var i = 0; i < imgCounts; i++ ) {
var img = new Image();
img.onload = count;
img.src = this.imgUrls[i];
}
}
};
这段代码并不难理解,game.imgUrls为存放图片url的数组,游戏加载的时候通过获取到这个数组的长度,判断是否进行图片加载。
如果不需要加载就切换到开始游戏的界面,并且添加事件处理。
其中,this.switchPage函数的代码为:
...
game.pages = $('.page');
...
//换页
game.switchPage = function ( index ) {
this.pages.css('display', 'none');
this.pages.eq(index).css('display', 'block');
};
当加载图片存在时,即imgUrls不为空数组,那么开始加载图片
var num = 0,
self = this;
function count () {
num++;
if ( num == imgCounts ) {
self.switchPage(1);
self.initEvent();
}
}
for ( var i = 0; i < imgCounts; i++ ) {
var img = new Image();
img.onload = count;
img.src = this.imgUrls[i];
}
代码中,循环遍历了imgUrls,将src赋给一个img对象,然后onload(加载完成)就将计数+1,当计数和图片数量一致时说明全部加载完成,然后就换页和添加事件。
开始游戏界面
这个界面主要功能是显示提示信息,和点击开始游戏按钮时开始游戏。
添加事件都放在game.initEvent函数中
...
game.startBtn = $('.start-game').eq(0);
...
game.eventType = document.ontouchstart ? 'touchstart' : 'click';
...
//初始化事件
game.initEvent = function () {
...
this.startBtn.on(this.eventType, this.start.bind(this));
...
};
给图中的“开始游戏”按钮绑定了touchstart(click)事件,处理为game.start()——开始游戏
游戏进行中的处理
游戏元素很少,有4个:得分、时间、暂停按钮、装方块的盒子。
思路为:
当游戏开始时,等级默认为0,等级为0的方块为 2 x 2 放置,其他等级n x n 由 levelMap确定,当level超过数组长度时,取7.
game.levelMap = [2, 3, 3, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7];
然后生成对应数字的方块html,放进装方块的盒子,同时给每个方块生成颜色,其中一个不同,当点击方块时判断是否选到不同,选对则晋级,选错则游戏结束。
开始讲解代码:
game.time = 60; //游戏时间
game.score = 0; //得分
game.level = 0; //等级
game.levelMap = [2, 3, 3, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7]; // n x n 的方块
game.colorDiff = [115, 100, 100, 85, 85, 85, 70, 70, 70, 70, 55, 55, 55, 55, 55, 40, 40, 40, 40, 40, 40, 25, 25, 25, 25, 25, 25, 25, 10, 10, 10, 10, 10, 10, 10, 10]; // 色差,等级越高,色差越小
game.diffIndex = 0; //不同颜色方块的index
...
//开始游戏(点击开始游戏就触发这个函数)
game.start = function () {
this.switchPage(2); //换页到游戏界面
this.render(); //render函数处理游戏界面渲染,主要包括生成方块,颜色等等
this.timeCount(); //开启计时器
};
//游戏界面渲染
game.render = function () {
/**
* num 为n * n 的方块中的n,由等级决定,等级超过数组最大就取最后一个
* colorDiff 为色差,由等级决定,等级超过数组最大就取最后一个
*/
var num = this.levelMap[this.level] ?
this.levelMap[this.level] :
this.levelMap[this.levelMap.length - 1],
colorDiff = this.colorDiff[this.level] ?
this.colorDiff[this.level] :
this.colorDiff[this.colorDiff.length - 1],
color = [],
lvColor = [];
//显示分数
this.scoreBox.html('得分:' + this.score);
//显示时间
this.timeBox.html(this.time);
//给box添加类名(主要是css需要,不同数量的方块宽高应该不一样,比如 2 * 2,每个方块为50%)
this.blockBox[0].className = 'lv' + num;
//得到不同颜色块的index
this.diffIndex = Math.floor(Math.random() * num * num);
//获取颜色
color = this.getColor(257 - colorDiff);
lvColor = this.getLvColor(color[0], colorDiff);
//添加block
var str = '';
num *= num;
for ( var i = 0; i < num; i++ ) {
if ( i == this.diffIndex ) {
str += '<span style="background-color: ' + lvColor[1] + ';"></span>';
} else {
str += '<span style="background-color: ' + color[1] + ';"></span>';
}
};
this.blockBox.html(str);
};
//得到随机颜色
game.getColor = function (max) {
var t = [Math.floor(Math.random() * max), Math.floor(Math.random() * max), Math.floor(Math.random() * max)];
return [t, "rgb(" + t.join(",") + ")"];
};
//得到不同的颜色
game.getLvColor = function (color, diff) {
var r = [];
r[0] = color[0] + diff;
r[1] = color[1] + diff;
r[2] = color[2] + diff;
return [r, "rgb(" + r.join(",") + ")"];
};
思路不难,先知道方块是n×n的布局,然后在 n×n个方块中随机生成一个不同的方块,记录在diffIndex这个变量里。
这里说一下Math.randon()是生成[0,1)的函数,可能等于0,不会等于1,如果是想生成n以内的一个整数(不包括n),写为:
Math.floor(Math.randon() * n)
Math.floor()表示向下取整,即舍去小数位。
然后生成随机颜色,所有方块共有两种颜色,一个方块不同,其他相同。
//得到随机颜色
game.getColor = function (max) {
var t = [Math.floor(Math.random() * max), Math.floor(Math.random() * max), Math.floor(Math.random() * max)];
return [t, "rgb(" + t.join(",") + ")"];
};
这个函数返回一个数组,array(0)也是一个数组,保存了rgb的值,array(1)为字符串,型如:”rgb(0,0,0)”。
render函数中是下面这样的,原因为另一个颜色是这个颜色加上色差得到,这样保证最大不超过256
color = this.getColor(257 - colorDiff);
另一个颜色:
//得到不同的颜色
game.getLvColor = function (color, diff) {
var r = [];
r[0] = color[0] + diff;
r[1] = color[1] + diff;
r[2] = color[2] + diff;
return [r, "rgb(" + r.join(",") + ")"];
};
得到了两种颜色,就可以给方块添加:
//获取颜色
color = this.getColor(257 - colorDiff);
lvColor = this.getLvColor(color[0], colorDiff);
//添加block
var str = '';
num *= num;
for ( var i = 0; i < num; i++ ) {
if ( i == this.diffIndex ) {
str += '<span style="background-color: ' + lvColor[1] + ';"></span>';
} else {
str += '<span style="background-color: ' + color[1] + ';"></span>';
}
};
this.blockBox.html(str);
方块生成后,自然想到点击方块要判断是否正确,这里使用事件委托的方式,将点击事件绑定在方块盒子上:
this.blockBox.on(this.eventType, this.clickBlock.bind(this));
//点击方块事件
game.clickBlock = function ( event ) {
if ( event.target.tagName.toLowerCase() === 'span' ) {
//选对了
if ( $(event.target).index() == this.diffIndex ) {
this.level++;
this.score++;
this.render();
} else {
this.over()
}
}
};
代码结构一眼看去就了解什么意思了,先判断点的是不是方块,如果是,得到方块的index,然后和diffIndex进行对比,看是否选对。
选对了就加level,加分数,重新渲染游戏界面
选错了就执行over()函数,游戏结束。
讲一下游戏开始后执行的定时器函数:
//计时器
game.timeCount = function () {
var self = this;
game.timer = setInterval(function(){
self.time--;
self.timeBox.html(self.time);
if ( self.time == 0 ) {
self.over.call(self);
}
}, 1000)
};
判断是否时间到达0,是就执行over函数,否就继续执行,减时间并显示。
在游戏界面中还有一个暂停按钮:
暂停页面
this.pauseBtn.on(this.eventType, this.pause.bind(this));
//游戏暂停处理
game.pause = function () {
clearInterval(this.timer);
this.switchPage(3);
};
很简单,只是关闭定时器,然后换页就好了
继续游戏的按钮绑定continue函数
this.continueBtn.on(this.eventType, this.continue.bind(this));
//继续游戏
game.continue = function () {
this.switchPage(2);
this.timeCount();
};
换页,再开启定时器就行了
游戏结束处理
需要做的事情有:
1、显示得分
2、初始化游戏参数:score、level等等
3、点击重新开始,重新开始游戏
//游戏结束处理
game.over = function () {
this.switchPage(4);
this.resultBox.html('总分:' + this.score);
clearInterval(this.timer);
this.time = 60;
this.score = 0;
this.level = 0;
};
this.restartBtn.on(this.eventType, this.start.bind(this));
到此游戏讲解完毕,再补充点css中用得比较多的东西:
1、让width,height已知水平,垂直都居中显示:
div-parent {
position: relative;
}
div {
position: absolute;
top: 50%;
left: 50%;
width: 300px;
height: 200px;
margin-left: -150px;
margin-top: -100px;
}
width和height未知的话,垂直居中需要用到css3的知识。
2、再来一个n*n方块的css,这里用2*2作为例子:
.lv1 span {
height: 50%;
width: 50%;
}
/* span作为一个方块 */
span {
display: block;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box; /* box-sizing IE8+支持 */
float: left;
position: relative;
border: 5px solid #ddd;
border-radius: 10px;
cursor: pointer;
}
box-sizing改变盒模型:
border-box:width = border + padding + 内容(IE6及以前浏览器默认)
content-box: width = 内容的宽度(现代浏览器默认)