Spring Festival Mini‑Game Development with Vue.js: Mechanics, Bullet Chat, Sound, and Quiz System
This article walks through the creation of a Spring Festival themed mini‑game using Vue.js, covering game rules, global settings, menu generation, sound handling, bullet‑chat implementation, enemy (NianShou) behavior, firecracker control, bullet mechanics, a timed quiz system, and end‑game handling, all illustrated with code snippets.
The author introduces a Spring Festival mini‑game built with Vue.js, inviting readers to try the game before reading the tutorial and encouraging them to submit bullet‑chat messages and victory wishes.
Game Rules : Players control a firecracker to attack a NianShou monster; questions appear periodically, and correct answers boost attack power. The game ends when the monster's HP reaches zero.
Global Configuration controls sound playback and bullet‑chat visibility:
setting: { isPlay: false, showBulletChat: true }Menu Generation uses v-for to iterate over menu items, attaching mouseover and click audio events:
<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>Menu items include start game, toggle sound on/off, and their visibility is determined by functions that react to the current sound state.
Sound Management defines a global background music object and a helper to play short audio effects only when sound is enabled:
window.backMusic = new Audio()
window.backMusic.src = require('../assets/mp3/back.mp3')
window.backMusic.loop = true
window.backMusic.load()
window.backMusic.currentTime = 127.2 playAudio (state, src) {
if (state.setting.isPlay) {
const audio = new Audio()
audio.src = src
audio.load()
audio.volume = .5
audio.play()
}
}Bullet‑Chat (Danmaku) uses ten fixed lanes to avoid overlap. Bullets are created on random lanes, and new bullets are spawned only after the previous one has fully entered the screen and a random spacing is satisfied.
ballistic: 0,
bulletSpeed: 2,
bulletInterval: [300, 500],
screenWidth: document.documentElement.clientWidth,
screenHeight: document.documentElement.clientHeight showBullet () {
let ballisticArr = [0,1,2,3,4,5,6,7,8,9]
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()
}Each bullet is a div positioned at the chosen lane and moves leftward with requestAnimationFrame , spawning the next bullet when enough space is available and removing itself when off‑screen.
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'
// ... set up movement and removal logic ...
}NianShou (Monster) is a simple element with HP and horizontal movement that reverses direction at screen edges:
<div class="nianshou" :style="'marginLeft:' + nianshouLeft + 'px'" v-show="nianshouHP">
HP: {{ nianshouHP }}
</div> nianshouMove () {
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)
}Firecracker (Player Weapon) follows the mouse horizontally; mouse events record the previous clientX and update margin‑left accordingly.
<div class="paozhu" ref="paozhu" @mousedown="addMove" :style="'marginLeft:' + paozhuLeft + 'px'">
<img src="../assets/paozhu.png" alt="" />
</div> addMove (e) {
e = e || window.event
this.clientX = e.clientX
this.$refs.gemeWrap.onmousemove = this.moveFunc
},
moveFunc (e) {
e = e || window.event
e.preventDefault()
let movementX = e.clientX - this.clientX
this.paozhuLeft += movementX
this.clientX = e.clientX
},
removeMove () {
this.$refs.gemeWrap.onmousemove = null
}Bullet (Projectile) is generated at the firecracker's position, moves upward, checks collision with NianShou, applies damage, plays hit sounds, and is removed when off‑screen or upon collision.
createBullet () {
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'))
// movement and collision logic using requestAnimationFrame
this.lastBulletTime = now
}
this.createBulletInterval = requestAnimationFrame(this.createBullet)
}Quiz System presents timed questions (8 s answer time, 5 s interval), randomly selects from a question pool without repetition, and grants buffs (attack speed, fire rate, damage) for correct answers. The question data structure includes title, options, answer key, and description.
{
"title": "以下哪位是神舟十三号航天员?",
"option": [
{"key": "A", "value": "翟志刚"},
{"key": "B", "value": "刘伯明"},
{"key": "C", "value": "聂海胜"}
],
"answer": "A",
"desc": "神舟十三号航天员是翟志刚、王亚平、叶光富"
}Each question object in the game includes the original question, countdown timers, and the player's result, enabling the UI to show countdowns, answer options, results, and buff messages.
Game End displays the final score and randomly shows a user‑submitted victory wish before concluding.
The article ends with an invitation for readers to ask questions in the comments or submit issues on GitHub, and a New Year greeting.
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.