vue新春游戏-年兽大作战,欢欢喜喜过大年(可在线体验)

this.gameBegin()

},

show: () => true

},

{

name: ‘打开声音(强烈建议)’,

clickHandle: () => {

this.$store.commit(‘tooglePlay’, true)

},

show: () => !this.$store.state.setting.isPlay

},

{

name: ‘关闭声音’,

clickHandle: () => {

this.$store.commit(‘tooglePlay’, false)

},

show: () => this.$store.state.setting.isPlay

}

],

菜单的每一项主要有三个属性,名称、点击事件和控制显示,因为有些菜单项需要根据实际情况决定是否显示,比如打开声音和关闭声音,需要根据当前声音是否打开来判断谁显示谁隐藏,如果我们定义数据的时候直接把控制声音的变量赋值给show,那么后续声音变化的时候,show是不会动态更新的,这里我们我们赋值给show一个函数,就可以达到冬天更新的目的了。

声音

游戏没有声音怎么行,这里引用的音乐是序曲,哈哈哈,是不是一下子就有年味了。游戏中的声音主要有两个类型,一种是长时间播放,需要控制播放暂停的,比如背景音乐,另一种是即时性的,比如菜单滑动声、子弹撞击声等,所以背景音乐的实例我们需要存储下来,而即时音效随用随建就行,我这里偷了个懒,没有写单独的声音配置文件,直接写vuex里了。

背景音乐

window.backMusic = new Audio()

window.backMusic.src = require(‘…/assets/mp3/back.mp3’)

window.backMusic.loop = true

window.backMusic.load()

window.backMusic.currentTime = 127.2 // 背景音乐默认定位到舒缓片段

这样我们在任何地方控制播放直接调用或更改window.backMusic就行了。

即时音效

playAudio (state, src) {

if (state.setting.isPlay) {

const audio = new Audio()

audio.src = src

audio.load()

audio.volume = .5

audio.play()

}

}

这里播放音效的时候需要判断当前的声音开关是否打开,如果打开的话在进行播放,注意,这里不能通过给单一的audio对象改变地址的方式播放不同的音效,因为如果在当前声音正在播放时候,修改音效地址会报错。

弹幕

这个创意是我在听春节序曲的背景音乐时想到的,因为一听这个就想到春晚,想到短片中全国各地的人民送祝福,于是我就想把这个加进来,结合背景音乐,是不是一下子感觉就来了。也希望大家可以送上自己的祝福,我也会把你的祝福更新到弹幕里的。这里的弹幕就只为了满足游戏的需求,不会太复杂。

首先,我们需要梳理一下弹幕的需求和注意点

  • 弹幕横向和纵向不能重叠

  • 两天弹幕之间的间隔最好可以随机

  • 弹幕超出屏幕要自动移除

首先说弹幕不能重叠的问题,弹幕纵向不能重叠的话,我们就需要有一个弹道的概念,也就是让每一条弹幕都有自己的轨道,各走各的,当然就不会重叠了。我这里是根据屏幕高度,分成了10个弹道,本来打算屏幕越大,弹道越多的,但是考虑到性能问题,就采用了这种方案。

image.png

其次就是弹幕横向的防止重叠,我百度的时候看到其他作者提到的追及问题什么的,奈何我是个学渣,没有看太明白,于是就自己想了解决办法,我们这里每条弹幕的移动速度是一样的,那需要考虑的就是每条弹幕出现的时机问题了,我们需要在同一弹道的前一条弹幕完全出现后,再生成下一条弹幕,中间可以加一个我们规定好范围的随机距离,这样更美观一点。

下面来看一下代码怎么实现的。

ballistic: 0, // 弹道数量

bulletSpeed: 2, // 弹幕速度

bulletInterval: [300, 500], // 弹幕间隔

screenWidth: document.documentElement.clientWidth, // 屏幕宽度

screenHeight: document.documentElement.clientHeight, // 屏幕高度

/**

  • @description: 展示弹幕

  • @param {*}

  • @return {*}

*/

showBullet () {

// 此处直接设定了10条弹道,也可根据屏幕高度和弹幕高度计算弹道数

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()

// this.createBullet(2)

},

我这里的方法是先设定好弹道数,然后把这些的弹道的序号放进一个数组,开始时直接从这个数组去取编号,往这个弹道放进去一个弹幕,然后循环,直到每一条弹道都被用完为止,那么问题来了,这时候我们每条弹道只有一条弹幕,怎么生成后续弹幕呢,这里的思路是在每一条弹幕移动的时候,判断自己的移动距离,当达到合适的距离时(自身完全出现在屏幕中并且距离屏幕右侧达到了我们设定的两条弹幕间的距离)就调用加载下一条弹幕的方法,并把自身的弹道编码传入,加上我们这里弹幕是匀速的,就不会有重叠的问题了。

/**

  • @description: 添加弹幕

  • @param {*} 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 // 是否已创建下一个弹幕

bullet.nextSpace = Math.random() * (this.bulletInterval[1] - this.bulletInterval[0]) + this.bulletInterval[0] // 下一个弹幕间隔

// 从弹幕库随机取弹幕

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)

// 弹幕移动

let bulletMove = () => {

bullet.style.left = bullet.offsetLeft - this.bulletSpeed + ‘px’

if (!bullet.createNext) {

// 如果弹幕距离屏幕右侧距离超出弹幕间隔,则加载下一条弹幕

if (bullet.offsetLeft < (this.screenWidth - bullet.offsetWidth - bullet.nextSpace)) {

this.createBullet(index)

bullet.createNext = true

}

}

// 如果弹幕距离右侧距离大于等于屏幕宽度,则移除弹幕

if (bullet.offsetLeft < (-bullet.offsetWidth)) {

this.$refs.bulletChat.removeChild(bullet)

} else {

requestAnimationFrame(bulletMove)

}

}

bulletMove()

}

这里我们引入了一个弹幕库,每次从中随机取一条,这样就避免旧弹幕无法被看到的问题了,另外大家也都看到了,这里用的定时方法是requestAnimationFrame,这个真的比setinterval要好,本项目基本所有用到动画的地方都用的这个,也建议大家都用这个方法代替setinterval,好处比较多,这里就不占字数了,大家感兴趣自行百度吧。

年兽

image.png

这个可爱的小东西就是我们的年兽了,年兽的组成很简单,一个小图标,加一个血量,然后我们让它来回动起来就可以了。当血量为0时候我们就让它消失。

class=“nianshou”

:style=“‘marginLeft:’ + nianshouLeft + ‘px’”

v-show=“nianshouHP”

HP: {{ nianshouHP }}

nianshouLeft: 0, // 年兽距离左边的距离

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)

},

我们的游戏规则是用时越少越厉害,所以我们需要计算游戏用时多少,这里我们以年兽开始移动时为游戏开始时间,另外我们还需要在年兽撞墙的时候往反方向运动,所以这里我们判断了年兽距离屏幕左边和右边的距离,一旦达到界定值的时候,则改变移动方向,也就是改变移动值的正负

炮竹

image.png

这个小玩意儿就是我们的炮竹了,也相当于我们的武器,我本来想找一个烟花筒来释放烟花的,奈何资源有限,就用这个将就吧。这个小炮竹会不断的发出光束去打年兽,这里关于炮竹,就是鼠标按下的时候添加移动事件,让他左右移动就可以了。

第一步肯定就是炮竹的移动,这个我们不做的太复杂,直接让鼠标拖动进行左右移动就行了,不让上下移动是为了你举着炮竹往年兽脸上怼。

思路,鼠标点击炮竹,给整个区域添加移动事件,不给炮竹添加移动事件时因为鼠标移动过快的话很容易超出炮竹的范围,造成不好的游戏体验,当鼠标抬起时,我们再把这个事件给移除。至于移动,我们需要先定义一个clientx,每次鼠标移动的时候存储鼠标距离屏幕左侧的距离,当鼠标再次移动的时候,我们用当前光标距离左侧的距离建议刚刚存储的,就可以得出鼠标移动的距离,然后我们把这个值的变化赋值给炮竹的margin-left

class=“paozhu”

ref=“paozhu”

@mousedown=“addMove”

:style=“‘marginLeft:’ + paozhuLeft + ‘px’”

clientX: 0, // 鼠标上次的位置

paozhuLeft: 0 // 炮竹距离左边的距离

// 鼠标按下,添加移动事件

addMove (e) {

e = e || window.event

this.clientX = e.clientX

this.clientY = e.clientY

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

},

子弹

我们暂且称炮竹发出的光束为子弹吧,子弹的实现原理很简单,定时发射子弹,发射子弹时获取炮竹的横向坐标,再以屏幕高度减去炮竹高度为纵向坐标,生成之后让子弹往上跑就行了,当子弹距离顶部距离小于等于年兽的高度时,判断子弹的横向坐标是否和年兽的横向坐标重合,如果重合就对年兽扣血,播放击中音效,移除子弹,如果未重合,则在子弹跑出屏幕时移除子弹。

这里我们设置了一个子弹飞行速度,如果你玩过了游戏,一定会发现,刚开始不好射中吧,哈哈哈,这也算是增加了难度,当然,如果答对了问题,射速,攻速,伤害都会相应的增加。

image.png

createBulletInterval: null, // 创建子弹的定时器

frequency: 5, // 发射子弹频率

bulletSpeed: 10, // 子弹飞行速度

damage: 2,// 子弹攻击力

lastBulletTime: 0, // 上次发射子弹时间

// 生成子弹

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’))

// 子弹移动

let bulletMove = () => {

bullet.style.top = bullet.offsetTop - this.bulletSpeed + ‘px’

// 如果子弹距离顶部的距离为年兽的高度时,判断子弹和年兽的水平位置是否重合

if (bullet.offsetTop <= 250 && bullet.offsetLeft >= this.nianshouLeft && bullet.offsetLeft <= this.nianshouLeft + 200) {

// 年兽掉血

this.nianshouHP -= this.damage

this.$store.commit(‘playAudio’, require(‘…/assets/mp3/boom.wav’))

if (this.nianshouHP <= 0) {

this.nianshouHP = 0

this.gameOver()

}

// 子弹消失

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)

}

由于requestAnimationFrame不能设置间隔时间,所以这里我们就在生成子弹的时候记录下生成子弹的时间,在requestAnimationFrame下一次运行的时候,判断时间间隔是否满足我们对子弹频率的要求,如果满足则往下执行,如果不满足跳过本次执行。

问题

本游戏的一大特色,就是加入了答题系统,否则一直在那biubiubiu的打怪兽有啥意思呢,年兽的血量为2021,靠初始攻速和伤害得打半天,如果答对问题,则会增加buff,打年兽能力蹭蹭的往上涨。

首先来分析一下问题的需求

  • 每道题的答题时间是8秒钟,无论是否提前选择均展示8秒

  • 答对题目则增加buff

  • 答错或者在倒计时结束未选择答案将展示正确答案

  • 每道题的间隔时间是5秒钟

  • 每次出题从题库随机取题,出现过的题目不会第二次抽取

先从最简单的开始,从题库抽取题目

questionJson: require(‘@/assets/data/question.json’), //问题源数据

questionData: [], // 本轮游戏题库

questionList: [],// 问题列表

let dataLength = this.questionData.length

let randomIndex = Math.floor(Math.random() * dataLength)

let question = this.questionData.splice(randomIndex, 1)[0]

很简单,接下来就是添加倒计时,先加的是题目间隔倒计时,在一道题目被添加时候,展示5秒钟倒计时,然后展示题目并开始答题倒计时

image.png

// 添加展示倒计时

let showCountDown = () => {

data.showTime–

if (data.showTime > 0) {

setTimeout(showCountDown, 1000)

} else {

// 倒计时结束,展示问题并开始答题倒计时

最后

如果你已经下定决心要转行做编程行业,在最开始的时候就要对自己的学习有一个基本的规划,还要对这个行业的技术需求有一个基本的了解。有一个已就业为目的的学习目标,然后为之努力,坚持到底。如果你有幸看到这篇文章,希望对你有所帮助,祝你转行成功。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值