一 窗口的創(chuàng)建
1.創(chuàng)建一個(gè)Gamewin的類器贩,讓這個(gè)類繼承JFrame
2.將以下代碼打入類中,運(yùn)行后會(huì)出現(xiàn)一個(gè)白色運(yùn)行框
3.在窗口中我還自己加入了一些代碼,來優(yōu)化運(yùn)行
public static int state=0;
Image offScreenImage=null;
int width=600;
int height=600;
public class GameWin extends JFrame {
public void launch() {
//設(shè)置窗口是否可見
this.setVisible(true);
//設(shè)置窗口大小
this.setSize(width,height);
//設(shè)置窗口位置
this.setLocationRelativeTo(null);
//設(shè)置窗口標(biāo)題
this.setResizable(false);//設(shè)置窗體不可調(diào)節(jié)(自加)
this.setTitle("飛機(jī)大戰(zhàn)");
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);//關(guān)閉窗口的同時(shí)程序也隨之關(guān)閉(自加)
}
補(bǔ)充:
1.該代碼的作者并非本人,所以代碼框架的構(gòu)建有點(diǎn)奇葩涝滴,我在這里補(bǔ)充一下
2.這個(gè)游戲創(chuàng)建了三個(gè)包,com.sxt周荐,com.sxt.obj,com.sxt.utils
3.這三個(gè)包的作用分別是放窗口和主方法狭莱,放對(duì)象僵娃,放圖片
4.在第二個(gè)包obj中囊括了Gameobj,我稱為主對(duì)象概作,背景對(duì)象,我方飛機(jī)對(duì)象默怨,我方子彈對(duì)象讯榕,敵方飛機(jī)對(duì)象,敵方boss對(duì)象匙睹,敵方子彈對(duì)象愚屁,還有我自己加的音樂對(duì)象
5.主對(duì)象里寫著一些對(duì)象包可能需要的方法,這些對(duì)象類會(huì)繼承主對(duì)象類痕檬,然后寫一些方法來實(shí)現(xiàn)自己圖片的移動(dòng)和排版
6.而主方法包中需要調(diào)用這些類來創(chuàng)建對(duì)象時(shí)霎槐,就需要導(dǎo)obj的包了
7.以下是gameobj包中的方法
8.然后是該游戲的游戲狀態(tài)是靠數(shù)字來實(shí)現(xiàn)的,游戲狀態(tài) 0未開始 1游戲中 2暫停 3通關(guān)失敗 4通關(guān)成功
package com.sxt.obj;
import com.sxt.GameWin;
import java.awt.*;
public class Gameobj {
Image img;
public int x;
public int y;
int width;
int height;
double speed;
GameWin frame;
public Image getImg() {
? ? return img;
}
public void setImg(Image img) {
? ? this.img = img;
}
public int getX() {
? ? return x;
}
public void setX(int x) {
? ? this.x = x;
}
public int getY() {
? ? return y;
}
public void setY(int y) {
? ? this.y = y;
}
public int getWidth() {
? ? return width;
}
public void setWidth(int width) {
? ? this.width = width;
}
public int getHeight() {
? ? return height;
}
public void setHeight(int height) {
? ? this.height = height;
}
public double getSpeed() {
? ? return speed;
}
public void setSpeed(double speed) {
? ? this.speed = speed;
}
public GameWin getFrame() {
? ? return frame;
}
public void setFrame(GameWin frame) {
? ? this.frame = frame;
}
public Gameobj() {
}
public Gameobj( int x, int y) {
? ? this.x = x;
? ? this.y = y;
}
public Gameobj(Image img, int x, int y, double speed) {
? ? this.img = img;
? ? this.x = x;
? ? this.y = y;
? ? this.speed = speed;
}
public Gameobj(Image img, int x, int y, int width, int height, double speed, GameWin frame) {
? ? this.img = img;
? ? this.x = x;
? ? this.y = y;
? ? this.width = width;
? ? this.height = height;
? ? this.speed = speed;
? ? this.frame = frame;
}
public void paintSelf(Graphics gImage){
? ? gImage.drawImage(img,x,y,null);
}
public Rectangle getRec(){
? ? return new Rectangle(x,y,width,height);
}
}
二 背景圖片的添加
1.首先我們要明確自己需要的背景圖是需要滾動(dòng)
2.插入一張圖片梦谜,而且需要?jiǎng)拥那鸬覀冃枰獎(jiǎng)?chuàng)建一個(gè)背景圖類bgobj袭景,然后創(chuàng)建一個(gè)對(duì)象讓它的y軸不斷變化
3.但是這樣是不夠的,背景圖是有限的闭树,所以當(dāng)背景圖走完時(shí)耸棒,我們需要將背景圖的坐標(biāo)歸零。這樣才能保證圖片的循環(huán)使用报辱,不會(huì)出bug
插入圖片的代碼思路:
1.創(chuàng)建相對(duì)路徑与殃,復(fù)制圖片到自己創(chuàng)建的包中,我們習(xí)慣命名為images
2.然后再創(chuàng)建Gameutils類中調(diào)用圖片
我在飛機(jī)大戰(zhàn)的調(diào)用方法:
public static Image bgImg=new ImageIcon("src/imgs/bg.jpg").getImage();
這個(gè)方法比較快捷穩(wěn)定推薦使用碍现。
3.在obj包中創(chuàng)建一個(gè)bgobj,然后將以下代碼寫入其的painself方法中
super.paintSelf(gImage);
y+=speed;
//圖片的循環(huán)
if(y>=0) y=-2000;
4.然后再Gamewin類中創(chuàng)建一個(gè)背景圖對(duì)象
//背景圖對(duì)象
Bgobj bgObj=new Bgobj(GameUtils.bgImg,0,-2000,5);
三 啟動(dòng)頁(yè)面的制作
制作成果如下
1.我們要將圖片貼到我們的畫板上幅疼,并且加入“開始游戲”這行提示語(yǔ)
2.我們根據(jù)作者思路將游戲模式設(shè)為0兵睛,也就是“未開始”狀態(tài)
public void paint(Graphics g) {
//獲取off的畫筆對(duì)象
Graphics gImage=offScreenImage.getGraphics();
gImage.fillRect(0, 0, width, height);
if(state==0) {
gImage.drawImage(GameUtils.bgImg,0,0,600,600,null);
gImage.drawImage(GameUtils.bossImg,220,120,null);
gImage.drawImage(GameUtils.explodeImg,270,350,null);
GameUtils.drawWord(gImage,"點(diǎn)擊開始游戲", Color.yellow, 40, 180, 300);
// gImage.setColor(Color.yellow);
// gImage.setFont(new Font("仿宋",Font.BOLD,40));
// gImage.drawString("點(diǎn)擊開始游戲", 180, 300);
}
啟動(dòng)頁(yè)面的點(diǎn)擊事件
我們需要?jiǎng)?chuàng)建一個(gè)鼠標(biāo)監(jiān)聽事件力图,來監(jiān)聽鼠標(biāo)左鍵的點(diǎn)擊,然后改變游戲的狀態(tài)酝静,由于這是游戲后期代碼所以我自己在鼠標(biāo)監(jiān)聽中加了一些初始化游戲的代碼
this.addMouseListener(new MouseAdapter() {
@Override
?
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
if(e.getButton()==1&&state==0) {? //getButton()==1,2,3分別對(duì)應(yīng)鼠標(biāo)的左鍵 中鍵 右鍵
state = 1;
repaint();
}
if(e.getButton()==1&&state==3||e.getButton()==1&&state==4) {
? ? //分?jǐn)?shù)得分
score = 0;
//我方飛機(jī)對(duì)象
// planeObj=new PlaneObj(GameUtils.planeImg,290,550,20,30,0,this);
//敵方boss的對(duì)象
//BossObj bossObj=new BossObj();
bossObj=null;
state=0;
enemycount=0;
GameUtils.gameObjList.clear();
//背景圖對(duì)象
Bgobj bgObj=new Bgobj(GameUtils.bgImg,0,-2000,5);
GameUtils.gameObjList.add(bgObj);
GameUtils.gameObjList.add(planeObj);
repaint();
? ?
}
}
});
四 雙緩存的添加
由于背景圖片的滾動(dòng)辩棒,我們會(huì)發(fā)現(xiàn)加載圖片狼忱,由于組件都是畫筆一直在重繪而閃動(dòng),所以我們需要加入雙緩存思想一睁。那么如何實(shí)現(xiàn)雙緩存呢钻弄?
所以我們需要加入一張圖片讓所有組件都畫在它上面,然后它來做開始界面
首先添加offScreenImage對(duì)象到Gamewin中
Image offScreenImage=null;
int width=600;
int height=600;
其次添加條件和畫筆到painself方法中去
if(offScreenImage==null) {
offScreenImage =createImage(width,height);
}
//獲取off的畫筆對(duì)象
Graphics gImage=offScreenImage.getGraphics();
gImage.fillRect(0, 0, width, height);
g.drawImage(offScreenImage,0, 0, null);
五 我方戰(zhàn)斗機(jī)的添加和鼠標(biāo)控制
1.按照作者的思路者吁,我們應(yīng)該創(chuàng)建Planeobj窘俺,然后繼承Gameobj,在其中調(diào)用Gameobj的方法進(jìn)行重載复凳,然后首先小飛機(jī)能夠出現(xiàn)在畫板上
2.添加圖片瘤泪,只要按照創(chuàng)建背景圖片的方法,就可以插入飛機(jī)的圖片育八,獲取飛機(jī)圖片的相對(duì)路徑对途,然后我們需要在planeobj的類中添加鼠標(biāo)監(jiān)聽,讓飛機(jī)圖像隨著鼠標(biāo)移動(dòng)髓棋。
有一些細(xì)節(jié)的地方需要大家自己看看代碼和注釋
public void mouseMoved(MouseEvent e) {
PlaneObj.super.x=e.getX() -24; //鼠標(biāo)光標(biāo)的坐標(biāo)減去飛機(jī)的一半
PlaneObj.super.y=e.getY() -32;
System.out.println(e.getY());
}
然后我們需要在Gamewin中創(chuàng)建一個(gè)飛機(jī)對(duì)象实檀,加入一個(gè)游戲開始的條件然后讓這個(gè)對(duì)象重繪就行
public PlaneObj? planeObj=new PlaneObj(GameUtils.planeImg,290,550,20,30,0,this);
由于前期代碼和后期的代碼思路上有很大的不同所以我不在這里展示代碼了
六 添加飛機(jī)的子彈
這部分分為圖片添加和批量發(fā)射倆部分
我們先講講圖片的添加吧,在GameutilS的包內(nèi)完成圖像的調(diào)用按声。
在obj包內(nèi)創(chuàng)建一個(gè)shellobj的類膳犹,然后同樣的繼承Gameobj,然后重寫一些方法
同樣的在Gamewin里創(chuàng)建一個(gè)對(duì)象签则,然后我們添加了一個(gè)createObj的方法用來存儲(chǔ)對(duì)象的生成
于是這里多了一個(gè)新的方法那就是關(guān)于對(duì)象的ArrayList须床,在這里作者希望通過將創(chuàng)建的元素對(duì)象放入集合中實(shí)現(xiàn)批量添加
1.在Gameutils中創(chuàng)建了三個(gè)集合
? ? ? //所有游戲的集合
public static List <Gameobj> gameObjList=new ArrayList<>();
//刪除元素的集合
public static List <Gameobj> removeObjList=new ArrayList<>();
//我方子彈的集合
public static List <ShellObj> shellObjList=new ArrayList<>();
這些集合有著對(duì)于全局的思考,后面會(huì)提到渐裂,現(xiàn)在我們只需要其中的第一和第三集合即可豺旬。
思路是將子彈對(duì)象放入3集合中余赢,然后將3集合的最后一項(xiàng)添加到1集合中,這樣就能使所有的元素都能按照順序依次在畫板中重繪
GameUtils.gameObjList.add(bgObj);
GameUtils.gameObjList.add(planeObj);
void createObj() {
? ? ? ? //我方子彈
if(count%10==0) {? //count是重繪次數(shù)哈垢,所以每2.5秒會(huì)生成一顆子彈
GameUtils.shellObjList.add(new ShellObj(GameUtils.shellImg, planeObj.getX()+3, planeObj.getY()-16, 14, 25, 5, this));
//獲取shellObj最后一個(gè)元素將其添加到GameObj里
GameUtils.gameObjList.add(GameUtils.shellObjList.get(GameUtils.shellObjList.size() - 1));
}
}
像這樣就可以將對(duì)象添加到游戲總集合里了
而且還關(guān)于這個(gè)方法還得開個(gè)線程
while(true) {
// System.out.println("進(jìn)入");
if(state==1) {
createObj();
repaint();
}
try {
Thread.sleep(25);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
還有一點(diǎn)就是我方子彈的生成是需要追蹤飛機(jī)的
GameUtils.shellObjList.add(new ShellObj(GameUtils.shellImg, planeObj.getX()+3, planeObj.getY()-16, 14, 25, 5, this));
七? 敵方飛機(jī)的批量增加
本質(zhì)上與飛機(jī)子彈的添加方法無(wú)異妻柒,但是我們需要的是控制生成的數(shù)量還有隨機(jī)生成的
所以我們需要在這兩個(gè)方面注意
控制生成的數(shù)量
還是簡(jiǎn)單的,只需要加入條件判斷即可
if(count%10==0) {?
..........
}
隨機(jī)生成肯定就得用偽隨機(jī)數(shù)
GameUtils.enemyObjList.add(new EnemyObj(GameUtils.enemyImg,(int)(Math.random()*12)*50,0,59,36,10,this));
八 碰撞處理
該部分分為子彈對(duì)敵方飛機(jī)的碰撞耘分,我方飛機(jī)對(duì)敵方飛機(jī)的碰撞
我們需要對(duì)敵方飛機(jī)添加爆炸和刪除
爆炸obj我們需要在obj的包中添加explorerobj举塔,然后我們需要用一個(gè)for循環(huán)來展示所有的爆炸圖片
static Image[]pic =new Image[16];
int explodeCount=0;
static {
for(int i=0;i<pic.length;i++) {
pic[i]=new ImageIcon("src/explode/e"+(i+1)+ ".gif").getImage();
}
}
public void paintSelf(Graphics gImage) {
// TODO Auto-generated method stub
//位置問題,第16張圖片出bug? ?
? ? ? ? ? super.paintSelf(gImage);?
if(explodeCount<16) {
img=pic[explodeCount];
super.paintSelf(gImage);
explodeCount++;
}
}
爆炸的圖片對(duì)象需要添加到爆炸集合之中
然后到敵方飛機(jī)的obj中求泰,添加體積碰撞的監(jiān)測(cè)央渣,
? ? if (this.getRec().intersects(this.frame.planeObj.getRec())){
? ? ? ? ? ? GameWin.state = 3;
? ? ? ? }
//游戲結(jié)束
? ? ? ? for(ShellObj shellObj: GameUtils.shellObjList) {
if(this.getRec().intersects(shellObj.getRec())) {
ExplodeObj explodeObj=new ExplodeObj(x, y);
GameUtils.explodeObjList.add(explodeObj);
GameUtils.removeObjList.add(explodeObj);
shellObj.setX(-100);
shellObj.setY(100);
this.x=-200;
this.y=200;
GameUtils.removeObjList.add(shellObj);
GameUtils.removeObjList.add(this);
? ? GameWin.score++;
}
?
九 敵方boss的添加和處理
boss的添加就有些條件要求了,比如說我可以選擇小飛機(jī)出現(xiàn)100架后再出現(xiàn)boss,或者是殲滅100架小飛機(jī)等等的情況boss才會(huì)出現(xiàn)渴频。
if(enemycount>100&&bossObj==null) {
? ? bossObj=new BossObj(GameUtils.bossImg,250,35,155,100, 10, this);
? ? GameUtils.gameObjList.add(bossObj);
? ? }
然后我們要? 添加boss的圖片? 和? boss的移動(dòng)算法? 還有? boss的攻擊手段? 以及? boss的血條
boss圖片添加可以省略了
boss的移動(dòng)算法無(wú)非就是改變bossx軸的移動(dòng)芽丹,讓boss在畫板最上邊交替移動(dòng)
public void paintSelf(Graphics gImage) {
super.paintSelf(gImage);
if (x > 550 || x < -50){
//速度的正負(fù)交替
speed = -speed;
}
x += speed;
添加boss的子彈是和我方子彈基本一致的,只是y軸的變化和子彈生成的位置不同卜朗。
接下來就是boss血條的描繪了拔第。
//血條的白色背景
gImage.setColor(Color.white);
gImage.fillRect(20, 40, 100, 10);
//血條的繪制
gImage.setColor(Color.red);
gImage.fillRect(20, 40, life*100/10, 10);
life是boss的生命值,可以根據(jù)碰撞監(jiān)測(cè)來減少场钉。
結(jié)束游戲的方法有兩種蚊俺,一種是將boss的血條清空贏得游戲,還有一種就是我方飛機(jī)碰撞到敵方子彈或者是敵方飛機(jī)墜機(jī)后失敗逛万。
//我方子彈與敵方boss的碰撞檢測(cè)
for(ShellObj shellObj:GameUtils.shellObjList) {
if (this.getRec().intersects(shellObj.getRec())){
shellObj.setX(-100);
shellObj.setY(100);
GameUtils.removeObjList.add(shellObj);
life--;
}
if(life<=0) {
GameWin.state =4;
}
}
//我方飛機(jī)與敵方boss的碰撞檢測(cè)
if (this.frame.bossObj!=null&&this.getRec().intersects(this.frame.planeObj.getRec())){
GameWin.state = 3;
}
十 積分面板的編寫
就是每次打掉一架敵方飛機(jī)就會(huì)加分泳猬,只需要在碰撞代碼那里計(jì)數(shù)就行
到這里這個(gè)作者又開始抽象了,把之前的文字代碼刪了宇植,到Gameutils里寫了一個(gè)drawWord方法得封,然后改了其他的文字代碼后
//繪制字符串的工具類
public static void drawWord(Graphics gImage,String str,Color color,int size,int x,int y) {
gImage.setColor(color);
gImage.setFont(new Font("仿宋",Font.BOLD,size));
gImage.drawString(str,x,y);
}
把GameUtils.drawWord(gImage,score+"分", Color.green, 40, 30, 100);加上后結(jié)束
十一 游戲規(guī)則的設(shè)置
就是boss出現(xiàn)還有游戲勝負(fù)的判定
if(state==0) {
? ? ? ? ? ? ? ? gImage.drawImage(GameUtils.bgImg,0,0,600,600,null);
? ? ? ? ? ? ? ? gImage.drawImage(GameUtils.bossImg,220,120,null);
? ? ? ? ? ? ? ? gImage.drawImage(GameUtils.explodeImg,270,350,null);
? ? ? ? ? ? ? ? GameUtils.drawWord(gImage,"點(diǎn)擊開始游戲", Color.yellow, 40, 180, 300);
? ? ? ? ? ? ? ? // gImage.setColor(Color.yellow);
? ? ? ? ? ? ? ? // gImage.setFont(new Font("仿宋",Font.BOLD,40));
? ? ? ? ? ? ? ? // gImage.drawString("點(diǎn)擊開始游戲", 180, 300);
? ? ? ? ? }
if(state==1) {
GameUtils.gameObjList.addAll(GameUtils.explodeObjList);
for(int i=0;i<GameUtils.gameObjList.size();i++) {
GameUtils.gameObjList.get(i).paintSelf(gImage);
}
GameUtils.gameObjList.removeAll(GameUtils.removeObjList);
}
if(state==3) {
gImage.drawImage(GameUtils.explodeImg,planeObj.getX()-35,planeObj.getY()-50,null);
GameUtils.drawWord(gImage,"游戲結(jié)束",Color.RED, 50, 220, 300);
? // gImage.setColor(Color.RED);
// gImage.setFont(new Font("仿宋",Font.BOLD,40));
// gImage.drawString("游戲結(jié)束", 220, 300);
}
if(state==4) {
gImage.drawImage(GameUtils.explodeImg,bossObj.getX()+50,bossObj.getY(),null);
GameUtils.drawWord(gImage,"游戲通關(guān)",Color.green, 50, 200, 300);
}
十二? 游戲的暫停功能的添加
添加一個(gè)鍵盤監(jiān)聽還有一個(gè)Switch語(yǔ)句來改變游戲狀態(tài)即可
//暫停
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
super.keyPressed(e);
if(e.getKeyCode()==32) {
switch(state) {
case 1:
state=2;
break;
case 2:
state=1;
break;
default:
}
}
}
});
十三 子彈和敵機(jī)的越界消失
這里就需要用到刪除元素的集合removeObjList了
思路很簡(jiǎn)單,就是判斷元素越界后句將其坐標(biāo)改到畫板的外邊然后將元素從自己的集合轉(zhuǎn)移打刪除元素的集合中指郁,從而減少內(nèi)存占用
以敵方飛機(jī)為例
我們?cè)跀撤斤w機(jī)obj中的painself方法中加入判斷
if(y>600) {
this.x=-200;
this.y=200;
GameUtils.removeObjList.add(this);
}
如果越界了就會(huì)被刪除
這樣可以大大減少內(nèi)存空間的占用
在state=1中有這么一行代碼
GameUtils.gameObjList.removeAll(GameUtils.removeObjList);
就是實(shí)現(xiàn)刪除的命令忙上,可見removeObjList起到的是標(biāo)記的作用
十四 新增的項(xiàng)目
1.實(shí)現(xiàn)開關(guān)程序和后臺(tái)運(yùn)行同步
2.實(shí)現(xiàn)初始化化操作
3.boss三重子彈的難度增加
4.部分特殊小飛機(jī)的移速增加
5.添加背景音樂
6.優(yōu)化了飛機(jī)的圖片