植物大戰僵尸java(植物大戰僵尸online秦始皇陵)


本站AI自動判斷提供您所需要的app下載:點我下載安裝,你懂的APP

本篇文章給大家談談植物大戰僵尸java,以及植物大戰僵尸online秦始皇陵的知識點,希望對各位有所幫助,不要忘了收藏本站喔。

文章詳情介紹:

邊玩邊學,學習三天用Java寫出植物大戰僵尸,自學Java真的很簡單

有誰沒玩過植物大戰僵尸嗎?用Java語言開發了自己的植物大戰僵尸游戲。雖然系統相對簡單,但是麻雀雖小五臟俱全,對游戲開發感興趣的小伙伴可以學習一下。

小編在文末給大家準備了除植物大戰僵尸外別的游戲的教程和全套Java教程作為禮物哦。

游戲設計

植物大戰僵尸中有一個小游戲關卡,屏幕的正上方有一個滾輪機,會隨機生成植物,玩家可以選中植物后自由選擇草坪來進行安放。基于此游戲模式,我將該關卡抽取出來,單獨做成了一個簡易版的植物大戰僵尸。游戲的畫面大概如下:

屏幕左側會自動生成植物的卡牌,單擊選中后可以放置在草坪上。右側會自動生成僵尸,不同的僵尸移動速度不同,血量不同,還有的僵尸有隱藏獎勵,比如:全屏僵尸靜止、全屏僵尸死亡等。當時竟然沒有做游戲的暫停的功能,導致現在截圖的時機很難把控,那這里就先說一下游戲暫停的功能應該怎么做吧。

最簡單的一種暫停方式是鼠標移出屏幕,游戲暫停。所以這里需要引入一個鼠標監聽器事件。

public?void?mouseMoved(MouseEvent e)?{ ??// 當游戲處于運行狀態時 ??if?(status == start) { ????// 通過鼠標移動事件的對象獲取當前鼠標的位置 ????int?x = e.getX(); ????int?y = e.getY(); ????// 如果鼠標超出了游戲界面 ????if?(x > Game.WIDTH || y > Game.HEIGHT) { ??????// 將游戲的狀態改為暫停狀態 ??????status = pause; ????} ??} }

當然,這只是一個簡單的通過監聽鼠標的位置來改變游戲狀態方法。還可以使用鍵盤監聽器,當按下某個鍵時游戲暫停,這樣的用戶體驗更好。但原理是一樣的,這里就不展示代碼了。

游戲對象

首先分析一下游戲中有哪些對象。各式各樣的植物,各式各樣的僵尸,各式各樣的子彈。那么這里就可以抽出三個父類,分別是植物、僵尸、子彈。在面向對象中,子類將繼承父類所有的屬性和方法。所以可以將三大類中,共有的屬性和方法抽到各自的父類中。比如僵尸父類:

public?abstract?class?Zombie?{ ??// 僵尸父類 ??// 僵尸共有的屬性 ??protected?int?width; ??protected?int?height; ??protected?int?live; ??protected?int?x; ??protected?int?y; ??...... ??// 僵尸的狀態 ??public?static?final?int?LIFE =?0; ??public?static?final?int?ATTACK =?1; ??public?static?final?int?DEAD =?2; ??protected?int?state = LIFE; ??/* ??* 這里補充一下為什么父類是抽象類,比如每個僵尸都有移動方法, ??* 但每個僵尸的移動方式是不同,所以該方法的方法體可能是不同的, ??* 抽象方法沒有方法體,在子類中再去進行重寫就可以了, ??* 但有抽象方法的類必須是抽象類,因此父類一般都是抽象類 ??*/ ??// 移動方式 ??public?abstract?void?step(); ??.... }

植物父類、子彈父類就同理可得了。上面說到子類共有的方法需要抽到父類中,那么部分子類共有的方法該如何處理呢?比如,豌豆射手、寒冰射手可以發射子彈,堅果墻就沒有射擊的這個行為。所以這里就需要用到接口(Interface)。

public?interface?Shoot?{ ??// 射擊接口 - 將部分子類共有的行為抽取到接口中 ??// 接口中的方法默認是public abstract的,規范的編碼應該將該字段舍去 ??public?abstract?Bullet[]?shoot(); }

到此為止,游戲對象的屬性、方法基本都定義完了,至于圖片的顯示以及如何將圖片畫出來,只需要使用相應的API即可,這里就不做描述了。工作一年回過來看看,這里能優化的地方還有很多,比如對象的血量、攻擊力、移動等都可以統統寫入到配置文件中,這樣在做游戲參數的調整時,不需要去修改代碼相關的內容,只需要修改配置文件里面的參數即可。

游戲內容

現在我們有了游戲的對象,該開始讓對象加入到游戲中來,接著讓他們動起來,最后還得讓他們打起來。首先,讓對象加入到游戲中來我是這么做的,這里還是以僵尸為例:

// 首先要有一個僵尸的集合 // 僵尸集合 private?List zombies =?new?ArrayList(); // 接著定義隨機生成僵尸方法 public?Zombie?nextOneZombie()?{ ????Random rand =?new?Random(); ????// 控制不同種類僵尸出現的概率 ????int?type = rand.nextInt(20); ????if(type<5) { ??????return?new?Zombie0(); ????}else?if(type<10) { ??????return?new?Zombie1(); ????}else?if(type<15) { ??????return?new?Zombie2(); ????}else?{ ??????return?new?Zombie3(); ????} } // 僵尸入場 // 設置進場間隔 /* * 這里補充一下為什么要設置進場的間隔 * 因為游戲的運行是基于定時器的, * 每隔一段時間定時器就會執行一次你所加入定時器的方法, * 所以這里需要設置進場間隔來控制游戲的速度。 */ int?zombieEnterTime =?0; public?void?zombieEnterAction()?{ ??zombieEnterTime++; ??????// 對自增量zombieEnterTime進行取余計算 ????if(zombieEnterTime%300==0) { ??????// 滿足條件就調用隨機生成僵尸方法,并將生成的僵尸加入到僵尸的集合中 ??????zombies.add(nextOneZombie()); ????} }

最早時候我用的數據結構是數組,但在后續的編碼中發現,對僵尸對象有很多的遍歷以及增刪操作,數組的增刪操作是十分麻煩復雜的,所以我就換成了集合。在工作中也一樣,先思考在編碼,選擇正確的數據結構往往能起到事半功倍的效果。

植物入場的設計,是我當時自認為很精妙的一個點。先說一下當時在編碼中發現的問題。首先植物入場時是在滾輪機上的,滾輪機上的移動就會涉及到追擊和停止的問題。追擊的方式當然是追前一個植物卡牌,但當第一個植物卡牌被選中放置到草地上后,那該如何追擊呢?

最開始我的做法是給植物多加幾個狀態來解決這個問題,但是發現狀態過多會導致if判斷中的條件將大大增加,并且在嘗試后還是沒有實現想要的效果,于是我就將植物集合一分為二,在后面的游戲功能設計中,回頭過來看才發現將植物集合分為滾輪機上的集合和戰場上的集合實在是太精妙了。請聽我娓娓道來:

// 滾輪機上的植物,狀態為stop和wait private?List plants =?new?ArrayList(); // 戰場上的植物,狀態為life和move -move為被鼠標選中移動的狀態,這里設計不合理,會引發后面的一個BUG private?List plantsLife =?new?ArrayList(); // 植物在滾輪機上的碰撞判定 public?void?plantBangAction()?{ ????// 遍歷滾輪機上植物集合,從第二個開始 ????for(int?i=1;i0&&plants.get(0).isStop()) { ????????plants.get(0).goWait(); ??????} ??????// 如果第i個植物y小于i-1個植物的y+height,則說明碰到了,改變i的狀態為stop ??????if((plants.get(i).isStop()||plants.get(i).isWait())&& ??????????(plants.get(i-1).isStop()||plants.get(i-1).isWait())&& ??????????plants.get(i).getY()<=plants.get(i-1).getY()+plants.get(i-1).getHeight() ??????????) { ????????plants.get(i).goStop(); ??????} ??????/* ???????* 如果第i個植物y大于于i-1個植物的y+height,則說明還沒碰到或者第i-1個 ???????* 植物被移走了,改變i的狀態為wait,可以繼續往上走 ???????*/ ??????if(plants.get(i).isStop()&& ??????????plants.get(i).getY()>plants.get(i-1).getY()+plants.get(i-1).getHeight()) { ????????plants.get(i).goWait(); ??????} ????} ??} ??// 檢測滾輪機上的植物狀態 ??public?void?checkPlantAction1()?{ ????// 迭代器 ????Iterator it = plants.iterator(); ????while(it.hasNext()) { ??????Plant p = it.next(); ??????/* ???????* 如果滾輪機集合里有move或者life狀態的植物 ???????* 則添加到戰場植物的集合中,并從原數組中刪除 ???????*/ ??????/* ??????* 現在發現把滾輪機上move狀態的植物添加到 ??????* 戰場上植物集合的最佳操作時間點應該是 ??????* 等植物狀態變為life后再添加。 ??????* / ??????if(p.isMove()||p.isLife()) { ????????plantsLife.add(p); ????????it.remove(); ??????} ????} ??}

當然,滾輪機上的對植物狀態判斷的代碼還是顯得生澀,也正是自己想優化這段代碼時萌生了分享游戲設計過程和游戲代碼的念頭。那么下面就說說,這段代碼該如何優化:

// 先對狀態做下說明 // wait - 植物卡牌在滾輪機上移動狀態,因為是等著被鼠標選中,所以取名為wait // stop - 植物卡牌在滾輪機上停止狀態,有兩種情況,1 - 到頂了 2 - 撞到上一個卡牌了 // 開始對以下代碼進行優化 // 如果第i個植物y小于i-1個植物的y+height,則說明碰到了,改變i的狀態為stop // if((plants.get(i).isStop()||plants.get(i).isWait())&& // (plants.get(i-1).isStop()||plants.get(i-1).isWait())&& // plants.get(i).getY()<=plants.get(i-1).getY()+plants.get(i-1).getHeight() // ) { // plants.get(i).goStop(); // } // 優化后的代碼是這樣的 // 將一個復雜的boolean拆成多個if條件 if?(!(plants.get(i).isStop()||plants.get(i).isWait()) { ??break; } if?(!(plants.get(i-1).isStop()||plants.get(i-1).isWait())) { ??break; } if?(!(plants.get(i).getY()<=plants.get(i-1).getY()+plants.get(i-1).getHeight())) { ??break; } plants.get(i).goStop();

boolean條件當然也可以進行優化,甚至還可以簡化一下植物的狀態。這里因為游戲的規則,僵尸只能攻擊在草坪上的植物,所以把帶放置的植物和草坪上的植物分為兩個集合,是十分合理精妙的。在判斷僵尸是否攻擊植物,只需要去遍歷草坪上的植物集合即可。如果不拆分,當要判斷僵尸是否攻擊植物的時候,需要遍歷的集合將是所有的植物集合,并且需要增加至少2個狀態來區分植物是在草坪上還是在滾輪機上,這段代碼想想就是又臭又長。

接下來該讓對象們都動起來了。之前說到在父類中的移動方法是抽象方法,在各自的子類中都進行重寫后,不同的對象移動方式就是各式各樣的了。

// 子彈移動 public?void?BulletStepAction()?{ ??for(Bullet b:bullets) { ????b.step(); ??} } //僵尸移動 //設置移動間隔 int?zombieStepTime =?0; public?void?zombieStepAction()?{ ??if(zombieStepTime++%3==0) { ????for(Zombie z:zombies) { ??????//只有活著的僵尸會移動 ??????if(z.isLife()) { ????????z.step(); ??????} ????} ??} }

看著代碼中對集合復雜的遍歷,不得不感概lambda表達式真是個好東西:

// 子彈移動 public?void BulletStepAction() { ??bullets.forEach((b)->b.step()); ??.... }

這里好像還是沒法展示lambda表達式強大的功能,請看下面的例子:

// 為了應對產品不斷變更的需求,前輩們總結經驗得出的設計模式已經能在一定程度上應對此問題 // 設計模式,聲明策略接口,在實現類中完成過濾邏輯 public?List filterStudentByStrategy(List students, SimpleStrategy strategy){ ???????List filterStudents =?new?ArrayList<>(); ???????for?(Student student : filterStudents) { ???????????if(strategy.operate(student)){ ???????????????filterStudents.add(student); ???????????} ???????} ???????return?filterStudents; } // 當需求變更時,只需要在策略接口的實現類中,變更判斷邏輯即可 public?interface?SimpleStrategy?{ ????public?boolean operate(T t); }

但好像還是有點麻煩,又要寫接口,又要寫實現類,后續的維護也是個頭疼問題,這個時候救世主lambda表達式就出現了:

// 無需接口便可實現需求的快速變更 List lambdaStudents = ??students.stream().filter(student -> student.getGender()==1).collect(Collectors.toList());

讓我們看看上面到底發生了啥。首先將數據的集合流化,接著調用過濾方法,強大lambda表達式讓代碼變得簡潔,并且判斷條件的修改可在代碼中直接維護無需在策略接口的實現類維護。最后在轉成集合,返回一個滿足產品需求的集合。

回到正題,如何讓對象們打起來呢?下面以僵尸攻擊植物為例:

// 僵尸的超類中定義了僵尸的攻擊方法, // 由于僵尸們的攻擊行為是相同,所以這里是普通方法 // 僵尸攻擊植物 public?boolean?zombieHit(Plant p)?{ ????int?x1 =?this.x-p.getWidth(); ????int?x2 =?this.x+this.width; ????int?y1 =?this.y-p.getHeight(); ????int?y2 =?this.y+this.width; ????int?x = p.getX(); ????int?y = p.getY(); ????return?x>=x1 && x<=x2 && y>=y1 && y<=y2; }

結合圖片來看,上述代碼應該就更好理解。黑框P代表植物,黑框Z代表植物,虛線是指兩者接觸的極限距離,當僵尸進入虛線內,就保證可以攻擊到植物。

// 僵尸攻擊 // 設置攻擊間隔 int?zombieHitTime =?0; public?void?zombieHitAction()?{ ??if(zombieHitTime++%100==0) { ????for(Zombie z:zombies) { ??????// 如果戰場上沒有植物,則把所有僵尸的狀態改為life ??????/* ??????* 這里補充一下為什么要先將所有的僵尸的狀態先改成life狀態,也就是移動狀態 ??????* 因為下面對僵尸是否攻擊的植物的判斷,是從遍歷戰場上的植物集合開始的 ??????* 假如有只僵尸在吃植物,把戰場上唯一的一個植物吃掉了, ??????* 那么僵尸的狀態將從攻擊改成移動呢? ??????* 所以這里運用了逆向的思想,先將所有的僵尸改為移動狀態 ??????* 如果符合攻擊的條件,那么再改為攻擊狀態, ??????* 即便是戰場上沒有植物,那么僵尸還依然是移動的狀態 ??????*/ ??????if(!z.isDead()) { ????????z.goLife(); ??????} ??????// 這里應該有個對戰場上植物集合的判斷在進行遍歷 ??????for(Plant p:plantsLife) { ????????// 如果僵尸是活的,并且植物是活的,并且僵尸進入攻擊植物的范圍 ????????/* ????????* 這里有個BUG,僵尸竟然會攻擊鼠標選中還未放下的植物, ????????* 所以下面的判斷條件中應該還需要移除被鼠標選中狀態下植物 ????????*/ ????????if(z.isLife()&&!p.isDead()&&z.zombieHit(p)&&!(p?instanceof?Spikerock)) { ??????????// 僵尸狀態改為攻擊狀態 ??????????z.goAttack(); ??????????// 植物掉血 ??????????p.loseLive(); ????????} ??????} ????} ??} }

如果出現了一些效果的偏移,造成的原因是圖片大小不一造成的坐標偏移,因為圖片都是網上找的,所以效果不是太理想。至此,游戲的基本功能基本實現了。Java是一門面向對象的語言,萬物皆對象,特征皆屬性,行為皆方法。肉眼能看到的僵尸、植物、草坪都是對象,對象的特性比如血量、移動速度都是屬性,對象的行為比如移動、攻擊、死亡都是方法。

下面說說對游戲功能的優化。

游戲優化

1.放置植物的優化

已經放置過植物的草地不能再放置植物了。之前是將草地設計成empty和hold兩種狀態,現在來看其實只需要返回一個true和false就行了,將整個植物集合定義成一個虛擬的boolean集合即可。

2.移除植物的優化

設計思路是新增一個鏟子對象:

// 鏟子集合 private?List shovels =?new?ArrayList(); // 鏟子入場 public?void?shovelEnterAction()?{ ??// 鏟子只有一把 ??if(shovels.size()==0) { ????shovels.add(new?Shovel()); ??} } // 使用鏟子 Iterator it = shovels.iterator(); Iterator it2 = plantsLife.iterator(); while(it.hasNext()) { ??Shovel s = it.next(); ??// 如果鏟子是移動狀態,就遍歷植物集合 ??if(s.isMove()) { ????while(it2.hasNext()) { ??????Plant p = it2.next(); ??????int?x1 = p.getX(); ??????int?x2 = p.getX()+p.getWidth(); ??????int?y1 = p.getY(); ??????int?y2 = p.getY()+p.getHeight(); ??????if((p.isLife()||((Blover) p).isClick())&&Mx>x1&&Mxy1&&My

看著這極其復雜好像很厲害的代碼,我又萌生了痛下狠手的想法,但為了保持原生,我忍住。于是乎還發現了一個BUG。如果選中鏟子后,戰場上唯一的植物被僵尸吃掉了,那么這個鏟子將一直跟隨著鼠標無法達到使用后消除的效果了。解決方案當然也很簡單,當戰場上植物集合的size為0時,清空鏟子集合即可。

3.游戲可玩性的優化

上文在游戲設計中提到的擊殺僵尸后可能隨機獲得獎勵類型是這樣實現的。還是從設計分析開始,并非擊殺任何類型的僵尸都可以獲得獎勵,所以獎勵應該放在接口中:

public?interface?Award?{ ??// 獎勵接口 ??/* ??* 這里還是存在代碼不規范的問題 ??* 接口的方式默認是public abstract ??* 接口中的變量默認是public static final ??* 這些默認的字段應該舍去 ??*/ ??// 全屏靜止 ??public?static?final?int?CLEAR =?0; ??// 全屏清除 ??public?static?final?int?STOP =?1; ??public?abstract?int?getAwardType(); }

當僵尸死亡時,需要去判斷該僵尸是否有獎勵接口,如果有則執行相應獎勵的方法:

// 檢測僵尸狀態 public?void?checkZombieAction()?{ ??// 迭代器 ??Iterator it = zombies.iterator(); ??while(it.hasNext()) { ????Zombie z = it.next(); ????// 僵尸血量小于0則死亡,死亡的僵尸從集合中刪除 ????if(z.getLive()<=0) { ??????// 判斷僵尸是否有獎勵的接口 ??????if(z instanceof Award) { ????????Award a = (Award)z; ????????int?type = a.getAwardType(); ????????switch(type) { ????????case?Award.CLEAR: ??????????for(Zombie zo:zombies) { ????????????zo.goDead(); ??????????} ??????????break; ????????case?Award.STOP: ??????????for(Zombie zom:zombies) { ????????????zom.goStop(); ????????????timeStop =?1; ????????????//zombieGoLife(); ??????????} ??????????break; ????????} ??????} ??????z.goDead(); ????????it.remove(); ????} ????// 僵尸跑進房子,而游戲生命減一,并刪除僵尸 ????if(z.OutOfBound()) { ??????gameLife--; ??????it.remove(); ????} ??} }

4.添加游戲背景音樂

bgm是一個游戲的靈魂之一。這里給游戲添加背景音樂,我的選擇是新建一條線程專門用來執行音樂的解析和播放:

// 啟動線程加載音樂 Runnable r =?new?zombieAubio("bgm.wav"); Thread t =?new?Thread(r); t.start(); public?class?zombieAubio?implements?Runnable{ ??// 讀音頻WAV格式專用線程 ??private?String filename; ??public?zombieAubio(String wavfile){ ??????filename=wavfile; ??} ??......

這里需要注意的是,Java中解析音樂的API只支持WAV格式的文件,文件格式的轉換大多數音樂播放器都可以做到。

后續優化

1.植物種類的擴充及對應功能的實現

比如殺傷力最大的玉米加農炮。需要4個小玉米進行合成,那么在判斷是否能夠合成玉米加農炮時,需要對植物集合進行遍歷來做坐標的判斷,所以這邊建議最好把可合成的植物單獨放在一個集合中,這樣在做合成判斷的時候會簡單很多,當集合的size小于4時,就可以提示合成失敗了。冰凍西瓜的設計思路也是如此。

2.動作類僵尸的加入,如撐桿跳僵尸、跳舞僵尸等

說一下撐桿跳僵尸的設計思路,此類僵尸和其他僵尸相比,多了一種跳的行為,所以會有一個單獨的方法和單獨的狀態。并且,跳只能觸發一次,所以撐桿跳僵尸的狀態變化應該是行走->遇到植物跳過去->再遇到植物就開始攻擊,在執行狀態變化的時候,應該要去考慮當前的狀態是否還可跳躍。

3.當植物攻擊范圍內不存在僵尸時,植物停止攻擊

這個就簡單拉,在植物執行攻擊方法時,校驗一下是否有Y坐標相同的僵尸即可。


除植物大戰僵尸外,小編還有飛機大戰,坦克大戰等的教程+源碼,同時小編也準備了300集全套Java教程送給大家。

轉發+關注并私信小編“資料”即可領取


以上這些課程如果想要了解的請私信小編“ 資料”即可了解,小編回復的比較慢,喜歡小編的可以關注支持一下,謝謝大家支持!