We've all played pinball to eliminate bricks , Key control around the bottom of a small board translation , Catch the falling ball , Pop up the ball and remove the pile of bricks at the top of the screen .

Then use it VUE+Canvas How to achieve it ? The idea is very simple , First of all, let's split the content to be painted on the canvas ：

（1） Use the left and right keys of the keyboard to control the translation of the board ;

（2） Small balls bouncing around in the canvas ;

（3） Fixed at the top of the screen , And a pile of bricks that disappear after being hit by the ball .

The above three objects , use requestAnimationFrame() Function translation movement , Combined with all kinds of collision inspection , You can get the final result .

Let's take a look at the final result ：

one , A board that moves left and right

The bottom board is the simplest part , Because it's wooden y The coordinates are fixed , We set the initial parameters of the board , Including its width , height , Translation speed, etc , And then realize the function of drawing the board ：
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, 5 ); }, drawRoundRect(x, y, width,
height, radius) { // round rectangle 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(); }
When the program is initialized , Monitor the left and right direction keys of the keyboard , To move the board , Judge whether it has moved to the left and right boundary through the length, so that it can not continue to move out of the picture ：
document.onkeydown = function(e) { let key = window.event.keyCode; if (key ===
37) { // Left key _this.pannel.dx = -_this.pannel.speed; } else if (key === 39) { //
Right click _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; } },
two , Bouncing ball and collision detection

The movement of the ball is similar to that of the board , It's just that there's more than that dx Offset of , also dy Offset of .

And collision detection ：

（1） When the collision is on , right , On the left wall and on the board, it rebounds ;

（2） When it hits the lower boundary outside the board , You lose the game ;

（3） When it's bricks that collide , The hit brick disappears , fraction +1, The ball rebounded .

So it's like a board , Divide the sphere part into the function of drawing the sphere drawBall() And the ball motion function 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() { //
Touch brick detection 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() { // edge detection // Hit the top and bounce if
(this.ball.y - this.ball.r < 0) { this.ball.dy = -this.ball.dy; } if ( //
Hit the left and right walls 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 ) { //
Ball's x Within the board and touching the board 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 ) { // The ball hit the bottom edge this.gameOver = true;
this.getCurshBreaks(); } }
three , Generation of bricks

The generation of bricks is also relatively simple , Here we start with some data ：
breaksConfig: { row: 6, // Row number height: 25, // Brick height width: 130, // Brick width radius:
5, // Rectangle fillet space: 0, // spacing colunm: 6 // Number of columns }
According to these configuration items and the width of the canvas , We can calculate the lateral clearance of each brick ：
// Calculate the brick gap width this.breaksConfig.space = Math.floor( (this.clientWidth -
this.breaksConfig.width * this.breaksConfig.colunm) / (this.breaksConfig.colunm
+ 1) );
So we can get each brick in the canvas x,y coordinate （ Refers to the coordinates of the upper left corner of the brick ）
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 }); } }
Plus the function of drawing bricks ：
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 ); } }); }
four , Let the top three parts move
(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(); }
five , Effect after the end of the game

You can see it in the first motion picture , After the game , The bricks smashed into balls and fell , This is actually similar to drawing a single ball , The idea is to produce some bricks of different sizes at the center coordinates of the remaining bricks , Unequal trajectories , Balls of different colors , Then continue the animation .
getCurshBreaks() { let _this = this; this.breaks.forEach(item => { if
(item.show) { item.show = false; for (let i = 0; i < 8; i++) { // Each brick is broken into pieces 8 It's a little ball
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 ( // Hit the left and right walls item.x - item.r < 0 || item.x +
item.r > this.clientWidth ) { item.dx = -item.dx; } if ( // Up and down the wall item.y -
item.r < 0 || item.y + item.r > this.clientHeight ) { item.dy = -item.dy; } });
},

The above is the realization idea and part of the code of the desktop Pinball brick elimination game , It's easy to implement , Two or three hundred lines of code can achieve this small game . Continuous optimization can be carried out in the movement of the ball , And you can also increase the difficulty of option operation .

At the end of the paper, all the documents are attached vue Document code , For your reference ：
<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"> Current round score ：{{score}} branch </p> <p
class="once-again"> Funny !</p> <p class="once-again"> Once more ~~</p> <el-button
class="once-again-btn" @click="init"> start </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, // Row number height:
25, // Brick height width: 130, // Brick width radius: 5, // Rectangle fillet space: 0, // spacing colunm: 6 //
Number of columns } }; }, 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) { // Left key _this.pannel.dx =
-_this.pannel.speed; } else if (key === 39) { // Right click _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 =
[]; // Calculate the brick gap width 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() { // Touch brick detection
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() { // edge detection // Hit the top and bounce if (this.ball.y - this.ball.r < 0) {
this.ball.dy = -this.ball.dy; } if ( // Hit the left and right walls 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 ) { // Ball's x Within the board and touching the board 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 ) { //
The ball hit the bottom edge this.gameOver = true; this.getCurshBreaks(); } }, drawScore(){
this.ctx.beginPath(); this.ctx.font="14px Arial"; this.ctx.fillStyle = "#FFF";
this.ctx.fillText(" fraction ："+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 ( // Hit the left and right walls item.x - item.r < 0 || item.x +
item.r > this.clientWidth ) { item.dx = -item.dx; } if ( // Up and down the wall 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, 5 ); }, 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>

Technology
Daily Recommendation