第一次写博客也不知怎么写,反正就按照我自己的想法来吧!怎么说呢?还是不要扯那些多余的话了,直接上正题吧! 第一次用canvas写游戏,所以挑个简单实现点的来干:别踩白块儿,其他那些怎么操作的那些就不用再扯了,大家应该都懂,不懂的看到黑色的就点就是了,扯多了我打字手也累,大概。链接给你们:http://nowtd.cn/white/
咱不是理论派,所以在这里不会扯多少理论。
首先看看html的结构吧
1 <header class="container"> 2 <article class="game-info"> 3 <span class="show sorce rounded">分数:<span class="num">0</span></span> 4 <span class="time">时间: <span class="num">0</span>s</span> 5 </article> 6 </header> 7 <main class="container"> 8 <canvas id="game-body">你的浏览器不支持canvas!!!</canvas> 9 </main> 10 <section class="control container"> 11 <button class="btn rounded" id="game-play">开始</button> 12 <button class="btn rounded" id="game-over">结束</button> 13 </section>
很简单,只有一个canvas和一些按钮 and 用作显示分数那些的span
css的内容就不贴了,反正只是给它作一下简单布局而已,重要的还是js怎么去实现它的。
首先先看看怎么调用吧
1 <script> 2 (function () { 3 let wb = new WhiteBlock({ 4 canvas: ‘#game-body‘, 5 play: ‘#game-play‘, 6 over: ‘#game-over‘, 7 sorce: ‘.sorce > .num‘, 8 time: ‘.time > .num‘, 9 width: 330, 10 height: 450, 11 col: 4, 12 row: 4, 13 }); 14 })(); 15 </script>
就这么点东西:
canvas: 画布,
play: 开始按钮,
over: 结束按钮,
sorce: 显示成绩,
time: 显示时间,
white: 画布的宽,
height: 画布的高,
col: 一列多少个砖块
row: 一共多少行
内部的一些参数
1 this.speedLevel = [4, 5, 6, 7, 8]; 2 3 // 成绩 4 this.sorceNumber = 0; 5 this.Time = 0; 6 7 //定时器 8 this.timer = null; 9 10 // 是否已开始游戏 11 this.isPlay = false; 12 // 是否已结束游戏 13 this.isOver = false; 14 15 // 画布参数 16 this.width = null; 17 this.height = null; 18 this.canvas = null; 19 this.ctx = null; 20 21 // 砖块 22 this.blockQueue = []; 23 this.blockWidth = null; 24 this.blockHeight = null; 25 this.col = 4; 26 this.row = 6; 27 // 速度 28 this.offset = this.speedLevel[0];
这里要说的大概就只有speedLevel和this.blockQueue了:
speedLevel: 输入一个规定速度的数组,然后根据成绩的提升来获取相应的速度值
1 changeSpeed() { 2 if (this.sorceNumber < (this.speedLevel.length * 10)) { 3 let num = Math.floor(this.sorceNumber / 10); 4 if (this.speedLevel[num]) { 5 this.offset = this.speedLevel[num]; 6 } else { 7 this.offset = this.speedLevel[this.speedLevel.length - 1]; 8 } 9 10 } 11 }
当成绩超过一定值之后,就以speedLevel的最后一个值为速度,不再加速。
this.blockQueue:生成一组队列做点阵来绘制砖块,分别由createBlock, addBlock,removeBlock,fillBlock这四个方法来操控,drawing方法会遍历this.blockQueue
1 // 创建砖块 2 createBlock() { 3 let len = this.col, 4 i = 0, 5 list = [], 6 rand = Math.floor(Math.random() * len); // 随机黑块 7 8 for (; i < len; i ++) { 9 list.push(rand === i ? 1 : 0); 10 } 11 return list; 12 } 13 14 // 添加砖块队列 15 addBlock(i) { 16 this.blockQueue.unshift({top: -((i + 1) * this.blockHeight), data: this.createBlock()}); 17 } 18 19 // 移除砖块队列 20 removeBlock() { 21 (this.blockQueue.length > 0) && this.blockQueue.pop(); 22 }29 30 fillBlock() { 31 let len = this.row + 1, // 多加一队,以免刚好一屏 32 i = 0; 33 34 for (; i < len; i ++) { 35 this.addBlock(i); 36 } 37 } 38 39 drawing() { 40 this.ctx.clearRect(0, 0, this.width, this.height); 41 this.each(this.blockQueue, (index, item) => { 42 let top = item[‘top‘], 43 block = item[‘data‘], 44 ctx = this.ctx; 45 46 this.each(block, (index, item) => { 47 ctx.fillStyle = parseInt(item) === 1 ? ‘#000‘ : ‘#fff‘; 48 ctx.beginPath(); 49 ctx.fillRect(index * this.blockWidth, top, this.blockWidth, this.blockHeight); 50 }); 51 }); 52 }
因为个人喜欢从数组第一个值迭代,所以这里把队列反转过来操作了
而每个砖块的宽高都由col和row决定:
1 getBlockWidthAndHeight() { 2 this.blockWidth = this.width / this.col; 3 this.blockHeight = this.height / this.row; 4 return this; 5 }
把砖块绘制好了之后就是要让砖块跑起来了,我让每一队砖块都加一个offset值,this,offset为一个速度值
1 this.offset = this.speedLevel[num];
1 runPlay() { 2 this.each(this.blockQueue, (index, item) => { 3 item[‘top‘] += this.offset; 4 }); 5 this.drawing(); 6 this.timer = setTimeout(this.runPlay.bind(this), 20); 7 // 修改时间 8 this.changeTime(); 9 }
runplay方法设定为20毫秒重绘一次,这个20是随便写上去的,至于是否合理就暂时不管了
点击开始按钮触发事件, 开始游戏
1 playGame(e) { 2 if (this.isOver) { 3 // 重新开始 4 this.replay(); 5 console.log(this); 6 } 7 // 让砖块跑起来 8 if (!this.isPlay) { 9 // 检测是否有黑块到底 10 this.checkState(); 11 // 让砖块跑进来 12 this.runPlay(); 13 this.play.html(‘暂停‘); 14 this.isPlay = true; 15 } else { 16 // 暂停游戏 17 this.puaseGame(); 18 this.play.html(‘继续‘); 19 } 20 }
暂停游戏其实就是清除定时器
1 puaseGame() { 2 clearTimeout(this.timer); 3 this.isPlay = false; 4 }
判断输赢: 怎么个输赢法就不讲了,只说实现。
1 checkState() { 2 let i = this.blockQueue.length - 1, 3 item = this.blockQueue[i], 4 top = item[‘top‘], 5 data = item[‘data‘]; 6 if ((this.height) <= top) { 7 this.checkBlock(data) ? (() => { 8 this.again(); 9 requestAnimationFrame(this.checkState.bind(this)); 10 })() : this.puaseGame(); 12 requestAnimationFrame(this.checkState.bind(this)); 13 } 14 }
每次运行都取出队列中最后的一队,并判断它的top值是否大于或小于画布的高,如果最前面的一队(数组最后一个)的top大于画布高,同时遍历这个队列,如果有一个值为1(黒块),则游戏失败,并调用gameOver方法。
否则,即说明当前队列安全通过,就调用again方法,这个方法里移除队首,并在队尾添加一队,这样不断循环生成砖块。
点击判断输赢,这里有人可能想到了用getImageData来获取颜色值来判断。这样做对于这个只有黒和白的游戏来说的确是很方便的,但是这里我没用获取颜色值来做(最开始是那样打算来着),因为这里不仅要判断输赢,当点击到了黒块之后也要修改黒块的颜色的,最终还是逃不出要判断当前点击的是哪个砖块这一步,所以这里用碰撞检测来做就好了,也节省了一个方法的代码量(但其实在这里一个方法内的代码量也不会很多),下面是代码:
1 isWin(e) { 2 let x = e.offsetX || e.originalEvent.layerX, 3 y = e.offsetY || e.originalEvent.layerY, 4 data = this.blockQueue; 5 6 this.each(data, (index, item) => { 7 let top = item[‘top‘]; 8 if (y > top && y < (data[index + 1] ? data[index + 1][‘top‘] : top + this.blockHeight)) { 9 // 判断点击的列中的黑块是否被点中 10 this.checkCloumn(x, item[‘data‘], index); 11 } 12 }); 13 } 14 15 checkCloumn(x, data, i) { 16 this.each(data, (index, item) => { 17 let left = index * this.blockWidth; 18 19 if (x > left && x < (left + this.blockWidth)) { 20 if (item === 1) { 21 this.blockQueue[i][‘data‘][index] = 0; 22 // 记录成绩 23 this.addSorce(); 24 this.drawing(); 25 } else { 26 this.gameOver(); 27 this.puaseGame(); 28 } 29 } 30 }); 31 }
点击结束按钮就调用一个replay方法,重置那些配置参数
1 replay() { 2 // 清空砖块队列 3 this.blockQueue = []; 4 // 重置数据 5 this.offset = this.speedLevel[0]; 6 this.sorceNumber = 0; 7 this.Time = 0; 8 this.isPlay = false; 9 this.timer = null; 10 this.play.html(‘开始‘); 11 this.sorce.html(0); 12 this.time.html(0); 13 //重新填充队列 14 this.fillBlock(); 15 // 重新维制 16 this.drawing(); 17 18 this.isOver = false; 19 }
接下来成绩和时间那个就简单了,剩下就是处理游戏结束后的消息提示了,由于无论是有黒块到了底下还是点击了白块,都会调用一个gameOver方法,
1 gameOver() { 2 this.isOver = true; 3 this.play.html(‘开始‘); 4 // 显示游戏结束提示 5 this.showMess(); 6 }
我在这个方法里调用了一个showMess方法,而我写到最后不想写这个了,要写的话一开始就会写好这块了,所心只是简单地弹出一个警告框算了
1 showMess() { 2 alert(‘游戏已结束!‘); 3 }
到哪天想要弄个好看点的提示时,只要重写这个方法就好了。
嘛,反正到这里算是结束了。总感觉有点一直在贴代码的感觉,哈哈!!
这是源码地址: https://gitee.com/nowtd/test/tree/master/white
第一次写博客,不清楚写得怎样?各位请多多指教!!!
补充一点: 这个demo引入了JQ,用来获取dom和绑定事件,修改属性那些,但现在感觉好像不引入JQ所需要写的代码也不会很多,不过嘛,能少写就少写吧。