好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

VUE+Canvas 实现桌面弹球消砖块小游戏的示例代码

大家都玩过弹球消砖块游戏,左右键控制最底端的一个小木板平移,接住掉落的小球,将球弹起后消除画面上方的一堆砖块。

那么用VUE+Canvas如何来实现呢?实现思路很简单,首先来拆分一下要画在画布上的内容:

(1)用键盘左右按键控制平移的木板;

(2)在画布内四处弹跳的小球;

(3)固定在画面上方,并且被球碰撞后就消失的一堆砖块。

将上述三种对象,用requestAnimationFrame()函数平移运动起来,再结合各种碰撞检查,就可以得到最终的结果。

先看看最终的效果:

一、左右平移的木板

最底部的木板是最简单的一部分,因为木板的y坐标是固定的,我们设置木板的初始参数,包括其宽度,高度,平移速度等,然后实现画木板的函数:

?

pannel: {

         x: 0,

         y: 0,

         height: 8,

         width: 100,

         speed: 8,

         dx: 0

},

 

....

 

drawPannel() {

       this .drawRoundRect(

         this .pannel.x,

         this .pannel.y,

         this .pannel.width,

         this .pannel.height,

       );

},

drawRoundRect(x, y, width, height, radius) { // 画圆角矩形

       this .ctx.beginPath();

       this .ctx.arc(x + radius, y + radius, radius, Math.PI, (Math.PI * 3) / 2);

       this .ctx.lineTo(width - radius + x, y);

       this .ctx.arc(

         width - radius + x,

         radius + y,

         radius,

         (Math.PI * 3) / 2,

         Math.PI * 2

       );

       this .ctx.lineTo(width + x, height + y - radius);

       this .ctx.arc(

         width - radius + x,

         height - radius + y,

         radius,

         0,

         (Math.PI * 1) / 2

       );

       this .ctx.lineTo(radius + x, height + y);

       this .ctx.arc(

         radius + x,

         height - radius + y,

         radius,

         (Math.PI * 1) / 2,

         Math.PI

       );

       this .ctx.fillStyle = "#008b8b" ;

       this .ctx.fill();

       this .ctx.closePath();

}

程序初始化的时候,监听键盘的左右方向键,来移动木板,通过长度判断是否移动到了左右边界使其不能继续移出画面:

?

document.onkeydown = function (e) {

       let key = window.event.keyCode;

       if (key === 37) {

         // 左键

         _this.pannel.dx = -_this.pannel.speed;

       } else if (key === 39) {

         // 右键

         _this.pannel.dx = _this.pannel.speed;

       }

};

document.onkeyup = function (e) {

       _this.pannel.dx = 0;

};

....

 

  movePannel() {

       this .pannel.x += this .pannel.dx;

       if ( this .pannel.x > this .clientWidth - this .pannel.width) {

         this .pannel.x = this .clientWidth - this .pannel.width;

       } else if ( this .pannel.x < 0) {

         this .pannel.x = 0;

       }

},

二、弹跳的小球和碰撞检测

小球的运动和木板类似,只是不仅有dx的偏移,还有dy的偏移。

而且还要有碰撞检测:

(1)当碰撞的是上、右、左墙壁以及木板上的时候则反弹;

(2)当碰撞到是木板以外的下边界的时候,则输掉游戏;

(3)当碰撞的是砖块的时候,被碰的砖块消失,分数+1,小球反弹。

于是和木板一样,将小球部分分为画小球函数drawBall()和小球运动函数moveBall():

?

drawBall() {

       this .ctx.beginPath();

       this .ctx.arc( this .ball.x, this .ball.y, this .ball.r, 0, 2 * Math.PI);

       this .ctx.fillStyle = "#008b8b" ;

       this .ctx.fill();

       this .ctx.closePath();

},

moveBall() {

       this .ball.x += this .ball.dx;

       this .ball.y += this .ball.dy;

       this .breaksHandle();

       this .edgeHandle();

},

breaksHandle() {

       // 触碰砖块检测

       this .breaks.forEach(item => {

         if (item.show) {

           if (

             this .ball.x + this .ball.r > item.x &&

             this .ball.x - this .ball.r < item.x + this .breaksConfig.width &&

             this .ball.y + this .ball.r > item.y &&

             this .ball.y - this .ball.r < item.y + this .breaksConfig.height

           ) {

             item.show = false ;

             this .ball.dy *= -1;

             this .score ++ ;

             if ( this .showBreaksCount === 0){

               this .gameOver = true ;

             }

           }

         }

       });

},

edgeHandle() {

       // 边缘检测

       // 碰到顶部反弹

       if ( this .ball.y - this .ball.r < 0) {

         this .ball.dy = - this .ball.dy;

       }

       if (

         // 碰到左右墙壁

         this .ball.x - this .ball.r < 0 ||

         this .ball.x + this .ball.r > this .clientWidth

       ) {

         this .ball.dx = - this .ball.dx;

       }

       if (

         this .ball.x >= this .pannel.x &&

         this .ball.x <= this .pannel.x + this .pannel.width &&

         this .ball.y + this .ball.r >= this .clientHeight - this .pannel.height

       ) {

         // 球的x在板子范围内并触碰到了板子

         this .ball.dy *= -1;

       } else if (

         ( this .ball.x < this .pannel.x ||

           this .ball.x > this .pannel.x + this .pannel.width) &&

         this .ball.y + this .ball.r >= this .clientHeight

       ) {

         // 球碰到了底边缘了

         this .gameOver = true ;

         this .getCurshBreaks();

       }

}

三、砖块的生成

砖块的生成也比较简单,这里我们初始了一些数据:

?

breaksConfig: {

         row: 6, // 排数

         height: 25, // 砖块高度

         width: 130, // 砖块宽度

         radius: 5, // 矩形圆角

         space: 0, // 间距

         colunm: 6 // 列数

}

根据这些配置项以及画布宽度,我们可以计算出每个砖块的横向间隙是多少:

?

// 计算得出砖块缝隙宽度

       this .breaksConfig.space = Math.floor(

         ( this .clientWidth -

           this .breaksConfig.width * this .breaksConfig.colunm) /

           ( this .breaksConfig.colunm + 1)

       );

于是我们可以得到每个砖块在画布中的x,y坐标(指的砖块左上角的坐标)

?

for (let i = 0; i < _this.breaksConfig.row; i++) {

         for (let j = 0; j < _this.breaksConfig.colunm; j++) {

           _this.breaks.push({

             x: this .breaksConfig.space * (j + 1) + this .breaksConfig.width * j,

             y: 10 * (i + 1) + this .breaksConfig.height * i,

             show: true

           });

         }

       }

再加上绘制砖块的函数:

?

drawBreaks() {

       let _this = this ;

       _this.breaks.forEach(item => {

         if (item.show) {

           _this.drawRoundRect(

             item.x,

             item.y,

             _this.breaksConfig.width,

             _this.breaksConfig.height,

             _this.breaksConfig.radius

           );

         }

       });

}

四、让上面三个部分动起来

?

( function animloop() {

       if (!_this.gameOver) {

         _this.movePannel();

         _this.moveBall();

         _this.drawAll();

       } else {

         _this.drawCrushBreaks();

       }

       window.requestAnimationFrame(animloop);

})();

....

  drawAll() {

       this .ctx.clearRect(0, 0, this .clientWidth, this .clientHeight);

       this .drawPannel();

       this .drawBall();

       this .drawScore();

       this .drawBreaks();

}

五、游戏结束后的效果

在最开始的动图里可以看到,游戏结束后,砖块粉碎成了若干的小球掉落,这个其实和画单独的小球类似,思路就是把剩余的砖块中心坐标处生产若干大小不等,运动轨迹不等,颜色不等的小球,然后继续动画。

?

getCurshBreaks() {

       let _this = this ;

       this .breaks.forEach(item => {

         if (item.show) {

           item.show = false ;

           for (let i = 0; i < 8; i++) { // 每个砖块粉碎为8个小球

             this .crushBalls.push({

               x: item.x + this .breaksConfig.width / 2,

               y: item.y + this .breaksConfig.height / 2,

               dx: _this.getRandomArbitrary(-6, 6),

               dy: _this.getRandomArbitrary(-6, 6),

               r: _this.getRandomArbitrary(1, 4),

               color: _this.getRandomColor()

             });

           }

         }

       });

},

drawCrushBreaks() {

       this .ctx.clearRect(0, 0, this .clientWidth, this .clientHeight);

       this .crushBalls.forEach(item => {

         this .ctx.beginPath();

         this .ctx.arc(item.x, item.y, item.r, 0, 2 * Math.PI);

         this .ctx.fillStyle = item.color;

         this .ctx.fill();

         this .ctx.closePath();

         item.x += item.dx;

         item.y += item.dy;

         if (

           // 碰到左右墙壁

           item.x - item.r < 0 ||

           item.x + item.r > this .clientWidth

         ) {

           item.dx = -item.dx;

         }

         if (

           // 碰到上下墙壁

           item.y - item.r < 0 ||

           item.y + item.r > this .clientHeight

         ) {

           item.dy = -item.dy;

         }

       });

},

以上就是桌面弹球消砖块小游戏的实现思路和部分代码,实现起来很简单,两三百行代码就可以实现这个小游戏。在小球的运动上可以进行持续优化,并且也可以增加难度选项操作。

最后附上全部的vue文件代码,供大家参考学习:

?

<template>

   <div class= "break-ball" >

     <canvas id= "breakBall" width= "900" height= "600" ></canvas>

     <div class= "container" v- if = "gameOver" >

       <div class= "dialog" >

         <p class= "once-again" >本轮分数:{{score}}分</p>

         <p class= "once-again" >真好玩!</p>

         <p class= "once-again" >再来一次~~</p>

         <el-button class= "once-again-btn" @click= "init" >开始</el-button>

       </div>

     </div>

   </div>

</template>

 

<script>

const randomColor = [

   "#FF95CA" ,

   "#00E3E3" ,

   "#00E3E3" ,

   "#6F00D2" ,

   "#6F00D2" ,

   "#C2C287" ,

   "#ECFFFF" ,

   "#FFDC35" ,

   "#93FF93" ,

   "#d0d0d0"

];

export default {

   name: "BreakBall" ,

   data() {

     return {

       clientWidth: 0,

       clientHeight: 0,

       ctx: null ,

       crushBalls: [],

       pannel: {

         x: 0,

         y: 0,

         height: 8,

         width: 100,

         speed: 8,

         dx: 0

       },

       ball: {

         x: 0,

         y: 0,

         r: 8,

         dx: -4,

         dy: -4

       },

       score: 0,

       gameOver: false ,

       breaks: [],

       breaksConfig: {

         row: 6, // 排数

         height: 25, // 砖块高度

         width: 130, // 砖块宽度

         radius: 5, // 矩形圆角

         space: 0, // 间距

         colunm: 6 // 列数

       }

     };

   },

   mounted() {

     let _this = this ;

     let container = document.getElementById( "breakBall" );

     this .ctx = container.getContext( "2d" );

     this .clientHeight = container.height;

     this .clientWidth = container.width;

     _this.init();

     document.onkeydown = function (e) {

       let key = window.event.keyCode;

       if (key === 37) {

         // 左键

         _this.pannel.dx = -_this.pannel.speed;

       } else if (key === 39) {

         // 右键

         _this.pannel.dx = _this.pannel.speed;

       }

     };

     document.onkeyup = function (e) {

       _this.pannel.dx = 0;

     };

     ( function animloop() {

       if (!_this.gameOver) {

         _this.movePannel();

         _this.moveBall();

         _this.drawAll();

       } else {

         _this.drawCrushBreaks();

       }

       window.requestAnimationFrame(animloop);

     })();

   },

   computed:{

     showBreaksCount(){

       return this .breaks.filter(item=>{

         return item.show;

       }).length;

     }

   },

   methods: {

     init() {

       let _this = this ;

       _this.gameOver = false ;

       this .pannel.y = this .clientHeight - this .pannel.height;

       this .pannel.x = this .clientWidth / 2 - this .pannel.width / 2;

       this .ball.y = this .clientHeight / 2;

       this .ball.x = this .clientWidth / 2;

       this .score = 0;

       this .ball.dx = [-1,1][Math.floor(Math.random() * 2)]*4;

       this .ball.dy = [-1,1][Math.floor(Math.random() * 2)]*4;

       this .crushBalls = [];

       this .breaks = [];

       // 计算得出砖块缝隙宽度

       this .breaksConfig.space = Math.floor(

         ( this .clientWidth -

           this .breaksConfig.width * this .breaksConfig.colunm) /

           ( this .breaksConfig.colunm + 1)

       );

 

       for (let i = 0; i < _this.breaksConfig.row; i++) {

         for (let j = 0; j < _this.breaksConfig.colunm; j++) {

           _this.breaks.push({

             x: this .breaksConfig.space * (j + 1) + this .breaksConfig.width * j,

             y: 10 * (i + 1) + this .breaksConfig.height * i,

             show: true

           });

         }

       }

     },

     drawAll() {

       this .ctx.clearRect(0, 0, this .clientWidth, this .clientHeight);

       this .drawPannel();

       this .drawBall();

       this .drawScore();

       this .drawBreaks();

     },

     movePannel() {

       this .pannel.x += this .pannel.dx;

       if ( this .pannel.x > this .clientWidth - this .pannel.width) {

         this .pannel.x = this .clientWidth - this .pannel.width;

       } else if ( this .pannel.x < 0) {

         this .pannel.x = 0;

       }

     },

     moveBall() {

       this .ball.x += this .ball.dx;

       this .ball.y += this .ball.dy;

       this .breaksHandle();

       this .edgeHandle();

     },

     breaksHandle() {

       // 触碰砖块检测

       this .breaks.forEach(item => {

         if (item.show) {

           if (

             this .ball.x + this .ball.r > item.x &&

             this .ball.x - this .ball.r < item.x + this .breaksConfig.width &&

             this .ball.y + this .ball.r > item.y &&

             this .ball.y - this .ball.r < item.y + this .breaksConfig.height

           ) {

             item.show = false ;

             this .ball.dy *= -1;

             this .score ++ ;

             if ( this .showBreaksCount === 0){

               this .gameOver = true ;

             }

           }

         }

       });

     },

     edgeHandle() {

       // 边缘检测

       // 碰到顶部反弹

       if ( this .ball.y - this .ball.r < 0) {

         this .ball.dy = - this .ball.dy;

       }

       if (

         // 碰到左右墙壁

         this .ball.x - this .ball.r < 0 ||

         this .ball.x + this .ball.r > this .clientWidth

       ) {

         this .ball.dx = - this .ball.dx;

       }

       if (

         this .ball.x >= this .pannel.x &&

         this .ball.x <= this .pannel.x + this .pannel.width &&

         this .ball.y + this .ball.r >= this .clientHeight - this .pannel.height

       ) {

         // 球的x在板子范围内并触碰到了板子

         this .ball.dy *= -1;

       } else if (

         ( this .ball.x < this .pannel.x ||

           this .ball.x > this .pannel.x + this .pannel.width) &&

         this .ball.y + this .ball.r >= this .clientHeight

       ) {

         // 球碰到了底边缘了

         this .gameOver = true ;

         this .getCurshBreaks();

       }

     },

     drawScore(){

       this .ctx.beginPath();

       this .ctx.font= "14px Arial" ;

       this .ctx.fillStyle = "#FFF" ;

       this .ctx.fillText( "分数:" + this .score,10, this .clientHeight-14);

       this .ctx.closePath();

     },

     drawCrushBreaks() {

       this .ctx.clearRect(0, 0, this .clientWidth, this .clientHeight);

       this .crushBalls.forEach(item => {

         this .ctx.beginPath();

         this .ctx.arc(item.x, item.y, item.r, 0, 2 * Math.PI);

         this .ctx.fillStyle = item.color;

         this .ctx.fill();

         this .ctx.closePath();

         item.x += item.dx;

         item.y += item.dy;

         if (

           // 碰到左右墙壁

           item.x - item.r < 0 ||

           item.x + item.r > this .clientWidth

         ) {

           item.dx = -item.dx;

         }

         if (

           // 碰到上下墙壁

           item.y - item.r < 0 ||

           item.y + item.r > this .clientHeight

         ) {

           item.dy = -item.dy;

         }

       });

     },

     getRandomColor() {

       return randomColor[Math.floor(Math.random() * randomColor.length)];

     },

     getRandomArbitrary(min, max) {

       return Math.random() * (max - min) + min;

     },

     getCurshBreaks() {

       let _this = this ;

       this .breaks.forEach(item => {

         if (item.show) {

           item.show = false ;

           for (let i = 0; i < 8; i++) {

             this .crushBalls.push({

               x: item.x + this .breaksConfig.width / 2,

               y: item.y + this .breaksConfig.height / 2,

               dx: _this.getRandomArbitrary(-6, 6),

               dy: _this.getRandomArbitrary(-6, 6),

               r: _this.getRandomArbitrary(1, 4),

               color: _this.getRandomColor()

             });

           }

         }

       });

     },

     drawBall() {

       this .ctx.beginPath();

       this .ctx.arc( this .ball.x, this .ball.y, this .ball.r, 0, 2 * Math.PI);

       this .ctx.fillStyle = "#008b8b" ;

       this .ctx.fill();

       this .ctx.closePath();

     },

     drawPannel() {

       this .drawRoundRect(

         this .pannel.x,

         this .pannel.y,

         this .pannel.width,

         this .pannel.height,

       );

     },

     drawRoundRect(x, y, width, height, radius) {

       this .ctx.beginPath();

       this .ctx.arc(x + radius, y + radius, radius, Math.PI, (Math.PI * 3) / 2);

       this .ctx.lineTo(width - radius + x, y);

       this .ctx.arc(

         width - radius + x,

         radius + y,

         radius,

         (Math.PI * 3) / 2,

         Math.PI * 2

       );

       this .ctx.lineTo(width + x, height + y - radius);

       this .ctx.arc(

         width - radius + x,

         height - radius + y,

         radius,

         0,

         (Math.PI * 1) / 2

       );

       this .ctx.lineTo(radius + x, height + y);

       this .ctx.arc(

         radius + x,

         height - radius + y,

         radius,

         (Math.PI * 1) / 2,

         Math.PI

       );

       this .ctx.fillStyle = "#008b8b" ;

       this .ctx.fill();

       this .ctx.closePath();

     },

     drawBreaks() {

       let _this = this ;

       _this.breaks.forEach(item => {

         if (item.show) {

           _this.drawRoundRect(

             item.x,

             item.y,

             _this.breaksConfig.width,

             _this.breaksConfig.height,

             _this.breaksConfig.radius

           );

         }

       });

     }

   }

};

</script>

 

<!-- Add "scoped" attribute to limit CSS to this component only -->

<style scoped lang= "scss" >

. break -ball {

   width: 900px;

   height: 600px;

   position: relative;

   #breakBall {

     background: #2a4546;

   }

   .container {

     position: absolute;

     top: 0;

     right: 0;

     bottom: 0;

     left: 0;

     background-color: rgba(0, 0, 0, 0.3);

     text-align: center;

     font-size: 0;

     white-space: nowrap;

     overflow: auto;

   }

   .container:after {

     content: "" ;

     display: inline-block;

     height: 100%;

     vertical-align: middle;

   }

   .dialog {

     width: 400px;

     height: 300px;

     background: rgba(255, 255, 255, 0.5);

     box-shadow: 3px 3px 6px 3px rgba(0, 0, 0, 0.3);

     display: inline-block;

     vertical-align: middle;

     text-align: left;

     font-size: 28px;

     color: #fff;

     font-weight: 600;

     border-radius: 10px;

     white-space: normal;

     text-align: center;

     .once-again-btn {

       background: #1f9a9a;

       border: none;

       color: #fff;

     }

   }

}

</style>

到此这篇关于VUE+Canvas 实现桌面弹球消砖块小游戏的文章就介绍到这了,更多相关vue弹球消砖块小游戏内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/denglouhen/article/details/115487802

dy("nrwz");

查看更多关于VUE+Canvas 实现桌面弹球消砖块小游戏的示例代码的详细内容...

  阅读:38次