vue new year game - new year beast battle, happy New Year (online experience)

Posted by plasko on Tue, 11 Jan 2022 05:35:11 +0100

Game address: https://heyongsheng.github.io...
Development language: vue
Running platform: Chrome
gitee address: https://gitee.com/ihope_top/n...
github address: https://github.com/heyongshen...
The game is open source. You are welcome to experience it. You can also modify it for the company's annual meeting games

preface

It's a new year for xdm nuggets. I'm here to pay a new year's greeting to all brothers in advance. I wish you good health and all the best. Today's article is for the birth of the essay for the Nuggets new year. Here I specially wrote a small game for you. The so-called technology is not creative enough. Although the technology used in the game is very general and simple, it also made me prepare a lot of time. All the small games are completed by myself. The resources pieced together on the Internet, art and sound effects may not be perfect, Everyone will make do with it. I hope you can like it. It is strongly recommended that you click the game link before reading the article https://heyongsheng.github.io... Go and experience two.

Then let's officially start the explanation of game development.

There are a lot of content in small games, and unimportant places will be taken or omitted. If someone is interested in technologies not mentioned in the game, they can put forward them in the comment area. Later, they can publish targeted articles to explain them. In addition, the code in the article only posts the key part of the code. If you need to check the complete code, please move to gitee or GitHub.

Rules of the game

Players need to press and hold the firecracker to move left and right to attack Nian beast. Questions will appear in the middle of the screen regularly. Answering the right questions will increase their attack power. The answer time of each question is 8 seconds, and the interval between questions is 5 seconds. When Nian beast's blood volume is 0, the game ends. The less time it takes to defeat Nian beast, the more powerful it is.

Menu and global configuration

Global configuration

setting: {
  isPlay: false,
  showBulletChat: true
}

In fact, there are only two global configurations: sound control and barrage control. After testing, the game will get stuck on machines with very poor performance. All give the control of whether to display the barrage. As for the size, color and density of the barrage, they are not written due to time. As for sound control, it must be necessary, first, to prevent the sudden playing of music from affecting users, and second, the browser also has restrictions to prohibit the automatic playing of sound.

menu

I won't talk about the layout. Let's briefly talk about my ideas when generating the menu. Because the sound effects of mouse sliding and clicking are added to the menu, it's better to use the method of v-for circular data, otherwise the mouse events will be written several times. The specific codes are 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>
// Excerpt
menuList: [
    {
      name: 'Start the game',
      clickHandle: () => {
        this.gameBegin()
      },
      show: () => true
    },
    {
      name: 'Turn on sound(Strongly recommended)',
      clickHandle: () => {
        this.$store.commit('tooglePlay', true)
      },
      show: () => !this.$store.state.setting.isPlay
    },
    {
      name: 'turn sound off',
      clickHandle: () => {
        this.$store.commit('tooglePlay', false)
      },
      show: () => this.$store.state.setting.isPlay
    }
  ],

Each menu item mainly has three properties, name, click event and control display. Because some menu items need to be displayed according to the actual situation, such as turning on sound and turning off sound. We need to judge who displays and hides according to whether the current sound is turned on. If we directly assign the variable controlling sound to show when defining data, Then, show will not be updated dynamically during subsequent sound changes. Here, we assign a function to show to achieve the purpose of updating in winter.

voice

How can the game go without sound? The music quoted here is a prelude. Hahaha, does it have the flavor of youth all at once. There are two main types of sounds in the game. One is playing for a long time and needs to control the pause of playing, such as background music, and the other is real-time, such as menu sliding sound, bullet impact sound, etc. Therefore, we need to store the examples of background music, and the real-time sound effect can be built with use. I stole a lazy here and didn't write a separate sound configuration file, It's written directly in vuex.

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 // The background music is positioned to the soothing clip by default

In this way, we can control the playback anywhere and directly call or change the window Just backmusic.

Instant sound

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

When playing the sound effect here, you need to judge whether the current sound switch is on. If it is on, it is playing. Note that you can't play different sound effects by changing the address of a single audio object, because if you modify the sound effect address while the current sound is playing, an error will be reported.

bullet chat

I thought of this idea when listening to the background music of the Spring Festival Overture, because when I heard this, I thought of the Spring Festival Gala and the blessing of people all over the country in the short film, so I wanted to add this in and combine it with the background music. Did I feel it all at once. I also hope you can send your blessings, and I will update your blessings to the barrage. The barrage here is only to meet the needs of the game and will not be too complex.

First of all, we need to sort out the needs and points for attention of the barrage

  • The barrage shall not overlap horizontally and vertically
  • The interval between two barrages should preferably be random
  • If the barrage exceeds the screen, it will be removed automatically

First of all, if the barrage cannot overlap vertically, we need to have a ballistic concept, that is, let each barrage have its own track and go its own way. Of course, there will be no overlap. Here, I divided 10 trajectories according to the screen height. Originally, the larger the screen, the more trajectories, but considering the performance problem, I adopted this scheme.

The second is to prevent the horizontal overlap of barrages. When I was in Baidu, I saw the problems mentioned by other authors. However, I was a learning slag and didn't understand them very well, so I thought of a solution myself. The moving speed of each barrage here is the same, and what needs to be considered is the timing of each barrage, We need to generate the next barrage after the previous barrage of the same trajectory completely appears. In the middle, we can add a random distance within the specified range, which is more beautiful.

Let's take a look at how the code is implemented.

ballistic: 0, // Ballistic quantity
bulletSpeed: 2, // Barrage velocity
bulletInterval: [300, 500], // Barrage interval
screenWidth: document.documentElement.clientWidth, // Screen width
screenHeight: document.documentElement.clientHeight, // Screen height
/**
 * @description: Display barrage
 * @param {*}
 * @return {*}
 */
showBullet () {
  // 10 trajectories are directly set here, and the number of trajectories can also be calculated according to the screen height and barrage height
  let ballisticArr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  // Add barrages 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)
},

My method here is to set the number of ballistics first, and then put the serial numbers of these ballistics into an array. At the beginning, I directly take the number from this array, put a barrage into this trajectory, and then cycle until each trajectory is used up. Then the problem comes. At this time, we have only one barrage per trajectory. How to generate subsequent barrages, The idea here is to judge the moving distance of each bullet screen when it moves. When it reaches the appropriate distance (it completely appears in the screen and reaches the distance between the two bullet screens set by us on the right side of the screen), it will call the method of loading the next bullet screen and pass in its own trajectory code. In addition, the bullet screen here has a uniform speed, There will be no overlap.

/**
 * @description: Add barrage
 * @param {*} index 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 // Have you created the next barrage
  bullet.nextSpace = Math.random() * (this.bulletInterval[1] - this.bulletInterval[0]) + this.bulletInterval[0] // Next barrage interval
  // Randomly take 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)

  // Barrage movement
  let bulletMove = () => {
    bullet.style.left = bullet.offsetLeft - this.bulletSpeed + 'px'
    if (!bullet.createNext) {
      // If the distance between the barrage and the right side of the screen exceeds the barrage interval, the next barrage will be loaded
      if (bullet.offsetLeft < (this.screenWidth - bullet.offsetWidth - bullet.nextSpace)) {
        this.createBullet(index)
        bullet.createNext = true
      }
    }

    // If the distance from the barrage to the right is greater than or equal to the screen width, remove the barrage
    if (bullet.offsetLeft < (-bullet.offsetWidth)) {
      this.$refs.bulletChat.removeChild(bullet)
    } else {
      requestAnimationFrame(bulletMove)
    }
  }
  bulletMove()
}

Here, we have introduced a barrage library and randomly select one from it each time, so as to avoid the problem that the old barrage cannot be seen. In addition, we have also seen that the timing method used here is requestAnimationFrame, which is really better than setinterval. This is used in almost all places where animation is used in this project. We also suggest that you use this method instead of setinterval, There are many advantages. It doesn't account for the number of words here. We are interested in Baidu by ourselves.

Nian beast

This lovely little thing is our Nian beast. The composition of Nian beast is very simple. A small icon and a blood volume are added, and then we can let it move back. When the HP is 0, we let it disappear.

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

nianshouMove () {
  // Update 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)
},

Our game rule is that the less time it takes, the more powerful it is, so we need to calculate the game time. Here, we take the time when Nian beast starts moving as the game start time. In addition, we also need to move in the opposite direction when Nian beast hits the wall. Therefore, here we judge the distance between Nian beast and the left and right sides of the screen. Once the defined value is reached, change the moving direction, That is, change the positive and negative of the movement value

Firecracker

This gadget is our firecracker and is also equivalent to our weapon. I wanted to find a fireworks tube to release fireworks. However, with limited resources, I'll make do with it. This small firecracker will constantly send out a light beam to hit the new year beast. Here about firecracker, you can add a movement event when you press the mouse, and let him move left and right.

The first step must be the movement of firecrackers. What we don't do is too complicated. Just let the mouse drag to move left and right. Don't let it move up and down because you have to face up and down with firecrackers in previous years.

Idea: click the firecracker with the mouse to add a mobile event to the whole area. If you don't add a mobile event to the firecracker, it is easy to exceed the scope of the firecracker because the mouse moves too fast, resulting in a bad game experience. When the mouse is raised, we will remove this event. As for movement, we need to define a clientx to store the distance between the mouse and the left side of the screen every time the mouse moves. When the mouse moves again, we use the distance between the current cursor and the left side to suggest the distance just stored, and then we can get the distance of mouse movement, and then we assign the change of this value to the margin left of firecracker

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

// Press the mouse to add a move event
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
},
// Lift the mouse to remove the move event
removeMove () {
  this.$refs.gemeWrap.onmousemove = null
},

bullet

Let's temporarily call the light beam emitted by firecrackers as bullets. The implementation principle of bullets is very simple. The bullets are fired regularly. When the bullets are fired, the horizontal coordinates of firecrackers are obtained, and then the screen height minus the height of firecrackers is used as the vertical coordinates. After generation, let the bullets run upward. When the distance from the bullet to the top is less than or equal to the height of Nian beast, Judge whether the horizontal coordinates of the bullet coincide with the horizontal coordinates of Nian beast. If so, deduct blood from Nian beast, play the hit sound effect, and remove the bullet. If not, remove the bullet when the bullet runs out of the screen.

Here we set a bullet flying speed. If you have played the game, you will find that it is difficult to hit at the beginning. Hahaha, this also increases the difficulty. Of course, if you answer the question correctly, the firing speed, attack speed and damage will increase accordingly.

createBulletInterval: null, // Timer for creating bullets
frequency: 5, // Firing bullet frequency
bulletSpeed: 10, // Missle Velocity 
damage: 2,// Bullet attack power
lastBulletTime: 0, // Last bullet fired

// Generate bullets
createBullet () {
  // 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'))
    // Bullet movement
    let bulletMove = () => {
      bullet.style.top = bullet.offsetTop - this.bulletSpeed + 'px'
      // If the distance between the bullet and the top is the height of Nian beast, judge whether the horizontal position of bullet and Nian beast coincide
      if (bullet.offsetTop <= 250 && bullet.offsetLeft >= this.nianshouLeft && bullet.offsetLeft <= this.nianshouLeft + 200) {
        // Nian beast loses blood
        this.nianshouHP -= this.damage
        this.$store.commit('playAudio', require('../assets/mp3/boom.wav'))
        if (this.nianshouHP <= 0) {
          this.nianshouHP = 0
          this.gameOver()
        }
        // The bullet disappeared
        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)
}

Since the requestAnimationFrame cannot set the interval time, here we will record the time of generating bullets when generating bullets. When the requestAnimationFrame runs next time, we will judge whether the interval meets our requirements for bullet frequency. If it is full, proceed to the next execution. If not, skip this execution.

problem

A major feature of this game is the addition of the question answering system. Otherwise, what's the meaning of fighting monsters in biubiubiu biu? The blood volume of Nian beast is 2021. It takes half a day to fight by the initial attack speed and damage. If you answer the question correctly, it will increase the buff and the ability to fight Nian beast will rise.

First, let's analyze the needs of the problem

  • The answer time of each question is 8 seconds, and 8 seconds will be displayed regardless of whether it is selected in advance
  • If you answer the right question, add buff
  • A wrong answer or no answer selected at the end of the countdown will show the correct answer
  • The interval between each question is 5 seconds
  • Every time a question is given, the question will be taken randomly from the question bank. The questions that have appeared will not be taken for the second time

Start with the simplest and extract questions from the question bank

questionJson: require('@/assets/data/question.json'), //Problem source data
questionData: [], // Current round of game question bank
questionList: [],// Problem list

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

Very simple. The next step is to add the countdown. First, add the countdown of topic interval. When a topic is added, display the countdown of 5 seconds, and then display the topic and start the countdown of answer

// Add show countdown
  let showCountDown = () => {
    data.showTime--
    if (data.showTime > 0) {
      setTimeout(showCountDown, 1000)
    } else {
      // At the end of the countdown, show the questions and start the countdown
      answerCountDown()
    }
  }

The next step is the countdown to the answer. The game sets 5 questions. At the end of each question, it will first judge whether the user answers. If there is no answer, the result will be automatically set as the wrong answer, and then judge whether the question reaches 5. If not, continue to add until it reaches 5.

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

Next, let's take a look at the dom structure of the question panel

<!-- Problem 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 in{{ question.answerTime }}Click the correct answer within seconds</p>
      </div>
      <div class="question-panel-title">problem {{ 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">◇</span>
          </div>
        </div>
        <div
          class="buff"
          v-if="question.result === question.question.answer"
        >
          Attack speed+1 Rate of fire+1 hurt+1
        </div>
        <div
          class="desc"
          v-if="
            question.result && question.result !== question.question.answer
          "
        >
          {{ question.question.desc }}
        </div>
      </div>
    </div>
  </div>

Let's look at the structure of the questions in the question bank

  {
    "title": "Which of the following is the astronaut of Shenzhou 13?",
    "option": [
      {
        "key": "A",
        "value": "Zhai Zhigang"
      },
      {
        "key": "B",
        "value": "Bo Ming Liu"
      },
      {
        "key": "C",
        "value": "Nie Haisheng"
      }
    ],
    "answer": "A",
    "desc": "The astronauts of Shenzhou 13 are Zhai Zhigang, Wang Yaping and ye Guangfu"
  },

Combined with our countdown and answer above, the complete structure of a question should be as follows

{
    question: question, // Questions in question bank
    answerTime: 9, // Answer the countdown,
    showTime: 6, // Show countdown
    result: null, // User answers
}

It's much easier to do this. We just need to assign the value of the option to result when we click the option again, and then judge whether the user answered the question and answered correctly according to the value of result.

Here is a line of code on the outermost dom structure

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

This determines whether the answer countdown is over. If it is not over, 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 game results will be displayed, and one of the user blessings will be randomly selected for display

The whole game is completed here. Due to the limited space, it is really impossible to explain every detail in detail. If a friend has any questions, please ask questions in the comment area or go to github or code cloud issue to pay a new year's call to you in advance! I wish you all success in your work, good health, family harmony, beauty and all the best!

Topics: Front-end Vue.js Game Development