PK creative Spring Festival, I am participating in the “Spring Festival creative submission contest”, please see: Spring Festival creative submission Contest

Record video game: www.bilibili.com/video/BV1vZ… It is recommended to browse the videos and articles with Chrome on PC and QQ browser on mobile phone. The sound of wechat built-in browser will be stuck, and the dark mode of built-in browser will make the picture unclear

Ihope_top.gigie.io/Tangyuan-ka… Gitee address: gitee.com/ihope_top/t… Github address: github.com/heyongsheng… This article has been published to the public account: Baili Qingshan

preface

Happy New Year to all my friends. I believe you have all arrived home now. I wish you a happy New Year and all the best in the New Year. I also just got home yesterday, these days is busy leaving, is busy moving, after returning home immediately to write an article, in order to be able to send out before the Spring Festival. Although very tired, but also very happy, after all can go home for the holiday, can’t say this year’s almost back to home, we all know the specific, can now home safely in the outbreak of the period, cannot leave each resistance to disease researchers insist and hard work, also cannot leave your persistence and cooperate, so the game will send to you, hope you can like it, I also hope that the knowledge used in it can help you. Because the time is really pressing, this small game also only wrote a week more time, is also written at night, also experienced moving, so a lot of places are not perfect, a lot of ideas have not been able to achieve, but fortunately there should be, also hope that we can give some suggestions and ideas after the experience.

We break for a little commercial: The game should be a recent marks, spent a lot of thought and time, for a long time should not write again, because I leave, to get ready for an interview, probably will prepare a month’s time, also have a good relax at the same time, do something they are interested in, then should be able to go to Beijing, I have four years of working experience, the technology stack is VUE, and I have been writing the management end project during my work. I have learned KOA +MongoDB by myself (I can only add, delete, check and change things simply). I have a part-time bachelor’s degree. Or contact the public account Baili Qingshan, thank you.

The game is introduced

Since there is a lot of content in this small game, we will not explain all the codes, but mainly explain the key codes and game ideas. If someone is interested, you can write a special article for details.

[Peter Roe] Free Mode – The Brightest Star in the Night Sky (Piano Version)

It is strongly recommended that friends who have conditions to experience small games should experience them first, and all the stories are revealed below

This mini-game is divided into two modes, story mode and free mode, the gameplay is roughly the same, the following will be introduced separately. Although the mobile phone mode is adapted this time, it is still recommended to use the computer for experience, because the screen width of the mobile phone is too narrow to see the pillar behind, so the position cannot be adjusted in advance, which will greatly increase the difficulty (why not do mobile phone zoom adaptation? Because I don’t have time, haha)

The free mode

Major game play reference little game flappybird, just join the parts of their ideas, as shown in the above, players through the blank space key or click on the screen control tangyuan (why choose sweet dumpling because represents the unity) jumping up and down, thus through the pillars in the middle of the gap, the gray pillar also represent the contaminated by the virus in the city, After the dumplings pass by, the pillars turn a soft orange color, indicating that the city is purified, while the counting board above also records the purified buildings, representing scores. The three small vertical lines below represent the life value, each encounter a pillar will lose a life value and invincible for a period of time, jump out of the screen from the top or fall out of the screen from the bottom will also lose a life value and invincible for a period of time, if the invincible time has not returned to the screen, it will be judged failure.

When health runs out, the final score is displayed and the player can choose to restart or return to the menu.

The story mode

The story mode can be said to have spent most of my energy, the first is the creative part, the story mode is combined with music, with the progressive relationship of music is divided into three stages

The first stage is similar to the free mode, in which the player controls the dumplings to pass through the gap between buildings. The difference is that after passing through, a small green light will be generated between buildings. We can think of it as energy, faith and people’s gratitude, and this energy will be used in the third stage.

Stage 2: As the music increases, the game becomes more difficult, with buildings moving faster and Spaces between buildings becoming smaller. In order to take care of the majority of mobile phone users and disabled party experience, the second level of health increased to 5 points (computer mode by my test can not drop a drop of blood)

As the music reaches the climax, we will also enter the third stage, but we will find that the third stage is more difficult, the gap between the buildings is only a little bigger than the tang Yuan, which is impossible to pass, ok? Is the author out of his mind? No, in fact, this reference is made to the originality of shuangji, a domestic single-player game, and I suggest that friends can experience it. Of course, it cannot be compared with the original game, and a lot of simplification is made here. Have you noticed that the ball of light is getting bigger and bigger? Now it will come in handy.

Believe that a lot of friends don’t can’t arrive here after many attempts, even with all the friends come to here, see the despair will become despair and anger, then you will find it will pop-up prompts, ask if you still insist, if you choose to be, before the accumulation of every energy one by one to help you, wait for energy fu can completely, Tangyuan will become invincible and move on

Tangyuan invincible after accelerated gradually, until you reach the fastest speed, the building will be quickly across and purification, the music will reach orgasm, because music climax will continue to a point many, so here has the same frames in fact some boring, so here above joined some epidemic reporting (fabrication), on the one hand, on the other hand, can alleviate boredom As the numbers get better, so does the game.

Along with the music, the epidemic reporting Numbers also gradually reset (here because of the different screen sizes, it’s hard to do music card point very accurately), after the pillars will all disappear, then show some blessing words, game over, there would have been all want to make the city bright lights, the night sky fireworks, the sky is clear, or alternative time is not enough, For the time being.

Core Technical Points

background

As for the background, the snow part has been written separately before, you can check it on my home page. In the game, in order to conform to the setting of the game, the names of moving cities have been added. This is the same as the loading screen and loading problem in my previous game, which is to randomly select an item from an array to display, without any difficulty.

// Select a random locale from the locale library
let dataLength = this.spaceData.length
let randomIndex = Math.floor(Math.random() * dataLength)
let space = this.spaceData[randomIndex]
let spaceDom = document.createElement('div')
spaceDom.className = 'space-name'
spaceDom.innerText = space
city.appendChild(spaceDom)
this.$refs.bgTop.appendChild(city)
Copy the code

Game features

The beating of tangyuan

The principles that we’re using here are essentially the physics of upthrow motion and free fall motion

Here we set a tangyuanG, the acceleration of gravity in the game, and then give tangyuanUpV, which is the initial speed of tangyuanUpV

// Tangyuan part
tangyuanG: 0.02.// Set the acceleration of gravity
tangyuanUpStartTime: 0.// The time when the dumplings start to be tossed
tangyuanDownStartTime: 0.// The time when the dumplings start to fall
tangyuanUpV: 5.// The initial speed of the dumpling
tangyuanUpInterval: null.// The timer on the dumplings
tangyuanDownInterval: null.// Dumplings fall timer
Copy the code

Then we can calculate the current speed of the dumpling as the displacement distance according to the formula v=v0− GTV =v0 – GTV =v0−gt. When the speed is equal to 0, it means that we have reached the peak. At this time, we start to let the dumpling perform free fall movement

/ * * *@description: Glutinous rice balls are thrown from the top@param {*}
     * @return {*}* /
    tangyuanUping () {
      let now = Date.now()
      let t = now - this.tangyuanUpStartTime
      let v0 = this.tangyuanUpV
      let g = this.tangyuanG
      let y = v0 - g * t
      if (y < 0) {
        this.tangyuanStartDown()
      } else {
        this.$refs.tangyuan.style.top = this.$refs.tangyuan.offsetTop - y + 'px'
        this.tangyuanUpInterval = requestAnimationFrame(this.tangyuanUping)
        if (this.$refs.tangyuan.offsetTop <= -this.$refs.tangyuan.offsetHeight) {
          this.gameFail()
        }
      }
    },
Copy the code

The current falling speed of tangyuan can also be calculated according to the formula v= GTV = GTV =gt as the displacement distance

/ * * *@description: Tangyuan falling *@param {*}
     * @return {*}* /
    tangyuanDown () {
      let now = Date.now()
      let t = now - this.tangyuanStartTime
      let g = this.tangyuanG
      let y = g * t
      this.$refs.tangyuan.style.top = this.$refs.tangyuan.offsetTop + y + 'px';
      this.tangyuanDownInterval = requestAnimationFrame(this.tangyuanDown)
      if (this.$refs.tangyuan.offsetTop >= this.screenHeight + this.$refs.tangyuan.offsetHeight / 2) {
        this.gameFail()
      }
    },
Copy the code

For more details about tangyuan motion timer, check out the source code

Generate the pillars

First we need to determine the height of the gap between the pillars, and then according to the height of the gap to determine the position of the upper and lower pillars.

// Column section
  pillarCount: 0.// The number of columns generated
  createPillarInterval: null.// Create a column timer
  createPillarLastTime: ' '.// The last time the column was created
  pillarFrequency: 4000.// Column generation frequency milliseconds/time
  pillarWidth: 100.// Width of column
  pillarGapHeight: 220.// Column spacing
  pillarMoveInterVal: null.// Column movement timer
  pillarSpeed: 2.// The speed of column movement
Copy the code
 / * * *@description: Start generating columns *@param {*}
     * @return {*}* /
    createPillar () {
      // Generate columns according to the generation time of the top column and the set generation frequency
      let now = new Date().getTime()
      if (now - this.createPillarLastTime > this.pillarFrequency) {
        // We need to calculate the gap range of the column according to the screen height
        // Temporarily set the usable range of a cell to 1/10 of the screen height
        let screenSpan = this.screenHeight / 10
        // Set the gap range to the middle of the screen, then the gap top coordinate range is between 3/10 of the screen to (7/10 - gap height)
        let gapTop = Math.floor(Math.random() * (screenSpan * 7 - this.pillarGapHeight)) + screenSpan * 3
        let gapBottom = gapTop + this.pillarGapHeight

        // Generate columns according to the gap range
        this.createPillarDom(0.this.screenHeight - gapTop, 'pillar-item-top')
        this.createPillarDom(gapBottom, 0.'pillar-item-bottom')
        this.createPillarLastTime = now
        this.pillarCount++
      }
    },
    / * * *@description: Pillar generator *@param {*}
     * @return {*}* /
    createPillarDom (top, bottom, className) {
      let pillar = document.createElement('div')
      // pillar.className = className
      pillar.className = ['pillar-item', className].join(' ')
      pillar.style.left = this.screenWidth + 'px'
      pillar.style.top = top + 'px'
      pillar.style.bottom = bottom + 'px'
      pillar.style.width = this.pillarWidth + 'px'
      this.$refs.pillarWrap.appendChild(pillar)
    },
Copy the code

Collision detection

The generation of pillars was mentioned above, but the movement of pillars was not mentioned, because I made the movement of pillars and collision detection be done together. We added a timer for the movement of all pillars, first to save performance, but to better control the movement of all pillars, for example, to stop the movement of pillars when the game failed. What we do here is add a timer, walk through all the pillars, make them move as required, find the nearest pillar to our right for collision detection, and find the nearest pillar to our left for purification effect.

    / * * *@description: Whole column movement *@param {*}
     * @return {*}* /
    movePillar () {
      // Get all columns
      let pillarDoms = this.$refs.pillarWrap.children
      let pillarList = Array.from(pillarDoms)
      for (let index = 0; index < pillarList.length; index++) {
        let item = pillarList[index]
        if (item.offsetLeft < -this.pillarWidth) {
          this.$refs.pillarWrap.removeChild(item)
        } else {
          item.style.left = item.offsetLeft - this.pillarSpeed + 'px'}}// Get the current column to the left of the nearest dumpling to add the purification effect
      let leftOne = this.$refs.tangyuan.offsetLeft
      let pillartListReverse = [...pillarList].reverse()
      let prevDomIndex = pillartListReverse.findIndex(item= > {
        return (item.offsetLeft + item.offsetWidth) < leftOne - this.$refs.tangyuan.offsetWidth / 2
      })
      if (prevDomIndex > -1) {
        PrevDomIndex +1 is the top one because the array is reversed here
        let prevTop = pillartListReverse[prevDomIndex + 1]
        let prevBottom = pillartListReverse[prevDomIndex]
        if(! prevTop.isClear) {// Add a purification effect to the column
          prevTop.classList.add('pillar-item-active')
          prevBottom.classList.add('pillar-item-active')
          // Generate energy from the column gap and fly to the energy pool
          // Get the column clearance center
          let gapCenterX = prevTop.offsetLeft + this.pillarWidth / 2
          let gapCenterY = prevTop.offsetHeight + this.pillarGapHeight / 2
          // Generate energy, stage 3 does not generate energy
          if (this.stage ! = =3) {
            this.createEnergy(gapCenterX, gapCenterY)
          }
          prevTop.isClear = true}}// Determine if the dumplings are invincible
      if (this.$refs.tangyuan.status ! = ='invincible') {
        this.pillarMoveInterVal = requestAnimationFrame(this.movePillar)

        // Get the column to the right of tangyuan as the object of collision detection
        let leftTwo = this.$refs.tangyuan.offsetLeft - this.pillarWidth - this.$refs.tangyuan.offsetWidth / 2
        let nextDomIndex = pillarList.findIndex(item= > {
          return item.offsetLeft > leftTwo
        })
        if (nextDomIndex > -1) {
          let pillarTop = pillarList[nextDomIndex]
          let pillarBottom = pillarList[nextDomIndex + 1]

          // Get the radius and center coordinates of tangyuan
          let tangyuanRadius = this.$refs.tangyuan.offsetWidth / 2
          let tangyuanCenterX = this.$refs.tangyuan.offsetLeft
          let tangyuanCenterY = this.$refs.tangyuan.offsetTop
          // Check whether the dumplings collide with the column above
          // Get the coordinates of the center of the column above
          let pillarTopCenterX = pillarTop.offsetLeft + this.pillarWidth / 2
          let pillarTopCenterY = pillarTop.offsetHeight / 2
          if (this.computeCollision(this.pillarWidth, pillarTop.offsetHeight, tangyuanRadius, tangyuanCenterX - pillarTopCenterX, tangyuanCenterY - pillarTopCenterY)) {
            this.gameFail()
          }
          // Check whether the dumplings collide with the column below
          // Get the coordinates of the center of the column below
          let pillarBottomCenterX = pillarBottom.offsetLeft + this.pillarWidth / 2
          let pillarBottomCenterY = pillarTop.offsetHeight + this.pillarGapHeight + pillarBottom.offsetHeight / 2

          if (this.computeCollision(this.pillarWidth, pillarBottom.offsetHeight, tangyuanRadius, tangyuanCenterX - pillarBottomCenterX, tangyuanCenterY - pillarBottomCenterY)) {
            this.gameFail()
          }
        }
      } else {
        this.pillarMoveInterVal = requestAnimationFrame(this.movePillar)
      }
    },
Copy the code

I actually looked it up on the Internet for collision detection of circles and rectangles

/ * * *@description: Collision detection of circle and rectangle *@param {*} W The width of the rectangle *@param {*} H height of rectangle *@param {*} R The radius of the circle *@param {*} Rx distance x from the center of the circle to the center of the rectangle *@param {*} Ry Y distance from the center of the circle to the center of the rectangle *@return {*}* /
    computeCollision (w, h, r, rx, ry) {
      var dx = Math.min(rx, w * 0.5);
      var dx1 = Math.max(dx, -w * 0.5);
      var dy = Math.min(ry, h * 0.5);
      var dy1 = Math.max(dy, -h * 0.5);
      return (dx1 - rx) * (dx1 - rx) + (dy1 - ry) * (dy1 - ry) <= r * r;
    },
Copy the code

Tangyuan off screen detection

This is actually just said when the dumplings throw and fall movement, the code has been reflected, here will not be detailed.

Other details class technical points

The fading in and out of sound

This is actually very simple, is to write a timer, regularly reduce or increase the volume of the sound, and then when the volume reached the target value of the timer can be cleared, the sound part of the complete code is as follows

/* * @author: He Yongsheng * @Date: 2022-01-23 17:23:34 * @email: [email protected] * @Lasteditors: He Yongsheng * @LasteditTime: 2022-01-30 13:58:43 * @Descripttion: */
class AudioObj {
  // static status = false
  constructor() {
    this.status = false
    this.backMusic = new Audio();
    this.playInterval = null
  }
  playAudio (src) {
    if (this.status) {
      const audio = new Audio()
      audio.src = src
      audio.load()
      audio.volume = . 5
      audio.play()
    }
  }
  backMusicPlay (src) {
    clearInterval(this.playInterval)
    if (this.status) {
      if (src) {
        this.backMusic.src = src
        this.backMusic.load()
      }
      this.backMusic.volume = 0
      this.backMusic.play()
      this.playInterval = setInterval(() = > {
        if (this.backMusic.volume < 1) {
          this.backMusic.volume = (this.backMusic.volume + 0.1).toFixed(1)}else {
          clearInterval(this.playInterval)
        }
      }, 100)
    }
  }
  backMusicStop () {
    clearInterval(this.playInterval)
    this.playInterval = setInterval(() = > {
      if (this.backMusic.volume > 0) {
        this.backMusic.volume = (this.backMusic.volume - 0.1).toFixed(1)}else {
        clearInterval(this.playInterval)
        this.backMusic.pause()
      }
    }, 100)}}export default new AudioObj();
Copy the code

The transition of column color gradient

You can observe, the pillars of our color changing is a transition effect, but there is no transition effects in CSS gradient, here is that we achieve the gradient effect by means of curve for national salvation, the gradient does not support the transition, but the background color support, so we are looking for a target, the background color of and then add a layer of translucent gradient on it, Then need to change is to modify the following background color can achieve the desired effect, but this method has large limitations, and requires a long time of debugging, it is better to directly superimpose a layer of target background color, and then change the transparency to directly.

.pillar-item {
  position: absolute;
  transition: background-color 1s;
}
.pillar-item-top {
  background: rgb(48.48.48)
    linear-gradient(to top, rgba(255.241.113.0), rgba(255.253.223.0.5));
}
.pillar-item-bottom {
  background: rgb(48.48.48)
    linear-gradient(to bottom, rgba(255.241.113.0), rgba(255.253.223.0.5));
}
.pillar-item-active {
  background-color: #ffdb8f;
  /* background-image: linear-gradient(to top, #ffd06c, #ffedc7); * /
}
Copy the code

Space bar control tangyuan jump

Add a tab-index attribute to the div to allow it to get focus, because only elements that can get focus can listen for keyboard events. Note that if you want to set this attribute to a full-screen DOM, you should set outline: None otherwise has a border that is visible to some browsers and invisible to others.

Click on the element below the current element

As you can see, many of the transitions in the game use the transition TAB from vue

.fade-enter-active..fade-leave-active {
  transition: opacity 1s, transform 1s;
}
.fade-enter /*.fade-leave-active below version 2.1.8 */ {
  opacity: 0;
  transform: translateX(200px)}.fade-leave-to /*.fade-leave-active below version 2.1.8 */ {
  opacity: 0;
  transform: translateX(-200px)}Copy the code

Effect is very good, is to have a small problem, is that sometimes we have to see the next page, obviously, because on a page is not completely leave, and unable to click on the next page elements, although is very short intervals, but the user experience will be very bad, CSS has an attribute perfect solve this problem, we That’s pointer-events: none; This property prevents the current user from receiving mouse events. All mouse events that are applied to it will pass through to the elements below it, but the elements inside it will also be unclickable. If you want to click the elements inside, you can add poor-events: auto;

In-game text alert

This is my temporary solution, because I don’t have much time, and I haven’t studied the optimal solution, but my solution basically meets my requirements. There is a transition effect when the text appears, and repeated calls can not overlap the text to control the display time of each message.

The transition effect is very simple, add a transition CSS, first set an offset position, then immediately change its position to the target position. The same goes for leaving. Change its position to the transition position, make a position offset, add a transparency change, and then remove the DOM.

It is also easy to control the display time, pass in the time as a variable, when the time is removed on the line.

Messages can’t overlap this because the game requires it, and I don’t have elements stacked on top of each other, partly because it doesn’t fit the scene, and partly because I can’t. My plan is to queue type here (I don’t know their so called, right), is the window of the message into a public class, the class to save a message queue, and a bomb news method, when we call the method that play message, will first determine if there’s a message in the message queue, so the current message delivery to the last one, if not directly, When the current message is finished, a callback is performed to determine if there is a message in the message queue, and if there is, the next message is loaded. The complete code is as follows

/* Prompt style */
.alert-text {
  position: absolute;
  top: 20%;
  left: 50%;
  transform: translate(-50%);
  font-size: 32px;
  opacity: 1;
  color: #fff;
  text-shadow: 0 0 5px #fff;
  transition: all 1s;
  text-align: center;
}
Copy the code
class Alert {
  constructor () {
    this.messageList = []
  }
  showText (text, time = 2000) {
    this.messageList.push({
      text,
      time
    })
    if (this.messageList.length <= 1) {
      this.showTextHandle(text, time)
    }
  }
  showTextHandle (text, time) {
    let dom = document.createElement('p')
    dom.innerText = text
    dom.style.opacity = 0
    dom.style.top = '30%'
    dom.className = 'alert-text'
    document.body.appendChild(dom)
    setTimeout(() = > {
      dom.style.opacity = 1
      dom.style.top = '20%'
      setTimeout(() = > {
        dom.style.opacity = 0
        dom.style.top = '10%'
        this.messageList.splice(0.1)
        if (this.messageList.length > 0) {
          this.showTextHandle(this.messageList[0].text, this.messageList[0].time)
        }
        setTimeout(() = > {
          dom.remove()
        }, 1000)
      }, time)
    }, 16)
  }
  clear () {
    this.messageList = []
    let alertDom = document.querySelectorAll('.alert-text')
    alertDom.forEach(item= > {
      item.remove()
    })
  }
}

export default new Alert()
Copy the code

conclusion

This article to this, really very little time, so can’t put all of the planning implementation, and no way to speak out all the implementation, and according to my observation, I find a small game to test the response is not how, but the game still poured me a lot of energy, a lot of people say little game is so difficult, is also very boring, but resistance to disease of life is not so? Nucleic acid again and again, again and again, finally for today’s victory, thanks to all the efforts to fight the epidemic, I wish you all a happy New Year, good health and all the best