項目的GitHub地址:https://github.com/KeneathGuo/PlaneGame
v0.1
1.畫一個主窗口 MyGameFrame 繼承自 JFrame用於初始化主窗口
2.設置title、大小、位置。
使用匿名內部類,重寫windowClosing方法,通過點擊右上角的×來真關閉窗口(進程)。
在main方法創建一個主窗口對象f測試。
//匿名內部類,使得可以通過右上角關閉窗口
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);//0表示正常關閉
}
});
v0.2
1.在MyGameFrame類中加入重寫的 paint方法(自動被調用,當首次顯示窗口或者需要修理時自動調用),變量g相當於一支畫筆,在f這個窗口實例中畫各種各類圖形。
遊戲中所有物體都可以看成矩形。查看API Graphics。可以設置g這支畫筆顏色
2.提供一個GameUtil類,提供靜態方法以加載圖像。
import java.awt.Image;
import java.awt.image.*;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;;
public class GameUtil {
//工具類最好將構造器私有化,都是static方法,沒必要去new一個對象
private 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;
}
}
3.建立兩個Image實例,放入兩張圖片,然後調用g.drawImage方法,在窗口裏畫出兩張圖,注意順序。
v0.3
1.利用多線程讓圖片動起來。定義一個內部類PaintThread,因爲內部類可以使用外部類的屬性和方法。
//內部類可以直接使用外部類的屬性和方法很方便
class PaintThread extends Thread{
@Override
public void run() {
while(true) {
System.out.println("窗口被重畫了一次!!!");
repaint();//這個方法定義在外部類MyGameFrame裏,重畫。
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
這個Paint Thread類幫助我們反覆重畫窗口。我們調用repaint,這是JFrame的父類Component的方法。
2.在初始化窗口裏啓動線程(寫在launchFrame()方法裏)。
//啓動重畫窗口的線程
new PaintThread().start();
3.定義飛機的座標---planex和planey。 paint方法里加入 planex++ ,這樣每次重畫時飛機右移一個像素。
v0.4
1.使用awt的Frame會有雙緩衝問題。而使用swing的JFrame就不會有這個問題。
2.創建GameObject類,作爲遊戲中飛機、坦克、車、炸彈等物體的父類。設計並提取它們的共同點:圖片、橫縱座標、寬度、高度等等(private)
3.定義一個方法 drawSelf()畫出自己。
4.爲了方便創造,重載幾個構造方法,包括一個無參構造器。
5.設計飛機類Plane繼承自 GameObject。然後在主窗口中畫出一個飛機。還可以畫很多個,可以利用容器或者數組來畫。
6.在GameObject類中加入一個方法:
/**
* 返回物體所在的矩形,便於後續的碰撞檢測,很重要的後期的一個方法
* @return
*/
public Rectangle getRectangle() {
return new Rectangle((int)x,(int)y, width, height);//爲以後的碰撞檢測做準備
}
v0.5
1.鍵盤控制原理。先在MyGameFrame裏定義這樣的內部類加入鍵盤監聽:
//定義鍵盤監聽的內部類
class KeyMonitor extends KeyAdapter{
@Override
public void keyPressed(KeyEvent e) {
//System.out.println(e.getKeyCode());//測試是否監聽成功
plane.addDirection(e);
}
@Override
public void keyReleased(KeyEvent e) {
plane.minusDirection(e);
}
}
然後在launchFrame()方法中增加代碼來給主窗口增加鍵盤監聽:
addKeyListener(new KeyMonitor());//給窗口增加鍵盤監聽。結合1中的測試代碼來測試
2.給飛機加上方向。在Plane類中加入四個boolean類型變量left,up,right,down分別對應上、下、左、右,同時修改drawSelf()方法以及添加一個addDirection()方法和一個minusDirection()方法:
@Override
public void drawSelf(Graphics g) {
g.drawImage(img, (int)x, (int)y, null);
if(left) {
x-=speed;
}
if(right) {
x+=speed;
}
if(up) {
y-=speed;
}
if(down) {
y+=speed;
}
}
//按下某個鍵增加相應的方向
public void addDirection(KeyEvent e) {
//System.out.println("####"+e.getKeyCode());
switch(e.getKeyCode()) {
case KeyEvent.VK_LEFT:
left=true;
break;
case KeyEvent.VK_UP:
up = true;
break;
case KeyEvent.VK_RIGHT:
right = true;
break;
case KeyEvent.VK_DOWN:
down = true;
break;
}
}
//擡起某個鍵取消相應的方向
public void minusDirection(KeyEvent e) {
switch(e.getKeyCode()) {
case KeyEvent.VK_LEFT:
left=false;
break;
case KeyEvent.VK_UP:
up = false;
break;
case KeyEvent.VK_RIGHT:
right = false;
break;
case KeyEvent.VK_DOWN:
down = false;
break;
}
3.在KeyMonitor內部類裏使用2中的兩個方法。
//定義鍵盤監聽的內部類
class KeyMonitor extends KeyAdapter{
@Override
public void keyPressed(KeyEvent e) {
plane.addDirection(e);
}
@Override
public void keyReleased(KeyEvent e) {
plane.minusDirection(e);
}
}
這裏可以把speed調高一些,比如調成3.
v0.6
1.炮彈類Shell基本設計。這裏用實心的黃色橢圓實現(也可以加載新圖片實現)。炮彈的方向隨機,遇到邊界會反彈。
Shell繼承自GameObject類。增加屬性: 角度degree 。在構造器中設置隨機的degree:
public Shell() {
x=200;//橫座標
y=200;//縱座標
width=10;
height=10;
speed=3;
degree = Math.random()*Math.PI*2;//生成一個0-2Π之間的隨機數,弧度制
}
2.寫一個draw()方法畫出自己:
public void draw(Graphics g) {
Color c= g.getColor();
g.setColor(Color.YELLOW);
g.fillOval((int)x, (int)y, width, height);
//炮彈沿着任意角度去飛
x += speed*Math.cos(degree);
y += speed*Math.sin(degree);
if(x<0||x>Constant.GAME_WIDTH-width) {
degree =Math.PI-degree;//左右方向翻轉,關於y軸對稱
}
if(y<30||y>Constant.GAME_HEIGHT-height/*30爲標題欄的寬度*/) {
degree=-degree;
}
g.setColor(c);
}
3.在主窗口中畫出一個炮彈Shell實例。 這裏建立一個Constant類專門放置所有常量,便於同步修改。
v0.7
1.使用數組來裝入多個炮彈,並且要實現(畫出來)。 在paint方法裏改。
v0.8
1.用了JFrame之後還在閃爍。當然,用Frame的話閃爍更嚴重。
雙緩衝技術繪圖過程如下:
1).在內存中創建與畫布一致的緩衝區
2)在緩衝區畫圖
3)將緩衝區位圖拷貝到當前畫布上
4)釋放內存緩衝區
雙緩衝即在內存中創建一個與屏幕繪圖區域一致的對象,先將圖形繪製到內存中的這個對象上,再一次性將這個對象上的圖形拷貝到屏幕上,這樣也能大大加快繪圖的速度。
在主窗口中加入以下代碼:
//解決雙緩衝問題的代碼
private Image offScreenImage = null;
public void update(Graphics g) {
if(offScreenImage == null)
offScreenImage=this.createImage(Constant.GAME_WIDTH, Constant.GAME_HEIGHT);
Graphics gOff = offScreenImage.getGraphics();
paint(gOff);
g.drawImage(offScreenImage, 0, 0, null);
}
然後改爲繼承自Frame(效果更好)。
2.增加碰撞檢測。使用v0.4的第6步中的方法。
判斷兩個矩形是否相交:Rectangle的intersects()方法。
在主窗口的paint()方法中加入代碼:
//飛機和炮彈的碰撞檢測
boolean peng= shells[i].getRect().intersects(plane.getRect());
還要修改飛機的構造器裏的寬和高,直接設置爲圖片的寬和高。
this.width=img.getWidth(null);
this.height=img.getHeight(null);
3.給飛機增加 boolean類型的live變量,定義飛機的生死。
4.增加爆炸類Explode的實現。爆炸類實際上存儲了一系列爆炸的圖片,然後,進行輪播。
package cn.gxg.game;
import java.awt.Graphics;
import java.awt.Image;
/*
* 爆炸類
*/
public class Explode {
double x, y;//爆炸的位置
static Image[] imgs = new Image[16];//靜態數組,因爲圖片不要反覆加載,節省資源
/**
* 靜態初始化塊,圖片是從1-16.循環是0-15
*/
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;
}
}
5.主窗口中加入爆炸相關代碼。先定義,paint的時候在根據情況爆炸(如果碰撞)。
6.加入計時功能。主類中定義一個int類型的period。
遊戲開始時,定義一個startTime;遊戲結束時,定義一個endTime。兩者相減,即遊戲持續時間。
總結和展望:
1.能夠根據此例子開發大多數2D遊戲了。
2.需要加入更多功能。