大家都玩过弹球消砖块游戏,左右键控制最底端的一个小木板平移,接住掉落的小球,将球弹起后消除画面上方的一堆砖块。
那么用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 实现桌面弹球消砖块小游戏的示例代码的详细内容...