使用swing實現飛行射擊遊戲(帶素材連結)
使用到的素材資料夾:
素材說明:bg0.jpg是背景圖片。bomb_enemy是敵機爆炸時按順序播放的四幀圖片,bullet_0.png是子彈圖片,enemy_alive.png是敵機圖片,player.png是主角圖片;
素材連結:https://pan.baidu.com/s/1mwFNPrnNed5jbVZSSrtaUg 提取碼: db6h
場景分析:玩家可以通過上下左右控制主角飛機的移動,主角每隔一段時間發射子彈,後臺隨機位置生成敵機,敵機如果碰到子彈,敵機死亡並播放爆炸效果,同時擊中敵機的子彈失效;場景畫布滾動播放。
設計子彈Bullet類:
package 雷電飛機射擊遊戲; import java.awt.Graphics; import java.awt.Image; import java.awt.Toolkit; import java.awt.image.ImageObserver; import javax.swing.JPanel; public class Bullet { static final int BULLET_STEP_X = 3; static final int BULLET_STEP_Y = 15; static final int BULLET_WIDTH = 30; public int m_posX = 0; public int m_posY = -20; boolean mFacus = true; private Image pic[] = null; private int mPlayID = 0; public Bullet(){ pic = new Image[1]; // System.out.println(pic.length); for(int i=0;i<pic.length;i++){ pic[i] = Toolkit.getDefaultToolkit().getImage("D:/Game/bullet_"+i+".png"); } } public void init(int x,int y){ m_posX = x; m_posY = y; mFacus = true; } public void DrawBullet(Graphics g,JPanel i){ g.drawImage(pic[mPlayID++], m_posX, m_posY,30,20, (ImageObserver)i); // System.out.println("當前mPlayID是 "+mPlayID+",當前pic.length是"+pic.length); if(mPlayID>=pic.length) mPlayID = 0; } public void UpdateBullet(){ m_posY -= BULLET_STEP_Y; } }
子彈類分析:pic陣列用於存放子彈的幀數,由於這裡子彈幀數只有一張,所以陣列長度設為1;UpdateBullet()用於更新子彈位置;m_posX,m_posY存放子彈座標;
設計敵機Enemy類:
package 雷電飛機射擊遊戲; import java.awt.Graphics; import java.awt.Image; import java.awt.Toolkit; import java.awt.image.ImageObserver; import javax.swing.JPanel; public class Enemy { public static final int ENEMY_ALIVE_STATE = 0; public static final int ENEMY_DEATH_STATE = 1; static final int ENEMY_STEP_Y = 5; public int m_posX = 0; public int m_posY = 0; public int mAnimState = ENEMY_ALIVE_STATE; private Image enemyExplorePic[] = new Image[4]; public int mPlayID = 0; public Enemy(){ for(int i=0;i<enemyExplorePic.length;i++){ enemyExplorePic[i] = Toolkit.getDefaultToolkit().getImage("D:/Game/bomb_enemy_"+i+".png"); } } public void init(int x,int y){ m_posX = x; m_posY = y; mAnimState = ENEMY_ALIVE_STATE; mPlayID = 0; } public void DrawEnemy(Graphics g,JPanel i){ if(mAnimState==ENEMY_DEATH_STATE&&mPlayID<enemyExplorePic.length){ g.drawImage(enemyExplorePic[mPlayID], m_posX, m_posY,30,30, (ImageObserver)i); mPlayID++; return; } Image pic = Toolkit.getDefaultToolkit().getImage("D:/Game/enemy_alive.png"); g.drawImage(pic, m_posX, m_posY, 30,30,(ImageObserver)i); } public void UpdateEnemy(){ m_posY += ENEMY_STEP_Y; } }
敵機類分析:m_posX,m_posY存放敵機座標;enemyExplorePic陣列存放敵機爆炸時順序播放的幀數圖片;mAnimState存放敵機狀態;UpdateEnemy()控制敵機位置的移動;
設計遊戲面板GamePanel類:
package 雷電飛機射擊遊戲; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Image; import java.awt.Toolkit; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.Iterator; import java.util.Random; import java.util.Vector; import javax.swing.JPanel; public class GamePanel extends JPanel implements Runnable,KeyListener{ private int mScreenWidth = 320;//屏寬 private int mScreenHeight = 480;//屏高 private static final int STATE_GAME = 0; private int mState = STATE_GAME; private Image mBitMenuBG0 = null;//背景圖1 private Image mBitMenuBG1 = null;//背景圖2 private int mBitposY0 = 0;//背景1的y軸起點 private int mBitposY1 = 0;//背景2的y軸起點 private Sound sound = new Sound(); private long createEnemyTime = System.currentTimeMillis();//用來監控敵機建立時間 final static int BULLET_POOL_COUNT = 15;//最多同時存在的子彈數 final static int PLAN_STEP = 10;//主角移動步長 final static int PLAN_TIME = 500;//隔500ms發射子彈 final static int ENEMY_POOL_COUNT = 5;//敵機最多同時存在個數 final static int ENEMY_POS_OFF = 65;//敵機偏移量 final static int BULLET_LEFT_OFFSET = 0;//子彈左偏移量 final static int BULLET_UP_OFFSET = 10;//子彈上偏移量 private Thread mThread = null;//主執行緒 private boolean mIsRunning = false;//初始遊戲狀態未執行 public int mAirPosX = 0;//主角x位置 public int mAirPosY = 0;//主角y位置 Vector<Enemy> mEnemy = new Vector<Enemy>();//儲存敵機 Vector<Bullet> mBullet = new Vector<Bullet>();//儲存子彈 public int mSendId = 0; public long mSendTime = 0L; Image myPlanePic[]; public int myPlaneID = 0; public GamePanel(){ setPreferredSize(new Dimension(mScreenWidth,mScreenHeight)); setFocusable(true); addKeyListener(this); sound.loadSound(); init(); setGameState(STATE_GAME); mIsRunning = true; mThread = new Thread(this); mThread.start(); setVisible(true); } public void init(){ try{ mBitMenuBG0 = Toolkit.getDefaultToolkit().getImage("D:/Game/bg0.jpg"); mBitMenuBG1 = Toolkit.getDefaultToolkit().getImage("D:/Game/bg0.jpg"); }catch(Exception e){ e.printStackTrace(); } mBitposY0 = 0; mBitposY1 = -mScreenHeight; mAirPosX = 150; mAirPosY = 400; myPlanePic = new Image[1]; for(int i=0;i<myPlanePic.length;i++){ myPlanePic[i] = Toolkit.getDefaultToolkit().getImage("D:/Game/player.png"); } for(int i=0;i<ENEMY_POOL_COUNT;i++){ Enemy tempEnemy = new Enemy(); tempEnemy.init(i*ENEMY_POS_OFF, i*ENEMY_POS_OFF - 300); mEnemy.add(tempEnemy); } mBullet.add(new Bullet()); mSendTime = System.currentTimeMillis(); } public void draw(){ switch(mState){ case STATE_GAME: renderBg();//更新主角飛機 updateBg();//更新場景畫布 break; } } private void setGameState(int newState){ mState = newState; } public void renderBg(){ myPlaneID++; if(myPlaneID == myPlanePic.length){ myPlaneID = 0; } repaint(); } public void paint(Graphics g){ g.drawImage(mBitMenuBG0, 0, mBitposY0, this); g.drawImage(mBitMenuBG1, 0, mBitposY1, this); g.drawImage(myPlanePic[myPlaneID], mAirPosX, mAirPosY, 30,30,this); for(int i=0;i<mBullet.size();i++){ if(mBullet.get(i).mFacus==true) mBullet.get(i).DrawBullet(g, this); } for(int i=0;i<mEnemy.size();i++){ mEnemy.get(i).DrawEnemy(g, this); } } public void updateBg(){ mBitposY0 += 10; mBitposY1 += 10; if(mBitposY0 == mScreenHeight){ mBitposY0 = -mScreenHeight; } if(mBitposY1 == mScreenHeight){ mBitposY1 = -mScreenHeight; } //檢測子彈是否超出螢幕以及更新子彈位置 for(int i=0;i<mBullet.size();i++){ if(mBullet.get(i).m_posY<=0) mBullet.get(i).mFacus = false; mBullet.get(i).UpdateBullet(); } for(int i=0;i<mEnemy.size();i++){ mEnemy.get(i).UpdateEnemy(); if(mEnemy.get(i).mAnimState==Enemy.ENEMY_DEATH_STATE&&mEnemy.get(i).mPlayID==4||mEnemy.get(i).m_posY>=mScreenHeight){ mEnemy.remove(i); } } long nowTime = System.currentTimeMillis(); if(mEnemy.size()<5&&nowTime-createEnemyTime>=1000)//至少要間隔1秒才生成飛機 { createEnemyTime = nowTime; Enemy tempEnemy = new Enemy(); tempEnemy.init(UtilRandom(0,mScreenWidth-30),0); mEnemy.add(tempEnemy); } if(mSendId<BULLET_POOL_COUNT){ long now = System.currentTimeMillis(); if(now-mSendTime>=PLAN_TIME){ Bullet tempBullet = new Bullet(); tempBullet.init(mAirPosX-BULLET_LEFT_OFFSET, mAirPosY-BULLET_UP_OFFSET); mBullet.add(tempBullet); mSendTime = now; mSendId++; } }else{ mSendId= 0; } Collision(); } public void Collision(){ for(int i=0;i<mBullet.size();i++) { for(int j=0;j<mEnemy.size();j++) { if(mEnemy.get(j).mAnimState==Enemy.ENEMY_ALIVE_STATE&&mBullet.get(i).mFacus==true&&mBullet.get(i).m_posX>mEnemy.get(j).m_posX-30&&mBullet.get(i).m_posX<mEnemy.get(j).m_posX+30 &&mBullet.get(i).m_posY>=mEnemy.get(j).m_posY&&mBullet.get(i).m_posY<=mEnemy.get(j).m_posY+30){ mEnemy.get(j).mAnimState = Enemy.ENEMY_DEATH_STATE; mBullet.get(i).mFacus = false;//如果子彈撞上敵機,敵機狀態修改為死亡,子彈狀態修改為失效 } } } } //返回的敵機x座標 private int UtilRandom(int bottom,int top){ return ((Math.abs(new Random().nextInt()) % (top - bottom)) + bottom); } @Override public void keyTyped(KeyEvent e) { // TODO Auto-generated method stub } @Override public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); //System.out.println(key); if(key == KeyEvent.VK_UP){ mAirPosY -= PLAN_STEP; } if(key == KeyEvent.VK_DOWN){ mAirPosY += PLAN_STEP; } if(key == KeyEvent.VK_LEFT){ mAirPosX -= PLAN_STEP; if(mAirPosX<0){ mAirPosX = 0; } } if(key == KeyEvent.VK_RIGHT){ mAirPosX += PLAN_STEP; if(mAirPosX > mScreenWidth-30){ mAirPosX = mScreenWidth - 30; } } // System.out.println("飛機當前座標是("+mAirPosX+","+mAirPosY+")"); } @Override public void keyReleased(KeyEvent e) { // TODO Auto-generated method stub } @Override public void run() { while(mIsRunning){ draw(); try{ Thread.sleep(100); }catch(Exception e){ e.printStackTrace(); } } } }
遊戲面板類分析:
①mThread執行緒用於定時生成子彈,如果當前生效的子彈數沒有超過15個,每隔500ms生成子彈,子彈位於主角飛機上方一個子彈上偏移量的位置;
②image0和image1是背景圖片,初始化背景圖片2位於背景圖片1上方,隨著遊戲的進行,背景圖片逐漸下移,當背景圖片1到螢幕外時,將背景圖片1放到背景圖片2上方;
③碰撞檢測:迴圈子彈陣列和敵機陣列,如果當前子彈生效,當前敵機狀態存活,子彈的x值>敵機的x值-子彈寬度&&子彈的x值<敵機的x值+敵機寬度&&子彈的y值>=敵機的y值&&子彈的y值<=敵機的y值+敵機高度,那麼修改當前敵機的狀態為死亡並播放爆炸動畫,修改當前子彈狀態為失效;
④如果敵機死亡並且爆炸動畫播放完畢,或者敵機超出螢幕顯示範圍,刪除敵機;
⑤如果子彈失效或者超過螢幕範圍,刪除子彈;
⑥敵機後臺隨機生成,位於螢幕上方,控制至少每隔1秒才能生成新的敵機,並且當前螢幕上的敵機數量最多不能超過五個;
最後還剩遊戲視窗類和背景音樂類程式碼:
package 雷電飛機射擊遊戲;
import java.awt.Container;
import javax.swing.JFrame;
public class planeFrame extends JFrame{
public planeFrame(){
setTitle("飛行射擊類遊戲");
GamePanel panel = new GamePanel();
Container contentPane = getContentPane();
contentPane.add(panel);
pack();
}
public static void main(String[] args) {
planeFrame e1 = new planeFrame();
e1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
e1.setVisible(true);
}
}
package 雷電飛機射擊遊戲;
import javax.sound.midi.*;
import java.io.File;
import java.io.IOException;
public class Sound {
String path = new String("D:/Music/");
String file = new String("nor.mid");
Sequence seq;
Sequencer midi;
boolean sign;
void loadSound(){
try {
seq = MidiSystem.getSequence(new File(path+file));
midi = MidiSystem.getSequencer();
midi.open();
midi.setSequence(seq);
midi.start();
midi.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
} catch (InvalidMidiDataException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (MidiUnavailableException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sign = true;
}
public Sound(String file,String path){
this.file = file;
this.path = path;
}
public Sound() {
// TODO Auto-generated constructor stub
}
void mystop(){midi.stop();midi.close();sign=false;}
boolean isplay(){return sign;}
void setMusic(String e){file=e;}
}
至此,遊戲開發已經完畢,執行效果如下: