芜湖!没想到上个做扫雷的阅读量这么高(激动)
先捞一下:做个小项目~纯原生JS手把手逐句解释写一个扫雷小游戏(附源码)
快看↑
这次就做一个别踩白块吧哈哈哈哈哈
最终版演示地址:钢琴块
源码在最后!最后!
1.画格子
首先,我们不做那么复杂的,每行四个格子,只有一个黑色块,也没有高级的连按的格子
然后整体格子往下跑,上面生成新的格子,格子按错或者触底了还没按算输,按对了分数+1
似乎比扫雷简单很多(笑)
<html>
<head>
<meta charset="utf-8" />
<title>别踩白块</title>
</head>
<body>
<div id="main">
<div id="con">
<div class="row">
<div class="cell"></div>
<div class="cell black"></div>
<div class="cell"></div>
<div class="cell"></div>
</div>
</div>
</div>
</body>
</html>
自然而然,CSS就应该是
* {
margin: 0;
padding: 0;
}
#con {
display: flex;
flex-direction: column-reverse;
/*设置成竖轴的翻转轴,这样就能让块自动往下掉*/
justify-content: center
}
.row {
display: flex;
width: 99%;
height: 19vh;
/*这里的高度并不一定是20%,
我只是想一页有五个,
如果改小了记得在init方法里把初始的格子的循环多来几次*/
max-width: 750px;
/*设置一个最大值,要不电脑端太宽了丑死了*/
}
.cell {
width: 25%;
height: 99%;
border-left: 1px solid #CCC;
}
.black{
background-color: #000
}
如果还不太熟悉flex布局的小伙伴看这里:
===首先在父元素上添加display: flex;
,子元素就会变成父元素的项目,默认按照主轴(默认是横轴)排列,flex-direction
属性可以修改主轴,这里flex-direction: column-reverse;
就修改成了竖轴的翻转轴,justify-content: center
表示元素在主轴怎么排列,这里就是居中啦
更多的可以去菜鸟教程什么的看看,非常好学
目前看起来一切正常,我们先把html里的行结构去掉,换上开始游戏的按钮
2.添加开始游戏按钮
<button id="start" onclick="start()">开始游戏</button>
<!--按钮对应的CSS-->
#start{
width: 100%;
height:100%;
border:0;
background-color: #000;
color: #FFF;
font-size: 25px;
}
<!--虽然把按钮占满整个屏幕的做法很草率。。但是就先这样吧-->
然后我们就可以准备JS了
3.开始游戏并用js生成格子
既然我们要频繁的生成一行方块,毫无疑问封装一个生成行的函数非常有用,同时也封装一个生成格子的,会更方便一些
当然每个块块是随机的,所以我们定义一个数组,初始值都是cell
,然后随机一个改成cell black
function create_row() {
var list = ['cell', 'cell', 'cell', 'cell'];
var i = Math.floor(Math.random() * 4);
list[i] = 'cell black';
var newrow = document.createElement('div');
newrow.className="row"
newrow.appendChild(create_cell(list[0]))
newrow.appendChild(create_cell(list[1]))
newrow.appendChild(create_cell(list[2]))
newrow.appendChild(create_cell(list[3]))
document.getElementById("con").appendChild(newrow)
}
function create_cell(classname) {
var newcell= document.createElement('div')
newcell.className=`${classname}`
return newcell
}
实现了这些东西东西之后,我们就能方便的考虑怎么开始这个游戏了
我们可以创建一个start
方法,这个方法需要先生成几行格子,并且在div #main
中添加时间监听,利用事件委托判断点击的是白块还是黑块:
function start(){
document.getElementById("start").style.display="none"
for(var i=0;i<5;i++){
//如果前面格子高度改了,就对应改这里
create_row()
}
document.getElementById('main').onclick = function (ev) {
ev = ev || event;
console.log(ev.target.classList);
//先打印出class留着备用
};
}
4.添加点击事件
到了这里,我们基本的框架都差不多了,我们可以开始完善方块的点击事件,如果点击的是黑块就消除这一行,如果是白块嘛,就GG
我们可以再定义一个函数is_black()
,用来判断点击的是什么
function is_black(classList){
if(classList.value.indexOf("black")!=-1){
return true
}
return false
}
那么我们可以修改一下start
:
function start() {
document.getElementById("start").style.display = "none"
for (var i = 0; i < 5; i++) {
create_row()
}
document.getElementById('main').onclick = function(ev) {
ev = ev || event;
if (is_black(ev.target.classList)) {
ev.path[2].removeChild(ev.path[1])
//这里path[1]是当前这一行,path[2]是整个div#con
//然后从DOM删除这一行
} else {
GG();
//失败了,执行的方法
};
};
}
好!现在很有那个味道了
然后就是,点一个块,他就没了,我们得再生成一个
so easy!我们只用在刚刚的删除DOM后面加一个create_row()
就好了
真是阶段性的胜利!
5.添加游戏失败和成功事件
随后GG()
这个游戏失败的方法就很好写了:
黑白块点到错误的格子,会有变红变白的动画,咱们也要有!
@keyframes flash
{
0% {background: red;}
50% {background: white;}
100% {background: red;}
}
.error_flash{
animation: flash 1s;
animation-iteration-count: infinite;
}
不熟悉css动画的朋友记得去学一学哦
然后我们只要白块的点击事件给她加一个css就好了:
function GG(eve) {
eve.className += " error_flash"
setTimeout(function() {
alert("输啦");
document.getElementById("start").style.display = "";
document.getElementById("con").innerHTML = ""
}, 3000)
}
当然,在前面的调用GG()
里,我们要把ev.target
作为参数传过来
那怎么实现游戏结束呢?我们定义一个总长度就好了嘛,当黑块总数到这个长度了,也不再继续生成了,当所有黑块都点完了,就算赢
我们来总结一下源码
》》基础版源码
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
}
#con {
display: flex;
flex-direction: column-reverse;
/*这样就能让块自动往下掉*/
justify-content: center
}
.row {
display: flex;
width: 99%;
height: 19vh;
/*这里的高度并不一定是20%,
我只是想一页有五个,
如果改小了记得在init方法里把初始的格子的循环多来几次*/
max-width: 750px;
/*设置一个最大值,要不电脑端太宽了丑死了*/
}
.cell {
width: 25%;
height: 99%;
border-left: 1px solid #CCC;
}
.black {
background-color: #000;
border: 1px solid #CCC;
}
#start {
width: 100%;
height: 100%;
border: 0;
background-color: #000;
color: #FFF;
font-size: 25px;
}
@keyframes flash {
0% {
background: red;
}
50% {
background: white;
}
100% {
background: red;
}
}
.error_flash {
animation: flash 1s;
animation-iteration-count: infinite;
}
</style>
<meta charset="utf-8" />
<title>别踩白块</title>
<link rel="stylesheet" href="css/index.css" />
</head>
<body>
<button id="start" onclick="start()">开始游戏</button>
<div id="main">
<div id="con">
</div>
</div>
</body>
</html>
<script type="text/javascript">
let allNum=10,index=0
function create_row() {
index++
var list = ['cell', 'cell', 'cell', 'cell'];
var i = Math.floor(Math.random() * 4);
if(index <= allNum){
list[i] = 'cell black';}
var newrow = document.createElement('div');
newrow.className = "row"
newrow.appendChild(create_cell(list[0]))
newrow.appendChild(create_cell(list[1]))
newrow.appendChild(create_cell(list[2]))
newrow.appendChild(create_cell(list[3]))
document.getElementById("con").appendChild(newrow)
}
function create_cell(classname) {
var newcell = document.createElement('div')
newcell.className = `${classname}`
return newcell
}
function start() {
index=0
document.getElementById("start").style.display = "none"
for (var i = 0; i < 6; i++) {
create_row()
}
scroll(0,document.body.scrollHeight)
document.getElementById('main').onclick = function(ev) {
ev = ev || event;
if (is_black(ev.target.classList)) {
create_row()
ev.path[2].removeChild(ev.path[1])
if(index>=allNum+6){
alert("win")
}
} else {
GG(ev.target);
};
};
}
function is_black(classList) {
if (classList.value.indexOf("black") != -1) {
return true
}
return false
}
function GG(eve) {
eve.className += " error_flash"
setTimeout(function() {
alert("输啦");
document.getElementById("start").style.display = "";
document.getElementById("con").innerHTML = ""
}, 3000)
}
</script>
到这里,其实主要功能已经结束了
花活1:添加动画
接下来我们来玩一点花活,比如让方块的消失有一点动态效果
我们可以利用css过渡,让方块的高度变成0,然后删除dom
先给*
添加transition
:
* {
margin: 0;
padding: 0;
transition: all 0.5s;
}
start
方法变为
function start() {
index=0
document.getElementById("start").style.display = "none"
for (var i = 0; i < 6; i++) {
create_row()
}
scroll(0,document.body.scrollHeight)
document.getElementById('main').onclick = function(ev) {
ev = ev || event;
if (is_black(ev.target.classList)) {
create_row()
ev.path[1].style.height="0px"
//高度变为0
setTimeout(function(){
ev.path[2].removeChild(ev.path[1])
},500)
//0.5s后删除块,
if(index>=allNum+6){
setTimeout(function(){
alert("win")
},500)
}
} else {
GG(ev.target);
};
};
}
好!看着真棒
那我们来继续搞一搞花活吧
首先,我们添加一个计时器!
我们添加一个gametime
的全局变量,在开始游戏是赋值计时器,游戏结束停止计时,看上去完美极了:
/*对应CSS*/
.time_area{
position:fixed;
left: 50%;
top:10%;
font-size: 22px;
}
//全局添加
let gametime = null,
time = 0
//start()变为
function start() {
index = 0
gametime = setInterval(() => {
time++
document.getElementById("time_area").innerHTML=`${Math.floor(time/60)}"${time - 60*Math.floor(time/60)}`
}, 50)
document.getElementById("start").style.display = "none"
for (var i = 0; i < 6; i++) {
create_row()
}
scroll(0, document.body.scrollHeight)
document.getElementById('main').onclick = function(ev) {
ev = ev || event;
if (is_black(ev.target.classList)) {
create_row()
ev.path[1].style.height = "0px"
setTimeout(function() {
ev.path[2].removeChild(ev.path[1])
}, 500)
if (index >= allNum + 6) {
setTimeout(function() {
alert("win");
clearInterval(gametime)
}, 500)
}
} else {
GG(ev.target);
};
};
}
//GG()变为
function GG(eve) {
clearInterval(gametime)
eve.className += " error_flash"
setTimeout(function() {
alert("输啦");
document.getElementById("start").style.display = "";
document.getElementById("con").innerHTML = ""
}, 2000)
}
欧吼吼吼吼看着真不错
这是目前为止,实现了所有功能的源码:
》》有动画版源码
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
transition: all 0.5s;
}
#con {
display: flex;
flex-direction: column-reverse;
/*这样就能让块自动往下掉*/
justify-content: center
}
.row {
display: flex;
width: 99%;
height: 19vh;
/*这里的高度并不一定是20%,
我只是想一页有五个,
如果改小了记得在init方法里把初始的格子的循环多来几次*/
max-width: 750px;
/*设置一个最大值,要不电脑端太宽了丑死了*/
}
.cell {
width: 25%;
height: 99%;
border-left: 1px solid #CCC;
}
.black {
background-color: #000;
border: 1px solid #CCC;
}
#start {
width: 100%;
height: 100%;
border: 0;
background-color: #000;
color: #FFF;
font-size: 25px;
}
@keyframes flash {
0% {
background: red;
}
50% {
background: white;
}
100% {
background: red;
}
}
.error_flash {
animation: flash 1s;
animation-iteration-count: infinite;
}
.time_area{
position:fixed;
left: 50%;
top:10%;
font-size: 22px;
}
</style>
<meta charset="utf-8" />
<title>别踩白块</title>
<link rel="stylesheet" href="css/index.css" />
</head>
<body>
<button id="start" onclick="start()">开始游戏</button>
<div id="main">
<div id="con">
</div>
</div>
<div class="time_area" id="time_area"></div>
</body>
</html>
<script type="text/javascript">
let allNum = 10,
index = 0
let gametime = null,
time = 0
function create_row() {
index++
var list = ['cell', 'cell', 'cell', 'cell'];
var i = Math.floor(Math.random() * 4);
if (index <= allNum) {
list[i] = 'cell black';
}
var newrow = document.createElement('div');
newrow.className = "row"
newrow.appendChild(create_cell(list[0]))
newrow.appendChild(create_cell(list[1]))
newrow.appendChild(create_cell(list[2]))
newrow.appendChild(create_cell(list[3]))
document.getElementById("con").appendChild(newrow)
}
function create_cell(classname) {
var newcell = document.createElement('div')
newcell.className = `${classname}`
return newcell
}
function start() {
index = 0
gametime = setInterval(() => {
time++
document.getElementById("time_area").innerHTML=`${Math.floor(time/60)}"${time - 60*Math.floor(time/60)}`
}, 50)
document.getElementById("start").style.display = "none"
for (var i = 0; i < 6; i++) {
create_row()
}
scroll(0, document.body.scrollHeight)
document.getElementById('main').onclick = function(ev) {
ev = ev || event;
if (is_black(ev.target.classList)) {
create_row()
ev.path[1].style.height = "0px"
setTimeout(function() {
ev.path[2].removeChild(ev.path[1])
}, 500)
if (index >= allNum + 6) {
setTimeout(function() {
alert("win");
clearInterval(gametime)
}, 500)
}
} else {
GG(ev.target);
};
};
}
function is_black(classList) {
if (classList.value.indexOf("black") != -1) {
return true
}
return false
}
function GG(eve) {
clearInterval(gametime)
eve.className += " error_flash"
setTimeout(function() {
alert("输啦");
document.getElementById("start").style.display = "";
document.getElementById("con").innerHTML = ""
}, 2000)
}
</script>
花活2:添加音乐
我们知道,钢琴块点击是会播放声音的
我们也要!
上一个文章,我们介绍了AudioContext
捞一下:做个小钢琴~利用AudioContext获取振荡器并封装成光遇钢琴的样子
我们可以用利用数组,储存一首歌的旋律的音符的频率
用来生成黑块,点击黑块就播放这个声音
这里直接引用一段上面文章的代码
var son1=new AudioContext();
//创建AudioContext
var osc = son1.createOscillator()
//创建音频振荡器
var g = son1.createGain()
//获得音量控制器Gain
osc.connect(g)
//振荡器连接Gain
osc.type = 'sine'
//设置波形
osc.frequency.value = 440
//设置频率为440Hz,即中音la
g.connect(son1.destination)
//连接扬声器
g.gain.value = 1
//初始音高为1
osc.start();
//从时间轴的此时此刻当前开始发生
var stoptime = 1
osc.stop(stoptime);
这个可以播放一个一秒长的中音la
我们把他封装成一个函数:
var ctx
var sounds=[130,147,165,175,196,220,246,262,294,330,349,392,440,494,523,587,659,698,784,880,988,1047]
function setContent(){
if(!ctx){
ctx = new AudioContext()
}
}
function makeSound(hz){
// 获得音频上下文
setContent();
//得到音频振荡器
var osc = ctx.createOscillator();
//得到音量控制对象
var g = ctx.createGain();
// 连接振荡器和音量控制对象
osc.connect(g);
osc.type = 'sine';
osc.frequency.value = hz;
var duration = 1;
g.connect(ctx.destination);
g.gain.value = 0;
osc.start();
g.gain.linearRampToValueAtTime(0.6,ctx.currentTime + 0.01)
osc.stop(ctx.currentTime + duration);
g.gain.exponentialRampToValueAtTime(0.01 , ctx.currentTime + duration)
}
在这里补充一下乐理基础:
- 频率为波形的一个最小重复单元的长度,单位为Hz
- 第一国际音高为机械波440Hz,对应中音la,高音la为440折半,220,低音la为880(这里的高和低特指的是高八度和低八度),不过纯数字算出来的频率会让人听上去音调偏低
- 按照第一国际音高,从低8dao,到高8dao的频率约等于[130,147,165,175,196,220,246,262,294,330,349,392,440,494,523,587,659,698,784,880,988,1047](加粗标出了基准点)
这里就给出最终版的源码了
》》能演奏的源码
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
transition: all 0.5s;
}
#con {
display: flex;
flex-direction: column-reverse;
/*这样就能让块自动往下掉*/
justify-content: center
}
.row {
display: flex;
width: 99%;
height: 19vh;
/*这里的高度并不一定是20%,
我只是想一页有五个,
如果改小了记得在init方法里把初始的格子的循环多来几次*/
max-width: 750px;
/*设置一个最大值,要不电脑端太宽了丑死了*/
}
.cell {
width: 25%;
height: 99%;
border-left: 1px solid #CCC;
}
.black {
background-color: #000;
border: 1px solid #CCC;
}
#start {
width: 100%;
height: 100%;
border: 0;
background-color: #000;
color: #FFF;
font-size: 25px;
}
@keyframes flash {
0% {
background: red;
}
50% {
background: white;
}
100% {
background: red;
}
}
.error_flash {
animation: flash 1s;
animation-iteration-count: infinite;
}
.time_area{
position:fixed;
left: 50%;
top:10%;
font-size: 22px;
}
</style>
<meta charset="utf-8" />
<title>别踩白块</title>
<link rel="stylesheet" href="css/index.css" />
</head>
<body>
<button id="start" onclick="start()">开始游戏</button>
<div id="main">
<div id="con">
</div>
</div>
<div class="time_area" id="time_area"></div>
</body>
</html>
<script type="text/javascript">
let song=[0,0,2,0,0,2]
//↑谱子
var allNum = song.length,
index = 0,
create_index=0
let gametime = null,
time = 0
let ctx
var sounds=[130,147,165,175,196,220,246,262,294,330,349,392,440,494,523,587,659,698,784,880,988,1047]
function create_row() {
index++
var list = ['cell', 'cell', 'cell', 'cell'];
var i = Math.floor(Math.random() * 4);
if (index <= allNum) {
list[i] = 'cell black';
}
var newrow = document.createElement('div');
newrow.className = "row"
newrow.appendChild(create_cell(list[0]))
newrow.appendChild(create_cell(list[1]))
newrow.appendChild(create_cell(list[2]))
newrow.appendChild(create_cell(list[3]))
document.getElementById("con").appendChild(newrow)
}
function create_cell(classname) {
var newcell = document.createElement('div')
newcell.className = `${classname}`
return newcell
}
function start() {
index = 0
gametime = setInterval(() => {
time++
document.getElementById("time_area").innerHTML=`${Math.floor(time/60)}"${time - 60*Math.floor(time/60)}`
}, 50)
document.getElementById("start").style.display = "none"
for (var i = 0; i < 6; i++) {
create_row()
}
scroll(0, document.body.scrollHeight)
document.getElementById('main').onclick = function(ev) {
ev = ev || event;
if (is_black(ev.target.classList)) {
create_row()
makeSound(song[create_index])
create_index++
ev.path[1].style.height = "0px"
setTimeout(function() {
ev.path[2].removeChild(ev.path[1])
}, 500)
if (index >= allNum + 6) {
setTimeout(function() {
alert("win");
clearInterval(gametime)
}, 500)
}
} else {
GG(ev.target);
};
};
}
function is_black(classList) {
if (classList.value.indexOf("black") != -1) {
return true
}
return false
}
function GG(eve) {
clearInterval(gametime)
if(eve.className!="cell")return
eve.className += " error_flash"
document.getElementById('main').onclick=null
setTimeout(function() {
alert("输啦");
document.getElementById("start").style.display = "";
document.getElementById("con").innerHTML = ""
}, 2000)
}
function setContent(){
if(!ctx){
ctx = new AudioContext()
}
}
function makeSound(index){
// 获得音频上下文
setContent();
//得到音频振荡器
var osc = ctx.createOscillator();
//得到音量控制对象
var g = ctx.createGain();
// 连接振荡器和音量控制对象
osc.connect(g);
osc.type = 'sine';
osc.frequency.value = sounds[index];
var duration = 1;
g.connect(ctx.destination);
g.gain.value = 0;
osc.start();
g.gain.linearRampToValueAtTime(0.6,ctx.currentTime + 0.01)
osc.stop(ctx.currentTime + duration);
g.gain.exponentialRampToValueAtTime(0.01 , ctx.currentTime + duration)
}
</script>
let song=[0,0,2,0,0,2]
这一行是谱子
从0到20分别是从低音dao到高音si
[0,0,2,0,0,2]其实是葫芦娃的不知道家人们听出来没(乐
把葫芦娃完整地附上吧
[0, 0, 2, 0, 0, 2, 5, 5, 5, 4, 5, 4, 0, 2]