Java实现飞机大战(详细思路与过程,含源代码)
目錄
- 演示
- 構(gòu)思
- 詳細(xì)分析
- GameFrame
- 背景動(dòng)畫
- 菜單選項(xiàng)
- 添加(鍵盤)監(jiān)聽器
- GamePanel
- 動(dòng)態(tài)游戲顯示區(qū)(雙緩沖)
- Plane(myplane,enemyplane,bossplane)
- Break
- Collision
- Bullet
- Dialog
- Sound
- Main方法
- 總結(jié):
演示
飛機(jī)大戰(zhàn)
源代碼下載: https://github.com/Fattybenny/javaswingproject/tree/main/java%E9%A3%9E%E6%9C%BA%E5%A4%A7%E6%88%98.
構(gòu)思
首先,要把整體的游戲框架和內(nèi)容構(gòu)思出來(lái)(根據(jù)預(yù)先構(gòu)思游戲里存在的組件內(nèi)容和游戲功能抽象出指定類)。以我的小游戲?yàn)槔?#xff1a;
1.主界面框架類:GameFrame(extends JFrame)
? ? ? ? ? ?顯示開始界面
2.彈出界面類:Dialog (extends JDialog)
????????????彈出設(shè)置界面(聲音開關(guān))、彈出游戲成功、失敗界面
JDialog 窗體的功能是從一個(gè)窗體中彈出另一個(gè)窗體,就像是在使用 IE 瀏覽器時(shí)彈出的確定對(duì)話框,JDialog 窗體與 JFrame 窗體形式基本相同,設(shè)置窗體的特性時(shí)調(diào)用的方法名稱都基本相同,如設(shè)置窗體大小、窗體關(guān)閉狀態(tài)等
3.游戲面板類:GamePanel (extends JPanel)
? ? ? ? ? ?真正顯示飛機(jī)大戰(zhàn)動(dòng)態(tài)游戲畫面,并且還添加了按鈕JButton用于控制游戲開始暫停。
4.玩家飛機(jī)類:MyPlane
? ? ? ? ? ?移動(dòng)玩家飛機(jī)、畫玩家飛機(jī)等其他與玩家飛機(jī)相關(guān)的方法
5.敵機(jī)類:EnemyPlane
? ? ? ? ? ?移動(dòng)敵機(jī)、畫敵機(jī)
6.BOSS飛機(jī)類:BossPlane
? ? ? ? ? ?移動(dòng)BOSS飛機(jī)、畫BOSS飛機(jī)
7.子彈類(也可以分三個(gè)類:玩家飛機(jī)子彈、敵機(jī)子彈、BOSS子彈)
? ? ? ? ? ?移動(dòng)子彈、繪制子彈
8.碰撞類:Collision
? ? ? ? ? ?檢測(cè)各種碰撞情況
9.爆炸類:Break
? ? ? ? ? ?繪制飛機(jī)爆炸圖片
10.聲音類:Sound
? ? ? ? ? ?控制聲音的播放與暫停
11.主類:Main
? ? ? ? ? ? 開啟程序
詳細(xì)分析
GameFrame
背景動(dòng)畫
動(dòng)態(tài)的圖像(視頻)原理:視頻由一張張靜態(tài)的圖片快速變換形成,連續(xù)的圖像變化每秒超過(guò)24幀(frame)畫面以上時(shí),根據(jù)視覺暫留原理,人眼無(wú)法辨別單幅的靜態(tài)畫面;看上去是平滑連續(xù)的視覺效果。
在java程序中,如果通過(guò)人手動(dòng)點(diǎn)擊一次換一次圖片,那么要想實(shí)現(xiàn)肉眼看見的視頻效果需要我們一秒鐘至少點(diǎn)擊24次,這是非常困難的。而我們希望的是通過(guò)一次點(diǎn)擊就可以產(chǎn)生動(dòng)畫效果,讓其自動(dòng)每隔一段極短的時(shí)間就換一次圖片。于是可以采用多線程的方法來(lái)實(shí)現(xiàn)。由于動(dòng)畫效果是在當(dāng)前的GameFrame類中實(shí)現(xiàn)的,可以直接定義一個(gè)內(nèi)部類繼承Thread,當(dāng)然也可以新建一個(gè)class文件定義。
獲取包中的圖片:
Image img; img=Toolkit.getDefaultToolkit().getImage(this.getClass().getResource("/Capture/飛機(jī).mp4_"+i+".png")); //獲得資源的URL:this.getClass().getResource(String name) // 單斜杠 /開頭表示從根目錄開始 private class setBackground2 extends Thread {Image img;Graphics mg;@Overridepublic void run() {while(true){for(int i=0;i<200;i++)//200張圖片為一個(gè)完整動(dòng)畫{ img=Toolkit.getDefaultToolkit().getImage(this.getClass().getResource("/Capture/飛機(jī).mp4_"+i+".png"));mg.drawImage(img, 0, 0, null);try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();} }} } }菜單選項(xiàng)
設(shè)置不同的JLabel標(biāo)簽添加到界面的相應(yīng)位置。
一般容器都有默認(rèn)布局方式,但是有時(shí)候需要精確指定各個(gè)組建的大小和位置,就需要用到空布局。
首先利用setLayout(null) 語(yǔ)句將容器的布局設(shè)置為null布局(空布局)
再調(diào)用組件的setBounds(int x, int y, int width,int height) 方法設(shè)置組件在容器中的大小和位置,單位均為像素。
通過(guò)JLabel組件添加一張圖片:
ImageIcon background = new ImageIcon(this.getClass().getResource("/images/mainback.png"));back = new JLabel(background);back.setBounds(0,700,1800, 300); this.getContentPane().add(back); //設(shè)置標(biāo)簽label01 = new JLabel("開始游戲");label01.setFont(new Font("acefont-family", Font.BOLD, 50));label01.setForeground(Color.blue);//設(shè)置字體背景顏色label01.setBounds(820, 740, 400, 120);//起點(diǎn)寬高label02 = new JLabel("選擇飛機(jī)");label02.setFont(new Font("acefont-family", Font.BOLD, 50));label02.setBounds(820, 830, 400, 120);label03 = new JLabel(icon);label03.setBounds(600, 740, 250, 120);label04 = new JLabel(icon);label04.setBounds(600, 830, 250, 120);label04.setVisible(false);... ...問(wèn)題與解決:
1.先添加的組件會(huì)覆蓋影響到后添加的組件。
例如這里有三個(gè)JLabel組件,其中一個(gè)是帶有背景圖片的,其他兩個(gè)是帶有文字的,一定要最后添加帶有背景圖片的,否則無(wú)法將文字顯示在圖片上。
2.注意畫筆繪制圖片的覆蓋問(wèn)題:
后面經(jīng)常要用到drawimage方法,要注意,畫筆后畫的內(nèi)容會(huì)覆蓋先畫的內(nèi)容;畫筆畫的內(nèi)容會(huì)覆蓋類似JLabel這種組件,無(wú)論組件先添加還是后添加(解決辦法:在組件添加到相應(yīng)的容器之后再設(shè)置坐標(biāo)位置(setbounds),或者如上文所說(shuō)的通過(guò)按正確順序添加JLabel組件的方法來(lái)達(dá)到設(shè)置背景圖片的功能,避免了用drawimage)。
添加(鍵盤)監(jiān)聽器
public void keyadapter(){this.requestFocusInWindow();this.addKeyListener(new KeyAdapter() {public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); //監(jiān)聽向上或向下按if(key == KeyEvent.VK_DOWN || key == KeyEvent.VK_UP) {label03.setVisible(!label03.isVisible());label04.setVisible(!label04.isVisible());if(label03.isVisible()) {label01.setForeground(Color.blue);label02.setForeground(Color.black);} else {label01.setForeground(Color.black);label02.setForeground(Color.blue);}}if(key == KeyEvent.VK_ENTER && label03.isVisible()) {//添加游戲面板JPaneladd(...);//并移除之前添加的JLabel組件remove(label02);remove(label03);remove(label04);remove(back);... ...}... ...}});}問(wèn)題與解決
焦點(diǎn)的獲取
在使用鍵盤監(jiān)聽器的時(shí)候,一定要讓監(jiān)聽對(duì)象獲取焦點(diǎn),如果焦點(diǎn)不在監(jiān)聽對(duì)象上,那么鍵盤輸入的內(nèi)容就無(wú)法被監(jiān)聽到。
使用requestFocusInWindow()方法
注意在監(jiān)聽器方法中的this關(guān)鍵字
在添加監(jiān)聽器時(shí)有兩種不同的方法:
1.this.addKeyListener(listener);
listener是用戶定義的監(jiān)聽器類,這個(gè)類implements相應(yīng)接口。
2.直接在當(dāng)前類定義監(jiān)聽器;
GamePanel
按鈕區(qū)和分?jǐn)?shù)區(qū)都分別添加相應(yīng)的JButton按鈕,并添加ActionListener監(jiān)聽器即可
動(dòng)態(tài)游戲顯示區(qū)(雙緩沖)
還是和GameFrame中的背景動(dòng)畫類似,都采用多線程方法。
private class MapPanel extends Canvas implements Runnable
問(wèn)題與解決:
1.Canvas使用
Canvas是AWT組件,JPanel是Swing組件,Swing組件是以AWT組件為基礎(chǔ)的,從理論上來(lái)說(shuō),Canvas要比JPanel更輕量些.如果canvas能滿足需求,就用canvas.Canvas 組件表示屏幕上一個(gè)空白矩形區(qū)域,應(yīng)用程序可以在該區(qū)域內(nèi)繪圖,或者可以從該區(qū)域捕獲用戶的輸入事件。
不能直接使用該類,需要繼承Canvas并重寫其paint方法.
repaint paint update 三個(gè)方法的調(diào)用順序:
repaint->update->paint
paint源碼:
update源碼:
public void update(Graphics g) {g.clearRect(0, 0, width, height);//清除界面paint(g); }由此看來(lái)我們可以選擇性的重寫update或者paint方法來(lái)滿足程序繪畫需要,如果不需要清除界面就去除 g.clearRect(0, 0, width, height)方法,在本例中就需要執(zhí)行這一步,因?yàn)槊看沃乩L圖片如果都執(zhí)行一遍清除界面操作就會(huì)出現(xiàn)閃爍現(xiàn)象。(如果是用戶自己定義的繪制方法,不需要用到paint方法進(jìn)行重繪,則不用重寫這些方法,需要用到雙緩沖技術(shù),如下所示)
除此以外解決圖片閃爍現(xiàn)象,還要用到雙緩沖技術(shù):
先在內(nèi)存中預(yù)先分配一定大小的圖片緩沖區(qū),在將所有繪圖方法繪制到緩沖區(qū)之后,再最后將圖片緩沖區(qū)的內(nèi)容繪制出來(lái);
Plane(myplane,enemyplane,bossplane)
這三個(gè)不同的飛機(jī)類內(nèi)容基本相同:
class Plane { 初始化飛機(jī)的圖片、坐標(biāo)等如果是一組圖片則用Image[]數(shù)組存儲(chǔ){planeimg=Toolkit.getDefaultToolkit().getImage(getClass().getResource());}繪制飛機(jī)方法:{先判斷飛機(jī)是否還存活存活:g.drawImage(planeimg,x, y, null); 死亡:調(diào)用爆炸類里的繪制爆炸圖片方法(下文所示)}移動(dòng)飛機(jī)方法:{修改飛機(jī)圖片的x,y坐標(biāo)} }Break
class Break { 初始化爆炸圖片 { plane_b = new Image[6]; for(int i = 0; i < plane_b.length; i++) {plane_b[i] = Toolkit.getDefaultToolkit().getImage(getClass().getResource("/images/bomb_enemy_" + i + ".png"));} ... }繪制爆炸圖片 { g.drawImage(plane_b[i/5], x, y, EnemyPlane.ENEMY_SIZE, EnemyPlane.ENEMY_SIZE, null); g.drawImage(plane_b[i/5], x, y, MyPlane.PLANE_SIZE, MyPlane.PLANE_SIZE, null); ... } }Collision
兩張圖片(飛機(jī)和飛機(jī),飛機(jī)和子彈)是否相碰,需要判斷的是矩形圖片是否有重疊部分。
以飛機(jī)與飛機(jī)相碰為例:
按照x,y坐標(biāo)的大小不同,總共有2*2四種情況:
Bullet
class Bullet {初始化子彈圖片{bullet = Toolkit.getDefaultToolkit().getImage(getClass().getResource());}繪制子彈{g.drawImage(bullet, bullet_x, bullet_y, BULLET_WIDTH,BULLET_HEIGHT, null);}移動(dòng)子彈 ... }存儲(chǔ)子彈:
private ArrayList<Bullet> mybulletarray;//玩家飛機(jī)子彈數(shù)組 private ArrayList<Bullet> enemybulletarray;//敵機(jī)子彈數(shù)組 private ArrayList<Bullet[]> bossbulletarray;//boss子彈數(shù)組Dialog
彈出對(duì)話框和JFrame類似,往里面添加各種組件和監(jiān)聽器即可。
public class Dialog extends JDialog { public Dialog(JFrame j, int i) {super(j, true);setLayout(null);setResizable(false);if(i == 1)showFail(j);//顯示挑戰(zhàn)失敗else if(i == 2)showSuccess(j);//顯示挑戰(zhàn)成功對(duì)話elseshowSetting(j);//顯示設(shè)置對(duì)話setVisible(true);}... //游戲失敗 private void showFail(JFrame j) {setTitle("提示"); setBounds(800, 400, 500, 300);jl01 = new JLabel("挑戰(zhàn)失敗");jl01.setFont(new Font("acefont-family", Font.BOLD, 50));jl01.setForeground(Color.blue);jl01.setBounds(65, 40, 400, 50);add(jl01);jl02 = new JLabel("分?jǐn)?shù)" + GamePanel.sum);jl02.setFont(new Font("acefont-family", Font.BOLD, 30));jl02.setForeground(Color.RED);jl02.setBounds(65, 120, 400, 50);add(jl02);}.... }Sound
public class Sound { private Clip clip;static boolean[] b = new boolean[]{true, true, true, true};//控制聲音播放//按鍵音 //打開聲音文件的方法。public Sound(String path){AudioInputStream audio;try {URL url = this.getClass().getResource(path);audio = AudioSystem.getAudioInputStream(url);clip = AudioSystem.getClip();clip.open(audio);}catch (Exception e) {e.printStackTrace();}}/*** 停止播放*/void stop() {clip.stop();//暫停音頻播放}/*** 開始播放*/void start() {clip.start();//播放音頻}/*** 回放背景音樂(lè)設(shè)置*/void loop() {clip.loop(20);//回放} }想要在某個(gè)界面或時(shí)刻實(shí)現(xiàn)播放聲音,直接new一個(gè)對(duì)象,并傳入文件地址就行。
如下Main方法所示:
Main方法
public class Main {static GameFrame f1;static Sound sound;public static void main(String[] args) {f1=new GameFrame();f1.setDefaultCloseOperation(3);f1.setVisible(true);f1.setResizable(false);if(sound==null){sound=new Sound("/sounds/mainback.wav");sound.start();sound.loop();}} }總結(jié):
除了上文中所解決的問(wèn)題,還有一點(diǎn)就是,對(duì)于多線程到底該什么時(shí)候去創(chuàng)建?或者說(shuō)多線程該設(shè)置在什么位置?(以下是我自己的理解)
直觀的感受就是多線程是在自動(dòng)持續(xù)執(zhí)行時(shí)引入的,對(duì)于飛機(jī)大戰(zhàn)來(lái)說(shuō),動(dòng)畫界面一旦開始執(zhí)行(在滿足條件的情況下),會(huì)一直執(zhí)行直到滿足結(jié)束條件就退出,很明顯,背景、玩家飛機(jī)、敵機(jī)、boss飛機(jī)等等這些圖片都是在自動(dòng)不斷刷新,剛開始會(huì)想到讓這些自動(dòng)執(zhí)行的類都實(shí)現(xiàn)多線程(extends Thread),要完成刷新的同步,只需要統(tǒng)一好阻塞時(shí)間就行了,但是這樣實(shí)現(xiàn)會(huì)發(fā)現(xiàn)代碼開銷比較大,且不便于統(tǒng)一管理。所以可以將這些類統(tǒng)一用一個(gè)Thread類實(shí)現(xiàn)同步的控制(只需要在這個(gè)Thread類中調(diào)用各個(gè)類中的方法即可)。
在本例中就是用GamePanel(可以直接讓GamePanel extends Thread 或者創(chuàng)建一個(gè)內(nèi)部Thread類)來(lái)控制背景、飛機(jī)等各種圖片的繪制和圖片移動(dòng)。
總結(jié)
以上是生活随笔為你收集整理的Java实现飞机大战(详细思路与过程,含源代码)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 语言三做一年级算题_一年级数学期末考试,
- 下一篇: XMind使用教程