##源代碼:http://download.csdn.net/download/u012234452/9583953
##QQ:1151315936
##完成後的基本樣式圖:
##結構圖:
###父類:FlyingObject,子類:Airplane(小飛機),BigPlane(大飛機),Bullet子彈,Bee(蜜蜂),Hero(英雄機),Sky(天空).世界:World.接口:Award(獎勵),Enemy(敵人)
##根據邏輯分析把大飛機,小飛機,蜜蜂,子彈,英雄機的公共屬性和方法泛化到父類(FlyingObject)中去.
##讓World類繼承JPanel,構建出窗口,重寫paint方法
#FlyingObject.java
##1.默認構造器,用於初始化
public FlyingObject() {
life = 1;
state = ACTIVE;
}
##2.有參構造器
public FlyingObject(double width,double heigth){
this();//調用無參數的構造器,必須寫在第一行.
this.x = (int)(Math.random()*(480-width));
this.y = -heigth;
this.width = width;
this.heigth = heigth;
step = Math.random()*3+0.8;//初始化step爲[0.8,3.8)之間的數
}
##3.重寫了toString()方法可以用來測試用
public String toString() {
return x+","+y+","+width+","+heigth+","+image;
}
##4.父類中重寫了paint(Graphics g)方法,方便了子類的使用,用於繪圖
public void paint(Graphics g) {
g.drawImage(image, (int)x, (int)y, null);//繪製圖片
}
##5.父類中重構了move方法,實現了各種飛行物的移動和播放銷燬動畫功能
public void move(){
if(state == ACTIVE){
y += step;
return ;
}
if(state == DEAD){
//從子類對象中獲取下一張照片
BufferedImage img = nextImage();
if(img == null){
state = REMOVE;//沒有照片則回收
}else{
image = img;//否則把子類的圖片傳給image
}
//越界則銷燬
if(y>=825){
state = REMOVE;
}
}
}
##6.子類中必須有的方法,返回下一個要播放的照片引用,如果返回null表示沒有可播放的照片了.(雖然只有一句,但是非常重要)
protected abstract BufferedImage nextImage();
##7.飛行物被打了一下,生命-1,當生命等於0的時候爲死亡狀態
public void hit(){
if(life>0){
life--;
}
if(life==0){
state = DEAD;
}
}
##8.經典算法:碰撞檢測的方法,用於檢測物體的位置是否在碰撞的範圍內
public boolean duang(FlyingObject obj){
//this(x,y,w,h)
//obj(x,y,w,h)
double x1 = this.x - obj.width;
double x2 = this.x + this.width;
double y1 = this.y - obj.width;
double y2 = this.y + this.heigth;
return x1<obj.x&&obj.x<x2&&y1<obj.y&&obj.y<y2;
}
##9.狀態檢查方法,用於返回狀態
public boolean isDead(){
return state == DEAD;
}
/** 檢查飛行物是否活動的 */
public boolean isActive(){
return state == ACTIVE;
}
/** 檢查飛行是否可以被刪除*/
public boolean canRemove(){
return state == REMOVE;
}
/** 飛行物添加"去死"方法*/
public void goDead(){
if(isActive()){
state = DEAD;
}
}
#Airplane.java
##1.獲取的小飛機的資源
private static BufferedImage[] imgs;// 定義一個唯一的圖片素材
// 靜態代碼塊用於獲取圖片資源
static {
imgs = new BufferedImage[5];
try {
for (int i = 0; i < imgs.length; i++) {
String png = "cn/feike/shoot/airplane" + i + ".png";//獲取圖片路徑
imgs[i] = ImageIO.read(Airplane.class.getClassLoader().getResourceAsStream(png));
}
} catch (Exception e) {
e.printStackTrace();
}
}
##2.初始化小飛機,同時初始化照片
public Airplane() {
super(49, 36);// 初始化小飛機
this.image = imgs[0];
}
##3.繼承父類的抽象方法,播放下一張圖片,用於銷燬
protected BufferedImage nextImage() {
index++;
if(index >= imgs.length){
return null;//如果下標大於等於數組長度,無圖可播,返回null.
}
return imgs[index];
}
#BigPlane.java
##1.初始化大飛機,讓生命等於3(即子彈打三下才打死)
public BigPlane() {
super(69,99);//初始化大飛機
this.image = imgs[0];
life = 3;
}
#BigPlaneAward.java
##1.繼承了大飛機,重寫了paint方法,在飛機外面加上一個框框
public void paint(Graphics g){
super.paint(g);//重載父類的paint方法
//在大飛機外面加上一個框框
g.drawRect((int)x, (int)y, (int)width, (int)heigth);
}
##2.固定獎勵,打掉返回雙倍獎勵
public int getAward() {
return DOUBLE_FIRE;//獲取雙倍獎勵
}
#Hero.java
##1.初始化英雄飛機,讓其在地圖的下方出現
public Hero() {
this.width = 97;
this.heigth = 124;
this.x = (480-width)/2;
this.y =500;
this.image = imgs[0];
}
##2.重寫了move()的無參方法,用於顯示動態效果和英雄飛機的銷燬效果
public void move() {
if(isActive()){
n++;
int i = n%2;//用這個i的值來控制切換的圖片(i=n%2)的切換速度快於(i=n/2%2)
this.image = imgs[i];//來回切換圖片,顯示出動畫的效果.
}
if(isDead()){
//從子類獲取下一張照片
BufferedImage img = nextImage();
if(img==null){
state = REMOVE;
}else{
image = img;
}
}
}
##3.再次重寫了move()有參方法,用於英雄飛機隨着鼠標移動
public void move(int x,int y){
this.x = x-width/2;
this.y = y-heigth/2;
}
##4.射擊方法,實現是單槍射擊還是雙槍射擊
public Bullet[] shoot(int type){
int x = (int)(this.x+width/2-8/2);//子彈的出場的x位置
int y = (int)(this.y-30);//子彈的出場的y位置
if(type == 1){//單槍發射
return new Bullet[]{
new Bullet(x,y)//創建一顆子彈
};
}
if(type == 2){
return new Bullet[]{
new Bullet(x-30, y),//第一個子彈原出場的位子向左移動30
new Bullet(x+30,y)//第二個子彈原出場的位子向右移動30
};
}
return new Bullet[0];
}
#Bullet.java
##1.初始化子彈,因爲子彈跟着英雄飛機一起走,所以需要傳入兩個參數.
public Bullet(int x,int y) {
this.x = x;
this.y = y;
width = 8;
heigth = 14;
this.image = img;
}
##2.重寫子彈move方法,子彈是從下往上移動
public void move() {
if(state == ACTIVE){
y -= 8;
if(y<=-heigth){
state = REMOVE;
}
}
}
##3.重寫了nextImage()和hit()方法,子彈撞擊後直接銷燬
protected BufferedImage nextImage() {
return null;
}
public void hit(){
state = REMOVE;//子彈撞擊後銷燬
}
#Bee.java
##1.初始化蜜蜂,蜜蜂的出場方向是隨機的
public Bee() {
super(60, 50);
this.image = imgs[0];
direction = Math.random() > 0.5 ? -2 : 2;// 初始化蜜蜂的移動方向,各佔50%的概率.
}
##2.重寫了move()方法,蜜蜂的移動方向實現斜着的
public void move() {
super.move();// y += step;即把y座標交個父類來處理
if (state == ACTIVE) {
x += direction;
// 如果蜜蜂移動到右邊邊界,則反方向移動
if (x >= 480 - width) {
direction = -2;
}
// 如果蜜蜂移動到左邊邊界,則反方向移動
if (x < 0) {
direction = 2;
}
}
// 蜜蜂的動漫效果
n++;
int i = n % 2;// 用來控制蜜蜂的扇翅膀的速度
this.image = imgs[i];// 來回切換圖片,顯示出動畫的效果.
}
#Sky.java
##1.初始化天空,改變了天空的移動速度,和第二種圖片的高度
public Sky() {
this.x = 0;
this.y = 0;
this.width=480;
this.heigth=825;
this.image = img;
this.step = 1;//初始化圖片移動的速度
y1 = -heigth;//初始化第二張圖片的位置
}
##2.重寫了move()方法,兩張圖片同時移動,一張圖片移除下邊界就重新定義高度再移動.
public void move() {
y++;
y1++;
if(y>=heigth){
y = -heigth;//如果第一張圖移動出下邊界位置,則返回頂部
}
if(y1>=heigth){
y1 = -heigth;//如果第二張圖移動出下邊界位置,則返回頂部
}
}
##3.重寫了paint,用於繪製兩張背景圖片的位置.
public void paint(Graphics g) {
g.drawImage(image, (int)x, (int)y, null);
g.drawImage(image, (int)x, (int)y1, null);
}
##4.天空沒有銷燬圖片,所以返回null
protected BufferedImage nextImage() {
return null;
}
#Award.java
##1.獎勵接口,用於獲取獎勵
public interface Award {
int LIFE = 0;
int FIRE = 1;
int DOUBLE_FIRE = 2;
int getAward();
}
#Enemy.java
##1.敵人接口,用於獲取得分
public interface Enemy {
int getScore();
}
#World.java
##1.初始化三張開始,暫停,結束照片
private static BufferedImage pause;//暫停
private static BufferedImage ready;//開始
private static BufferedImage gameOver;//結束
static{
try {
String png = "cn/feike/shoot/start.png";
ready = ImageIO.read(World.class.getClassLoader().getResourceAsStream(png));
png = "cn/feike/shoot/pause.png";
pause = ImageIO.read(World.class.getClassLoader().getResourceAsStream(png));
png = "cn/feike/shoot/gameover.png";
gameOver = ImageIO.read(World.class.getClassLoader().getResourceAsStream(png));
} catch (Exception e) {
e.printStackTrace();
}
}
##2.初始化世界中的物體
public World() {
flyingObjects = new FlyingObject[]{};//初始化飛行物數組
bullets = new Bullet[]{};//初始化子彈數組
hero = new Hero();//初始化英雄飛機
sky = new Sky();//初始化一片天空
nextTime = System.currentTimeMillis()+1000;//初始化下一秒的系統時間
}
##3.做每隔一秒出場一個飛行物
//nextOne方法被定時(1/24秒)調用一次
public void nextOne(){
//方法調用一次,獲取一下當前時間.
long now = System.currentTimeMillis();//獲取當前系統時間的毫秒數
if(now>=nextTime){
nextTime = now + 1000;//控制飛行物的出場時間間隔,即每隔一秒出場一個飛行物
FlyingObject obj = randomOne();//調用randomOne方法,隨機生成一個飛行物
flyingObjects = Arrays.copyOf(flyingObjects, flyingObjects.length+1);//數組擴容
flyingObjects[flyingObjects.length-1]=obj;//隨機生成的飛行物放到數組中去
}
}
##4.隨機出場一個飛行物,出場的概率不同
private static FlyingObject randomOne(){
int n = (int)(Math.random()*10);//[0,10)
//這個上面這個隨機數的大小也可以調整不同飛行物的出場概率
switch(n){
case 0 : return new Bee();
case 1 :
case 2 : return new BigPlane();
case 3 :
case 4 : return new BigPlaneAward();
default : return new Airplane();//概率最高
}
}
##5.重寫了paint(Graphics g)方法,繪製出天空的物體
public void paint(Graphics g) {
sky.paint(g);//畫出天空,即背景圖片
hero.paint(g);//畫出英雄飛機
for(FlyingObject fly : flyingObjects){
fly.paint(g);//繪製每個飛行物
}
for(Bullet bullet : bullets){
bullet.paint(g);//繪製每個子彈
}
//添加分數,生命,子彈類型的顯示
g.drawString("SCORE:"+score, 20, 30);
g.drawString("LIFE:"+life, 20, 50);
g.drawString("FIRE:"+fireType, 20, 70);
switch(state){
case PAUSE : g.drawImage(pause, 0, 0, null);
case READY : g.drawImage(ready, 0, 0, null);
case GAME_OVER : g.drawImage(gameOver, 0, 0, null);
}
}
##6.添加一個定時器和啓動定時器的方法.每1/24秒一個動作
public void action(){
timer = new Timer();//初始化一個定時器
timer.schedule(new TimerTask() {
@Override
public void run() {
if(state == RUNNING){
nextOne();//每(1/24秒)生成一個飛行物
move();//各種物體的移動
shoot();//射擊封裝到shoot方法
duangDuang();//碰撞方法
removeObjects();//回收方法
heroLifeCircle();//英雄的聲明週期
}
repaint();//重新繪製JPanel
}
}, 0,1000/24);//從0開始,間隔爲(1/24)秒
//創建鼠標的監聽即開啓鼠標的監聽
MouseAdapter l = new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
if(state == RUNNING){
int x = e.getX();
int y = e.getY();
hero.move(x,y);//傳遞移動的x,y座標.
}
}
@Override
public void mouseExited(MouseEvent e) {
if(state == RUNNING){
state = PAUSE;
}
}
@Override
public void mouseEntered(MouseEvent e) {
if(state == PAUSE){
state = RUNNING;
}
}
@Override
public void mouseClicked(MouseEvent e) {
if(state == READY){
state = RUNNING;
}
if(state == GAME_OVER){//遊戲結束所有參數重置。
score = 0;
life = 3;
fireType = 1;
hero = new Hero();
bullets = new Bullet[0];
flyingObjects = new FlyingObject[0];
state = READY;
}
}
};
//將監聽器加入到當前的面板中
addMouseListener(l);
addMouseMotionListener(l);
}
##7.英雄飛機的生命週期
public void heroLifeCircle(){
//檢查飛行物和英雄的碰撞
if(hero.isActive()){
for(FlyingObject plane : flyingObjects){
if(plane.isActive()&&plane.duang(hero)){
plane.goDead();
hero.goDead();
}
}
}
if(hero.canRemove()){
if(life>0){
life--;
hero = new Hero();//新生一個英雄
//清場,死了的瞬間,再碰撞英雄不死,碰撞物死.
for(FlyingObject plane : flyingObjects){
if(plane.isActive()&&plane.duang(hero)){
plane.goDead();
}
}
}else{
//遊戲結束
state = GAME_OVER;
}
}
}
##8.刪除掉沒有用的子彈和飛機
public void removeObjects(){
//刪除掉廢棄的子彈
Bullet[] ary = {};//初始化一個子彈數組
for(Bullet b : bullets){
if(b.canRemove()){
continue;//忽略掉要刪除的子彈
}
ary = Arrays.copyOf(ary, ary.length+1);//數組擴容
ary[ary.length-1] = b;//把沒有刪掉的子彈重新添加到數組中去
}
bullets = ary;//bullets變量引用到ary數組上,拋棄原來的引用
//刪除掉廢棄的飛機
FlyingObject[] arr = {};//初始化一個飛行物數組
for(FlyingObject obj : flyingObjects){
if(obj.canRemove()){
continue;//忽略掉廢棄的飛行物
}
arr = Arrays.copyOf(arr, arr.length+1);//數組擴容
arr[arr.length-1] = obj;////把沒有刪掉的飛行物添加到數組中去
}
flyingObjects = arr;
}
##9.在world裏面添加碰撞檢測方法
public void duangDuang(){
for(FlyingObject plane : flyingObjects){
if(plane.isActive()){
if(shootByBullet(plane)){
plane.hit();
if(plane.isDead()){
//計分,獲取獎勵
if(plane instanceof Enemy){
Enemy enemy = (Enemy)plane;
int s = enemy.getScore();
score += s;
}
if(plane instanceof Award){
Award award =(Award)plane;
int awd = award.getAward();
if(awd == Award.LIFE){
life++;
}else if(awd == Award.FIRE){
fireType = 1;
}else if(awd == Award.DOUBLE_FIRE){
fireType = 2;
}
}
}
}
}
}
}
##10.檢查每個子彈是否和飛行物碰撞
public boolean shootByBullet(FlyingObject plane){
for(Bullet bullet : bullets){
if(bullet.duang(plane)){
bullet.hit();
return true;
}
}
return false;
}
##11.射擊控制方法被定時器定時調用,控制英雄飛機射擊子彈的速度
public void shoot(){
//方法調用一次,獲取一下當前時間.
long now = System.currentTimeMillis();//獲取當前系統時間的毫秒數
if(now>nextShootTime){
nextShootTime=now + 500;//半秒發射一發
Bullet[] arr = hero.shoot(fireType);//子彈射擊類型
bullets = Arrays.copyOf(bullets, bullets.length+arr.length);
System.arraycopy(arr, 0, bullets, bullets.length-arr.length, arr.length);
}
}
##12.各種物體的移動
private void move() {
//每個飛行物移動一下,重新繪製JPanel方法
for(FlyingObject fly : flyingObjects){
fly.move();
}
//每個子彈的移動
for(Bullet bullet : bullets){
bullet.move();
}
sky.move();//天空移動
hero.move();//英雄飛機移動
}
##13.main()方法,設置窗口大小並顯示
public static void main(String[] args) {
World world = new World();//創建一個world面板
JFrame frame = new JFrame();//創建一個窗口
frame.setSize(400, 680);//窗口大小
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//關閉窗口即關閉程序
frame.setLocationRelativeTo(null);//讓窗口居中
frame.add(world);//把world面板加到JFrame窗口裏去
frame.setVisible(true);//窗口可見
world.action();//啓動定時器
}