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

Address: game heyongsheng. Making. IO/game/index…. Development language: Vue Running platform: Chrome Gitee address: gitee.com/ihope_top/n… Github address: github.com/heyongsheng… The game has been open source, welcome everyone to experience, can also be modified for the company’s annual games

preface

Everybody nuggets XDM, another year New Year, here in advance to the brothers happy New Year, I wish everyone good health, all the best. Today, this article is for the sake of the Denver nuggets of the birth of a new essay, here give you wrote a little game, specially the so-called technology creative enough to gather together, although the game used technology is generally very simple, but also let I have prepared a lot of time, little game to complete all by myself, and online resources together, fine arts, sound may not be perfect, you will do well, I hope you can like it, it is strongly recommended that you before you read the article. Click on the game link heyongsheng lot. IO/game/index…. Go and experience them.

Game barrage and the end of the game blessing message collection

I believe that interested students have already experienced the game, then you must have seen the blessing message appeared during the game, and the blessing message to the player appeared at the end of the game, do you want your blessing message to appear in the bullet screen, then please leave a message in the comment section, I will see the message timely update to the bullet screen oh.

Barrage message

Bullet-screen message format: bullet-screen + nickname + blessing (one word, not too long), such as bullet-screen + Henan guy + wish all the people of the country an early victory over the coronavirus

Game victory blessing

Victory blessing format: victory + nickname + blessing (a word, don’t be too long), such as victory + Nuggets user Xiaoming + WISH you a smooth New Year work

You can take screenshots of random messages when you win the game and then send them to the boiling point, to see if there will be luck to meet people who send blessings to you.

So let’s start the game development talk.

If someone is interested in the technology not mentioned in the game, you can put it forward in the comment section, and the subsequent article can be targeted to explain. In addition, the code in this article is only posted key parts of the code, if you want to view the complete code, please go to Gitee or GitHub.

The rules of the game

Players need to press and hold the cannon to move left and right to attack Nian beast, there will be regular questions in the middle of the screen, the answer to the question will increase the attack power, etc., the answer time for each question is 8 seconds, the interval between questions is 5 seconds, nian Beast health is 0 when the game ends, the less time to beat Nian Beast, the more powerful.

Menu and global configuration

Global configuration

setting: {
  isPlay: false.showBulletChat: true
}
Copy the code

In fact, there are only two global configurations, voice control and barrage control, because after testing, the game will lag on the machine with very poor performance, so the control of whether to display the barrage is given. As for the size, color and density of the barrage, these are not written due to the time. As for sound control, it’s definitely a must, both to prevent users from being affected by sudden music playing and because browsers have limitations that prevent automatic sound playback.

The menu

Layout is not mentioned, here is a brief description of my menu generation ideas, because to add the mouse slide and click sound to the menu, so it is better to use the V-for loop data method, otherwise the mouse event will have to write several times. The specific code is as follows

<div class="menu-box">
  <div
    class="menu-item"
    v-for="(item, index) in menuList"
    :key="index"
    @mouseover="$store.commit('playAudio', hoverMusic)"
    @click="$store.commit('playAudio', clickMusic),item.clickHandle()"
    v-show="item.show()"
  >
    {{item.name}}
  </div>
</div>
Copy the code
/ / excerpt
menuList: [{name: 'Start the game'.clickHandle: () = > {
        this.gameBegin()
      },
      show: () = > true
    },
    {
      name: 'Turn on the sound (strongly recommended)'.clickHandle: () = > {
        this.$store.commit('tooglePlay'.true)},show: () = > !this.$store.state.setting.isPlay
    },
    {
      name: 'Turn off the sound'.clickHandle: () = > {
        this.$store.commit('tooglePlay'.false)},show: () = > this.$store.state.setting.isPlay
    }
  ],
Copy the code

Every menu there are three main attributes, name, click on the event and, according to the control because some menu items need to decide whether to show according to actual condition, such as open and close the voice, who need according to the current sound is on show who hide, if we define the data directly to the variable assignment of sound control to show, So when the sound changes, show will not be dynamically updated. Here we assign a function to show to achieve the purpose of winter update.

voice

The game does not have the sound how line, the music cited here is the overture, ha ha ha, is not all of a sudden have the flavor of the New Year. Voice in the game there are two main types, one is a long time, the need to control play pause, such as background music, the other is a real-time, such as sliding, the bullet whomp menu, so the background music of the instance we need to store, and the real-time audio built along with it, I am here to steal a lazy, no write separate sound configuration file, I’ll just write it in vuex.

The background music

window.backMusic = new Audio()
window.backMusic.src = require('.. /assets/mp3/back.mp3')
window.backMusic.loop = true
window.backMusic.load()
window.backMusic.currentTime = 127.2 // Background music is positioned to soothing segments by default
Copy the code

This way we can control playback anywhere by directly calling or changing window.backmusic.

Real-time sound

playAudio (state, src) {
  if (state.setting.isPlay) {
    const audio = new Audio()
    audio.src = src
    audio.load()
    audio.volume = . 5
    audio.play()
  }
}
Copy the code

Note that you cannot play different sounds by changing the address of a single audio object, because changing the address of the sound while the sound is currently playing will cause an error.

barrage

This idea came to me when I was listening to the background music of the Spring Festival overture, because when I listen to this, I think of the Spring Festival Gala, and I think of the people throughout the country in the short film to send blessings, so I want to add this, combined with the background music, is not suddenly feel come. I also hope you can send your blessing, I will update your blessing to the barrage. The barrage here is just to meet the needs of the game, not too complicated.

First of all, we need to sort out the requirements and attention points of bullet screen

  • The horizontal and vertical barrage should not overlap
  • The interval between two rounds should be random
  • The bullet screen is automatically removed when it exceeds the screen

First of all, there is the problem that shells cannot overlap. If they cannot overlap longitudinally, we need to have a concept of trajectory, that is, let each shell have its own orbit and go in its own way. Of course, there will be no overlap. Here, I have divided 10 ballistics according to the height of the screen. Originally, the bigger the screen, the more ballistics, but considering the performance problem, we adopted this scheme.

Followed by barrage of transverse prevent overlap, baidu when I see other authors mentioned chase and the question of what, but I’m a poor student, not quite understand, and then the solution you want to, we here each barrage movement speed is the same, that is to consider each of the barrage of timing, After the previous barrage of the same trajectory completely appears, we need to regenerate it into the next barrage. We can add a random distance within the specified range in the middle, which is more beautiful.

Let’s see how the code works.

ballistic: 0.// Number of trajectories
bulletSpeed: 2.// Barrage speed
bulletInterval: [300.500].// Barrage spacing
screenWidth: document.documentElement.clientWidth, // Screen width
screenHeight: document.documentElement.clientHeight, // Screen height
Copy the code
/ * * *@descriptionDisplay barrage *@param {*}
 * @return {*}* /
showBullet () {
  // The number of trajectory can also be calculated according to the height of the screen and the height of the barrage
  let ballisticArr = [0.1.2.3.4.5.6.7.8.9]
  // Add barrage to all trajectories in random order
  let ballisticLaunch = () = > {
    let randomIndex = Math.floor(Math.random() * ballisticArr.length)
    let ballisticIndex = ballisticArr.splice(randomIndex, 1) [0]
    this.createBullet(ballisticIndex)
    if (ballisticArr.length > 0) {
      setTimeout(ballisticLaunch, Math.random() * 1000)
    }
  }
  ballisticLaunch()
  // this.createBullet(2)
},
Copy the code

My method here is to set a good number of ballistic first, and then put the ballistic serial number in an array, directly from at the beginning of the array to get number, to put it in the trajectory a barrage, and circulation, until each trajectory has been used up, then the problem comes, at this moment we have only a barrage of each ballistic how to generate subsequent barrage, The idea here is in every time a barrage of mobile, determine their own mobile distance, when the appropriate distance is reached (itself fully in the screen on the right side of the screen and the distance to the distance we set two play ACTS) call load under a barrage of method, and put their incoming ballistic coding, plus we here barrage is uniform, There would be no overlap.

/ * * *@description: Added barrage *@param {*} Index Indicates the ballistic index *@return {*}* /
createBullet (index) {
  let bullet = document.createElement('div')
  let bulletHeight = document.documentElement.clientHeight / 10
  bullet.className = 'bullet-chat'
  bullet.style.left = this.screenWidth + 'px'
  bullet.style.top = index * bulletHeight + 'px'
  bullet.createNext = false // Whether the next barrage has been created
  bullet.nextSpace = Math.random() * (this.bulletInterval[1] - this.bulletInterval[0]) + this.bulletInterval[0] // Next barrage interval
  // Randomly fetch the barrage from the barrage library
  let dataLength = this.blessingData.length
  let randomIndex = Math.floor(Math.random() * dataLength)
  let blessing = this.blessingData[randomIndex]
  bullet.innerText = blessing.name + ":" + blessing.value
  this.$refs.bulletChat.appendChild(bullet)

  // The barrage moves
  let bulletMove = () = > {
    bullet.style.left = bullet.offsetLeft - this.bulletSpeed + 'px'
    if(! bullet.createNext) {// If the distance from the right side of the screen exceeds the interval of the barrage, the next barrage is loaded
      if (bullet.offsetLeft < (this.screenWidth - bullet.offsetWidth - bullet.nextSpace)) {
        this.createBullet(index)
        bullet.createNext = true}}// If the distance to the right of the barrage is greater than or equal to the width of the screen, remove the barrage
    if (bullet.offsetLeft < (-bullet.offsetWidth)) {
      this.$refs.bulletChat.removeChild(bullet)
    } else {
      requestAnimationFrame(bulletMove)
    }
  }
  bulletMove()
}
Copy the code

In this case, we introduced a library of bullets and randomly selected one at a time to avoid the problem of old bullets not being seen, and as you can see, the timing method used here is requestAnimationFrame, which is really better than setInterval, This project basically uses this method for all the places where animation is used. I also recommend that everyone use this method instead of setInterval. It has many advantages, so I won’t take up any words here.

nian

This lovely little thing is our year beast, the composition of the year beast is very simple, a small icon, add a health, and then we let it move back and forth. When it goes to zero we just let it disappear.

<! - nian - >
<div
  class="nianshou"
  :style="'marginLeft:' + nianshouLeft + 'px'"
  v-show="nianshouHP"
>
  <p>HP: {{ nianshouHP }}</p>
  <img src=".. /assets/nianshou.png" class="nianshou-img" />
</div>
Copy the code
nianshouLeft: 0.// The distance to the left of the year beast

nianshouMove () {
  // Update the game time
  this.gameDuration = new Date().getTime() - this.gameBeginTime
  if (this.nianshouLeft + 200> =this.screenWidth) {
    this.nianshouMoveDir = -4
  } else if (this.nianshouLeft < 0) {
    this.nianshouMoveDir = 4
  }
  this.nianshouLeft += this.nianshouMoveDir
  this.nianshouInterval = requestAnimationFrame(this.nianshouMove)
},
Copy the code

Our rules of the game is, the less the more powerful, so we need to calculate how much time, game here we begin with the nien beast moves for the game start time, we also need the nien wall movement in the opposite direction, so here we judge the nien beast distance on the left and the right of the screen distance, once to define value, change the direction of movement, So you change the value of the move

fireworks

This gadget is our firecracker, also equivalent to our weapons, I originally wanted to find a smoke tube to release fireworks, but resources are limited, with this will do. This small firecracker will continue to send out light beam to fight nian Beast, here about firecracker, is the mouse press the time to add movement events, so that he can move around.

The first step is definitely the movement of the firecracker, this we do not do too complicated, directly drag the mouse to move left and right on the line, do not let the up and down movement is for you to hold the firecracker in the face.

The idea is that when the mouse clicks on the firecracker, it will add a movement event to the whole area. When the mouse does not add a movement event to the firecracker, it will easily go beyond the scope of the firecracker if the mouse moves too fast, resulting in a bad game experience. When the mouse is raised, we will remove this event. As for the mobile, we need to define a clientx, each store when the mouse moves the mouse distance on the left side of the screen, when the mouse to move again, we use the current cursor distance on the left side of the suggestion just storage, can be concluded that the distance of the mouse, then we are the change in the value assigned to the margin to the left

<! - firecrackers - ><div
  class="paozhu"
  ref="paozhu"
  @mousedown="addMove"
  :style="'marginLeft:' + paozhuLeft + 'px'"
>
  <img src=".. /assets/paozhu.png" alt="" />
</div>
Copy the code
clientX: 0.// The last mouse position
paozhuLeft: 0 // The distance from the firecracker to the left

// Mouse down to add movement events
addMove (e) {
  e = e || window.event
  this.clientX = e.clientX
  this.clientY = e.clientY
  this.$refs.gemeWrap.onmousemove = this.moveFunc
},
// Drag the mouse to move the firecracker
moveFunc (e) {
  e = e || window.event
  e.preventDefault()
  let movementX = e.clientX - this.clientX
  this.paozhuLeft += movementX
  this.clientX = e.clientX
},
// Mouse up to remove the movement event
removeMove () {
  this.$refs.gemeWrap.onmousemove = null
},
Copy the code

The bullet

We call fireworks from the beam to bullets, the bullet the principle is very simple, time firing bullets, fired bullets for fireworks horizontal coordinates, to screen are highly minus the height of the longitudinal coordinate, generated after shot up to go, when the bullet distance at the top of the distance less than or equal to the height of the nien beast, Determine whether the bullet’s horizontal coordinates coincide with nian’s horizontal coordinates, if so, draw blood from Nian, play the sound of the hit, remove the bullet, if not, remove the bullet when it runs off the screen.

Here we set a bullet speed, if you have played the game, you will find that it is difficult to hit at the beginning, ha ha ha, this is also increased difficulty, of course, if you answer the question correctly, the rate of fire, attack speed, damage will be increased accordingly.

createBulletInterval: null.// Create a timer for the bullet
frequency: 5.// Firing frequency
bulletSpeed: 10.// The speed of the bullet
damage: 2.// Bullet damage
lastBulletTime: 0.// The last time a bullet was fired

// Generate bullets
createBullet () {
  / / the bullet
  let now = new Date().getTime()
  if (now - this.lastBulletTime > (1000 / this.frequency)) {
    let bullet = document.createElement('div')
    bullet.className = 'bullet'
    bullet.style.left = this.paozhuLeft + 25 + 'px'
    bullet.style.top = this.screenHeight - 123 + 'px'
    this.$refs.gemeWrap.appendChild(bullet)
    this.$store.commit('playAudio'.require('.. /assets/mp3/emit.mp3'))
    // The bullet moves
    let bulletMove = () = > {
      bullet.style.top = bullet.offsetTop - this.bulletSpeed + 'px'
      // If the distance between the bullet and the top is the height of nian, determine whether the bullet and Nian are in the same horizontal position
      if (bullet.offsetTop <= 250 && bullet.offsetLeft >= this.nianshouLeft && bullet.offsetLeft <= this.nianshouLeft + 200) {
        // Year beast shed blood
        this.nianshouHP -= this.damage
        this.$store.commit('playAudio'.require('.. /assets/mp3/boom.wav'))
        if (this.nianshouHP <= 0) {
          this.nianshouHP = 0
          this.gameOver()
        }
        // Bullets disappear
        this.$refs.gemeWrap.removeChild(bullet)
        // cancelAnimationFrame(bulletMove)
      } else if (bullet.offsetTop <= 0) {
        this.$refs.gemeWrap.removeChild(bullet)
        // cancelAnimationFrame(bulletMove)
      } else {
        requestAnimationFrame(bulletMove)
      }
    }
    bulletMove()
    this.lastBulletTime = now
  }
  this.createBulletInterval = requestAnimationFrame(this.createBullet)
}
Copy the code

Since requestAnimationFrame cannot set the interval, we will record the time when bullets are generated, and determine whether the interval meets our requirements for bullet frequency when requestAnimationFrame runs next time. If so, proceed. If no, skip this operation.

The problem

One of the main features of this game, is to join the answer system, otherwise has been in the Biubiubiu play monster have what meaning, year beast’s health is 2021, by the initial attack speed and damage to play along, if the answer to the question, will increase buff, year beast ability daddy-daddy-daddy-daddy-daddy-daddy-go up.

First, analyze the requirements of the problem

  • Each question has 8 seconds to answer, 8 seconds to answer whether you have selected it or not
  • Correct answers will give you a buff
  • A wrong answer or an answer not selected at the end of the countdown will display the correct answer
  • The interval for each question is 5 seconds
  • Every time the questions are randomly selected from the question bank, the existing questions will not be selected for the second time

So let’s start with the easiest one, and draw questions from the question bank

questionJson: require('@/assets/data/question.json'), // Problem source data
questionData: [].//
questionList: [].// List of problems

let dataLength = this.questionData.length
let randomIndex = Math.floor(Math.random() * dataLength)
let question = this.questionData.splice(randomIndex, 1) [0]
Copy the code

Very simple, the next is to add the countdown, first add the question interval countdown, when a question is added, show 5 seconds countdown, then show the question and start the countdown

// Add display countdown
  let showCountDown = () = > {
    data.showTime--
    if (data.showTime > 0) {
      setTimeout(showCountDown, 1000)}else {
      // When the countdown is over, show the question and start the countdown
      answerCountDown()
    }
  }
Copy the code

Next is the countdown to answer, the game set is 5 questions, at the end of each question will judge whether the user has answered, if not, automatically set the result as the wrong answer, and then judge whether the question reached 5, if not, continue to add, until the end of 5.

// Add a countdown to the answers
  let answerCountDown = () = > {
    data.answerTime--
    if (data.answerTime > 0) {
      setTimeout(() = > {
        showCountDown()
      }, 1000)}else {
      // If the correct answer is not selected, an incorrect answer will be added
      if(! data.result) { data.result ='2021'
      }
      // If there are less than 5 questions, add a question
      if (this.questionList.length < 5) {
        this.addQuestion()
      }
    }
  }
Copy the code

The next step is to take a look at the DOM structure of the questions panel

<! -- Questions panel --><div
    class="question-panel panel-item"
    :class="{ clientCenter: question.answerTime > 0 }"
    v-for="(question, index) in questionList"
    :key="index"
  >
    <p class="show-count-down" v-if="question.showTime > 0">
      {{ question.showTime }}
    </p>
    <div class="question-wrap" v-else>
      <div class="count-down" v-if="question.answerTime > 0">
        <p>Please click on the correct answer in {{question. AnswerTime}} seconds</p>
      </div>
      <div class="question-panel-title">Question {{index + 1}}</div>
      <div class="question-container">
        <div class="question-title">{{ question.question.title }}</div>
        <div class="answer-wrap show" v-if=! "" question.result">
          <div
            class="answer-item"
            v-for="item in question.question.option"
            :key="item.key"
            @mouseover="$store.commit('playAudio', hoverMusic)"
            @click="answerQuestion(item.key, question)"
          >{{item.key}} : {{item.value}}</div>
        </div>
        <div class="answer-wrap result" v-else>
          <div
            class="answer-item"
            v-for="item in question.question.option"
            :key="item.key"
            :class="{ result: question.question.answer === item.key, }"
          >{{item.key}} : {{item.value}}<span class="check" v-if="question.result === item.key">To cardiac</span>
          </div>
        </div>
        <div
          class="buff"
          v-if="question.result === question.question.answer"
        >Attack speed +1 Rate of fire +1 damage +1</div>
        <div
          class="desc"
          v-if=" question.result && question.result ! == question.question.answer "
        >
          {{ question.question.desc }}
        </div>
      </div>
    </div>
  </div>
Copy the code

Look again at the structure of the questions in the question bank

  {
    "title": "Which of the following is the shenzhou 13 astronaut?"."option": [{"key": "A"."value": "Zhai Zhigang"
      },
      {
        "key": "B"."value": "Liu Boming"
      },
      {
        "key": "C"."value": "Nie Haisheng"}]."answer": "A"."desc": "The shenzhou 13 astronauts are Zhai Zhigang, Wang Yaping and Ye Guangfu."
  },
Copy the code

Combined with our countdown, answers, etc., above, the complete structure of a question should look like this

{
    question: question, // What is the problem
    answerTime: 9.// Answer the countdown,
    showTime: 6.// Display the countdown
    result: null.// The answer to the user's answer
}
Copy the code

We just need to click the option again and assign the value of the option to result, and then judge whether the user answered the question and answered correctly according to the value of result.

Here, on the outermost DOM structure, is a line of code like this

:class="{ clientCenter: question.answerTime > 0 }"
Copy the code

If not, it will be displayed in the center of the screen for users to view and select. If it is over, it will be displayed on the left side of the screen for users to view and share.

Game over

At the end of the game, the results of the game will be displayed, and a random selection of user wishes will be displayed

By here the whole game is completed, due to the space is limited, it is really impossible to explain every detail in detail, if you have any questions, please feel free to ask questions in the comment area or go to Github or code cloud issue, here to wish you a happy New Year in advance! I wish you all a smooth work, good health, the whole family hehemeimei and all the best!