基于Java实现的植物大战僵尸游戏
資源下載地址:https://download.csdn.net/download/sheziqiong/86812910
資源下載地址:https://download.csdn.net/download/sheziqiong/86812910
基于Java設(shè)計的植物大戰(zhàn)僵尸游戲
一、植物大戰(zhàn)僵尸游戲規(guī)則
游戲簡介
玩家通過種植太陽花收取陽光來購買植物;通過種植不同的植物來抵御僵尸的攻擊。當(dāng)一個關(guān)卡里的僵尸全部被消滅時,玩家勝;當(dāng)僵尸越過地圖的右邊界時,僵尸勝利。
植物簡介
購買植物需要花費一定數(shù)量的陽光,每購買一次過后需要冷卻一段時間才能再次購買。每個關(guān)卡允許使用的植物各有不同,不同植物的特性也不同
植物主要分為:攻擊類,防御類和生產(chǎn)類。也可以分為夜間植物和白天植物(夜間植物只能在夜間出現(xiàn))
攻擊類
- 發(fā)射類
- 防御類
- 攻擊增強類
發(fā)射類
當(dāng)僵尸進(jìn)入射手的射程內(nèi)時,射手會發(fā)射豌豆攻擊。
-
豌豆射手
- 單發(fā)射手
- 攻擊:20
- 耐久:300
- 射程:正前方一整行
- 雙發(fā)射手
- 發(fā)射速度是單發(fā)射手的兩倍,其余同單發(fā)射手。
- 三線射手
- 該植物可同時向3排(植物所處排和左右排)同時發(fā)射出3顆豌豆 ,其余同單發(fā)射手。
- 寒冰射手
- 寒冰射手每次攻擊發(fā)射一顆冰豌豆,命中目標(biāo)后對僵尸造成傷害。其余同單發(fā)射手。
- 機槍射手
- 連續(xù)射出四發(fā)豌豆子彈
- 四倍的快樂
- 小噴菇(夜間植物)
- 小噴菇免費
- 射程短
- 膽小菇(夜間植物)
- 它是一種遠(yuǎn)程射手,敵人接近后會躲起來,不會攻擊。
- 攻擊:20
- 耐久:300
- 射程:正前方一整行
- 單發(fā)射手
爆炸類
- 櫻桃炸彈
- 種下0.6秒后爆炸,能炸死3*3范圍內(nèi)的僵尸
- 火爆辣椒
- 種下0.75秒后爆炸,能炸死同排的僵尸
- 土豆雷
- 種下后2s后長大,在此期間可以被僵尸吃掉。
- 長大后進(jìn)入等待狀態(tài),當(dāng)同排僵尸距離土豆雷半格時,土豆雷爆炸,僵尸被炸死。
- 耐久: 300
吞食類
- 食人花
- 能夠一口吞掉僵尸,然后要咀嚼消化一段時間,此時容易受到攻擊。
- 耐久:300
攻擊增強類
- 火炬樹樁
- 用來增強豌豆植物發(fā)射的豌豆的火力,當(dāng)豌豆劃過樹樁后,會變成火球,豌豆的威力變成2倍
- 但是如果穿過的是寒冰豌豆就會變成普通的豌豆了
防御類
- 堅果墻
- 攻擊性:0(無攻擊力)
- 防御力:3000(較高)
- 用于阻礙僵尸前進(jìn)的步伐
- 高堅果
- 巨大的堅果,可以更好的阻擋僵尸前行
- 攻擊性:0(無攻擊力)
- 防御力:8000
生產(chǎn)類
生產(chǎn)陽光,供拾取購買植物
- 向日葵
- 可生產(chǎn)陽光
- 耐久:300
- 陽光菇(夜間植物)
- 白天睡覺,可在夜間生產(chǎn)陽光
- 耐久:250
- 雙子向日葵
- 雙份的陽光,雙份的喜悅
- 耐久:300
僵尸簡介
在游戲中,不同種類的僵尸會一波波的攻擊。不同僵尸的攻擊值,耐久,特性有所不同。
僵尸分為:
- 普通僵尸
- 游戲中最普通的花園僵尸
- 血量:270(普通)
- 攻擊:啃食攻擊,100/秒
- 速度:約4.7秒/格(普通)
- 旗幟僵尸
- 該僵尸是領(lǐng)頭者,速度比普通僵尸快
- 血量:270
- 路障僵尸
- 防御能力是普通僵尸的兩倍
- 血量:640
- 鐵桶僵尸
- 防御能力比路障僵尸更強
- 血量:1100
- 橄欖球僵尸
- 速度很快,防御強于鐵桶僵尸
- 血量:1160
- 讀報僵尸
- 報紙可做防御,拿著報紙時速度很慢
- 多次受到攻擊后失去報紙,防御變低,速度變快
- 血量:420
附加道具簡介
- 小推車
- 位于最后防線的前方,當(dāng)僵尸瀕臨最后防線時,小推車出動,將同排僵尸碾壓致死。
- 陽光
- 可以靠天吃飯獲得
- 可以生產(chǎn)類植物處獲得
- 拾取后,可用來購買植物
關(guān)卡簡介
逐個關(guān)卡擊破,取得最終勝利。
關(guān)卡很有趣,請各位自行體驗,這里不再贅述。
二、文檔
1. 類圖
整體:
核心部分:
大圖見docs文件夾
2. 用例圖
3. 時序圖
I. 更新植物
II. 開始新游戲
III. 向僵尸總HP最少的行添加一個指定的僵尸
IV. 陽光下落
V. 向音頻池添加游戲音效
三、項目完成情況
這是一份項目開始時準(zhǔn)備的的TODO-List
游戲核心- [x] 單次派發(fā)系統(tǒng)- [x] 基于FSM的狀態(tài)機制- [x] 基于多線程的音頻池(使用.wav)- [x] 附帶(類似)回調(diào)函數(shù)的雜項GIF播放系統(tǒng)- [x] 交互界面- [x] 開始界面- [x] 游戲界面- [x] 后院- [x] 卡牌槽- [x] 陽光槽- [x] 卡牌槽- [x] 卡牌- [x] 關(guān)卡進(jìn)度- [x] 界面交互- [x] 放置植物- [x] 收集陽光- [x] 暫停界面- [x] 通關(guān)/失敗提示- [x] 關(guān)卡設(shè)計- 考慮一關(guān)總僵尸量一定- 每間隔一段時間放置- 原則:- 在僵尸總HP最少的行上放置 植物- [x] 普通植物- [x] 向日葵- [x] 雙子向日葵- [x] 豌豆射手- [x] 雙發(fā)射手- [x] 三線射手- [x] 機槍射手- [x] 寒冰射手- [x] 堅果墻- [x] 高堅果- [x] 食人花- [x] 櫻桃炸彈- [x] 火爆辣椒- [x] 土豆雷- [x] 火炬樹樁- [ ] 夜間植物- [x] 小噴菇- [x] 膽小菇- [ ] 大噴菇- [x] 陽光菇 僵尸- [x] 普通僵尸- [x] 旗幟僵尸- [x] 路障僵尸- [x] 鐵桶僵尸- [x] 橄欖球僵尸- [ ] 小丑僵尸- [x] 讀報僵尸- [ ] 撐桿跳僵尸 子彈類- [x] 豌豆- [x] 冰豌豆- [x] 燃燒豌豆- [x] 孢子- [ ] 大孢子(大噴菇)四、附錄
(魔幻)植物大戰(zhàn)(成精)僵尸擴展編程不Van全指南
原理簡介
一、名詞解釋
- 豌豆射手擁有以下幾種狀態(tài)
- 0:待機態(tài),沒有可以攻擊的僵尸
- 1:攻擊態(tài),攻擊態(tài)=攻擊+CD
- 2:HP耗盡,立即被回收
- 普通僵尸擁有以下幾種狀態(tài)
- 0 : 待機態(tài),未出場
- 1 : 有頭行走
- 2 : 有頭攻擊
- 3 : 無頭行走
- 4 : 無頭攻擊
- 5 : 被炸死,立即被回收
- 6 : 無頭死亡(正常死亡),立即被回收
- 一個狀態(tài)又兩部分組成,一是表示狀態(tài)的數(shù)字,這個數(shù)字是這個狀態(tài)的唯一標(biāo)示,二是狀態(tài)附帶的動作,即個體被判定處于這個狀態(tài)后應(yīng)該執(zhí)行的操作。例子接上面
- 對于豌豆射手
- 0/1:
- 當(dāng)HP耗盡 -> 2
- 0:
- 當(dāng)觀測到有可攻擊的僵尸 -> 1
- 1:
- 當(dāng)不再有可攻擊的僵尸(上面的檢測條件添加.negate()即可實現(xiàn)) -> 0
- 0/1:
- 對于普通僵尸
- 1/2/3/4:
- 當(dāng)受到足以致命的爆炸傷害 -> 5
- 3/4:
- 當(dāng)HP耗盡 -> 6
- 1:
- 觀測到可攻擊 -> 2
- HP低于閾值 -> 3(掉頭行走)
- 2:
- 不再可攻擊(等價于(1->2).negate()) -> 1
- HP低于閾值 -> 4(掉頭攻擊)
- 3:
- 觀測到可攻擊 -> 4
- 4:
- 不再可攻擊(等價于(3->4).negate()) -> 3
- 1/2/3/4:
- 要求:
- 任何對于死亡的檢測必須寫在前面,因為沒有實現(xiàn)狀態(tài)轉(zhuǎn)移的優(yōu)先級,當(dāng)檢測到可滿足的轉(zhuǎn)移條件后就不再檢測其他條件
- 對于豌豆射手
- Predicate:
- 接受參數(shù)并返回true/false
- 用作檢測是否滿足轉(zhuǎn)移條件
- 程序中使用BiPredicate,接受兩個參數(shù),一是游戲棋盤的總體,二是實體本身
- Consumer
- 接受參數(shù)并執(zhí)行,無返回值
- 用作狀態(tài)的執(zhí)行
- 程序中使用BiConsumer,接受兩個參數(shù),同上
- 背景音樂,同一時刻只允許存在一個背景音樂,且背景音樂自動設(shè)置為循環(huán)播放
- 游戲音效,子彈擊中僵尸、僵尸啃噬植物等均為音效,一經(jīng)添加立即播放,播放完畢后釋放資源(由JVM接管)
二、代碼組織形式
- model 模型
- base基本類型
- bullets子彈
- plants植物
- sound音頻
- zombies僵尸
- level 關(guān)卡
- view 視圖渲染部分
- controller 用戶交互界面
- 僵尸圖 zombieMap
- 植物圖 plantMap
- 子彈圖 bulletMap
- 雜項圖 extraMap
- 陽光圖 sumMap,原本設(shè)計在雜項里,但考慮到檢測的效率,故獨立之
核心API講解
注意,本部分代碼因項目后期沒有維護(hù),可能已過時,詳情請參考具體代碼
abstract void setStateTable(); //設(shè)置狀態(tài)表
- 任何繼承Root的子類都必須實現(xiàn)的方法
- 以豌豆射手Pershooter類為例:protected void setStateTable() {// 0 : 正常// 1 : 攻擊// 2 : HP耗盡BiConsumer<GameBoard, Root> attack = ((gameBoard, root) ->// 攻擊時應(yīng)該做什么{if ((intervalCount++) % attackPerTicks == 0){Plant pt = (Plant) root;for (Object ob : gameBoard.zombieMap.getRow(pt.getY())){Zombie zb = (Zombie) ob;if (zb.getX() - pt.getX() <= MAX_PROBE_RANGE){gameBoard.bulletMap.getRow(pt.getY()).add(new BeanBullet(pt.getX() + pt.getWidth() / 2, pt.getY()));SoundPool.addSound(GameRule.choice(GameRule.pea_shoot));return;}}}});addState(0, "peashooter.gif", null);addState(1, "peashooter.gif", attack);addState(2, "peashooter.gif", (gameBoard, root) -> finish = true); }
- 類中的方法addState
- 接受兩個參數(shù)
- 方法簽名為void addState(int state, String fName, BiConsumer<GameBoard, Root> action)
- state為當(dāng)前狀態(tài)碼
- fName為狀態(tài)要播放的gif,可為null
- action為BiConsumer,是該狀態(tài)要執(zhí)行的操作,可為null
abstract void setStateTransfer(); //設(shè)置轉(zhuǎn)移條件
- 任何繼承Root的子類都必須實現(xiàn)的方法
- 以豌豆射手Pershooter類為例:protected void setStateTransfer() {// BOTH -> HP耗盡死亡addTransfer(new int[] {0, 1}, 2, ((gameBoard, root) -> hp <= 0));// 待機 -> 開始攻擊addTransfer(0, 1, getAttackTransfer(MAX_PROBE_RANGE));// 攻擊 -> 待機addTransfer(1, 0, getAttackTransfer(MAX_PROBE_RANGE).negate()); }
- 類中的方法addTransfer
- 接受三個參數(shù)
- 方法簽名為void addTransfer(int from, int to, BiPredicate<GameBoard, Root> cond)
- from為來自的狀態(tài)
- to為滿足cond后轉(zhuǎn)移到的狀態(tài)
- cond為BiPredicate,是狀態(tài)轉(zhuǎn)移條件
- 還定義有方法void addTransfer(int[] froms, int to, BiPredicate<GameBoard, Root> cond)
- froms數(shù)組中各元素都會被展開成原方法形式
- 旨在實現(xiàn)快速添加對象死亡的轉(zhuǎn)移條件
音頻池SoundPool系統(tǒng)
- 音頻池為游戲提供了背景音樂與游戲音效支持
- public static void addSound(String fName)
- 添加音效
- 音效會被立刻播放
- 播放結(jié)束后自動回收
- 一時刻可能有很多很多音效在播放
- public static void setBGmusic(String fName)
- 設(shè)置背景音樂
- 音樂會被立刻播放
- 音樂循環(huán)
- 每一時刻只能有一首正在播放的BGM,重設(shè)后會替換原BGM
- 注意
- 上述兩方法為靜態(tài)方法,調(diào)用方式為
- SoundPool.addSound(xxxxx);
- SoundPool.setBGmusic(xxxxx);
- 編碼人員不用創(chuàng)建該類的任何實例,兩靜態(tài)方法會自動處理
- 上述兩方法為靜態(tài)方法,調(diào)用方式為
- 現(xiàn)狀:音頻系統(tǒng)是引起游戲很卡的罪魁禍?zhǔn)?
-
[完成]初步優(yōu)化:使用字典管理正在播放的音頻,限制同一音頻最多同時播放3個,稍有改善
-
[待進(jìn)行]進(jìn)一步構(gòu)思:
- 使用線程池技術(shù),預(yù)先開辟線程,消除線程構(gòu)造與回收造成的系統(tǒng)資源消耗
-
關(guān)卡Level & LevelManager & LevelFactory系統(tǒng)
- Level: 保存一個關(guān)卡的信息
- LevelManager: 組合一個Level并被多個類所傳遞,用于獲取關(guān)卡信息與處理游戲勝負(fù)
- LevelFactory: 關(guān)卡工廠,想添加新的關(guān)卡就寫在這里
- 如何寫一個新關(guān)卡:
- 你需要提供
- 關(guān)卡的編號
- 出現(xiàn)在本關(guān)的所有僵尸與他們的數(shù)量
- 總波數(shù)
- 總時間
- 下一關(guān)的編號(-1代表結(jié)束)
- 可以使用的植物
- 場景圖片
- 背景音樂
- 是否從天上掉落陽光
- 掉落陽光的間隔是多少
- 預(yù)先設(shè)置的植物
- 編寫成代碼:Level level = new Level(); HashMap<String, Integer> zombieCount = new HashMap<>(); ArrayList<PlantInfo> cards = new ArrayList<>(); ArrayList<PreSetPlant> pres = new ArrayList<>(); zombieCount.put("normal_zombie", 10); // 10個普通僵尸 zombieCount.put("football_zombie", 10); // 10個橄欖球僵尸 zombieCount.put("buckethead_zombie", 5); // 5個鐵桶僵尸 zombieCount.put("conehead_zombie", 5); // 5個路障僵尸 zombieCount.put("newspaper_zombie", 5); // 5個讀報僵尸cards.add(new PlantInfo("Chomper", 50, 2)); // 可使用食人花, 花費50陽光,冷卻2s cards.add(new PlantInfo("Jalapeno", 50, 2)); // 火爆辣椒 cards.add(new PlantInfo("Peashooter", 100, 2)); // 豌豆射手 cards.add(new PlantInfo("Threepeater", 100, 2)); // 三發(fā)射手 cards.add(new PlantInfo("CherryBomb", 100, 2)); // 櫻桃炸彈 cards.add(new PlantInfo("ScaredyShroom", 100, 2)); // 膽小菇// 預(yù)設(shè)植物, new (name, col, row) pres.add(new PreSetPlant("PotatoMine", 5, 0)); pres.add(new PreSetPlant("PotatoMine", 5, 1)); pres.add(new PreSetPlant("PotatoMine", 5, 2)); pres.add(new PreSetPlant("PotatoMine", 5, 3)); pres.add(new PreSetPlant("PotatoMine", 5, 4));level.setLevelNumber(1) // 關(guān)卡編號1.setZombies(zombieCount) // 設(shè)置所有僵尸(見上方).setWaves(10) // 10波.setLevelTime(50) // 50秒(最后一波僵尸出現(xiàn)的時間).setLevelNext(2) // 下一關(guān)進(jìn)入編號2.setPlantInfos(cards) // 設(shè)置可使用的植物.setlevelImg(GameRule.backgroundDay) // 關(guān)卡背景.setLevelBgmusic(GameRule.dayBG) // 背景音樂.setDropSun(true) // 白天,從天上掉陽光.setDropSunPerSeconds(5) // 第一關(guān),陽光掉落頻繁.setPrePlants(pres); // 預(yù)設(shè)植物,可為空
- 由于大量使用了Java的反射機制,使得程序具有特別強大的靈活性,可以看出,僅需要一個字符串便能調(diào)用一個類
- 所以,新建一個關(guān)卡只需要填寫很少的代碼
- 由于植物的花費與冷卻時間可控,我們還可以做出許多有趣的小游戲,比如:
- 一關(guān)只允許使用爆炸物,且爆炸物冷卻時間極短而且花費很低
- 一關(guān)的高堅果與堅果墻十分廉價,但攻擊方式只有普通豌豆射手
- 更多腦洞任君開發(fā)
- 你需要提供
僵尸、植物、子彈或雜項何時被系統(tǒng)刪除
- 在Root類中有一名為finish的布爾型變量,一旦為true,則會在最近一次的更新中被移除
- 對于動畫,例如僵尸的倒下死亡或被炸死,有兩種處理思路
- 轉(zhuǎn)移到這個狀態(tài),播放這個gif,sleep對應(yīng)時間后在設(shè)置為finish=true
- 轉(zhuǎn)移到這個狀態(tài),立刻設(shè)置為finish=true,在雜項map內(nèi)添加這樣的gif
- 本程序在簡潔性與易擴展型的考量下決定使用后者,具體使用方法舉例如下:// 僵尸被炸死 addState(5, null, (gameBoard, root) -> {finish = true;gameBoard.extraMap.add(new Extra(getPath() + "boom_die.gif", 3500, getX(), getY())); });// 僵尸HP耗盡 addState(6, null, (gameBoard, root) -> {finish = true;gameBoard.extraMap.add(new Extra(getPath() + "nohead_die.gif", 1500, getX(), getY())); });
- Extra類的構(gòu)造函數(shù)簽名如下
- public Extra(String fName, int ms, int x, int y, boolean bullet)
- 與public Extra(String fName, int ms, int x, int y)
- 其中
- fName為需要播放的gif文件
- ms為gif所需執(zhí)行時間,用于Thread.sleep()
- x, y,物體坐標(biāo)
- bullet,是否為子彈,true代表是,不填為否
- Extra類被構(gòu)造后會在最近一次的更新中被添加
- 在延遲時間到后自動被回收,實現(xiàn)方法同為finish=true
raMap.add(new Extra(getPath() + “nohead_die.gif”, 1500, getX(), getY()));
});
```
- Extra類的構(gòu)造函數(shù)簽名如下
- public Extra(String fName, int ms, int x, int y, boolean bullet)
- 與public Extra(String fName, int ms, int x, int y)
- 其中
- fName為需要播放的gif文件
- ms為gif所需執(zhí)行時間,用于Thread.sleep()
- x, y,物體坐標(biāo)
- bullet,是否為子彈,true代表是,不填為否
- Extra類被構(gòu)造后會在最近一次的更新中被添加
- 在延遲時間到后自動被回收,實現(xiàn)方法同為finish=true
END
資源下載地址:https://download.csdn.net/download/sheziqiong/86812910
資源下載地址:https://download.csdn.net/download/sheziqiong/86812910
總結(jié)
以上是生活随笔為你收集整理的基于Java实现的植物大战僵尸游戏的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 今天很累……
- 下一篇: 《码出高效:Java 开发手册》“码”