文章目錄 簡介 第一階段:游戲界面的繪畫及圖片加載 第二階段:使用多線程讓動畫動起來 第三階段:鍵盤操控飛機 第四階段:炮彈和飛機碰撞,爆炸 第五階段:顯示分數
簡介
我們仿照 QQ游戲“”雷電這個風靡全球的游戲,來把所有知識點串接起來了 多線程用來實現動畫效果、容器實現對于多發炮彈的存取和處理、常用類等等的應用。 但跟隨本文只能做出雷電的雛形,但你可以在這個基礎上改造出屬于你的“雷電” 這是最后效果圖:
第一階段:游戲界面的繪畫及圖片加載
游戲開發中,圖片加載是最常見的技術。我們在此處使用ImageIO類實現圖片加載,并且為了代碼的復用,將圖片加載的方法封裝到GameUtil工具類中,便于我們以后直接調用。
? 我們要先將項目用到的圖片拷貝到項目的src下面,我們可以建立新的文件夾images存放所有圖片
1.添加背景圖片
方法1 :通過在JFrame中添加一個JPanel,背景圖片放在JPanel上來實現
方法2 :我們用JLayeredPane,JLayeredPane 為 JFC/Swing 容器添加了深度,允許組件在需要時互相重疊。Integer 對象指定容器中每個組件的深度,其中編號較高的組件位于其他組件之上。常用的幾個層如下圖:
之前我再添加背景圖片時一貫使用絕對路徑,每次添加修改極為麻煩,本次我們學習如何使用相對路徑,首先通過GameUtil.class.getClaaaLoader().getResource來獲得資源的根目錄,從而獲取相對位置
paint會被自動被調用,g相當于一只畫筆
代碼如下:
/*** 2019年9月21日*/
package Thunder;import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyListener;
import java.util.ArrayList;import javax.swing.JFrame;/*** 飛機大戰雛形:可以發炮彈,但是沒有解決連發炮彈的問題* * 之后有兩個方向:畫面上有很多炮彈,控制飛機移動躲避炮彈* 或者經典的雷電游戲,擊落敵機** 2019年9月21日*/
public class GameUI extends JFrame {// 將背景和飛機圖片定義為成員變量Image background = GameUtil.getImage("images/back3.jpg");Image planeImg = GameUtil.getImage("images/plane1.png");Image shootImg = GameUtil.getImage("images/shoot1.png");public static int UIWidth = 1000;public static int UIHeigh = 1000;//ArrayList<Shell> shellList = new ArrayList<Shell>(); Plane plane1 = new Plane(planeImg,200,200,2,100,100);Shell shoot1 = new Shell(shootImg, 237,170,2,25,35);public void GFrame() {this.setTitle("雷電");this.setSize(UIWidth, UIHeigh);this.setLocationRelativeTo(null);this.setDefaultCloseOperation(3);this.addKeyListener(plane1);this.addKeyListener(shoot1);this.setVisible(true);//啟動線程PaintThread pt =new PaintThread();pt.start();}// paint方法作用是:畫出整個窗口及內部內容。被系統自動調用。public void paint(Graphics g) {g.drawImage(background, 0, 0, 1000, 1000, null);plane1.drawMySelf(g);shoot1.drawMySelf(g);}// 定義內部類class PaintThread extends Thread {// 重寫public void run() {while (true) {repaint();// 重畫try {Thread.sleep(10);} catch (Exception e) {e.printStackTrace();}}}}//雙緩沖解決閃爍private Image offScreenImage = null;public void update(Graphics g) {if(offScreenImage == null)offScreenImage = this.createImage(500,500);//這是游戲窗口的寬度和高度Graphics gOff = offScreenImage.getGraphics();paint(gOff);g.drawImage(offScreenImage, 0, 0, null);} // 主方法public static void main(String[] args) {// 類里面調用窗體GameUI gu = new GameUI();gu.GFrame();}}
【要點】:
1.繼承Frame類,畫出窗口 2. 了解坐標系,窗口坐標以左上角為(0,0)點 3. 物體就是矩形,物體的位置就是所在矩形左上角頂點的坐標 4. 窗口關閉,居中等我們需要自己添加功能,如下
在JFrame中我們可以這樣寫:
drawframe.setDefaultCloseOperation(3);//關閉時程序結束
drawframe.setLocationRelativeTo(null);
但在Frame中,我們需要
/ 增加關閉窗口監聽,這樣用戶點擊右上角關閉圖標,可以關閉游戲程�?this.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {System.exit(0);}});
第二階段:使用多線程讓動畫動起來
第三階段:鍵盤操控飛機
第四階段:炮彈和飛機碰撞,爆炸
第五階段:顯示分數
項目結構如圖: 所有代碼:
/*** 2019年9月21日*/
package Thunder;import java.awt.Graphics;
import java.awt.Image;import javax.swing.JFrame;/*** ** 2019年9月21日*/
public class GameUI extends JFrame {// 將背景和飛機圖片定義為成員變量Image background = GameUtil.getImage("images/back3.jpg");Image planeImg = GameUtil.getImage("images/plane1.png");public static int UIWidth = 1000;public static int UIHeigh = 1000;Plane plane1 = new Plane(planeImg,200,200,2,100,100);public void GFrame() {this.setTitle("雷電");this.setSize(UIWidth, UIHeigh);this.setLocationRelativeTo(null);this.setDefaultCloseOperation(3);//添加監聽器//Listener li = new Listener();//this.addKeyListener(li);this.addKeyListener(plane1);this.setVisible(true);//啟動線程PaintThread pt =new PaintThread();pt.start();}// paint方法作用是:畫出整個窗口及內部內容。被系統自動調用。public void paint(Graphics g) {g.drawImage(background, 0, 0, 1000, 1000, null);plane1.drawMySelf(g);}// 定義內部類class PaintThread extends Thread {// 重寫public void run() {while (true) {repaint();// 重畫try {Thread.sleep(10);} catch (Exception e) {e.printStackTrace();}}}}//雙緩沖解決閃爍private Image offScreenImage = null;public void update(Graphics g) {if(offScreenImage == null)offScreenImage = this.createImage(500,500);//這是游戲窗口的寬度和高度Graphics gOff = offScreenImage.getGraphics();paint(gOff);g.drawImage(offScreenImage, 0, 0, null);} // 主方法public static void main(String[] args) {// 類里面調用窗體GameUI gu = new GameUI();gu.GFrame();}}
package Thunder;import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;import javax.imageio.ImageIO;/*** GameUtil類:加載圖片代碼*GameUtil獲得程序運行類加載器,加載資源的根目錄,* 2019年9月22日*/
public class GameUtil {//工具類一般將構造器私有化public GameUtil() {}public static Image getImage(String path){BufferedImage bi = null;try {URL u = GameUtil.class.getClassLoader().getResource(path);bi = ImageIO.read(u);} catch (IOException e) {e.printStackTrace();}return bi;}}
/*** 2019年9月22日*/
package Thunder;import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;/*** 游戲中所有物體的父類** 2019年9月22日*/
public class GameObject {Image img; //該物體對應的圖片對象double x,y; //該物體的坐標int speed; //該物體的運行速度int width,height; //該物體所在矩形區域的寬度和高度public void drawMySelf(Graphics g){g.drawImage(img, (int)x, (int)y, (int)width, (int)height, null);}public GameObject(Image img, double x, double y) {this.img = img;this.x = x;this.y = y;if(img!=null){this.width = img.getWidth(null);this.height = img.getHeight(null);}}public GameObject(Image img, double x, double y, int width,int height) {this.img = img;this.x = x;this.y = y;this.width = width;this.height = height;}public GameObject(Image img, double x, double y, int speed, int width,int height) {this.img = img;this.x = x;//這只是構造方法初始化化時的值,并不能作為約束this.y = y;this.speed = speed;this.width = width;this.height = height;}public GameObject() {}/*** 返回物體對應矩形區域,便于后續在碰撞檢測中使用* @return*/public Rectangle getRect(){return new Rectangle((int)x,(int) y, width, height);} }
package Thunder;import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;/*** ** 2019年9月22日*/
public class Plane extends GameObject implements KeyListener {boolean left, right, up, down,shoot;boolean live = true;public void drawMySelf(Graphics g) {super.drawMySelf(g);// 這里可移動,但是下面不行,應該是判斷的問題this.x+=2;System.out.println("此時的坐標"+x);//System.out.println(left);if (left) {System.out.println("此時的坐標"+x);x -= speed;if(x<=0){x=0;}}if (right) {x += speed;if(x>=900){x=900;}}if (up) {y -= speed;if(y<=10){y=10;}}if (down) {y += speed;if(y>=900){y=900;}}}public Plane(Image img, double x, double y, int speed, int width, int height) {super(img, x, y,width, height);//System.out.println("有被重畫");//this.speed = speed;}public void keyTyped(KeyEvent e) {}public void keyPressed(KeyEvent e) {System.out.println(e.getKeyCode());int key = e.getKeyCode();switch (key) {case 37:left = true;break;case 38:up = true;break;case 39:right = true;break;case 40:down = true;break;case 32:shoot = true;break;default:break;}}public void keyReleased(KeyEvent e) {//System.out.println(e.getKeyCode());int key = e.getKeyCode();switch (key) {case 37:left = false;break;case 38:up = false;break;case 39:right = false;break;case 40:down = false;break;case 32:shoot = true;break;default:break;}}
}
我原來打算共享飛機坐標,這樣就可以將子彈和飛機關聯起來,但是這個語句導致 飛機完全不能動,因為坐標與圖片沒有關聯了,重繪只畫了一次 該怎么把子彈放入數組列表中呢? 現在子彈是一個新的類,而且有構造器,得隨著飛機移動,但是 應該直接放入監聽就可以了,點一下,延遲幾秒,畫一個。
現在到這個方向有兩種結果可以走: 第一種:屏幕隨機生成炮彈,我們控制飛機去躲避,生存時間越長,得分越高 第二鐘:和游戲雷電一樣,對面有敵機,我們在躲避敵機炮彈的同時擊落敵機,獲取分數。 在炮彈的任意角度飛行時,如果觸碰到邊界就反轉角度,模擬碰撞效果。 同時為了效果美觀,我們需要在一些細節上注意:例如碰撞邊界時要減去球 的直徑,碰撞標題欄時需要減去標題欄的寬度。 但是一個炮彈遠遠不夠,我們希望增大一些挑戰難度,有很多炮彈,就可以用到數組了。 分為三步: 1)定義數組 2)初始化五十個炮彈 3)畫出炮彈
當使用Frame時,屏幕閃爍如圖:
當使用JFrame解決屏幕閃爍問題時: 最后我們采用了frame+雙緩沖的技術來解決,解決效果如圖:
只需要把這段代碼放入界面的類中(任意位置即可)
//雙緩沖解決閃爍private Image offScreenImage = null;public void update(Graphics g) {if(offScreenImage == null)offScreenImage = this.createImage(1000,1000);//這是游戲窗口的寬度和高度Graphics gOff = offScreenImage.getGraphics();paint(gOff);g.drawImage(offScreenImage, 0, 0, null);}
在Jfram和frame切換中可能的報錯:
當我們使用JFrame時可以調用這行語句關閉窗口 但是在Frame里面沒有,所以我們需要添加監聽器
//this.setDefaultCloseOperation(3);//增加關閉窗口監聽,這樣用戶點擊右上角關閉圖標,可以關閉游戲程序this.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {System.exit(0);}});
游戲中,多個元素是否碰到一起,實際上,通常是用“矩形檢測”原理實現的。 我們在前面提到,游戲中所有的物體都可以抽象成“矩形”,我們只需判斷兩個矩形是否相交即可。對于一些復雜的多邊形、不規則物體,實際上是將他分解成多個矩形,繼續進行矩形檢測。
Java的API中,為我們提供了Rectangle 類來表示矩形相關信息,并且提供了intersects()方法,直接判斷矩形是否相交。 我們使用GameObject里面的矩形檢測來判斷,飛機有,炮彈也有,這也是繼承的隱形好處.
爆炸圖片輪播: 圖片加載一次很耗費資源,所以我們將他設為static,只初始化一次,之后直接用就可以,而不需要new了, 爆炸類如下
package Thunder_two;import java.awt.Graphics;
import java.awt.Image;
/*** * **/
public class Explode {double x,y;static Image[] imgs = new Image[16];static {for(int i=0;i<16;i++){imgs[i] = GameUtil.getImage("images/explode/e"+(i+1)+".gif");imgs[i].getWidth(null);}}int count;public void draw(Graphics g){if(count<=15){g.drawImage(imgs[count], (int)x, (int)y, null);count++;}}public Explode(double x,double y){this.x = x;this.y = y;}
}
到這一步,雷電游戲的雛形大概就完成了,但是我們卻少了最重要的獎勵機制,玩家不知道自己的得分,自然也不會有興趣繼續挑戰了,所有我們加入時間類,在玩家死亡之后,在屏幕上顯示獲得的分數。 先定義兩個成員變量 Date startTime = new Date(); //游戲起始時刻 Date endTime; //游戲結束時刻 如果飛機死亡,調用顯示的方法
if(!plane.live){if(endTime==null){endTime = new Date();}int period = (int)((endTime.getTime()-startTime.getTime())/1000);printInfo(g, "時間:"+period+"秒", 50, 120, 260, Color.white);}
//顯示的方法
public void printInfo(Graphics g,String str,int size,int x,int y,Color color){Color c = g.getColor();g.setColor(color);Font f = new Font("宋體",Font.BOLD,size);g.setFont(f);g.drawString(str,x,y);g.setColor(c);}
至此,雷電小游戲的第一個方向就完成了,但大家也許還有點意猶未盡呢吧!這和我們兒時玩的雷電差距太大了吧,下一篇文章中,我們就來開發自動發射+擊落敵機的部分,翹首以待吧。
以下是雷電Ⅰ的全部代碼 項目結構如圖:
package Thunder_one;import java.awt.Graphics;
import java.awt.Image;
/*** * **/
public class Explode {double x,y;static Image[] imgs = new Image[16];static {for(int i=0;i<16;i++){imgs[i] = GameUtil.getImage("images/explode/e"+(i+1)+".gif");imgs[i].getWidth(null);}}int count;public void draw(Graphics g){if(count<=15){g.drawImage(imgs[count], (int)x, (int)y, null);count++;}}public Explode(double x,double y){this.x = x;this.y = y;}
}
/*** 2019年9月22日*/
package Thunder_one;import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;/*** 游戲中所有物體的父類** 2019年9月22日*/
public class GameObject {Image img; //該物體對應的圖片對象double x,y; //該物體的坐標int speed; //該物體的運行速度int width,height; //該物體所在矩形區域的寬度和高度public void drawMySelf(Graphics g){g.drawImage(img, (int)x, (int)y, (int)width, (int)height, null);}public GameObject(Image img, double x, double y) {this.img = img;this.x = x;this.y = y;if(img!=null){this.width = img.getWidth(null);this.height = img.getHeight(null);}}public GameObject(Image img, double x, double y, int width,int height) {this.img = img;this.x = x;this.y = y;this.width = width;this.height = height;}public GameObject(Image img, double x, double y, int speed, int width,int height) {this.img = img;this.x = x;//這只是構造方法初始化化時的值,并不能作為約束this.y = y;this.speed = speed;this.width = width;this.height = height;}public GameObject() {}/*** 返回物體對應矩形區域,便于后續在碰撞檢測中使用* @return*/public Rectangle getRect(){return new Rectangle((int)x,(int) y, width, height);} }
package Thunder_one;import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import javax.swing.Timer;
import javax.swing.JFrame;
import javax.swing.JLabel;/*** :雷電第一個版本:* 屏幕有很多隨機生成的炮彈,我們需要控制飛機來躲避炮彈* 存活時間越長,得分越高** */
public class GameUI extends Frame {Date startTime = new Date(); // 游戲起始時刻Date endTime; // 游戲結束時刻private int score = 0;// �?獲分�?// 界面寬和�?public static int UIWidth = 1000;public static int UIHeigh = 1000;// 炮彈的數量int ShellNum = 20;// 飛機的速度int PlSpeed = 20;// 將背景和飛機圖片定義為成員變量Image background = GameUtil.getImage("images/back3.jpg");Image planeImg = GameUtil.getImage("images/plane1.png");Image shootImg = GameUtil.getImage("images/shoot1.png");Plane plane1 = new Plane(planeImg, 500, 800, PlSpeed, 100, 100);// 炮彈的數�?Shell[] shellArr = new Shell[ShellNum];// 創建爆炸對象Explode bao;// 爆炸的輪播圖Image[] imgs = new Image[16];// 主界面public void GFrame() {this.setTitle("雷電");this.setSize(UIWidth, UIHeigh);this.setLocationRelativeTo(null);// 增加關閉窗口監聽,這樣用戶點擊右上角關閉圖標,可以關閉游戲程�?this.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {System.exit(0);}});this.addKeyListener(plane1);// 初始化,生成炮彈for (int i = 0; i < ShellNum; i++) {shellArr[i] = new Shell();}this.setVisible(true);// 啟動線程PaintThread pt = new PaintThread();pt.start();}// paint方法作用是:畫出整個窗口及內部內容�被系統自動調用�public void paint(Graphics g) {g.drawImage(background, 0, 0, 1000, 1000, null);plane1.drawMySelf(g);// 畫出容器中所有的子彈for (int i = 0; i < shellArr.length; i++) {shellArr[i].draw(g);// 檢測碰撞boolean peng = shellArr[i].getRect().intersects(plane1.getRect());if (peng) {plane1.live = false;// 飛機死掉endTime = new Date();if (bao == null) {bao = new Explode(plane1.x, plane1.y);}bao.draw(g);}}if (!plane1.live) {if (endTime == null) {endTime = new Date();}int period = (int) ((endTime.getTime() - startTime.getTime()) / 1000);printInfo(g, "您的得分為:" + period + "�?", 50, 120, 260, Color.white);}}// �?後計�?public void printInfo(Graphics g, String str, int size, int x, int y, Color color) {Color c = g.getColor();g.setColor(color);Font f = new Font("宋體", Font.BOLD, size);g.setFont(f);g.drawString(str, x, y);g.setColor(c);}// 定義內部�?class PaintThread extends Thread {// 重寫public void run() {while (true) {repaint();// 重畫try {Thread.sleep(10);} catch (Exception e) {e.printStackTrace();}}}}// 雙緩沖解決閃�?private Image offScreenImage = null;public void update(Graphics g) {if (offScreenImage == null)offScreenImage = this.createImage(1000, 1000);// 這是游戲窗口的寬度和高度Graphics gOff = offScreenImage.getGraphics();paint(gOff);g.drawImage(offScreenImage, 0, 0, null);}// 主方�?public static void main(String[] args) {// 類里面調用窗�?GameUI gu = new GameUI();gu.GFrame();}
}
package Thunder_one;import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;import javax.imageio.ImageIO;/*** GameUtil類:加載圖片代碼*GameUtil獲得程序運行類加載器,加載資源的根目錄,* 2019年9月22日*/
public class GameUtil {//工具類一般將構造器私有化public GameUtil() {}public static Image getImage(String path){BufferedImage bi = null;try {URL u = GameUtil.class.getClassLoader().getResource(path);bi = ImageIO.read(u);} catch (IOException e) {e.printStackTrace();}return bi;}}
package Thunder_one;import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;/*** ** 2019年9月22日*/
public class Plane extends GameObject implements KeyListener {boolean left, right, up, down, shoot;boolean live = true;public void drawMySelf(Graphics g) {if(live){super.drawMySelf(g);// 這里可移動,但是下面不行,應該是判斷的問題// this.x+=2;// System.out.println(left);if (left) {x -= speed;if (x <= 0) {x = 0;}}if (right) {x += speed;if (x >= 900) {x = 900;}}if (up) {y -= speed;if (y <= 10) {y = 10;}}if (down) {y += speed;if (y >= 900) {y = 900;}}}}public Plane(Image img, double x, double y, int speed, int width, int height) {super(img, x, y, width, height);this.speed = speed;}public void keyTyped(KeyEvent e) {}public void keyPressed(KeyEvent e) {//System.out.println(e.getKeyCode());int key = e.getKeyCode();switch (key) {case 37:left = true;break;case 38:up = true;break;case 39:right = true;break;case 40:down = true;break;default:break;}}public void keyReleased(KeyEvent e) {// System.out.println(e.getKeyCode());int key = e.getKeyCode();switch (key) {case 37:left = false;break;case 38:up = false;break;case 39:right = false;break;case 40:down = false;break;default:break;}}
}
/*** 2019年9月26日*/
package Thunder_one;import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.util.Random;import Thunder.GameUtil;/*** ** 2019年9月26日*/
public class Shell extends GameObject{double degree;Random ra = new Random();public Shell(){degree = Math.random()*Math.PI*2;x = ra.nextInt(700)+250;y = ra.nextInt(700)+150;width = ra.nextInt(15)+10;height = ra.nextInt(15)+10;speed = ra.nextInt(3)+2;}public void draw(Graphics g){//將外部傳入對象g的狀態保存好Color c = g.getColor();g.setColor(Color.yellow);Image shootImg = GameUtil.getImage("images/shoot1.png");g.drawImage(shootImg, (int)x, (int)y, width, height, null);//g.fillOval((int)x, (int)y, width, height);//炮彈沿著任意角度飛行x += speed*Math.cos(degree);y += speed*Math.sin(degree);//如下代碼,用來實現碰到邊界,炮彈反彈回來(原理和打臺球游戲一樣)if(y>GameUI.UIHeigh-height||y<40){degree = -degree;}if(x<0||x>GameUI.UIWidth-width){degree = Math.PI-degree;}//返回給外部,變回以前的顏色//g.setColor(c);}}
總結
以上是生活随笔 為你收集整理的仿雷电——飞机大战类游戏Ⅰ 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。