天天看點

剛學會 TypeScript, 順手做個貪吃蛇小遊戲

📢 大家好,我是小丞同學,這篇文章将帶你制作一個貪吃蛇小遊戲 📢 非常感謝你的閱讀,不對的地方歡迎指正 🙏 📢 願你生活明朗,萬物可愛

最近在學習中,再次遇到了貪吃蛇的案例,之前剛學 <code>javascript</code> 的時候就有遇到過,趁着這段時間有一點點時間,就跟着做了一下,這篇文章将手把手帶你實作一個貪吃蛇的小遊戲,難度不會很大,嘻嘻

可以從這個案例中學到以下幾點:

面向對象程式設計、<code>this</code> 指向問題、<code>webpack</code> 簡單的配置、

剛學會 TypeScript, 順手做個貪吃蛇小遊戲

需要實作的功能有以下:

頁面布局

随機生成食物

分數統計(吃食物數量)

等級提升(加速)

蛇成長

事件監測

撞身檢測

撞壁檢測

結束判斷

做一個簡單的布局,這裡主要采用的是 <code>less</code> 和 <code>flex</code> 布局結合

比較有意思的幾點

在布局時,采用了全局變量 <code>bg-color</code> 來定義全局顔色,為代碼增加了更多的可擴充性

全局采用了 <code>css3</code> 中的盒模型 <code>border-box</code> ,避免了由于邊框以及邊距對盒原大小造成的影響

在繪制蛇身時,需要通過在容器内添加 <code>div</code> 标簽的方式來設定,蛇的長度,是以在布局時,需要對容器内的 <code>div</code> 标簽單獨設定樣式

對于食物的樣式,采用的是 <code>flex</code> 加一個小小的旋轉

對每個 div 設定旋轉一定的角度,好看一點點

這裡需要注意的是:由于我們的蛇身以及食物都是需要移動的,我們需要将它們設定為絕定定位方式,并注意父盒子開啟相對定位

我們先梳理一下,食物需要先什麼屬性或者方法吧

每個食物要有一個位置,我們通過 <code>x</code> 和 <code>y</code> 屬性定位

同時我們需要一個能夠随機生成食物位置的方法

在這裡我們建立了一個 <code>food</code> 類,用來定義食物的位置

首先聲明了一個 <code>element</code> 屬性,指定為 <code>htmlelement</code>,在<code>constructor</code> 中需要擷取到我們的 <code>food</code> 元素指派給 <code>element</code> 屬性

這裡由于 <code>ts</code> 的文法檢查機制比較嚴格,我們需要在擷取節點的最後加上一個 <code>!</code> ,表示信任此處的元素擷取

這裡 <code>ts</code> 其實是做了預判,它擔心我們擷取不到這個節點而出錯,習慣就好,加個 <code>!</code>

在擷取食物坐标的方法中,我們采用了 <code>getter</code> 取值函數來取值,我們就可以像使用普通變量一樣來擷取 <code>x</code> 和 <code>y</code> 值

由于每次食物被吃了之後,我們都需要生成一個新的食物,其實我們也隻是讓食物換一個位置而已,始終都是同一個 <code>food</code> 節點,這裡我們采用的是 <code>random</code> 來生成一個 <code>0-29</code> 的随機數,然後取10倍,這樣就能将位置選擇為随機的 <code>10</code> 的倍數,同時在地圖範圍之内

在這裡我們還有很多可以改進的地方,例如我門采用了 <code>29</code> 純數字,這不利于我們對地圖的更改,當地圖發生改變時,我們需要修改源碼才能改善代碼,這不大好,我們可以用一個變量來儲存噢

在寫好 <code>food</code> 類之後,我們再來寫個簡單的 <code>scorepanel</code> 類,用來設定底部的計分和等級

我們需要有一個分數記錄,一個等級記錄,以及修改它們的方法

為了提高可擴充性,我們需要兩個變量來控制限制的最大等級,以及達到多少分更新

我們建立了一個 <code>scorepanel</code> 類

在這個類中,我們預先設定了很多的變量,在 <code>ts</code> 中我們需要設定它們的使用類型

在這裡我們設定了加分的方法

當我們調用這個函數時,就可以實作分數的增加,然後我們需要對目前的分數進行判斷,當分數達到我們設定的更新分數時,我們調用類中的 <code>levelup</code> 方法,讓目前的等級提升

在定義完了基本的周邊功能後,我們需要正式的對蛇開始進攻了

我們先建立一個 <code>snake</code> 類,用來設定蛇自身的特性,比如,位置、長度

首先我們需要設定一些變量,用來存儲我們的節點

在 <code>ts</code> 中,我們盡量設定好,以確定我們的變量不會被我們誤用導緻錯誤

我們再來定義 <code>getter</code> 和 <code>setter</code> 方法,用來擷取蛇頭的位置,以及設定蛇頭的位置

為什麼要是蛇頭呢?

我們需要通過蛇頭的移動方向來驅動這個蛇身的移動,因為每個蛇身塊都是跟随着上一塊蛇身的

(<code>set</code> 中有很多判斷,太長了,影響篇幅)

設定好 <code>set</code> 和 <code>get</code> 方法後,我們需要寫一個能夠使蛇成長的方法,所謂的成長不過就是讓 <code>snake</code> 節點中添加多一個 <code>div</code> 元素

小科普

<code>insertadjacenthtml()</code> 方法将指定的文本解析為 <code>element</code> 元素,并将結果節點插入到dom樹中的指定位置。它不會重新解析它正在使用的元素,是以它不會破壞元素内的現有元素。這避免了額外的序列化步驟,使其比直接使用 <code>innerhtml</code> 操作更快。 指定位置有以下幾個 <code>'beforebegin'</code>:元素自身的前面。 <code>'afterbegin'</code>:插入元素内部的第一個子節點之前。 <code>'beforeend'</code>:插入元素内部的最後一個子節點之後。 <code>'afterend'</code>:元素自身的後面。

現在我們的蛇已經能夠添加身體了,但是我們沒有添加控制蛇移動的方法,沒有辦法來展示這個效果

我們繼續來看看如何使得蛇能夠移動?

我們采用鍵盤的方向鍵來控制蛇的移動方向,前面也有提到整個蛇的移動是通過蛇頭的驅動的,是以我們先實作控制蛇頭的移動

首先我們需要建立一個 <code>gamecontrol</code> 類,作為這個遊戲的控制器,用來控制蛇的移動

首先我們需要有一個鍵盤響應事件,用來擷取使用者的鍵盤事件,同時我們需要對按鍵進行判斷,是否是能夠控制蛇移動的四個鍵

是以我們可以編寫兩個函數 <code>keydownhandle</code> 鍵盤事件響應函數 、<code>run</code> 函數主要制器,判斷使用者按下的是什麼鍵執行對應變化

我們可以将這兩個函數封裝到 <code>init</code> 函數中,作為初始化函數一并啟動

在這個函數裡,由于我們需要采用 <code>ts</code> 的檢查機制,我們可以将事件回調分離成一個函數,但是由于這裡的回調調用對象是 <code>document</code> ,我們需要手動更改 <code>this</code> 的指向

我們在 <code>keydownhandle</code> 中處理鍵盤事件,通過一個 <code>direaction</code> 變量來記錄目前的按鍵

根據 <code>direction</code> 來判斷 蛇移動的方向

我們更改了 <code>x</code>、<code>y</code> 值後,我們需要将它重新指派給 <code>snake</code> 中的對應值,由于我們設定了 <code>setter</code> 函數,我們可以直接指派

我們通過對四個方向鍵的 <code>switch</code> 判斷,我們使得我們能夠控制蛇的移動,但是現在這樣還不足以達到不斷移動的效果,我們需要實作按下一個方向鍵後,就不停的向一個方向移動,是以我們可以在 <code>run</code> 中開啟一個定時器,使得它能夠遞歸的調用 <code>run</code>

由于我們的蛇有死亡機制,我們需要預先判斷以下,這裡也存在着 <code>this</code> 指向的問題,我們需要手動調整指向目前的類

在處理到這一步時,我們的蛇頭已經能夠移動了

剛學會 TypeScript, 順手做個貪吃蛇小遊戲

現在我們的蛇頭已經能夠移動了,我們可以去觸碰食物以及任何地方了,我們現在需要檢查是否吃到食物,吃到食物會怎麼樣,執行什麼函數

在檢查是否吃到食物的函數中,我們需要兩個參數,也就是蛇頭的位置,用來判斷是否和食物重疊,如果重疊則改變食物的位置,得分,并且身體加一

現在我們的蛇已經能夠吃食物了,但是我們會發現吃完食物後,它的身體不會和它一起走,而是定位到了左上角,是以我們需要處理蛇身移動的問題

由于涉及到 <code>snake</code> 本身的特性,是以我們回到 <code>snake</code> 類中編寫

我們通過循環,從蛇的最後一個蛇塊開始周遊,讓它的位置變成前一個蛇塊的位置

這樣就能一個接着一個移動了,不了解的可以想一想噢~

在這段代碼中,遇到了很多類型斷言的問題,由于 <code>ts</code> 檢查機制中不确定數組元素中有沒有 <code>offset</code> 類方法,是以會給我們報錯提示

當我們的蛇頭撞到牆時,我們需要結束遊戲,是以我們需要添加一點判斷,同時由于蛇隻能往一個方向走,是以我們需要優化以下代碼,不需要每次都調用 <code>set x</code> 和 <code>set y</code> ,當新值和舊值相同時,我們可以直接傳回

當撞牆時,我們抛出一個錯誤,然後可以在 <code>gamecontrol</code> 中采用 <code>try...catch</code> 來捕獲這個錯誤,做出訓示

同時結束蛇的生命

由于我們的蛇不能掉頭,是以我們需要判斷以下使用者想反向走時,對這個事件進行處理

我們繼續在設定值的函數中添加代碼

首先隻有一個身體的時候,我們是不需要考慮的,是以我們先要判斷是否有第二個蛇身的存在,同時最關鍵的一點是,這個蛇身的位置是不是和我們即将要行走的 <code>value</code> 值相等

什麼意思呢?

在蛇移動的時候,第二節蛇身的位置應該是第一節的位置,蛇頭的位置是<code>value</code> 的位置,當蛇頭反向時,它的值就會變成第二節身體的位置

剛學會 TypeScript, 順手做個貪吃蛇小遊戲

畫個圖好了解一點,圓圈表示蛇頭即将到達的位置,右邊的方塊是蛇頭

是以我們添加這段代碼,當滿足掉頭條件時,我們繼續讓它前進

當蛇吃到自己時,需要結束遊戲,是以我們需要檢測是否吃到自己的身體

我們需要周遊以下蛇身的所有位置,與蛇頭的位置進行比較,如果有和蛇頭相同的位置,則說明蛇頭吃到蛇身了

由于這裡我們需要多次類型斷言,就提取出來單獨斷言了

整個貪吃蛇遊戲的架構就這麼多了,在寫這篇文章的時候,可以有一些代碼篇幅過長,對代碼有一點的縮減,可能會影響到閱讀或者了解,請見諒

從這個案例中,簡單的對 <code>typescript</code> 有了一定的認知,但仍然有很多的知識沒有被涉及到,感覺這個案例不大行,還需要再練習一下。總的來說,<code>typescript</code> 相對于 <code>javascipt</code> 來說有很多的限制,這些限制讓潛在的未知 <code>bug</code> 都顯示了出來,有助于代碼的維護同時能夠讓開發者減少後期找 <code>bug</code> 的苦惱

自己對于 <code>typescript</code> 還有很多未探索的地方,繼續努力吧,也歡迎大家提出自己的意見,或者提一點點的建議,讓我們一起成長吧!

非常感謝您的閱讀,歡迎提出你的意見,有什麼問題歡迎指出,謝謝!🎈

繼續閱讀