基於 EasyX 庫開發經典90坦克大戰遊戲


寫在前面

昨天晚上突發奇想,想設計一款小遊戲,自己設計就算了(O(∩_∩)O),還是來款經典的,在網上找到了奇牛學院 Martin 老師的視頻課程,做了一晚上,基本的開發現在已經完成了。遊戲開發與普通的程序開發設計還是有一些區別的,因爲它的算法設計和實現更多樣化,本篇博文就詳細此款遊戲的實現思路與算法設計,爲自己做個記錄~


安裝EasyX圖形庫

EasyX 是針對 C++ 的圖形庫,可以幫助我們快速上手圖形和遊戲編程,官網安裝即可:https://easyx.cn/

現在最新版本已經適配到 VS2019,注意安裝時要把幫助手冊安裝上,這樣便於我們快速查閱EasyX爲我們提供的各種庫函數接口。

本項目就是基於EasyX庫開發90坦克大戰遊戲。


項目需求與概要設計

項目需求

實現一款和經典的《90坦克大戰》一樣的遊戲,任務是消滅敵對坦克,保護己方領地。防止敵方打破我方圍牆而把Boss鷹打敗。

下面是此款遊戲的實現界面:

下面是成功的情景:己方坦克消除了出現在地圖上的所有敵對坦克(不一定和敵對坦克的總量相同,有可能下臺坦克還沒有出場,地圖中的所有坦克就被全部消滅)
在這裏插入圖片描述

下面是失敗的情形之一:己方坦克被敵對坦克擊中(還有一種是Boss被擊中,之後的講解中會演示這種情景)
在這裏插入圖片描述

概要設計

整體概要設計圖如下:
在這裏插入圖片描述


遊戲初始化與開始界面設計

遊戲初始化部分,主要是在遊戲進入的菜單界面設計。如下圖所示:
在這裏插入圖片描述
遊戲的初始化主要完成以下幾個模塊:

  • 畫布:繪圖環境
  • Logo:美工圖片,遊戲標誌
  • 按鈕:實現“說明”和“開始”導航
  • 說明: 美工圖片,操作說明

首先我們需要進行畫布的大小設計,在這裏我們採用650 * 650像素的大小,注意這個畫布大小非常重要,因爲我們之後的地圖設計和遊戲算法實現,需要計算像素點位置。

然後我們需要添加我們的logo圖片,還要繪製兩個矩形框,裏邊是說明和啓動遊戲,可以直接使用EasyX 庫爲我們提供的一系列繪圖函數即可:

上面的座標參數都是經過詳細的測試得出的,是適用於畫布大小的。注意不同的畫布大小,每個元素的座標參數都應該進行測試,最終得到合適的元素位置。

畫出整體界面之後,我們便可以定義鼠標事件了,EasyX 庫也爲我們提供了一些捕獲鼠標事件的函數,我們主要用到了下面的函數:

// 這個函數用於獲取一個鼠標消息。如果當前鼠標消息隊列中沒有,就一直等待。
MOUSEMSG GetMouseMsg();

上述的MOUSEMSG結構體這用於保存鼠標消息,因此我們在程序中必須定義它,
下圖是這個結構體的uMsg成員的取值:

我們主要用到了鼠標移動事件、鼠標左鍵點擊事件:


遊戲地圖設計與初始化

上述搞定了之後,點擊“開始”按鈕,我們便進入了遊戲界面,如下圖:

那麼首先分析,不考慮坦克,我們的地圖有以下幾個元素:

  • 可消除的牆
  • 不可消除的牆
  • 己方領地、Boss

這些元素都是以矢量圖的形式存在的,那麼我們如何將它們拼接在一起呢?我們可以繼續使用我們繪製畫布時提到的像素概念,我們將整個畫布分爲26*26的像素單位,如下圖所示:

如上圖,我們將整個地圖(畫布)分爲了很多小的元素塊,因此我們便可以進行地圖的繪製了,建立座標系,計算每一個小元素塊的座標位置,我們只需計算有元素的地方,比如可消除的牆、不可消除的牆、己方領地、boss鷹等。

我們採用了二維數組的方式,以數字0表示空地,數字1表示可消除的牆,數字2表示可消除的牆,數字3表示Boss位置。

那麼對於地圖的繪製就可以完全按照地圖數組中定義的元素位置來進行了,根據地圖數組中的不同值輸出不同的元素圖片即可。

注意,我們的Boss佔了4個元素塊,這對於我們之後的炮彈碰撞檢測是很重要的條件限制。


己方坦克初始化

地圖繪製好後,就該是我方坦克出場了,如下圖:

它的位置在己方領地左側,這個很好實現,我們只需計算出它在地圖畫布中的位置,然後將其輸出到屏幕上即可,這裏坦克同一佔4個元素塊(很重要,之後碰撞檢測要用到)

我們接下來分析一下我們應該爲己方坦克定義哪些成員:

  • 己方坦克是由玩家動態控制的,因此必須有一個方向變量標識其位置(事實上,我們給己方和敵方坦克都提供了上下左右四幅圖像進行輸出)
  • 己方坦克若被敵對坦克炮彈擊中,那麼遊戲失敗,因此我們必須能夠知道己方坦克實時的位置,便於進行炮彈的碰撞檢測。
  • 坦克應該有它的生命狀態,初始應該是生存狀態,被擊中後成爲死亡狀態。

基於上述分析,我們定義的坦克結構體如下(請注意x,y的位置示坦克所屬的四個元素塊的左上方的座標位置):


我方坦克控制(熱鍵控制實現)

在這裏插入圖片描述

根據上圖,可以看到,我們可以實現熱鍵控制己方坦克的上下左右運動,原地轉向,並且若有牆等阻擋物坦克是不能前進的,下面講一下設計思路。

我們的坦克是四個元素塊,在地圖數組map中我們有標識所有障礙物,0表示空地,因此,坦克能否前進必須要進行一些條件判斷,如是否走到了地圖的邊界?是否有障礙物阻擋等等,那麼這就分爲了四種情況。

  • 坦克要向左行進,是否走到了地圖左側邊界,左側是否有障礙物阻擋
  • 坦克要向右行進,是否走到了地圖右側邊界,右側是否有障礙物阻擋
  • 坦克要向上行進,是否走到了地圖上側邊界,上側是否有障礙物阻擋
  • 坦克要向下行進,是否走到了地圖下側邊界,下側是否有障礙物阻擋

下面是條件判斷示意圖:

一定要注意x,y的位置在坦克所屬的四個元素塊的左上方,因此向右和向下的條件判斷需要給當前座標增加2而不是1。

坦克的動態運動過程也很容易實現,就是根據方向提供相應的矢量圖,例如,向前行進一步,之前的坦克區域被黑色覆蓋,矢量圖在新的位置出現。關於坦克的動態運動有兩種情況:

  • 坦克運動的方向與之前不同,即發生轉就地轉向,不行進。
  • 坦克運動的方向與之前相同,即向前行進一步即可。

下面是動態實現的代碼片段:

下面是向上和向下前進代碼片段:


子彈定義和初始化

我們先來看一下子彈的運動過程:
在這裏插入圖片描述

坦克初始化完畢後,子彈該上場了,己方坦克由玩家自己控制炮彈的發射,而敵方坦克是由系統自動控制發炮的。我們這裏先講解己方炮彈的設計過程。

炮彈也是動態的,因此它的運動過程和坦克一樣,因此它設計過程如下:

  • 炮彈具有生命期,打中敵對坦克、牆體、飛出地圖外、打中BOSS鷹都會銷燬
  • 炮彈具有方向,而且炮彈已經發出,其方向直至生命期結束都不會改變,即炮彈是不會轉向的
  • 炮彈必須知道其座標,因爲後面要進行碰撞檢測,也就是,炮彈到底是否已經擊中目標。

基於上述分析炮彈的定義如下:


敵方坦克初始化與出場設計

我們己方坦克和子彈都已經設計完畢了,那麼敵方坦克該出場了。它與我們上面講述的己方坦克最大的區別就是它的出場、前進、發射炮彈都是由系統自動控制的,
這裏我們簡單起見,遊戲設置敵方坦克的數量爲15個,每個敵方坦克出場後在map地圖數組上的值爲100 - 114,之前我們設計的己方坦克出場後在地圖數組上的值爲200,這樣便將他們區分開來,方便我們之後進行碰撞檢測。

那麼敵方坦克的結構定義與己方坦克是相同的。我們在初始化時默認出場三臺坦克,它們的位置分別位於左中右三個部分。默認的方向是向下的,之後每隔5s出場一臺坦克。當然每臺坦克都有要攻擊的目標(這裏有兩個:己方坦克與老鷹Boss),具體的選路算法我們之後在講解。

這裏我們主要看一下三臺坦克出場後,第四臺坦克的位置問題,首先,坦克的出場位置一定是在地圖最上的一行,默認情況下所有的坦克依次從左邊、中間、右邊的位置出場,但是有可能某臺坦克按照之前的約定位置出場,但是該位置已經有其他坦克了,因此新坦克不能再這裏出場,我們設計算法不斷的在第一行進行搜索,直到找到了空閒位置便出場。


敵方坦克選路算法

請注意我光標所指的坦克的運動軌跡
在這裏插入圖片描述
敵方坦克攻擊目標(Boss或己方坦克)只有四種情況(在同行我們可歸結爲其中任意一種)

  • 目標在敵方坦克的左上方位置
  • 目標在敵方坦克的左下方位置
  • 目標在敵方坦克的右上方位置
  • 目標在敵方坦克的右下方位置
我們對敵方坦克的控制主要在於對Boss和己方坦克的攻擊,這裏我們可以採用這樣的策略,就是偶數出場的坦克攻擊Boss,奇數出場的坦克攻擊己方坦克。如下:

具體的選路算法也很容易理解,如果我們要攻擊Boss,由於Boss的座標是固定的(12,24),因此我們只需要知道敵方坦克的位置便可以確定要攻擊的目標在坦克的哪個方位,例如Boss在敵方坦克的左下角,那麼敵方坦克只有兩種選擇,向左或者向下,具體選擇哪種方案我們交給隨機值去處理,使得遊戲更具有隨機化。
如果要攻擊己方坦克,那麼己方坦克是運動的,但是其在map數組中的位置也是動態同步變化的,因此我們完全可以和處理攻擊boss的方案一樣去處理該類情況。

下面我們給出目標位置在左邊的情況:


子彈運動與碰撞檢測

接下來便是真槍實彈的開火了~

子彈也是個動態運動的元素,因此它的動態實現和坦克的動態實現大致相似,這裏不再贅述。

由於我們對於己方坦克炮彈的碰撞檢測和敵對坦克的碰撞檢測在同一函數中實現,因此我們關注以下問題:

  • 必須能夠區分炮彈是己方坦克發射的還是敵對坦克發射的。
  • 己方坦克攻擊Boss不會產生任何影響,敵對坦克攻擊Boss,遊戲失敗。
  • 敵對坦克有多個,因此若敵對坦克的炮彈擊中了另一個正在運動的敵對坦克,對遊戲不產生影響。
  • 己方坦克炮彈擊中敵方坦克,該敵方坦克被銷燬;敵方坦克炮彈擊中己方坦克,遊戲失敗。
  • 炮彈可以打出地圖外,但是其生命終止。
  • 炮彈打中可消除牆後,根據打中的位置,擊中的牆體消失(打中兩面牆的中間位置,兩面牆都銷燬;打中一面牆,該面牆銷燬)

下面請注意己方坦克攻擊牆體的銷燬情況(最後一行):
在這裏插入圖片描述

下面是核心代碼框架:


敵方坦克炮擊算法

敵方坦克炮擊算法的描述如下:

  • 剛開始出場的坦克直接攜帶炮彈併發射
  • 之後每隔2s發射一次炮彈,並且在前一個發射的炮彈生命期未結束時,不能發射下一個炮彈
  • 遍歷所有敵方炮彈數組,只對處於生存狀態的炮彈進行碰撞檢測。
  • 基本的炮擊算法和上面的碰撞檢測算法相同,可參考上面的講解。

遊戲結束控制

遊戲結束有下面幾種情況:

  • boss老鷹被敵對坦克擊中 - 遊戲失敗
  • 己方坦克被敵對坦克擊中 - 遊戲失敗
  • 己方坦克消滅了所有的敵對坦克 - 遊戲成功

那麼我們只需要在碰撞檢測函數中返回相應的退出碼即可,然後在外部調用處接收。我們根據不同的退出碼向屏幕打印不同的退出界面。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章