JAVA開發類似冒險島的游戲Part1


JAVA開發類似冒險島的游戲Part1

一、總結

 

 

二、JAVA開發類似冒險島的游戲Part1

初學嘛) ,不過總的來說這個程序還是很有意思的。這里我重新再整理了一下,希望能幫助到其他想要開發類似程序的朋友,共同進步!

曬一下效果圖:

效果圖

哈,還是有模有樣的。左邊是自己寫的冒險島,右邊是真的冒險島。 
畢竟也是個游戲,方向鍵可以控制人物移動,然后可以攻擊,可以打怪,升級,做任務。

先說一下素材,有一個專門提供冒險島素材的紙娃娃系統,冒險島中各種素材都可以從這個網站中獲取: 
http://www.maplesimulator.com/programs/bannedstory

游戲中顯示出這樣的界面效果,可以用PS中的圖層來理解,所謂圖層就是含有文字或圖形等元素的膠片,一張張按順序疊放在一起,組合起來形成頁面的最終效果。

而這個膠片,就是JAVA中的容器JPanel。JPanel可以設置背景圖片,也可以多個JPanel放置於一個JPanel中,就拿游戲下方的狀態條來說: 
整個狀態條就是一個JPanel,我們就新建一個狀態條類

public class StatusBar extends JPanel

然后讓他繼承JPanel。 
我們給這個JPanel設置這樣的一個背景: 
這里寫圖片描述 
給JPanel設置背景的方法是重寫JPanel的paint函數,看下面代碼應該很清楚。

public void paint(Graphics g){ g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\狀態條\\狀態條.png"),0,0,this); }

接着,在這個JPanel上還要顯示等級的數字、血條、藍條、經驗條,以及一個系統按鈕(不過其實這個按鈕並不屬於StatusBar類,我們只是讓他顯示在這個位置)。 
這些都是StatusBar類的成員。我們只需要在固定的位置將相應的成員顯示就可以了。 
先說這個顯示等級的數字,很顯然就是一個JLabel嘛,不過我用的不是JLabel。 
依舊是在paint函數中,加入這樣一段:

g.setColor(Color.WHITE); g.setFont(new Font(Font.DIALOG_INPUT, Font.BOLD, 25));//設置字體 g.drawString(Integer.toString(lv), 50,38);//等級

直接把字畫在JPanel上的相應位置就可以了,顏色、字體、大小、字的內容、橫縱坐標,就這些參數。可以查看API文檔,這里就不細說。

血條藍條經驗條,這三個條條是大部分網絡游戲都有的東西,主要用來直觀的顯示玩家狀態,拿血條舉例子,看最前面的效果圖,當前血量800,總血量1000,所以血條的長度就是整個血槽的4/5,這樣一想,用進度條這種組件去實現血條藍條是很可行的,然而我查了才發現,JAVA中(至少是標准庫吧。。)並沒有進度條這個組件,所以還是要自己去實現它。 
還是在paint函數中:

g.setColor(Color.RED); //血條 g.fillRect(141, 23, (int)((double)hp/allhp*length), 15); g.setColor(Color.BLUE); //藍條 g.fillRect(340, 23, (int)((double)mp/allmp*length), 15); g.setColor(Color.YELLOW); //經驗條 g.fillRect(539, 23, (int)((double)exp/allexp*length), 15);

根據當前血量和總血量的比例去繪制相應長度相應顏色的矩形,這樣一來動態的血條藍條經驗條的主體部分就完成了,我們還需要在上面顯示具體的血量和血總量: 
這里寫圖片描述 
有了前面繪制等級的經驗,我們使用g.drawString函數就可以了,不過為了顯示的美觀嗎,我們還需要考慮一些問題,文字的顯示是與血條右邊對其的,而繪制文字的參數中提供的參數是這樣的一組x,y並不能符合我們右對其的要求。 
這里寫圖片描述 
所以我用了另外一種很機智(shabi)的方法:

//數字比例6.7619 /[比例3 String hptxt="["+Integer.toString(hp)+"/"+Integer.toString(allhp)+"]"; int hptxtnuml=Integer.toString(hp).length()+Integer.toString(allhp).length();//血量數字顯示的數字長度 int hptxtx=272-(int)(hptxtnuml*6.7619);//相應橫坐標 String mptxt="["+Integer.toString(mp)+"/"+Integer.toString(allmp)+"]"; int mptxtnuml=Integer.toString(mp).length()+Integer.toString(allmp).length();//藍量數字顯示的數字長度 int mptxtx=471-(int)(mptxtnuml*6.7619);//相應橫坐標 String exptxt="["+Integer.toString(exp)+"/"+Integer.toString(allexp)+"]"; int exptxtnuml=Integer.toString(exp).length()+Integer.toString(allexp).length();//藍量數字顯示的數字長度 int exptxtx=670-(int)(exptxtnuml*6.7619);//相應橫坐標

先把要繪制的字符串准備好,然后計算一下文本長度(這個長度是指真的長度。。顯示出來要幾個坐標。。別問我怎么測得,我用尺子量的。。),最后換算出相應的坐標繪制。

至此,狀態條的顯示就完成了。在我的設計中,人物的屬性(血,藍,經驗,等級,攻擊力等)是直接放在狀態條類里作為成員變量的,換句話說,你玩游戲玩的不是那個人物,你的所有數據都不存在人物的類里,而是存在狀態條類里,人物攻擊力高,一下秒掉怪物,那只是配合着狀態條里的屬性顯示給玩家看而已~

我們先把狀態條放到一邊,待會再來用它。

現在我們要實現方向鍵控制人物移動。 
依舊是前面圖層的思想,人物也是一個圖層。我們創建一個人物類:

public class Obj extends JPanel

先來分析一下人物的動作,人物有個朝向,臉朝左,臉朝右,站着不動的時候有站着不動的姿勢,走路的時候有走路的姿勢,按上鍵如果能抓到繩子,有爬繩子的姿勢,按下有趴着的姿勢。所以Obj類中需要有一個變量來控制人物的狀態,(向左走、向右走、向左趴下等):

private int zhuangtai=2;//人物狀態

然后在繪制背景圖片的時候,switch(zhuangtai)來決定該繪制哪張圖片就可以了。 
我們可以寫一個設置狀態的函數,以便控制人物的時候使用:

public void setzhuangtai(int a)//設置狀態 { zhuangtai=a; }

這里說明一下,人物站立的時候,並不是一張靜止的圖片,而是提前准備好的GIF圖像,站立的時候顯示的就是自然擺手的動畫分鏡頭如下: 
這里寫圖片描述 
當然如果不想使用GIF圖像的話,可以新建一個線程,專門在站立狀態控制人物擺手動畫的切換。 
類似攻擊的動畫顯示也是如此。 
這里寫圖片描述 
我們現在先來設想一下控制人物移動的思路: 
首先人物這個圖層(JPanel)是顯示在窗口上的,主類繼承JFream 
然后人物作為主類的成員,就和創建一個JButton一樣的寫法

private Obj ren=new Obj(0,0);//創建角色

用兩個整形變量x,y來代表人物的坐標 
接下來就是將,xy作為參數使用setBounds函數設置人物的顯示坐標以及顯示大小。 
對主窗口注冊鍵盤監聽和觸發事件: 
我們按下方向鍵后,比如說方向右鍵,調用Obj類中的setzhuangtai函數,修改人物狀態,再將x+5 (這個值自己定,值越大移動的越快)接着再次使用setBounds重新設置人物的顯示坐標。然后調用repaint進行重繪。 
這樣就可以實現人物的移動了。我這里沒有附具體代碼,因為我不是這樣寫的。。(噗!!)

在實際測試中,這樣去實現人物移動會引入一個很麻煩的問題——當你設置了游戲地圖之后,控制人物移動會出現背景跟不上人物,有殘影的情形,具體的解決方案是使用雙緩沖技術,不過我有另外的思路解決了這個問題,何樂而不為呢:

人物移動的時候,人物這個圖層后面的背景總是拖着之前的背景,使得顯示着很不協調,我的解決方案是設置人物這個圖層的大小的時候,讓他充滿整個屏幕,這樣的話,人物移動時,背景就是整個屏幕,而不用考慮舊背景的殘影問題了。不過這樣設計的話,人物移動的方式就要改變了,不能再是移動人物類的對象在主窗口中顯示的位置,因為人物類的對象在主窗口中已經是全屏顯示了,不能再改變,改變的只能是人物的圖片,在人物類的這個JPanel中內部的位置。所以x,y這個坐標也是存於人物類中。

    private int zhuangtai=2;//人物狀態 //屏幕坐標 private int x; private int y; public void setzhuangtai(int a)//設置狀態 { zhuangtai=a; } public void setxy(int m_x,int m_y) { x=m_x; y=m_y; } //人物在屏幕中移動 public void movex(int m_x) { x+=m_x; } public void movey(int m_y) { y+=m_y; }

顯示的地方就通過x和y的值來調整位置,不過還是有一個地方要注意,顯示圖片時的坐標參數是指以左上角為起點的,但是每一張圖片的大小都不一樣,所以如果不加換算的話就會出現這樣的問題: 
這里寫圖片描述這里寫圖片描述 
這里寫圖片描述這里寫圖片描述 
人物的顯示坐標自然是要以腳為坐標,所以我們對於每一張圖都要測定它的偏移(很幸運的是紙娃娃系統可以直接導出偏移的值),然后顯示的時候根據偏移去計算換算后的坐標。

paint函數如下:

public void paint(Graphics g)
    { switch(zhuangtai) { case 0:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\主角\\走路\\向右走0.png"),x-15,y-68,this);break; case 1:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\主角\\走路\\向右走1.png"),x-15,y-68,this);break; case 2:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\主角\\走路\\向右走2.png"),x-15,y-68,this);break; case 3:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\主角\\走路\\向右走3.png"),x-15,y-68,this);break; case 4:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\主角\\走路\\向左走0.png"),x-76,y-68,this);break; case 5:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\主角\\走路\\向左走1.png"),x-76,y-68,this);break; case 6:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\主角\\走路\\向左走2.png"),x-76,y-68,this);break; case 7:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\主角\\走路\\向左走3.png"),x-76,y-68,this);break; case 8:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\主角\\站立\\面朝右站.gif"),x-40,y-86,this);break; case 9:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\主角\\站立\\面朝左站.gif"),x-24,y-86,this);break; case 10:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\主角\\跳躍\\向右跳.png"),x-40,y-90,this);break; case 11:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\主角\\跳躍\\向左跳.png"),x-25,y-90,this);break; case 12:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\主角\\趴下\\面朝左趴下.png"),x-106,y-40,this);break; case 13:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\主角\\趴下\\面朝右趴下.png"),x,y-40,this);break; case 14:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\左\\0.png"),x-165,y-110,this);break; case 15:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\左\\1.png"),x-165,y-110,this);break; case 16:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\左\\2.png"),x-165,y-110,this);break; case 17:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\左\\3.png"),x-165,y-110,this);break; case 18:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\左\\4.png"),x-165,y-110,this);break; case 19:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\左\\5.png"),x-165,y-110,this);break; case 20:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\左\\6.png"),x-165,y-110,this);break; case 21:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\左\\7.png"),x-165,y-110,this);break; case 22:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\左\\8.png"),x-165,y-110,this);break; case 23:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\右\\0.png"),x-115,y-110,this);break; case 24:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\右\\1.png"),x-115,y-110,this);break; case 25:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\右\\2.png"),x-115,y-110,this);break; case 26:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\右\\3.png"),x-115,y-110,this);break; case 27:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\右\\4.png"),x-115,y-110,this);break; case 28:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\右\\5.png"),x-115,y-110,this);break; case 29:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\右\\6.png"),x-115,y-110,this);break; case 30:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\右\\7.png"),x-115,y-110,this);break; case 31:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\技能\\輕舞飛揚\\右\\8.png"),x-115,y-110,this);break; case 32:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\主角\\爬繩子\\0.png"),x-27,y-70,this);break; case 33:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\圖片素材\\主角\\爬繩子\\1.png"),x-27,y-70,this);break; } g.drawString("屏幕位置:"+"x="+Integer.toString(x)+"y="+Integer.toString(y),645,525); }

控制移動的代碼示例:

if(e.getKeyCode()==KeyEvent.VK_LEFT)//左 { if(Drop!=2) { Direction=0; if(Drop==0) ren.setzhuangtai(movetime++%4+4); if(map.AllowMoveL()==1) { //if(ren.getx()<=600||map.getrenx()>mapxmax-200) if(ren.getx()>=200||map.getrenx()<210) { ren.movex(-5); map.moverenx(-5); } else { map.movex(5); map.moverenx(-5); NpcOffsetx-=5; NPCshow(); } } MainWindow.this.repaint(); new Thread(DropTest).start(); } }

畢竟移動的時候不能只考慮人物的移動,玩游戲的時候,人物始終會顯示在屏幕上,而不會走出窗口,當人物坐標臨近窗口時,移動的就不是人物,而是地圖的背景了(地圖的圖片是比窗口大的多的,窗口只有800*600,只顯示地圖的一部分,當玩家向右走,走到接近窗口邊緣時,地圖的圖片向左移動,玩家坐標不變,動作保持向右走的姿勢)。這樣可以保證人物始終在窗口內移動。 
這里寫圖片描述 
這里寫圖片描述 
同樣的NPC的位置也要改變,NPC是對於地圖的獨立存在,但是相對於地圖,NPC是靜止的。 
這里寫圖片描述 
NPC的顯示,我們需要明確一下兩點,第一,NPC顯示在哪張地圖上(或是說NPC顯示在哪個場景中),第二,該NPC在該地圖上的坐標是多少。 
這里所需的NPC在地圖上的坐標而不是NPC在窗口上的坐標,並不是說要將NPC作為地圖類的成員,在地圖這個JPaenl中顯示(這樣做不可取,原因后面說),而是因為NPC與地圖是相對靜止的,我們有了NPC在地圖上的坐標,也有了地圖在窗口的顯示坐標,就可以將NPC在地圖上的顯示坐標換算出來,我們還可以設置一個NPC坐標的偏移參數,地圖移動時,就改變這個偏移值,然后NPC顯示的時候就將偏移值包含進去。這樣一來會更加直觀,后期若先要添加新的NPC也十分方便。 
那么我們把所有的NPC顯示專門放到一個函數里:

public void NPCshow()//顯示NPC { switch(mapNo) { case 0: { for(int i=0;i<7;i++) npc[i].setVisible(true); npc[0].setBounds(2350-NpcOffsetx,720+NpcOffsety,123,101);//希納斯 npc[1].setBounds(1800-NpcOffsetx,707+NpcOffsety,48,109);//南哈特 npc[2].setBounds(732-NpcOffsetx,515+NpcOffsety,97,103);//米哈爾 npc[3].setBounds(465-NpcOffsetx,705+NpcOffsety,97,109);//伊莉娜 npc[4].setBounds(788-NpcOffsetx,695+NpcOffsety,78,122);//奧茲 npc[5].setBounds(302-NpcOffsetx,420+NpcOffsety,77,116);//伊卡爾特 npc[6].setBounds(663-NpcOffsetx,635+NpcOffsety,77,119);//胡克 break; } case 1: { for(int i=0;i<7;i++) npc[i].setVisible(false); for(Monster tempgw:gw[0]) tempgw.setBounds(map.getx(), map.gety(), mapxmax, mapymax); //gw[0][0].setBounds(map.getx(), map.gety(), mapxmax, mapymax); //gw[0][1].setBounds(map.getx(), map.gety(), mapxmax, mapymax); break; } case 2: { for(int i=0;i<7;i++) npc[i].setVisible(false); for(Monster tempgw:gw[1]) tempgw.setBounds(map.getx(), map.gety(), mapxmax, mapymax); break; } case 3: { for(int i=0;i<7;i++) npc[i].setVisible(false); for(Monster tempgw:gw[2]) tempgw.setBounds(map.getx(), map.gety(), mapxmax, mapymax); break; } } }

整個函數的原理很簡單,先switch地圖編號,讓其他地圖的NPC不顯示,然后再對當前地圖的NPC一個一個的設置位置。

我們把游戲理解成一個一個的圖層組成的場景,那么圖層與圖層之間是有疊放次序的,處於最頂端的圖層會將后面的圖層蓋住。 
最頂端的圖層是系統菜單(如下圖),然后是狀態條上面的系統按鈕,再然后是狀態條,再再然后是NPC的圖層,再再再然后是玩家控制的人物圖層,最后是地圖。 
我們看這張圖,很清楚的反應了他們的關系: 
這里寫圖片描述 
前面說到NPC不能是作為地圖類中的成員,雖然說把NPC放到地圖類中去顯示會很方便,但是這樣一來NPC就沒有交互性了,游戲中如何與NPC交互?當然是要點她。。如果把NPC作為地圖類的成員,那么就永遠也點不到NPC了,因為人物的圖層(JPanel)大小是占滿整個屏幕(這在前面已經說到),這樣就像是一個JButton處在一個透明的JPaenl后面,看得到點不到。

現在來做系統按鈕,游戲中的按鈕自然不能用JButton,因為太丑了嘛,我們還是用JPanel來做,就和人物的顯示一樣,按鈕也分三個狀態,一個是未激活,再是激活,再是按下,激活就是鼠標放到按鈕上的時候按鈕邊緣發光。 
這里寫圖片描述這里寫圖片描述這里寫圖片描述 
從左到右一目了然,要做到這些,只需要給它注冊和設置鼠標監聽事件:

public void mouseEntered(MouseEvent e)//鼠標進入 public void mousePressed(MouseEvent e)//鼠標按下 public void mouseReleased(MouseEvent e)//鼠標釋放 public void mouseExited(MouseEvent e)//鼠標離開

將系統菜單的是否顯示,以及系統按鈕的狀態切換分別寫到這些事件之中。同理,系統菜單上的按鈕也是如此。

至此,我們的游戲已經是初見雛形,可以控制人物做各種動作,也有狀態欄去顯示人物的狀態,還有一個菜單提供各項操作選擇。

該對游戲的完整性更進一步了,現在的地圖,說到底也只是一張圖片,設置成地面的圖片,人物就在地面上走,設置成天空的圖片,人物就在天空上走,我們要對地圖和人物進一步編寫,引入“重力下墜”和“地面”。

先說下墜,這個動作執行起來人物是什么樣的呢?如圖: 
這里寫圖片描述 
人物保持跳躍的動作,從高空落下,坐標值y值不斷增加,期間我們按左鍵 ,右鍵,可以使得人物臉的朝向改變,並且可以往左或者右飄動,也可以在空中的時候發動攻擊(我嘗試了N遍才截到這個圖!!)。 
這里寫圖片描述 
所以很顯然啊,這個墜落,是一個多線程的函數,始玩家y坐標持續增大,並且設置玩家的狀態動畫為“跳躍”。

if(droping==0)//確保同一時間只執行一個下墜線程 { droping=1;//代表已經在下墜 jumping=1;//下墜中禁止跳躍 if(Drop!=2) Drop=map.ToGround(); while(Drop==1) { if(attacking==0) ren.setzhuangtai(Direction==0?11:10); try { Thread.sleep(3); } catch(Exception e1) { e1.printStackTrace(); } if(ren.gety()<=430/*||map.getreny()>=mapymax-200*/) ren.movey(1); else { map.movey(-1); NpcOffsety-=1; } map.movereny(1); Drop=map.ToGround(); NPCshow(); MainWindow.this.repaint(); if(attacking==0) ren.setzhuangtai(Direction==0?9:8); } droping=0;//下墜結束 jumping=0;//跳躍結束 //MainWindow.this.repaint(); }

代碼中很容易看出來的,墜落的條件是Drop的值,每下落一點,就調用地圖的ToGround函數,檢測是否到達地面,然后給Drop重新賦值。 
來看一下ToGround函數,比較長所以只貼一部分:

public int ToGround()//到地面 { switch(mapNo) { case 0: { if(reny==790 ||(renx>=597&&renx<=736&&reny==757) ||(renx>=600&&renx<=710&&reny==727) ||(renx>=589&&renx<=694&&reny==695) ||(renx>=586&&renx<=686&&reny==668) ||(renx>=589&&renx<=677&&reny==643) ||(renx>=594&&renx<=674&&reny==619) ||(renx>=600&&renx<=682&&reny==595)) return 0; break; }…

很顯然,人物坐標處於這些范圍,就不用繼續掉了,這里的坐標說的是人物的腳的坐標。 
而這些范圍,就是一條條的線,代表地面,或者台階,或者別的能站的地方如圖(這張地圖費了我3個小時啊!!): 
這里寫圖片描述
在所有可能發生墜落的操作后面都啟動一個墜落線程。類似的地圖元素還有傳送門,繩子,爬梯,牆壁,都可以在地圖類中用類似的函數來寫,以便於檢測。 
同樣的,跳躍也是一個線程,先是將y值減小,到了最高點再執行墜落線程:

if(jumping==0)//確保同一時間只能執行一個跳躍線程 { jumping=1;//代表正在跳躍 Drop=1;//代表人物以離開地面 droping=1;//跳躍中途禁止啟動下墜線程 for(int i=0;i<100;i++) { try { Thread.sleep(3); } catch(Exception e1) { e1.printStackTrace(); } if(ren.gety()>=100||map.getreny()<=100) ren.movey(-1); else { map.movey(1); NpcOffsety+=1; } map.movereny(-1); NPCshow(); MainWindow.this.repaint(); } try { Thread.sleep(5); } catch(Exception e1) { e1.printStackTrace(); } droping=0;//允許下墜 new Thread(DropTest).start();//啟動下墜 }

至此,人物終於可以在地圖上自由走動了。 
玩過冒險島的人都知道,換地圖的動作就是在光門的位置按上鍵,所以對上鍵注冊鍵盤監聽,然后檢測是否在光門上,在哪個光門上,以及切換到哪個地圖。 
以下是部分代碼:

public void gomap(int x,int y)//進入地圖 切換地圖時,要給地圖一個初始位置 { map.setmapNo(mapNo);//切換地圖 switch(mapNo) { case 0: { mapymax=941; mapxmax=3095; //new Thread(newMonster).start(); break; } case 1: { mapymax=535; mapxmax=2170; new Thread(newMonster).start(); break; } … ren.setxy((x>mapxmax-403?x-mapxmax+806:(x<806?x:403)),y>mapymax-315?y-mapymax+629:(y<629?y:315)); //map.moverenx(x>mapxmax-403?mapxmax-806:(x<806?0:x-403)); //map.moverenx(x); // map.movereny(y); map.setrenxy(x, y); //map.movex(-x); map.setxy(-(x>mapxmax-403?mapxmax-806:(x<806?0:x-403)),-(y>mapymax-315?mapymax-629:(y<629?0:y-315))); //map.movex(x>mapxmax-403?mapxmax-806:(x<806?0:x-403)); //map.movey(-y); NpcOffsetx=x>mapxmax-403?mapxmax-806:(x<806?0:x-403); NpcOffsety=-(y>mapymax-315?mapymax-629:(y<629?0:y-315)); menu.setVisible(false); for(ButtonBase temp:menubutton)//菜單按鈕 temp.setVisible(false);; MainWindow.this.repaint(); new Thread(DropTest).start(); }

切換地圖之后讓人物從空中掉下了就OK了。

接下來就是怪物系統: 
這里寫圖片描述 
怪物的主體和人物基本一致,不同的是,怪物的屬性,比如hp,攻擊力等都是存在於怪物自己的類中。所有的怪物由一個怪物線程進行控制。怪物能做什么呢?可以左右移動,可以攻擊,可以被玩家打,被打死了玩家能獲得經驗。

    private int MonsterID; private int zhuangtai=0; private int Target;//代表是否有攻擊目標 private int HP;//怪物血量 private int Defense;//防御力 private int DHP=0;//掉血量 private int x=1370; private int y=400; private int bx; private int by; private int xmax; private int xmin; private int Offsetx; private int Offsety;

如果怪物沒有被攻擊過,那么它會漫無目的的隨機在自己的移動范圍內移動,如果玩家攻擊了怪物,那么怪物就會朝着玩家移動。游戲中對於屬性,有很多可以設置的地方,最簡單的就是一個攻擊力一個血量,不過為了增強游戲的多樣性(keng qian)吧,陸續出來了防御力,回避率,恢復速度,XX屬性抗性等等等。。我這里也就是隨便設定了一下攻防計算規則。 
對於怪物的顯示位置,我們可以將其與NPC的顯示位置類比,怪物靜止的時候,就和NPC一樣,與地圖是相對靜止的,怪物還可以在自己的活動范圍自己移動,所以在NPC顯示的基礎上,再去進一步的增加一個移動函數就可以了。 
為了節約資源,我將一張地圖上的所有怪物共用的同一個線程,首先獲取地圖號,然后用一個循環來遍歷該地圖內所有的怪物,切換地圖之后,就開始執行這張地圖上的怪物的線程,線程根據怪物的狀態,有沒有被攻擊,有沒有目標來控制怪物做出各種動作。

任務系統 
這里寫圖片描述 
任務是游戲的精髓部分,它拉着玩家去展開游戲劇情,這里說一下我的單線任務設計的思路。一個整型變量來代表任務的進度,給NPC注冊鼠標監聽,點擊NPC后,根據當前的進度,以及NPC的編號,來確定NPC對話框中的顯示內容。 
示例如下面的代碼,先switch NPC編號,然后再switch 任務進度,最后就可以得到具體的內容了:

case 8: { g.drawString("聖地中到處都是黑魔法師的傀儡", 165, 30); g.drawString("這對女皇來說是很大的威脅", 165, 45); g.drawString("不能殺掉太多,不然會引起黑魔法師注意的", 165, 60); g.drawString("(請再和我對話)", 165, 75); break; } case 9: { g.drawString("往左一直走,前往 [前線1]、[前線2]", 165, 30); g.drawString("消滅痩刺客、胖刺客各 30 只吧!", 165, 45); break; } case 10: { g.drawString("你確認已經消滅了 痩刺客、胖刺客各 30 只嗎?", 165, 30); g.drawString("(打開任務窗口可以查看任務完成情況)", 165, 45); break; } case 11: { g.drawString("你完成的很好!", 165, 30); g.drawString("(請再和我對話)", 165, 45); break; }

這里寫圖片描述 
然后顯示對話框的時候就可以判斷,當前進度,這個NPC是否有內容,如果有的話,就顯示提前設置好的,沒有的話就顯示默認值。 
這里寫圖片描述 
至此,一個單機的冒險島的主要功能就基本實現了,剩下的就是多添幾張地圖,多加幾任務之類的了。

注:第一次寫博客。。(其實因為之前發了一次這個,不過寫的是亂七八糟,所以刪了重寫,這次算是正式的第一次!)前面寫的很詳細,后面比較簡略。。這篇博客我還會慢慢修改(排版什么的還不熟嘛)。 
然后是,大二結束的時候JAVA課程設計,我最后把它改成了聯網版,可以局域網一起玩。。空了我再整理一下也發出來(所以這個是Part1嘛)。

項目文件下載地址: 
http://download.csdn.net/detail/jdk999/9042405

 

參考:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM