其他
社区精选|「思否猫快跑」原生js小游戏
写在前面
游戏链接
https://simplerobort.github.io/sifoumaokuaipao/index.html#/
技术栈构成
由上可见js能够使用的游戏引擎还挺多,最近为了进军元宇宙在学threeJs,为了更好锻炼对于游戏的理解,因此没有使用任何游戏引擎(有轮子我偏不用,我造!)
采用的技术栈为: vue+vuex+vueRouter+原生js(人物的碰撞,跳跃,自由落体等)
实现过程与思考
将人物作为一个模块,将移动、死亡、跳跃相关代码逻辑放置在人物模块,主要是觉得这样更加合理,是因为有了人物才会有这个功能,所以这些功能应该和人物强绑定 将障碍物,触碰会死亡的障碍物作为模块复用 store管理公共状态:当前已过关卡、障碍物信息、人物是否正在移动相关信息 router管理页面:欢迎页面、每一个关卡、死亡页面、胜利页面 游戏核心模块:人物移动跳跃、物理碰撞
人物实现
<div class="human" :style="{left:Hleft,bottom:Hbottom}" ref="human">
<img :src="humanPic" alt="">
</div>
</template>
左右移动
监听键盘按下 监听键盘弹起 调用moveClock
document.onkeydown = () => {...
}
document.onkeyup = () => {...
}
this.moveClock()
},
如果已经是死亡状态就停止逻辑(因为死亡有动画,防止死亡动画内还能移动) goRight函数内将人物的向右移动状态改为true
if (this.isDead) return
const key = window.event.keyCode
switch (key) {
case 65:
// A
this.goLeft(0)
break
case 68:
// D
this.goRight(0)
break
case 87:
case 32:
// W
this.jump(8)
break
}
}
if (type === 0) {
this.changeStatus('isLeft', true) // changeStatus就是一个修改store的封装
} else if (type === 1) {
this.changeStatus('isLeft', false)
}
},
goRight: function (type) {
if (type === 0) {
this.changeStatus('isRight', true)
} else if (type === 1) {
this.changeStatus('isRight', false)
}
},
const key = window.event.keyCode
switch (key) {
case 65:
// A
this.goLeft(1)
break
case 68:
// D
this.goRight(1)
break
}
}
副作用拦截:在未移动状态或者死亡状态不执行,走return 游戏界面限制:当小人在左边或者最右边不允许继续走出 障碍物:判断是否碰到障碍物来决定是否死亡或者胜利或者停止移动 下坠:判断是否符合下坠条件 移动:最后执行移动
setInterval(() => {
const { human } = this.$refs
const { cantgo, freeFall } = this.$store.getters
const { stayIndex, humanInfo } = this.$store.state
const Hleft = parseFloat(this.Hleft)
const Hbottom = parseFloat(this.Hbottom)
// 因为计时器一直在走,不在左右移动状态或者在死亡状态时不执行逻辑
if (this.isDead || (!humanInfo.isLeft && !humanInfo.isRight)) return
// 当小人在最左边或者最右边时不允许在移动
const condition = humanInfo.isLeft
? human.offsetLeft <= 0
: humanInfo.isRight && (human.offsetLeft + 15) >= 1280
if (condition) {
this.Hleft = humanInfo.isLeft ? '0px' : '1265px'
return
}
// 判断是否碰到了障碍物来决定是走路还是死亡还是胜利还是停止
const left = humanInfo.isLeft ? Hleft - 5 : Hleft + 15
if (cantgo(left, Hbottom, 'stopArea')) return
if (cantgo(left, Hbottom, 'deadArea')) return this.gotDead()
if (cantgo(left, Hbottom, 'winArea')) return this.win()
// stayindex不是-1就代表当前踩的是障碍物而不是地面
// freefall判断有没有走出障碍物,走出就下坠
if (stayIndex !== -1 && freeFall(Hleft)) this.jump(0)
// 最后执行移动
const forWard = humanInfo.isLeft ? -2.5 : 2.5
this.Hleft = Hleft + forWard + 'px'
}, 10)
}
人物跳跃
拦截副作用:当已经在跳跃状态或者死亡状态不允许再次跳跃 修改跳跃状态:修改跳跃状态为真,将人物gif换成跳跃的gif 跳跃计算:执行一个递归函数jp
判断是否碰到禁止移动障碍物,触碰后如果是上升状态就将速度修正为0并且开始下降行为,如果是下降状态就停止跳跃行为
判断是否碰到死亡障碍物或者是胜利障碍物来决定是否死亡与进入胜利画面
判断这次位移后是否碰到地面,碰到就停止跳跃,若没有碰到就减速度并且递归jp函数直到碰到障碍物或者地面
// 如果死亡或者正在跳跃拒绝发起跳跃行为
if (this.isDead || this.$store.state.humanInfo.isJump) return
// 将跳跃状态改为真
this.changeStatus('isJump', true)
this.humanPic = require('@/assets/human/jump1.0.1.gif')
const jp = () => {
const { cantJump } = this.$store.getters
const jpheight = parseFloat(this.Hbottom)
const Hleft = parseFloat(this.Hleft)
// 判断上升或者下落时是否碰到障碍物触发特定行为
if (cantJump(speed > 0, jpheight, Hleft, 'stopArea')) {
// 当前处于下降就停止
if (speed <= 0) return this.jumpStop()
// 当前处于上升就直接将速度改为0变成下降
speed = 0
}
if (cantJump(speed > 0, jpheight, Hleft, 'deadArea')) return this.gotDead()
if (cantJump(speed > 0, jpheight, Hleft, 'winArea')) return this.win()
// 判断是否碰地来选择继续上升下降还是停止跳跃
const isTouchLand = jpheight + speed <= 187.61
this.Hbottom = isTouchLand ? '187.61px' : jpheight + speed + 'px'
if (isTouchLand) {
this.$store.commit('changestayIndex', -1)
return this.jumpStop()
}
// 没有碰到任何意外情况,减速度继续递归
speed -= 0.2
setTimeout(jp, 10)
}
jp()
},
障碍物实现
并且是以无状态组件的方式完成,目的是为了只需要关注障碍物的位置而不是我要用一次就要写一次逻辑
可以看到障碍物生成时只是把自己的宽高定位信息传给了store,store会把信息保存在数组里
<div id="rock" class="onTheLand" :style="{left:this.rleft,width:this.rwidth,height:this.rheight,bottom:this.rbottom}"></div>
</template>
<script>
export default {
name: 'rock',
props: ['rleft', 'rwidth', 'rheight', 'rbottom'],
created () {
this.init()
},
methods: {
init: function () {
let obj = {
rl: parseFloat(this.rleft.replace('px', '')),
rw: parseFloat(this.rwidth.replace('px', '')),
rh: parseFloat(this.rheight.replace('px', '')),
rb: parseFloat(this.rbottom.replace('px', ''))
}
this.$store.commit('addStopArea', obj)
}
}
}
</script>
<style scoped>
#rock {
border: 2px solid black;
/*background-color: black;*/
}
</style>
人物移动时根据障碍物暴露的三个api来完成碰撞计算:cantgo、cantJump、freeFall
进行障碍物判断的传的left其实是人物假设移动以后的left,判断的方式是循环判断移动后的left是否在障碍物内部,如果是的就代表碰到了
return function (left, feet, area) {
if (!state.humanInfo.isRight && !state.humanInfo.isLeft) return false
return state[area].some(
item => (
left >= item.rl &&
left < (item.rl + item.rw) &&
feet >= item.rb &&
feet < (item.rb + item.rh)
)
)
}
},
return function (isUp, feet, left, area) {
if (isUp) {
return state[area].some(
item => (
(feet + 60) >= item.rb &&
(feet + 60) < (item.rb + item.rh) &&
(left + 15) >= item.rl &&
left <= (item.rl + item.rw)
)
)
} else {
return state[area].some(
(item, index) => {
if (feet >= item.rb &&
feet <= (item.rb + item.rh + 6) &&
(left + 15) >= item.rl &&
left <= (item.rl + item.rw)
) {
state.stayIndex = index
return true
}
}
)
}
}
},
return function (left) {
if (state.isJump) return false
const isRightDown = state.humanInfo.isRight &&
left > (state.stopArea[state.stayIndex].rl + state.stopArea[state.stayIndex].rw)
const isLeftDown = state.humanInfo.isLeft &&
(left + 15) <= state.stopArea[state.stayIndex].rl
return isLeftDown || isRightDown
}
}
结束