VUE+Canvas implements the sample code of the desktop pinball brick-breaking game

VUE+Canvas implements the sample code of the desktop pinball brick-breaking game

Everyone has played the pinball and brick-breaking game. Use the left and right keys to control the translation of a small wooden board at the bottom to catch the falling ball, and then bounce the ball up to eliminate the pile of bricks at the top of the screen.

So how to achieve it with VUE+Canvas? The idea is very simple. First, let's split the content to be drawn on the canvas:

(1) Use the left and right keys on the keyboard to control the translation of the wooden board;

(2) A small ball bouncing around the canvas;

(3) A pile of bricks that are fixed at the top of the screen and disappear when hit by the ball.

The above three objects can be translated using the requestAnimationFrame() function and combined with various collision checks to get the final result.

Let’s take a look at the final effect first:

1. The wooden board that moves horizontally

The wooden board at the bottom is the simplest part because the y coordinate of the board is fixed. We set the initial parameters of the board, including its width, height, translation speed, etc., and then implement 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) { // Draw a rounded 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, the left and right arrow keys on the keyboard are monitored to move the board, and the length is used to determine whether it has moved to the left or right boundary so that it cannot move out of the screen further:

document.onkeydown = function(e) {
      let key = window.event.keyCode;
      if (key === 37) {
        //Left button_this.pannel.dx = -_this.pannel.speed;
      } else if (key === 39) {
        //Right button_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;
      }
},

2. Bouncing Ball and Collision Detection

The movement of the ball is similar to that of the board, except that there is not only a dx offset, but also a dy offset.

And there must be collision detection:

(1) When colliding with the upper, right, left walls and wooden boards, it will bounce back;

(2) If the collision is to the lower boundary outside the board, the game is lost;

(3) When the ball collides with a brick, the brick disappears, the score increases by 1, and the ball bounces back.

So, just like the wooden board, the ball part is divided into the ball drawing function drawBall() and the ball movement 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() {
      // Brick touch 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 // Bounce when it hits the top 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
      ) {
        // The ball's x is within the range of the board and touches 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 hits the bottom edge this.gameOver = true;
        this.getCurshBreaks();
      }
}

3. Generation of Bricks

The generation of bricks is also relatively simple. Here we initialize some data:

breaksConfig:
        row: 6, // number of rows height: 25, // brick height width: 130, // brick width radius: 5, // rectangle corner rounding space: 0, // spacing colunm: 6 // number of columns }

Based on these configuration items and the canvas width, we can calculate the horizontal gap between each brick:

// Calculate the width of the brick gap this.breaksConfig.space = Math.floor(
        (this.clientWidth -
          this.breaksConfig.width * this.breaksConfig.colunm) /
          (this.breaksConfig.colunm + 1)
      );

So we can get the x,y coordinates of each brick in the canvas (referring 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 for 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
          );
        }
      });
}

4. Make the above 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();
}

5. Effects after the game ends

In the first animated picture, you can see that after the game is over, the bricks are shattered into several small balls that fall down. This is actually similar to drawing individual small balls. The idea is to produce a number of small balls of different sizes, movement trajectories, and colors at the center coordinates of the remaining bricks, and 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 crushed into 8 small balls 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 (
          // Hitting the upper and lower walls item.y - item.r < 0 ||
          item.y + item.r > this.clientHeight
        ) {
          item.dy = -item.dy;
        }
      });
},

The above is the implementation idea and part of the code for the desktop pinball brick-breaking game. It is very simple to implement, and this game can be realized with two or three hundred lines of code. The movement of the ball can be continuously optimized, and the difficulty options can also be increased.

Finally, attach all the vue file codes 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">Score of this round: {{score}} points</p>
        <p class="once-again">It's so fun! </p>
        <p class="once-again">Once again~~</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, // number of rows height: 25, // brick height width: 130, // brick width radius: 5, // rectangle corner rounding 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 button_this.pannel.dx = -_this.pannel.speed;
      } else if (key === 39) {
        //Right button_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 width of the brick gap 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() {
      // Brick touch 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 // Bounce when it hits the top 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
      ) {
        // The ball's x is within the range of the board and touches 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 hits the bottom edge this.gameOver = true;
        this.getCurshBreaks();
      }
    },
    drawScore(){
      this.ctx.beginPath();
      this.ctx.font="14px Arial";
      this.ctx.fillStyle = "#FFF";
      this.ctx.fillText("Score: "+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 (
          // Hitting the upper and lower walls 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>

This is the end of this article about VUE+Canvas to realize the desktop pinball and brick-breaking game. For more related vue pinball and brick-breaking game content, please search 123WORDPRESS.COM's previous articles or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Vue uses Canvas to generate random sized and non-overlapping circles
  • Vue uses canvas to realize image compression upload
  • How to draw the timeline with vue+canvas
  • VUE+Canvas realizes the whole process of a simple Gobang game
  • VUE+Canvas implements the game of God of Wealth receiving ingots
  • How to use VUE and Canvas to implement a Thunder Fighter typing game
  • Vue uses the mouse to draw a rectangle on Canvas
  • Vue uses canvas to implement mobile handwritten signature
  • Vue+canvas realizes puzzle game
  • Vue uses canvas handwriting input to recognize Chinese

<<:  Summary of common functions of PostgreSQL regular expressions

>>:  Detailed explanation of FTP environment configuration solution (vsftpd)

Recommend

Share the 15 best HTML/CSS design and development frameworks

Professional web design is complex and time-consu...

JS function call, apply and bind super detailed method

Table of contents JS function call, apply and bin...

Each time Docker starts a container, the IP and hosts specified operations

Preface Every time you use Docker to start a Hado...

Detailed explanation of Vue element plus multi-language switching

Table of contents Preface How to switch between m...

How to install nginx in centos7

Install the required environment 1. gcc installat...

Sample code for making desktop applications with vue + Electron

1.vue packaging Here we use the vue native packag...

JavaScript to achieve lottery effect

This article shares the specific code of JavaScri...

Descending Index in MySQL 8.0

Preface I believe everyone knows that indexes are...

An example of implementing a simple finger click animation with CSS3 Animation

This article mainly introduces an example of impl...

How to automatically execute SQL statements when MySQL in Docker starts

When creating a MySQL container with Docker, some...

Web Design Tips: Simple Rules for Page Layout

Repetition: Repeat certain page design styles thr...