Java實現(xiàn)飛機大戰(zhàn)游戲?附完整源碼
飛機大戰(zhàn)詳細文檔
文末有源代碼,以及本游戲使用的所有素材,將plane2文件復(fù)制在src文件下可以直接運行。
實現(xiàn)效果:
結(jié)構(gòu)設(shè)計
- 角色設(shè)計
- 飛行對象類 FlyObject
- 戰(zhàn)機類
- 我的飛機 MyPlane
- 敵方飛機 EnemyPlane
- 子彈類
- 我的子彈 MyBullet
- 敵方子彈 EnemyBullet
- 道具類 Prop
- 加分,加血,升級
- 戰(zhàn)機類
- 地圖背景類 Background
- 玩家類 Player
- HP,得分
- 飛行對象類 FlyObject
- 線程類
- 繪制線程 DrawThread
- 移動線程 MoveThread
- 生成敵方飛機線程 EnemyPlaneThread
- 敵方飛機生成子彈線程 EnemyButtleThread
- 檢測碰撞線程 TestCrashThread
- 界面類
- 主界面 GameUI
- 選擇地圖界面 SelectMapUI
- 監(jiān)聽器類 KListener
- 通過按壓鍵盤改變我方飛機的速度
- 數(shù)據(jù)結(jié)構(gòu)
- 我方戰(zhàn)機(只有一個)
- 我方飛機子彈集合
- 敵方飛機集合
- 敵方子彈集合
- 道具集合
詳細分析
Main界面類使用邊框布局,給面板分三個區(qū),如圖所示
關(guān)鍵代碼:
JFrame jf = new JFrame("飛機大戰(zhàn)"); //創(chuàng)建窗體 jf.setSize(670,800); jf.setLocationRelativeTo(null); jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); jf.setLayout(new BorderLayout()); //布局 //創(chuàng)建三個JPanel,左上為按鈕,左下為分數(shù)顯示 右為游戲頁面 JPanel left = new JPanel(); JPanel leftUp = new JPanel(); //左上 JPanel leftDown = new JPanel(); //左下 game = new JPanel(); //游戲顯示區(qū) left.setPreferredSize(new Dimension(170,800)); left.setBackground(new Color(-3355444)); jf.add(left,BorderLayout.WEST); jf.add(game,BorderLayout.CENTER); game.requestFocus(); left.setLayout(new BorderLayout()); leftUp.setPreferredSize(new Dimension(0,250)); leftUp.setBackground(new Color(-3355444)); left.add(leftUp,BorderLayout.NORTH); leftDown.setBackground(new Color(-6710887)); leftDown.setPreferredSize(new Dimension(0,550)); left.add(leftDown,BorderLayout.SOUTH);
繪制背景地圖
飛行道具類UML圖
判斷FlyObject對象是否碰撞
public boolean judge_crash(FlyObject fo){ if(x+sizeX<fo.x || y+sizeY<fo.y || x > fo.x + fo.sizeX || y > fo.y+ fo.sizeY ){ return false; }else{ return true; } }
繪制線程: 如何讓我們的游戲動起來
- 視頻原理:我們在屏幕上看見的動態(tài)圖像圖像實際上由若干個靜止圖像構(gòu)成,由于人眼有暫留特性,剛顯示的圖像在大腦中停留一段時間,若靜態(tài)圖像每
- 秒鐘變化25幅,那么人的感覺屏幕上的圖像是動的。
- 繪制時要把所有的飛行物都繪制一遍,所以我們需要在每一個飛行物被創(chuàng)建時,添加到相關(guān)的飛行物集合中。(為了方便傳值,我們將集合設(shè)為靜態(tài)變量)
- 我們的繪制線程,選擇每30ms繪制一次,注意先畫背景,然后再遍歷飛行物集合畫飛行物。
背景的繪制
要想繪制動態(tài)的背景,首先我們要先畫一張靜態(tài)的背景圖,那么如何繪制一張靜態(tài)的背景圖呢?
獲取包中的圖片:
String fileName_0 = "src\\plane2\\z_img\\img_bg_0.jpg"; //相對地址(和絕對地址區(qū)分開) BufferedImage bufferedImage; bufferedImage = ImageIO.read(new File(fileName_0)); //將文件讀出記錄在bufferedImage中,記得拋出異常 g.drawImage(bufferedImage,0,0,null); // 將bufferedImage中的內(nèi)容畫在畫筆g對應(yīng)的地方
我們的地圖是一張可以從上往下無縫滾動的圖片,就像是這樣的圖
接下來,如何讓畫出連續(xù)的圖片呢?
在繪制函數(shù)中,有一個函數(shù)可以完美實現(xiàn)我們的需求
img – the specified image to be drawn. This method does nothing if img is null. dx1 – the x coordinate of the first corner of the destination rectangle. dy1 – the y coordinate of the first corner of the destination rectangle. dx2 – the x coordinate of the second corner of the destination rectangle. dy2 – the y coordinate of the second corner of the destination rectangle. sx1 – the x coordinate of the first corner of the source rectangle. sy1 – the y coordinate of the first corner of the source rectangle. sx2 – the x coordinate of the second corner of the source rectangle. sy2 – the y coordinate of the second corner of the source rectangle. observer – object to be notified as more of the image is scaled and converted. public abstract boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer);
比如說,我們的圖片高度為712個像素點,我們在下一時刻,圖片向下移動了m個像素點,那么我們就將這張圖片的0 ~ 712-m 部分,繪制到游戲界面的m ~ 712部分,
再將712-m ~ 712 部分繪制到游戲界面的0 ~ m 部分;
接下來,我們就要確定 m 的值,這個就很簡單了,在繪制線程中,定義一個整數(shù)變量m ,每次繪制完 m++ 就可以了。(個人建議m+=2比較舒服)
/** * @author liTianLu * @Date 2022/5/21 23:33 * @purpose 繪制背景 * 提醒: 這里我寫了四種地圖的繪制,后面在選擇地圖時會用到。 */ public class BackGround { Graphics g; BufferedImage bufferedImage_1; BufferedImage bufferedImage_2; BufferedImage bufferedImage_3; BufferedImage bufferedImage_4; int w; int h; String fileName_1 = "src\\plane2\\z_img\\img_bg_1.jpg"; //地圖1 String fileName_2 = "src\\plane2\\z_img\\img_bg_2.jpg"; //地圖2 String fileName_3 = "src\\plane2\\z_img\\img_bg_3.jpg"; //地圖3 String fileName_4 = "src\\plane2\\z_img\\img_bg_4.jpg"; //地圖4 public BackGround(Graphics g) throws IOException { this.g = g; bufferedImage_1 = ImageIO.read(new File(fileName_1)); bufferedImage_2 = ImageIO.read(new File(fileName_2)); bufferedImage_3 = ImageIO.read(new File(fileName_3)); bufferedImage_4 = ImageIO.read(new File(fileName_4)); w = bufferedImage_1.getWidth(); h = bufferedImage_1.getHeight(); } /** * i : 向下移動了i個像素 * num : 用來控制繪制哪一個地圖 */ public void draw(int i , int num){ switch(num){ case 1 : g.drawImage(bufferedImage_1,0,i,w,i+h,0,0,w,h,null); g.drawImage(bufferedImage_1,0,0,w,i,0,h-i,w,h,null); break; case 2 : g.drawImage(bufferedImage_2,0,i,w,i+h,0,0,w,h,null); g.drawImage(bufferedImage_2,0,0,w,i,0,h-i,w,h,null); break; case 3 : g.drawImage(bufferedImage_3,0,i,w,i+h,0,0,w,h,null); g.drawImage(bufferedImage_3,0,0,w,i,0,h-i,w,h,null); break; case 4 : g.drawImage(bufferedImage_4,0,i,w,i+h,0,0,w,h,null); g.drawImage(bufferedImage_4,0,0,w,i,0,h-i,w,h,null); break; } } public int getH() { return h; } }
繪制線程:
backGround.draw(m, player.mapNum); m = m+2; if(m>= backGround.getH()){ m = 0; }
我的飛機的繪制
使用的飛機素材圖片:
飛機扇動翅膀的原理與視頻的原理相同,不停更換圖片,形成視覺暫留效果
//這里僅使用了三張圖片來回切換,更多的圖片會有更好的效果 public void draw(int i){ //此處的i是用來控制顯示哪一張圖片的 int j = i%30; // 150ms換一張 if (j<10){ g.drawImage(plane_img,x,y,x+sizeX,y+sizeY,0,0,sizeX,sizeY,null); }else if(j<20) { g.drawImage(plane_img,x,y,x+sizeX,y+sizeY,0,sizeY,sizeX,2*sizeY,null); }else if(j<30){ g.drawImage(plane_img,x,y,x+sizeX,y+sizeY,288,0,424,112,null); } }
敵方飛機,敵方子彈等飛行物的繪制原理與MyPlane相同,后面不在贅述。(為了簡化開發(fā)流程,飛行物可以不”扇動翅膀“)
移動線程
- 我們已經(jīng)給每個飛行對象設(shè)置了X軸移動速度和Y軸移動速度,所以每次移動的時候,我們只需要遍歷所有的飛行對象,
- 然后逐個移動一個speedX 和 speedY 單位即可。
- 多久移動一次呢?和繪制線程的間隔時間相同就好了,我們都設(shè)為30ms.
- 當(dāng)飛行物飛出屏幕時,將飛行物移出集合,減少計算機資源的消耗。
如何控制我的飛機移動?
- 當(dāng)然是通過鍵盤的 ↑ ↓ ← → 來控制了,我們需要設(shè)置一個鍵盤監(jiān)聽器給game界面,
- 注意要先使用 game.requestFocus(); 獲取焦點,鍵盤監(jiān)聽器才可以使用。
@Override //鍵盤按壓時,設(shè)置速度 public void keyPressed(KeyEvent e) { int c = e.getKeyCode(); if(DrawThread.myPlane!=null){ switch (c){ case 37: DrawThread.myPlane.setSpeedX(-speed); break; case 38: DrawThread.myPlane.setSpeedY(-speed); break; case 39: DrawThread.myPlane.setSpeedX(speed); break; case 40: DrawThread.myPlane.setSpeedY(speed); break; } } } @Override //鍵盤釋放時,速度設(shè)為0 public void keyReleased(KeyEvent e) { int c = e.getKeyCode(); switch (c){ case 37: case 39: DrawThread.myPlane.setSpeedX(0); break; case 38: case 40: DrawThread.myPlane.setSpeedY(0); break; } }
敵方飛機線程 : 如何生成敵方飛機呢?
每隔一段時間,在游戲面板的頂部,產(chǎn)生一個敵方飛機
/** * @author liTianLu * @Date 2022/5/22 0:30 * @purpose 產(chǎn)生敵機的線程 */ @Override public void run() { int sleepTime = 800; while (true){ if(DrawThread.player.score>=500){ //當(dāng)分數(shù)高于500時,加快敵機產(chǎn)生的頻率 sleepTime = 300; } EnemyPlane enemyPlane = null; try { enemyPlane = new EnemyPlane(); } catch (IOException e) { e.printStackTrace(); } enemyPlanes.add(enemyPlane); new Thread(new EnemyBulletThread(enemyPlane)).start(); //啟動一個發(fā)射子彈線程 try { sleep(sleepTime+ random.nextInt(300)); } catch (InterruptedException e) { e.printStackTrace(); } } }
敵方子彈線程 : 使每一個敵方飛機開火
我們?yōu)槊恳粋€敵方飛機創(chuàng)建一個生成子彈的線程,要確定子彈產(chǎn)生的具體位置,就要知道敵方飛機的位置,所以我們要傳入一個敵方飛機對象給該線程。
public EnemyBulletThread(EnemyPlane enemyPlane){ this.enemyPlane = enemyPlane; } @Override public void run() { try { sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } while(enemyPlane.isAlive() ){ EnemyBullet enemyBullet = null; int enemyBullet_x = enemyPlane.getX()+25; int enemyBullet_y = enemyPlane.getY()+66; try { enemyBullet = new EnemyBullet(enemyBullet_x,enemyBullet_y); } catch (IOException e) { e.printStackTrace(); } enemyBullets.add(enemyBullet); try { sleep(2000+ random.nextInt(2000)); } catch (InterruptedException e) { e.printStackTrace(); } } }
檢測碰撞線程 : 在子彈與敵機碰撞時,移除敵機
- 此時我們會遇到一個問題,就是在遍歷時,move移動線程有可能將其中的一個飛行物移出集合,會出現(xiàn)IndexOutOfBoundsException異常
- ,我們只需要在兩個線程使用飛行物集合時,加上synchronized關(guān)鍵字,即可解決。
- MoveThread 遍歷我的子彈集合
synchronized (MyPlane.myBulletList){ if(MyPlane.myBulletList.size()!=0){ for (int i = 0; i < MyPlane.myBulletList.size(); i++) { MyPlane.myBulletList.get(i).setY(MyPlane.myBulletList.get(i).getY()+MyPlane.myBulletList.get(i).getSpeedY() ); if(MyPlane.myBulletList.get(i).getY() <= -100){ MyPlane.myBulletList.remove(i); continue; } } } }
TestCrashThread 檢測我的子彈與敵方飛機碰撞
synchronized (MyPlane.myBulletList){ for (int i = 0; i < MyPlane.myBulletList.size(); i++) { for (int j = 0; j < EnemyPlaneThread.enemyPlanes.size() ;j++) { if(MyPlane.myBulletList.get(i).judge_crash(EnemyPlaneThread.enemyPlanes.get(j)) ){ EnemyPlaneThread.enemyPlanes.get(j).setAlive(false); //關(guān)線程 DrawThread.player.score+=5; //分數(shù)+5 EnemyPlaneThread.enemyPlanes.remove(j); MyPlane.myBulletList.remove(i); j = -1; } if(i >= MyPlane.myBulletList.size()){ break; } } } }
其他功能:顯示玩家hp,掉落道具,得分,升級,更換地圖
- 顯示hp:每次檢測到我的飛機與敵方飛機,敵方子彈碰撞,就減分。減到<=0時,游戲結(jié)束。
- 得分:子彈打到敵方飛機時,加分,并將當(dāng)前分數(shù)通過繪制線程繪制在屏幕上。
- 掉落道具:敵機消失的時候,隨機掉落一個道具,我的飛機碰到道具時,回血/加分/升級
- 升級:我的飛機初始為1級,最高為3級,等級改變時,使用switch 根據(jù)等級改變我的飛機的子彈發(fā)射方式。
- 更換地圖: 使用一個新的窗體,設(shè)置幾個單選按鈕,選擇時通過監(jiān)聽器,改變地圖的控制變量,從而改變地圖的繪制。
源碼下載:
鏈接: https://pan.baidu.com/s/1ORdS-Ep0MNmVsslv9YFjiQ
提取碼: hvz4
到此這篇關(guān)于Java實現(xiàn)飛機大戰(zhàn)游戲 附完整源碼的文章就介紹到這了,更多相關(guān)java飛機大戰(zhàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java ExecutorServic線程池異步實現(xiàn)流程
這篇文章主要介紹了Java ExecutorServic線程池異步實現(xiàn)流程,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-12-12Spring Boot Maven Plugin打包異常解決方案
這篇文章主要介紹了Spring Boot Maven Plugin打包異常解決方案,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-11-11idea中安裝VisualVM監(jiān)控jvm的圖文教程
這篇文章主要介紹了idea中安裝VisualVM監(jiān)控jvm的教程,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09java實現(xiàn)文件和base64相互轉(zhuǎn)換
這篇文章主要為大家詳細介紹了java如何實現(xiàn)文件和base64相互轉(zhuǎn)換,文中的示例代碼講解詳細,具有一定的參考價值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-11-11