每個(gè)人都喜歡經(jīng)典游戲。你們當(dāng)中有多少人還記得舊諾基亞手機(jī)上的貪吃蛇游戲?相信很多人都記得。這就是為什么本課我們決定使用HTML5重新創(chuàng)建它的原因。我們將使用一個(gè)很棒的開(kāi)源游戲開(kāi)發(fā)框架,稱為Phaser。
您將了解游戲狀態(tài)以及如何使用預(yù)加載,創(chuàng)建和更新方法。這是我們正在構(gòu)建的游戲的最終版本:
建立
在文末掃碼進(jìn)群下載包含游戲文件結(jié)構(gòu)的zip存檔。它包含游戲所需的所有圖像資產(chǎn),但沒(méi)有代碼。接下來(lái)我們將要寫(xiě)。
現(xiàn)在打開(kāi)index.html,為頁(yè)面添加標(biāo)題并創(chuàng)建指向所有JS文件的鏈接。稍后,要玩游戲,只需在瀏覽器中打開(kāi)此文件。
<!doctype?html>?<?html?>?<?頭?>?????<?meta??charset?=?“?UTF-8”?/>?????<?標(biāo)題?>?Snake?</?標(biāo)題?>?????<?腳本?src?=?“?assets?/?js?/?phaser.min.js”?></?script?>?????<?script??src?=?“?assets?/?js?/?menu.js”?></?script?>?????<?script??src?=?“?assets?/?js?/?game.js”?></?script?>?????<?script??src?=?“?assets?/?js?/?game_over.js”?></?script?>?????<?script??src?=?“?assets?/?js?/?main.js”?></?腳本?>?</?頭?>?<?正文?></?body?>?</?html?>
您的目錄應(yīng)如下所示:

游戲的組織方式
將Phaser中的狀態(tài)視為游戲的不同部分。以下是我們游戲的狀態(tài):
在菜單狀態(tài)。它由menu.js處理,并且僅顯示開(kāi)始圖像。單擊后,游戲?qū)⑥D(zhuǎn)換為“?游戲”狀態(tài)。
游戲狀態(tài)。它由game.js處理。這是游戲的實(shí)際游戲區(qū)域。您控制著蛇,吃了蘋(píng)果,玩得很開(kāi)心。死亡時(shí),您將過(guò)渡到Game_Over狀態(tài)。
Game_Over狀態(tài)。它顯示gameover.png并顯示您的最后得分。單擊它時(shí),您將轉(zhuǎn)換為游戲狀態(tài)。
main.js是我們的主要JavaScript文件。在這里我們將創(chuàng)建一個(gè)新的游戲?qū)嵗⑻砑右粋€(gè)菜單狀態(tài)。
1.加載圖像
目前,我們的游戲沒(méi)有任何作用。讓我們對(duì)Menu狀態(tài)進(jìn)行編碼,并使其顯示標(biāo)題屏幕。
在設(shè)置過(guò)程中,我們?cè)贖TML文件中包含了Phaser庫(kù)。這給我們提供了一個(gè)名為的全局對(duì)象Phaser。通過(guò)它,我們可以訪問(wèn)庫(kù)的構(gòu)建游戲的方法和功能。
現(xiàn)在,我們將使用全局Phaser對(duì)象,并創(chuàng)建一個(gè)新的游戲實(shí)例。這是代表我們整個(gè)游戲的對(duì)象。我們將狀態(tài)添加到它。
main.js
var?game;//創(chuàng)建一個(gè)新的游戲?qū)嵗?,?00像素,高450像素:?game?=?new?Phaser.Game(600,450,Phaser.AUTO,'');//第一個(gè)參數(shù)是如何調(diào)用我們的狀態(tài)。//第二個(gè)參數(shù)是一個(gè)對(duì)象,其中包含狀態(tài)功能?game.state?所需的方法。添加('Menu',Menu);game.state.start('Menu');
現(xiàn)在我們需要初始化菜單狀態(tài)對(duì)象。在menu.js中定義一個(gè)新對(duì)象并添加以下功能。當(dāng)狀態(tài)開(kāi)始時(shí),將首先調(diào)用預(yù)加載功能,加載我們游戲所需的所有資產(chǎn)。預(yù)加載完成后,將調(diào)用create,初始化游戲環(huán)境以及我們要在其上進(jìn)行的所有操作:
menu.js
var?Menu?=?{????預(yù)載:功能(}?{?????????//需要加載圖片,以便以后我們可以基于它們創(chuàng)建精靈。????????//第一個(gè)參數(shù)是圖像的引用方式,????????//第二個(gè)參數(shù)是文件的路徑。????????game.load.image('menu','./assets/images/menu.png');????},????創(chuàng)建:功能()?{?????????//添加一個(gè)精靈到你的游戲,這里的精靈將是游戲的標(biāo)志????????//參數(shù)為:X,Y,圖像名稱(見(jiàn)上文)????????這個(gè)?.add.sprite(0,0,“菜單”);????}};由于瀏覽器的安全性限制,要啟動(dòng)游戲,您需要本地運(yùn)行的Web服務(wù)器。換句話說(shuō),如果您只是雙擊index.html,那么它將不起作用。
如果一切都正確完成,則帶有“開(kāi)始”屏幕的頁(yè)面應(yīng)出現(xiàn)在瀏覽器中。

2.畫(huà)蛇
正如我們前面提到的,游戲狀態(tài)是實(shí)際游戲的發(fā)生位置。這也是我們畫(huà)蛇的地方。就像我們對(duì)Menu狀態(tài)所做的一樣,我們需要在main.js中向全局游戲?qū)ο笞?cè)Game狀態(tài)。您的代碼應(yīng)如下所示:
main.js
var?game;游戲=?新?Phaser.Game(600,450,Phaser.AUTO,'');game.state。添加('Menu',Menu);//添加游戲狀態(tài)。game.state。添加('Game',Game);game.state.start('Menu');
我們還想在menu.js中添加一些代碼,以使我們?cè)趩螕?strong>游戲狀態(tài)時(shí)啟動(dòng)游戲狀態(tài)。為此,我們將用一個(gè)按鈕替換精靈。添加按鈕與添加精靈基本相同,只需要提供一個(gè)單擊按鈕即可調(diào)用的功能。這是最終的menu.js代碼:
menu.js
var?Menu?=?{????預(yù)載:功能(}?{?????????//加載菜單所需的所有資源。????????game.load.image('menu','./assets/images/menu.png');????},????創(chuàng)建:功能()?{????????//添加菜單屏幕。????????//它將作為開(kāi)始游戲的按鈕。????????此?.add.button(0,0,'菜單',這?.startGame,此);????},????startGame:函數(shù)()?{????????//將狀態(tài)更改為實(shí)際游戲。????????這個(gè)?.state.start('Game');????}};現(xiàn)在,我們可以對(duì)游戲狀態(tài)進(jìn)行編碼并繪制蛇了。結(jié)構(gòu)類似于菜單狀態(tài)之一。
game.js
var蛇,蘋(píng)果,squareSize,得分,速度,????updateDelay,方向,new_direction,????addNew,游標(biāo),scoreTextValue,speedTextValue,?????textStyle_Key,textStyle_Value;var?Game?=?{????preload:function?()??{?????????//這里,我們加載了關(guān)卡所需的所有資源。????????//在我們的例子中,只有兩個(gè)正方形-一個(gè)用于蛇形,另一個(gè)用于蘋(píng)果。????????game.load.image('snake','./assets/images/snake.png');????????game.load.image('apple','./assets/images/apple.png');????},????create:function?()??{????????//通過(guò)在create函數(shù)中設(shè)置全局變量,我們?cè)谟螒蜷_(kāi)始時(shí)對(duì)其進(jìn)行初始化。????????//我們需要它們?cè)谌蚍秶鷥?nèi)可用,以便更新功能可以更改它們。????????蛇=?[];?????????????????????//這將作為一個(gè)堆棧工作,其中包含我們的蛇?????????蘋(píng)果的各個(gè)部分?=?{};?????????????????????//蘋(píng)果的對(duì)象;????????squareSize?=?15?;????????????????//正方形邊的長(zhǎng)度。我們的圖像是15x15像素。????????分?jǐn)?shù)=?0?;??????????????????????//游戲得分。????????速度=?0?;??????????????????????//?游戲速度。????????updateDelay?=?0?;????????????????//用于控制更新率的變量。????????方向=?'正確'?;????????????//我們的蛇的方向。????????new_direction?=?null?;???????????//用來(lái)存儲(chǔ)新方向的緩沖區(qū)。????????addNew?=?false?;?????????????????//食用蘋(píng)果后使用的變量。????????//設(shè)置用于鍵盤(pán)輸入的Phaser控制器。????????游標(biāo)=?game.input.keyboard.createCursorKeys();????????game.stage.backgroundColor?=?'#061f27'?;????????//生成初始的蛇形堆棧。我們的蛇將長(zhǎng)10個(gè)元素。????????//從X?=?150?Y?=?150開(kāi)始,并在每次迭代中增加X(jué)。????????對(duì)于(var?i?=?0?;?i?<?10?;?i?++){????????????snake?[i]?=?game.add.sprite(150?+?i?*?squareSize,150,'snake');??//參數(shù)是(X坐標(biāo),Y坐標(biāo),圖像)????????}????????//生成第一個(gè)蘋(píng)果。????????this.generateApple();????????//將文字添加到游戲頂部。????????textStyle_Key?=?{字體:“?bold?14px?sans-serif”,填充:“#46c0f9”,align:“?center”?};????????textStyle_Value?=?{字體:“粗體18px?sans-serif”,填充:“#fff”,對(duì)齊:“居中”?};????????//?得分。????????game.add.text(30,20,“SCORE”,textStyle_Key);????????scoreTextValue?=?game.add.text(90,18,score.toString(),textStyle_Value);????????//速度。????????game.add.text(500,20,“SPEED”,textStyle_Key);????????speedTextValue?=?game.add.text(558,18,speed.toString(),textStyle_Value);????},????update:function?()??{?????????//更新函數(shù)不斷被高速率調(diào)用(大約60fps),????????//每次都更新游戲場(chǎng)。????????//我們現(xiàn)在暫時(shí)將其留空。????},????generateApple:function?()?{????????//在網(wǎng)格上選擇一個(gè)隨機(jī)位置。????????//?X在0到585(39?*?15)????????之間//?Y在0到435(29?*?15)之間????????var?randomX?=?Math.floor(Math.random()*?40)*?squareSize,????????????randomY?=?Math.floor(Math.random()*?30)*?squareSize;????????//添加一個(gè)新蘋(píng)果。????????蘋(píng)果=?game.add.sprite(randomX,randomY,'apple');????}};這是蛇和蘋(píng)果的樣子:

3.運(yùn)動(dòng)與控制
為了使蛇移動(dòng),我們將使用game.js的更新功能。
首先,我們創(chuàng)建事件監(jiān)聽(tīng)器,以使用箭頭鍵控制蛇的方向。
實(shí)際的移動(dòng)有點(diǎn)復(fù)雜,因?yàn)楦碌挠|發(fā)速度非???,如果每次移動(dòng)蛇時(shí)都移動(dòng)它,那么最終將得到一個(gè)無(wú)法控制的快速爬行動(dòng)物。為了改變這一點(diǎn),我們建立了一個(gè)if語(yǔ)句,它使用名為updateDelay的計(jì)數(shù)器變量來(lái)檢查天氣,這是update()的第十次連續(xù)調(diào)用。
如果確實(shí)是第十次調(diào)用,我們將刪除蛇的最后一個(gè)正方形(堆棧中的第一個(gè)元素),根據(jù)當(dāng)前方向?yàn)槠滟x予新坐標(biāo),并將其放置在蛇的頭部前面(堆棧的頂部) 。代碼如下所示:
更新:function?()??{????//按下箭頭鍵,同時(shí)不允許非法改變方向,這會(huì)殺死玩家。????如果(cursors.right.isDown?&&?direction!=?'left')????{????????new_direction?=?'正確'?;????}????否則?if(cursors.left.isDown?&&?direction!=?'right')????{????????new_direction?=?'左'?;????}????否則?if(cursors.up.isDown?&&?direction!=?'down')????{????????new_direction?=?'向上'?;????}????否則?if(cursors.down.isDown?&&?direction!=?'up')????{????????new_direction?=?'向下'?;????}????//根據(jù)得分計(jì)算游戲速度的公式。????//得分越高,游戲速度越高,最高為10;????速度=?Math.min(10,Math.floor(score?/?5));????//在游戲屏幕上更新速度值。????speedTextValue.text?=?''?+速度;????//由于Phaser的更新功能的更新速率約為60?FPS,????//我們需要放慢速度以使游戲可玩。????//在每次更新調(diào)用時(shí)增加一個(gè)計(jì)數(shù)器。????updateDelay?++;????//僅當(dāng)計(jì)數(shù)器等于(10-游戲速度)時(shí)才進(jìn)行游戲。????//速度越高,執(zhí)行的頻率就越高,????//使蛇移動(dòng)得更快。????如果(updateDelay%(10?-speed)==?0){????????//蛇的運(yùn)動(dòng)????????var?firstCell?=?snake?[snake.length-?1?],????????????lastCell?=?snake.shift(),????????????oldLastCellx?=?lastCell.x,????????????oldLastCelly?=?lastCell.y;????????//如果已從鍵盤(pán)選擇了新的方向,請(qǐng)立即使其成為蛇的方向。????????如果(new_direction){????????????方向=?new_direction;????????????new_direction?=?null?;????????}????????//根據(jù)方向更改相對(duì)于蛇頭的最后一個(gè)單元格的坐標(biāo)。????????如果(方向==?'正確'){????????????lastCell.x?=?firstCell.x?+?15?;????????????lastCell.y?=?firstCell.y;????????}????????否則?如果(方向==?“左”){????????????lastCell.x?=?firstCell.x-?15?;????????????lastCell.y?=?firstCell.y;????????}????????否則,?如果(direction?==?'up'){????????????lastCell.x?=?firstCell.x;????????????lastCell.y?=?firstCell.y-?15?;????????}????????否則,?如果(direction?==?'down'){????????????lastCell.x?=?firstCell.x;????????????lastCell.y?=?firstCell.y?+?15?;????????}????????//將最后一個(gè)單元格放在堆棧的前面。????????//將其標(biāo)記為第一個(gè)單元格。????????snake.push(lastCell);????????firstCell?=?lastCell;????}}嘗試通過(guò)鍵盤(pán)上的箭頭鍵控制蛇。
4.碰撞檢測(cè)
蛇在運(yùn)動(dòng)場(chǎng)中自由漫游的游戲沒(méi)有什么好玩的。我們需要檢測(cè)蛇何時(shí)與墻壁,蘋(píng)果或本身接觸。這稱為碰撞檢測(cè)。
這通常是通過(guò)使用物理引擎完成的,Phaser框架支持其中的一些物理引擎。但是對(duì)于像這樣的簡(jiǎn)單游戲,它們太復(fù)雜了。相反,我們將通過(guò)比較坐標(biāo)來(lái)進(jìn)行自己的碰撞檢測(cè)。
在更新功能中,在移動(dòng)蛇的代碼之后,我們調(diào)用了許多方法。他們將比較坐標(biāo)以告訴我們是否發(fā)生了碰撞。
更新:功能()?{????????//蛇移動(dòng)????????//?...?????????//蛇移動(dòng)結(jié)束????????//如果吃了蘋(píng)果,則增加蛇的長(zhǎng)度。????????//在蛇的后面創(chuàng)建一個(gè)塊,并使用上一個(gè)最后一個(gè)塊的舊位置????????//(它與蛇的其余部分一起移動(dòng)了)。????????如果(addNew){????????????snake.unshift(game.add.sprite(oldLastCellx,oldLastCelly,'snake'));????????????addNew?=?false?;????????}????????//檢查蘋(píng)果是否碰撞。????????這個(gè)?.appleCollision();????????//檢查是否與自身沖突。參數(shù)是蛇的頭。????????這個(gè)?.selfCollision(firstCell);????????//檢查是否與墻壁碰撞。參數(shù)是蛇的頭。????????這個(gè)?.wallCollision(firstCell);????}},appleCollision:函數(shù)()?{????//檢查蛇的任何部分是否與蘋(píng)果重疊。????//如果蘋(píng)果在蛇的內(nèi)部產(chǎn)卵,則需要這樣做。????for(var?i?=?0?;?i?<snake.length;?i?++){?????????if(snake?[i]?.x?==?apple.x?&&?snake?[i]?.y?==?apple.y){????????????//下次蛇移動(dòng)時(shí),將在其長(zhǎng)度上添加一個(gè)新塊。????????????addNew?=?true?;????????????//銷毀舊蘋(píng)果。????????????apple.destroy();????????????//制作一個(gè)新的。????????????這個(gè)?.generateApple();????????????//增加分?jǐn)?shù)。????????????分?jǐn)?shù)++;????????????//刷新記分板。????????????scoreTextValue.text?=?score.toString();????????}????}},selfCollision:函數(shù)(頭)?{????//檢查蛇的頭部是否與蛇的任何部分重疊。????for(var?i?=?0?;?i?<snake.length-?1?;?i?++){?????????if(head.x?==?snake?[i]?.x?&&?head.y?==?snake?[i]?.y){????????????//如果是這樣,請(qǐng)通過(guò)屏幕進(jìn)入游戲。????????????game.state.start('Game_Over');????????}????}},wallCollision:函數(shù)(頭)?{????//檢查蛇的頭部是否在游戲區(qū)域的邊界內(nèi)。????如果(head.x>?=?600?||?head.x?<?0?||?head.y>?=?450?||?head.y?<?0){????????//如果不在,我們碰到了墻。通過(guò)屏幕轉(zhuǎn)到游戲。????????game.state.start('Game_Over');????}}當(dāng)蛇與蘋(píng)果碰撞時(shí),我們會(huì)增加蛇的分?jǐn)?shù)和長(zhǎng)度。但是當(dāng)與墻壁或蛇體發(fā)生碰撞時(shí),我們應(yīng)該結(jié)束游戲。為此,我們需要進(jìn)入Game_Over狀態(tài)。同樣,我們需要在main.js中注冊(cè)它。在該文件底部附近添加以下行:
main.js
game.state。添加('Game_Over',Game_Over);
game_over.js
var?Game_Over?=?{????預(yù)載:功能()?{?????????//加載此游戲屏幕所需的圖像。????????game.load.image('gameover','./assets/images/gameover.png');????},????創(chuàng)建:功能()?{????????//創(chuàng)建按鈕以像在菜單中一樣開(kāi)始游戲。????????此?.add.button(0,0,'GAMEOVER'?,這?.startGame,此);????????//添加有關(guān)上一場(chǎng)比賽得分信息的文本。????????game.add.text(235,350,“LAST?SCORE”,{?字體:“粗體16px的無(wú)襯線”,填充:“#46c0f9”?,對(duì)齊:“中心”?});????????game.add.text(350,348,score.toString(),{?字體:“粗體20像素?zé)o襯線”,填充:“#FFF”?,對(duì)齊:“中心”?});????},????startGame:函數(shù)()?{????????//將狀態(tài)更改回游戲。????????這個(gè)?.state.start('Game');????}};
OK!我們的游戲準(zhǔn)備好了!同時(shí)我也試玩了一下,挺不錯(cuò)的,那么今天的分享就到這里,想要這套游戲教程源碼的小伙伴可以掃描下方二維碼去我的前端學(xué)習(xí)交流群獲取,里面還會(huì)不定時(shí)放一些前端學(xué)習(xí)資料,碼字不易,點(diǎn)個(gè)贊再走哦!

好東西別私藏,趕緊分享給身邊的小伙伴哦!特別是剛學(xué)前端的小伙伴,趕緊實(shí)際測(cè)試一下,讓你很有成就感的項(xiàng)目!