Java 版植物大戰(zhàn)僵尸思路和源碼分享!
游戲設(shè)計(jì)

屏幕左側(cè)會(huì)自動(dòng)生成植物的卡牌,單擊選中后可以放置在草坪上。右側(cè)會(huì)自動(dòng)生成僵尸,不同的僵尸移動(dòng)速度不同,血量不同,還有的僵尸有隱藏獎(jiǎng)勵(lì),比如:全屏僵尸靜止、全屏僵尸死亡等。當(dāng)時(shí)竟然沒有做游戲的暫停的功能,導(dǎo)致現(xiàn)在截圖的時(shí)機(jī)很難把控,那這里就先說一下游戲暫停的功能應(yīng)該怎么做吧。
public?void?mouseMoved(MouseEvent e)?{
??// 當(dāng)游戲處于運(yùn)行狀態(tài)時(shí)
??if?(status == start) {
????// 通過鼠標(biāo)移動(dòng)事件的對(duì)象獲取當(dāng)前鼠標(biāo)的位置
????int?x = e.getX();
????int?y = e.getY();
????// 如果鼠標(biāo)超出了游戲界面
????if?(x > Game.WIDTH || y > Game.HEIGHT) {
??????// 將游戲的狀態(tài)改為暫停狀態(tài)
??????status = pause;
????}
??}
}
public?abstract?class?Zombie?{
??// 僵尸父類
??// 僵尸共有的屬性
??protected?int?width;
??protected?int?height;
??protected?int?live;
??protected?int?x;
??protected?int?y;
??......
??// 僵尸的狀態(tài)
??public?static?final?int?LIFE =?0;
??public?static?final?int?ATTACK =?1;
??public?static?final?int?DEAD =?2;
??protected?int?state = LIFE;
??/*
??* 這里補(bǔ)充一下為什么父類是抽象類,比如每個(gè)僵尸都有移動(dòng)方法,
??* 但每個(gè)僵尸的移動(dòng)方式是不同,所以該方法的方法體可能是不同的,
??* 抽象方法沒有方法體,在子類中再去進(jìn)行重寫就可以了,
??* 但有抽象方法的類必須是抽象類,因此父類一般都是抽象類
??*/
??// 移動(dòng)方式
??public?abstract?void?step();
??....
}
上面說到子類共有的方法需要抽到父類中,那么部分子類共有的方法該如何處理呢?比如,豌豆射手、寒冰射手可以發(fā)射子彈,堅(jiān)果墻就沒有射擊的這個(gè)行為。所以這里就需要用到接口(Interface)。
public?interface?Shoot?{
??// 射擊接口 - 將部分子類共有的行為抽取到接口中
??// 接口中的方法默認(rèn)是public abstract的,規(guī)范的編碼應(yīng)該將該字段舍去
??public?abstract?Bullet[]?shoot();
}
游戲內(nèi)容
// 首先要有一個(gè)僵尸的集合
// 僵尸集合
private?Listzombies =?new?ArrayList ();
// 接著定義隨機(jī)生成僵尸方法
public?Zombie?nextOneZombie()?{
????Random rand =?new?Random();
????// 控制不同種類僵尸出現(xiàn)的概率
????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();
????}
}
// 僵尸入場(chǎng)
// 設(shè)置進(jìn)場(chǎng)間隔
/*
* 這里補(bǔ)充一下為什么要設(shè)置進(jìn)場(chǎng)的間隔
* 因?yàn)橛螒虻倪\(yùn)行是基于定時(shí)器的,
* 每隔一段時(shí)間定時(shí)器就會(huì)執(zhí)行一次你所加入定時(shí)器的方法,
* 所以這里需要設(shè)置進(jìn)場(chǎng)間隔來控制游戲的速度。
*/
int?zombieEnterTime =?0;
public?void?zombieEnterAction()?{
??zombieEnterTime++;
??????// 對(duì)自增量zombieEnterTime進(jìn)行取余計(jì)算
????if(zombieEnterTime%300==0) {
??????// 滿足條件就調(diào)用隨機(jī)生成僵尸方法,并將生成的僵尸加入到僵尸的集合中
??????zombies.add(nextOneZombie());
????}
}
// 滾輪機(jī)上的植物,狀態(tài)為stop和wait
private?Listplants =?new?ArrayList ();
// 戰(zhàn)場(chǎng)上的植物,狀態(tài)為life和move -move為被鼠標(biāo)選中移動(dòng)的狀態(tài),這里設(shè)計(jì)不合理,會(huì)引發(fā)后面的一個(gè)BUG
private?ListplantsLife =?new?ArrayList ();
// 植物在滾輪機(jī)上的碰撞判定
public?void?plantBangAction()?{
????// 遍歷滾輪機(jī)上植物集合,從第二個(gè)開始
????for(int?i=1;i??????// 如果第一個(gè)植物的y大于0,并且是stop狀態(tài),則狀態(tài)改為wait
??????if(plants.get(0).getY()>0&&plants.get(0).isStop()) {
????????plants.get(0).goWait();
??????}
??????// 如果第i個(gè)植物y小于i-1個(gè)植物的y+height,則說明碰到了,改變i的狀態(tà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個(gè)植物y大于于i-1個(gè)植物的y+height,則說明還沒碰到或者第i-1個(gè)
???????* 植物被移走了,改變i的狀態(tài)為wait,可以繼續(xù)往上走
???????*/
??????if(plants.get(i).isStop()&&
??????????plants.get(i).getY()>plants.get(i-1).getY()+plants.get(i-1).getHeight()) {
????????plants.get(i).goWait();
??????}
????}
??}
??// 檢測(cè)滾輪機(jī)上的植物狀態(tài)
??public?void?checkPlantAction1()?{
????// 迭代器
????Iteratorit = plants.iterator();
????while(it.hasNext()) {
??????Plant p = it.next();
??????/*
???????* 如果滾輪機(jī)集合里有move或者life狀態(tài)的植物
???????* 則添加到戰(zhàn)場(chǎng)植物的集合中,并從原數(shù)組中刪除
???????*/
??????/*
??????* 現(xiàn)在發(fā)現(xiàn)把滾輪機(jī)上move狀態(tài)的植物添加到
??????* 戰(zhàn)場(chǎng)上植物集合的最佳操作時(shí)間點(diǎn)應(yīng)該是
??????* 等植物狀態(tài)變?yōu)閘ife后再添加。
??????* /
??????if(p.isMove()||p.isLife()) {
????????plantsLife.add(p);
????????it.remove();
??????}
????}
??}
// 先對(duì)狀態(tài)做下說明
// wait - 植物卡牌在滾輪機(jī)上移動(dòng)狀態(tài),因?yàn)槭堑戎皇髽?biāo)選中,所以取名為wait
// stop - 植物卡牌在滾輪機(jī)上停止?fàn)顟B(tài),有兩種情況,1 - 到頂了 2 - 撞到上一個(gè)卡牌了
// 開始對(duì)以下代碼進(jìn)行優(yōu)化
// 如果第i個(gè)植物y小于i-1個(gè)植物的y+height,則說明碰到了,改變i的狀態(tà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();
// }
// 優(yōu)化后的代碼是這樣的
// 將一個(gè)復(fù)雜的boolean拆成多個(gè)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();
// 子彈移動(dòng)
public?void?BulletStepAction()?{
??for(Bullet b:bullets) {
????b.step();
??}
}
//僵尸移動(dòng)
//設(shè)置移動(dòng)間隔
int?zombieStepTime =?0;
public?void?zombieStepAction()?{
??if(zombieStepTime++%3==0) {
????for(Zombie z:zombies) {
??????//只有活著的僵尸會(huì)移動(dòng)
??????if(z.isLife()) {
????????z.step();
??????}
????}
??}
}
// 子彈移動(dòng)
public?void BulletStepAction() {
??bullets.forEach((b)->b.step());
??....
}
// 為了應(yīng)對(duì)產(chǎn)品不斷變更的需求,前輩們總結(jié)經(jīng)驗(yàn)得出的設(shè)計(jì)模式已經(jīng)能在一定程度上應(yīng)對(duì)此問題
// 設(shè)計(jì)模式,聲明策略接口,在實(shí)現(xiàn)類中完成過濾邏輯
public?ListfilterStudentByStrategy(List students, SimpleStrategy strategy){
???????ListfilterStudents =?new?ArrayList<>();
???????for?(Student student : filterStudents) {
???????????if(strategy.operate(student)){
???????????????filterStudents.add(student);
???????????}
???????}
???????return?filterStudents;
}
// 當(dāng)需求變更時(shí),只需要在策略接口的實(shí)現(xiàn)類中,變更判斷邏輯即可
public?interface?SimpleStrategy<T>?{
????public?boolean operate(T t);
}
// 無需接口便可實(shí)現(xiàn)需求的快速變更
ListlambdaStudents =
??students.stream().filter(student -> student.getGender()==1).collect(Collectors.toList());
// 僵尸的超類中定義了僵尸的攻擊方法,
// 由于僵尸們的攻擊行為是相同,所以這里是普通方法
// 僵尸攻擊植物
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;
}

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

如果出現(xiàn)了一些效果的偏移,造成的原因是圖片大小不一造成的坐標(biāo)偏移,因?yàn)閳D片都是網(wǎng)上找的,所以效果不是太理想。關(guān)注公眾號(hào) 逆鋒起筆,回復(fù) pdf,下載你需要的各種學(xué)習(xí)資料。
游戲優(yōu)化
1.放置植物的優(yōu)化
2.移除植物的優(yōu)化
// 鏟子集合
private?Listshovels =?new?ArrayList ();
// 鏟子入場(chǎng)
public?void?shovelEnterAction()?{
??// 鏟子只有一把
??if(shovels.size()==0) {
????shovels.add(new?Shovel());
??}
}
// 使用鏟子
Iteratorit = shovels.iterator();
Iteratorit2 = plantsLife.iterator();
while(it.hasNext()) {
??Shovel s = it.next();
??// 如果鏟子是移動(dòng)狀態(tài),就遍歷植物集合
??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 ????????// 移除植物
????????it2.remove();
????????// 移除鏟子
????????it.remove();
????????shovelCheck =?false;
??????}
????}
??}
}


看著這極其復(fù)雜好像很厲害的代碼,我又萌生了痛下狠手的想法,但為了保持原生,我忍住。于是乎還發(fā)現(xiàn)了一個(gè)BUG。如果選中鏟子后,戰(zhàn)場(chǎng)上唯一的植物被僵尸吃掉了,那么這個(gè)鏟子將一直跟隨著鼠標(biāo)無法達(dá)到使用后消除的效果了。解決方案當(dāng)然也很簡(jiǎn)單,當(dāng)戰(zhàn)場(chǎng)上植物集合的size為0時(shí),清空鏟子集合即可。
public?interface?Award?{
??// 獎(jiǎng)勵(lì)接口
??/*
??* 這里還是存在代碼不規(guī)范的問題
??* 接口的方式默認(rèn)是public abstract
??* 接口中的變量默認(rèn)是public static final
??* 這些默認(rèn)的字段應(yīng)該舍去
??*/
??// 全屏靜止
??public?static?final?int?CLEAR =?0;
??// 全屏清除
??public?static?final?int?STOP =?1;
??public?abstract?int?getAwardType();
}
// 檢測(cè)僵尸狀態(tài)
public?void?checkZombieAction()?{
??// 迭代器
??Iteratorit = zombies.iterator();
??while(it.hasNext()) {
????Zombie z = it.next();
????// 僵尸血量小于0則死亡,死亡的僵尸從集合中刪除
????if(z.getLive()<=0) {
??????// 判斷僵尸是否有獎(jiǎng)勵(lì)的接口
??????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();
????}
????// 僵尸跑進(jìn)房子,而游戲生命減一,并刪除僵尸
????if(z.OutOfBound()) {
??????gameLife--;
??????it.remove();
????}
??}
}
4.添加游戲背景音樂
// 啟動(dòng)線程加載音樂
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;
??}
??......
后續(xù)優(yōu)化
源碼分享
- 推薦閱讀 -
抓了!程序員因受不了 996,怒建 6 個(gè)涉黃平臺(tái),涉案 5000 余萬元
點(diǎn)贊+在看,小編感恩大家??
評(píng)論
圖片
表情
