close
今天小編為大家帶來的是社區作者Yangπ的文章,讓我們一起來怎麼製作一個原生js小遊戲吧~

寫在前面


SegmentFault 思否的吉祥物是一隻獨一無二、特立獨行、熱愛自由的(>^ω^<)獨角貓,也是社區的首席摸魚官。於是本次作品是以思否貓為原型搭配原生js完成了一個類似跑酷的小遊戲

遊戲鏈接

https://simplerobort.github.io/sifoumaokuaipao/index.html#/


技術棧構成


(javascript做遊戲可用的引擎:1、Babylon.js;2、Three.js;3、Turbulenz;4、Famo.us;5、PlayCanvas.js;6、Goo Engine;7、CooperLicht;8、Voxel等。)
由上可見js能夠使用的遊戲引擎還挺多,最近為了進軍元宇宙在學threeJs,為了更好鍛煉對於遊戲的理解,因此沒有使用任何遊戲引擎(有輪子我偏不用,我造!)
採用的技術棧為: vue+vuex+vueRouter+原生js(人物的碰撞,跳躍,自由落體等)

實現過程與思考


設計思路

效果圖如下(障礙物很潦草哈哈)

將人物作為一個模塊,將移動、死亡、跳躍相關代碼邏輯放置在人物模塊,主要是覺得這樣更加合理,是因為有了人物才會有這個功能,所以這些功能應該和人物強綁定
將障礙物,觸碰會死亡的障礙物作為模塊復用
store管理公共狀態:當前已過關卡、障礙物信息、人物是否正在移動相關信息
router管理頁面:歡迎頁面、每一個關卡、死亡頁面、勝利頁面
遊戲核心模塊:人物移動跳躍、物理碰撞

人物實現


人物樣式

人物的靜止、跳躍、死亡使用gif,通過雙向綁定src,根據邏輯替換就完成了任務狀態的變化

<template><divclass="human":style="{left:Hleft,bottom:Hbottom}"ref="human"><img:src="humanPic"alt=""></div></template>

左右移動
從人物樣式的代碼截圖可以看到,人物的移動其實只是將人物的left與bottom雙向綁定到組件狀態中,在之後便可以實現通過快速修改left與bottom來完成人物的移動

左右移動的邏輯:

在人物組件初始化時會調用moveOpen並且moveOpen只做了三件事

監聽鍵盤按下
監聽鍵盤彈起
調用moveClock

moveOpen:function(){document.onkeydown=()=>{...}document.onkeyup=()=>{...}this.moveClock()},

下面詳細聊聊這三件事內都做了什麼

1.監聽鍵盤按下

假設我們現在按住了D鍵移動,其實做了這兩件事
如果已經是死亡狀態就停止邏輯(因為死亡有動畫,防止死亡動畫內還能移動)
goRight函數內將人物的向右移動狀態改為true

document.onkeydown=()=>{if(this.isDead)returnconstkey=window.event.keyCodeswitch(key){case65://Athis.goLeft(0)breakcase68://Dthis.goRight(0)breakcase87:case32://Wthis.jump(8)break}}
goLeft:function(type){if(type===0){this.changeStatus('isLeft',true)//changeStatus就是一個修改store的封裝}elseif(type===1){this.changeStatus('isLeft',false)}},goRight:function(type){if(type===0){this.changeStatus('isRight',true)}elseif(type===1){this.changeStatus('isRight',false)}},

2.監聽鍵盤彈起

彈起只做了一件事:將人物的對應的移動狀態改為false

document.onkeyup=()=>{constkey=window.event.keyCodeswitch(key){case65://Athis.goLeft(1)breakcase68://Dthis.goRight(1)break}}

3.調用moveClock

moveclock其實就是開啟了一個定時器,每次執行4件事

副作用攔截:在未移動狀態或者死亡狀態不執行,走return
遊戲界面限制:當小人在左邊或者最右邊不允許繼續走出
障礙物:判斷是否碰到障礙物來決定是否死亡或者勝利或者停止移動
下墜:判斷是否符合下墜條件
移動:最後執行移動

moveClock:function(){setInterval(()=>{const{human}=this.$refsconst{cantgo,freeFall}=this.$store.gettersconst{stayIndex,humanInfo}=this.$store.stateconstHleft=parseFloat(this.Hleft)constHbottom=parseFloat(this.Hbottom)//因為計時器一直在走,不在左右移動狀態或者在死亡狀態時不執行邏輯if(this.isDead||(!humanInfo.isLeft&&!humanInfo.isRight))return//當小人在最左邊或者最右邊時不允許在移動constcondition=humanInfo.isLeft?human.offsetLeft<=0:humanInfo.isRight&&(human.offsetLeft+15)>=1280if(condition){this.Hleft=humanInfo.isLeft?'0px':'1265px'return}//判斷是否碰到了障礙物來決定是走路還是死亡還是勝利還是停止constleft=humanInfo.isLeft?Hleft-5:Hleft+15if(cantgo(left,Hbottom,'stopArea'))returnif(cantgo(left,Hbottom,'deadArea'))returnthis.gotDead()if(cantgo(left,Hbottom,'winArea'))returnthis.win()//stayindex不是-1就代表當前踩的是障礙物而不是地面//freefall判斷有沒有走出障礙物,走出就下墜if(stayIndex!==-1&&freeFall(Hleft))this.jump(0)//最後執行移動constforWard=humanInfo.isLeft?-2.5:2.5this.Hleft=Hleft+forWard+'px'},10)}
人物跳躍
人物跳躍為了仿真逐漸變慢直到下降落地的過程採用了遞歸遞減

人物跳躍主要做了三件事

攔截副作用:當已經在跳躍狀態或者死亡狀態不允許再次跳躍
修改跳躍狀態:修改跳躍狀態為真,將人物gif換成跳躍的gif
跳躍計算:執行一個遞歸函數jp

jp函數內部主要做了3件事

判斷是否碰到禁止移動障礙物,觸碰後如果是上升狀態就將速度修正為0並且開始下降行為,如果是下降狀態就停止跳躍行為

判斷是否碰到死亡障礙物或者是勝利障礙物來決定是否死亡與進入勝利畫面

判斷這次位移後是否碰到地面,碰到就停止跳躍,若沒有碰到就減速度並且遞歸jp函數直到碰到障礙物或者地面


jump:function(speed){//如果死亡或者正在跳躍拒絕發起跳躍行為if(this.isDead||this.$store.state.humanInfo.isJump)return//將跳躍狀態改為真this.changeStatus('isJump',true)this.humanPic=require('@/assets/human/jump1.0.1.gif')constjp=()=>{const{cantJump}=this.$store.gettersconstjpheight=parseFloat(this.Hbottom)constHleft=parseFloat(this.Hleft)//判斷上升或者下落時是否碰到障礙物觸發特定行為if(cantJump(speed>0,jpheight,Hleft,'stopArea')){//當前處於下降就停止if(speed<=0)returnthis.jumpStop()//當前處於上升就直接將速度改為0變成下降speed=0}if(cantJump(speed>0,jpheight,Hleft,'deadArea'))returnthis.gotDead()if(cantJump(speed>0,jpheight,Hleft,'winArea'))returnthis.win()//判斷是否碰地來選擇繼續上升下降還是停止跳躍constisTouchLand=jpheight+speed<=187.61this.Hbottom=isTouchLand?'187.61px':jpheight+speed+'px'if(isTouchLand){this.$store.commit('changestayIndex',-1)returnthis.jumpStop()}//沒有碰到任何意外情況,減速度繼續遞歸speed-=0.2setTimeout(jp,10)}jp()},

障礙物實現

障礙物初始化
目前共有三種類型的障礙物:死亡障礙物、停止障礙物、勝利障礙物
並且是以無狀態組件的方式完成,目的是為了只需要關注障礙物的位置而不是我要用一次就要寫一次邏輯
可以看到障礙物生成時只是把自己的寬高定位信息傳給了store,store會把信息保存在數組裡

<template><divid="rock"class="onTheLand":style="{left:this.rleft,width:this.rwidth,height:this.rheight,bottom:this.rbottom}"></div></template><script>exportdefault{name:'rock',props:['rleft','rwidth','rheight','rbottom'],created(){this.init()},methods:{init:function(){letobj={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><stylescoped>#rock{border:2pxsolidblack;/*background-color:black;*/}</style>

障礙物碰撞原理

人物在每次移動時會調用store提供的api來判斷是否碰到某一類型的障礙物,所以只需要實現障礙物碰撞的計算判斷即可
人物移動時根據障礙物暴露的三個api來完成碰撞計算:cantgo、cantJump、freeFall

cantgo是人物左右移動時判斷是否碰到障礙物的api,傳入三個參數:人物的left、人物的bottom、需要判斷的障礙物類型
進行障礙物判斷的傳的left其實是人物假設移動以後的left,判斷的方式是循環判斷移動後的left是否在障礙物內部,如果是的就代表碰到了

cantgo:function(state){returnfunction(left,feet,area){if(!state.humanInfo.isRight&&!state.humanInfo.isLeft)returnfalsereturnstate[area].some(item=>(left>=item.rl&&left<(item.rl+item.rw)&&feet>=item.rb&&feet<(item.rb+item.rh)))}},

cantJump是人物跳躍時是否碰到障礙物的api,與cantgo同理,只是新增一個是否在上升階段,因為降落階段如果碰到了障礙物停止,會保存障礙的index到stayindex,這個值在freeFall會用到

cantJump:function(state){returnfunction(isUp,feet,left,area){if(isUp){returnstate[area].some(item=>((feet+60)>=item.rb&&(feet+60)<(item.rb+item.rh)&&(left+15)>=item.rl&&left<=(item.rl+item.rw)))}else{returnstate[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=indexreturntrue}})}}},

freeFall是在障礙物上移動時判斷是否走出障礙物的api

通過stayIndex判斷在哪個障礙物上,在判斷left是否小於障礙物左側或者大於右側

freeFall:function(state){returnfunction(left){if(state.isJump)returnfalseconstisRightDown=state.humanInfo.isRight&&left>(state.stopArea[state.stayIndex].rl+state.stopArea[state.stayIndex].rw)constisLeftDown=state.humanInfo.isLeft&&(left+15)<=state.stopArea[state.stayIndex].rlreturnisLeftDown||isRightDown}}

結束


前端一昧的開發必定枯燥,要能夠從枯燥的生活中找到你工作的樂趣

點擊左下角閱讀原文,到SegmentFault 思否社區和文章作者展開更多互動和交流,「公眾號後台「回復「入群」即可加入我們的技術交流群,收穫更多的技術文章~

-END -

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()