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
©2019-2020 Toolsou All rights reserved,
Hikvision - Embedded software written test questions C Language application 0 The length of array in memory and structure is 0 In depth analysis data structure --- The preorder of binary tree , Middle order , Subsequent traversal How to do it ipad Transfer of medium and super large files to computer elementui Shuttle box el-transfer Display list content text too long 2019 The 10th Blue Bridge Cup C/C++ A Summary after the National Games ( Beijing Tourism summary )unity Shooting games , Implementation of first person camera python of numpy Module detailed explanation and application case Study notes 【STM32】 Digital steering gear Horizontal and vertical linkage pan tilt Vue Used in Element Open for the first time el-dialog Solution for not getting element