先上效果图:

视频封面

00:32飞机大战

首先我们定义一个GameFrame类来显示一个窗口:

public class GameFrame extends JFrame{
public void showGameFram(){
JFrame f = new JFrame();
f.setTitle("飞机大战");
f.setSize(600, 1000);
f.setLayout(null);
f.setResizable(false);
f.setLocationRelativeTo(null);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}

然后我们来制作用来显示飞机的类BasicPlain:

public class BasicPlane {

int locx;
int locy;
int speedx = 15;
int speedy = 15;
int ideax;
int ideay;
int type = 1; //敌机的种类区分标识符
private boolean ifenter = false; //判断敌机是否进入了理想范围

public BasicPlane() {
// TODO Auto-generated constructor stub
}
public BasicPlane(int locx,int locy,int ideax,int ideay,int type){
this.locx = locx;
this.locy = locy;
this.ideax = ideax;
this.ideay = ideax;
this.type = type;
}

Image img = chooseimage(type);
//根据选择的敌机类型进行图案绘制
public Image chooseimage( int type){
ImageIcon imageicon;
Image imag = null;
switch (type) {
case 1:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\1-1.png");
imag = imageicon.getImage();
return imag;
case 2:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\1-2.png");
imag = imageicon.getImage();
return imag;
case 3:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\1-5-1.png");
imag = imageicon.getImage();
return imag;
case 4:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\1-3--2.png");
imag = imageicon.getImage();
return imag;
case 5:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\1-3--1.png");
imag = imageicon.getImage();
return imag;
case 6:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\1-6.png");
imag = imageicon.getImage();
return imag;
default:
break;
}
return imag;
}
public void draw(Graphics g){
g.drawImage(chooseimage(type), locx, locy, null);
}
public void move(){
// System.out.println("x y speedx speedy"+locx+" "+locy+" "+speedx+" "+speedy);
//如果还没有进入预定区域
if(!ifenter){
switch (type) {
case 1:
locx = locx+speedx;
locy = locy+speedy;
if(locx == ideax||locy == ideay){
ifenter = true;
}
break;
case 2:
locx = locx-speedx;
locy = locy+speedy;
if(locx == ideax||locy == ideay){
ifenter = true;
}
break;
case 3:
locx = locx +0;
locy = locy+speedy;
if(locy == 150){
ifenter = true;
}
break;
case 4 :
locx = locx-speedx;
locy = locy+0;
break;
case 5:
locx = locx+speedx;
locy = locy+0;
break;
case 6:
break;
default:
break;
}
}
//进入预定区域后,进行设想的飞行模式
if(ifenter){
switch (type) {
case 1:
move1();
break;
case 2:
move2();
break;
case 3:
move3();
break;

default:
break;
}
}
}

public void fire(ArrayList<Boom> booms){
int x = (int)(img.getHeight(null)/2);
int y = (int)(img.getHeight(null)/2);
Boom boom = new Boom(type,locx+x,locy+y);
booms.add(boom);
}

public boolean isout(){
boolean result = false;

if(locx>600||locy>1000||locx+this.img.getWidth(null)<0||locy+this.img.getHeight(null)<0){
result = true;
}
return result;
}

private void move1(){

locx = locx + 12;
locy = locy + 6;

}
private void move2(){
locx = locx-2;
locy = locy+5;
}
private void move3() {
locx = locx-speedx;
locy = locy;
if(locx<15||locx+img.getWidth(null)+15>600){
speedx = -speedx;
}
}
}

这里我们给飞机的类定义了这样几个方法:

draw方法——用于绘制自身的图案,具体的图案根据chooseimage进行选择

move方法——每次调用实现飞机位置的移动,这里我们可以考虑给每个飞机的飞行设置一个预定要到达的位置,到达后就产生其他的一系列动作,就是里面的ifenter参数。

fire方法——每次调用,产生一个Boom类(开火!)

isout方法——判断飞机是否飞出边框,如果飞出,就返回一个True.

这里我们为了使飞机的图案更加的多样,我们在move和chooseimage方法处都使用了switch的选择,开始时也可以选择直接指定为一类。这样以后,敌机类就可以通过直接实例化这个BasicPlain类来实现了。

由于我们控制的飞机(以下称为英雄机HeroPlain)和普通的飞机有很大的不同,我们在这儿就新定义一个HeroPlain类来处置:

public class HeroPlane {

int locx = 240;
int locy = 850;
int HP = 100;
int TP = 100;
int type = 0;
ImageIcon imageicon;
Image image
public Image chooseimage(int type){
ImageIcon imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\2-1.png");
Image image = imageicon.getImage();
retuen image;
}

int speedx = 5;
int speedy = 5;

public void draw(Graphics g){
Image img = chooseimage(type);
g.drawImage(img, locx, locy, null);
}
public void move(){
//使用KeyListener进行操作
}
public void fire(){
//使用KeyListener进行发射弹丸
}
public boolean hit(ArrayList<BasicPlane> bp){
boolean result = false;
return result;
}
}

HeroPlain中,我们给他设置了血量(HP)和能量值(TP),也有draw和move的方法,由于我们是要用键盘来控制飞机的移动,那我们在这儿就不用把移动的方法展开来写,只需要知道,他是有这样一个方法就好。

然后对于发射的炮弹,我们也把他们作为对象来处理,新建一个Boom的类

public class Boom {

int locx;
int locy;

int type; //炮弹的种类的标识符

public Boom(int type,int locx,int locy) {
// TODO Auto-generated constructor stub
this.type = type;
this.locx = locx;
this.locy = locy;
}
Image imag = chooseBoom(type);
//根据选择的类型选择弹丸的图案
private Image chooseBoom(int type){
ImageIcon imageicon;
Image imag = null;
switch (type) {
case 0: // 英雄机弹丸
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\3-3-1.png");
imag = imageicon.getImage();
return imag;
case 1:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\3-1-1.png");
imag = imageicon.getImage();
return imag;
case 2:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\3-1-1.png");
imag = imageicon.getImage();
return imag;
case 3:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\3-2-1.png");
imag = imageicon.getImage();
return imag;
case 4:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\3-1--1.png");
imag = imageicon.getImage();
return imag;
case 5:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\3-1--1.png");
imag = imageicon.getImage();
return imag;
default:
break;
}
return imag;
}
public void draw(Graphics g){
g.drawImage(chooseBoom(type), locx, locy, null);

}
public void move(){
switch(type){
case 0: //英雄机的导弹发射方法
locy = locy-20;
break;
case 1: //第一类敌机的导弹发射方式
locy = locy+15;
break;
case 2: //第二类敌机的导弹发射方法
locy = locy+15;
break;
case 3:
locy = locy +22;
break;
case 4:
locy = locy +20;
break;
case 5:
locy = locy +20;
break;

default:
break;

}

}
//判断是否击中敌机
public boolean hit(ArrayList<BasicPlane> bp){
boolean result = false;
//对打中敌军的结果进行判断
for (int i = 0; i < bp.size(); i++) {
if(bp.get(i).locx<locx&&bp.get(i).locx+bp.get(i).img.getWidth(null)>locx+imag.getWidth(null)&&
bp.get(i).locy<locy&&bp.get(i).locy+bp.get(i).img.getHeight(null)>locy ){
bp.remove(i);
result = true;
break;
}
}
return result;
}
//判断是否击中英雄机
public boolean hithero(HeroPlane hp){
boolean result = false;
//对打中敌军的结果进行判断

if(hp.locx<locx&&hp.locx+hp.image.getWidth(null)>locx+imag.getWidth(null)&&
hp.locy<locy&&hp.locy+hp.image.getHeight(null)>locy ){
result = true;
}

return result;
}
public boolean hitboom(ArrayList<Boom> booms){
boolean result = false;
for(int i = 0;i<booms.size();i++){
int boomx = booms.get(i).locx;
int boomx2 = booms.get(i).locx+booms.get(i).imag.getWidth(null);
int heroboomx = locx;
int heroboomx2 = locx+imag.getWidth(null);
if((boomx<=heroboomx2&&boomx>=heroboomx)||
(boomx2>=heroboomx&&boomx2<=heroboomx2)||
(heroboomx>=boomx&&heroboomx<=boomx2+15)||
(heroboomx2>=boomx&&heroboomx2<=boomx2)){
if(booms.get(i).imag.getHeight(null)+booms.get(i).locy>locy){
booms.remove(i);
System.out.println(">>>>>>>>>>");
result = true;
break;
}

}
}
return result;
}

//判断是否移出边界
public boolean isout(){
boolean result = false;
//对是否移动出窗口进行判断
if(locx>600||locy>1000||locx+imag.getWidth(null)<0||locy+imag.getHeight(null)<0){
result = true;
}
return result;
}

}

这里对于炮弹的处理和对飞机的处理是类似的,开始时,我们也可以不适用这么多的图案选择

然后我们来写一下控制飞机移动的KeyListener:

c class Keylistener extends MouseAdapter implements KeyListener{

HeroPlane hp = new HeroPlane();
private boolean L;
private boolean R;
private boolean U;
private boolean D;

@Override
public void keyPressed(KeyEvent e) {
// TODO 自动生成的方法存根

switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
L=true;
break;
case KeyEvent.VK_RIGHT:
R=true;
break;
case KeyEvent.VK_UP:
U=true;
break;
case KeyEvent.VK_DOWN:
D=true;
break;
case KeyEvent.VK_SPACE:
if(hp.TP>=0){
Boom boom = new Boom(0,hp.locx+55,hp.locy);
herobooms.add(boom);
Boom boom2 = new Boom(0, hp.locx+15,hp.locy);
herobooms.add(boom2);
hp.TP = hp.TP - 10;
}
break;
default:
break;
}

if(D){
hp.locy = hp.locy+hp.speedy;
if(hp.locy+hp.image.getHeight(null)>960){
hp.locy = 960-hp.image.getHeight(null);
}
}else if(U){
hp.locy = hp.locy-hp.speedy;
if(hp.locy<40){
hp.locy = 40;
}
}
if(L){
hp.locx = hp.locx-hp.speedx;
if(hp.locx<2){
hp.locx = 2;
}
}else if(R){
hp.locx = hp.locx+hp.speedx;
if(hp.locx+hp.image.getWidth(null)>598){
hp.locx = 598-hp.image.getWidth(null);

}
}

}

@Override
public void keyReleased(KeyEvent e) {
// TODO 自动生成的方法存根
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
L=false;
break;
case KeyEvent.VK_RIGHT:
R=false;
break;
case KeyEvent.VK_UP:
U=false;
break;
case KeyEvent.VK_DOWN:
D=false;
break;
}

}

@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub

}

}

这里,我们使用了记录是否按下某个键的判断标志符:L,R,U,D,然后根据标识符的状态来进行具体的移动变化,从而就可以实现同时响应两个键的效果。

然后进入线程的部分,我们将多个任务分开来做,放在不同的线程中间来进行

第一个线程:

用来接收已经存在的飞机(包括英雄机)和炮弹,每隔一段时间,画出此时的飞机和炮弹所处的位置,对于飞出或者被击中的飞机和炮弹,及时的进行清理。

public class MyRunnable implements Runnable{

HeroPlane hp;
Graphics g;
ArrayList<Boom> booms;
ArrayList<BasicPlane> emenies;
ArrayList<Boom> herobooms;
ArrayList<Explore> ex = new ArrayList<>();

ScoreShow sshow;
//记录得分的数值
int score = 0;
public void run(){
ImageIcon imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\0-1-1.jpg");
Image img = imageicon.getImage();
int count = 0;
//无限循环来处理画图和移动,移除消失的对象
while(true){

g.drawImage(img, 0, 0, null);
hp.draw(g);
g.setColor(Color.RED);
g.fillRect(10, 30, hp.HP,15);
g.setColor(Color.BLUE);
g.fillRect(10, 50, hp.TP, 15);
sshow.show(score);

count++;
if(count%100 == 0){
if(hp.TP<100){
hp.TP = hp.TP+5;
}
}else if(count == 1001){
count = 0;
}
for(int i = 0;i<emenies.size();i++){
emenies.get(i).move();
emenies.get(i).draw(g);
if(emenies.get(i).isout()){
emenies.remove(i);
}

}
for(int i = 0;i<booms.size();i++){
booms.get(i).move();
booms.get(i).draw(g);
if (booms.get(i).hithero(hp)) {
hp.HP = hp.HP-5;
if(hp.HP<=0){
System.out.println("游戏结束");
}
booms.remove(i);
}
if(booms.get(i).isout()){
booms.remove(i);
}

}

for(int i = 0;i<herobooms.size();i++){

herobooms.get(i).move();
herobooms.get(i).draw(g);
if(herobooms.get(i).hit(emenies)){

Explore e = new Explore(herobooms.get(i).locx, herobooms.get(i).locy,
herobooms.get(i).imag.getHeight(null),herobooms.get(i).imag.getWidth(null));
ex.add(e);

score = score+100;

herobooms.remove(i);

if(hp.TP<100){
hp.TP = hp.TP+15;
if(hp.TP>100){
hp.TP = 100;
}
}

}else if(herobooms.get(i).hitboom(booms)){
herobooms.remove(i);
score =score +50;
}else if(herobooms.get(i).isout()){
herobooms.remove(i);
}
}

try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}

}

第二个线程:产生敌机,并添加到队列中去:

public class Myrunnable3 implements Runnable{
//该线程用于产生敌机
ArrayList<BasicPlane> emenies;
Graphics g;
@Override
public void run() {
// TODO Auto-generated method stub
int i = 0;

while(true){
if(i<10){
BasicPlane bp = new BasicPlane(20,20,370,350,1);
emenies.add(bp);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else if(i<20){
BasicPlane bp = new BasicPlane(370,20,20,350,2);
emenies.add(bp);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else if(i<30){
BasicPlane bp = new BasicPlane(240,0,0,110,3);
emenies.add(bp);
try {
Thread.sleep(700);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else if(i<50){

BasicPlane bp1 = new BasicPlane(599,300,0,0,4);
bp1.speedx = 10;
emenies.add(bp1);
BasicPlane bp2 = new BasicPlane(1,300,0,0,5);
bp2.speedx = 10;
emenies.add(bp2);
try {
Thread.sleep(1200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
i = (int) Math.random()*40;
}
i++;
}

}

}

第三个线程:从敌机的队列中取出敌机,发炮,将炮弹添加到炮弹的队列中去:

public class MyRunnable2 implements Runnable {
ArrayList<BasicPlane> emenies;
ArrayList<Boom> booms;
public void run() {
while(true){

for(int i = 0;i<emenies.size();i++){
emenies.get(i).fire(booms);

}

try {
Thread.sleep(600);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

}

为了添加我们打中后的爆炸效果我们先是建立一个新的对象叫做Explore:

public class Explore {

int bpx;
int bpy;
int bpwidth;
int bpheight;
int choose = 10;
public Explore( int bpx,int bpy,int bpheight,int bpwight) {
// TODO Auto-generated constructor stub
this.bpx = bpx;
this.bpy = bpy;
this.bpwidth = bpwight;
this.bpheight = bpheight;
}

Image image = chooseimage(choose).getImage();

public ImageIcon chooseimage(int choose){
ImageIcon imageicon = null;
switch (choose) {
case 10:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\5-1-1.png");
break;
case 9:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\5-1-2.png");
break;
case 8:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\5-1-3.png");
break;
case 7:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\5-1-4.png");
break;
case 6:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\5-1-5.png");
break;
case 5:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\5-1-5.png");
break;
case 4:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\5-1-4.png");
break;
case 3:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\5-1-3.png");
break;
case 2:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\5-1-2.png");
break;
case 1:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\5-1-1.png");
break;
default:
break;
}

return imageicon;
}
public int returnx(){
int x = 0;
Image image = this.image;
x = (int)(bpx+bpwidth/2-image.getWidth(null)/2);
return x;
}
public int returny(){
int y = 0;
Image image = this.image;
y = (int)(bpy+bpheight/2-image.getHeight(null)/2);
return y;
}
}

其中的returnx和returny两个函数是为了确定这个图片所处的最佳位置而设定的。

然后定义一个新的数组来 运行爆炸时的效果:

逻辑很简单,遍历列表,如果不为空,就去取一张图片,画在我们选择好的最佳位置处,然后就循环完这一遍后就将每一个Explore类内部的choose减一,再次绘制,直到choose为零,将这个对象直接取消掉:

public class Myrunnable1 implements Runnable{

ArrayList<Explore> ex;
Graphics g;
@Override
public void run() {
// TODO Auto-generated method stub
while(true){

for (int i = 0; i < ex.size(); i++) {
if(ex.get(i)!= null){

g.drawImage(ex.get(i).image, ex.get(i).returnx(), ex.get(i).returny(), null);
ex.get(i).choose--;
if(ex.get(i).choose==0){
ex.remove(i);
}
break;
}

}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

}

}

对于分数的显示,为了能够更清晰,我在这而定义了一个新的ScoreShow类,这个类接收一个数字,然后从右向左,依次画出每一位数字:

public class ScoreShow {

int score;

Graphics g;
public ScoreShow(Graphics g) {
// TODO Auto-generated constructor stub
this.g= g;
}

public void show(int score){
int x = 590;
int y = 40;
for(int i = 0;;i++){
int number = score%10;
score = score/10;
Image imag = chooseimage(number);
x = x- imag.getWidth(null)-10;
g.drawImage(imag, x, y, null);
if(score == 0){
break;
}

}

}
public Image chooseimage(int number){
ImageIcon imageicon = null;
Image image = null;
switch (number) {
case 0:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\6-10.png");
image = imageicon.getImage();
break;
case 1:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\6-1.png");
image = imageicon.getImage();
break;
case 2:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\6-2.png");
image = imageicon.getImage();
break;
case 3:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\6-3.png");
image = imageicon.getImage();
break;
case 4:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\6-4.png");
image = imageicon.getImage();
break;
case 5:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\6-5.png");
image = imageicon.getImage();
break;
case 6:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\6-6.png");
image = imageicon.getImage();
break;
case 7:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\6-7.png");
image = imageicon.getImage();
break;
case 8:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\6-8.png");
image = imageicon.getImage();
break;
case 9:
imageicon = new ImageIcon("D:\eclipse\workspace\basicStudy\src\AirBattle190224\6-9.png");
image = imageicon.getImage();
break;

default:
break;
}

return image;
}
}

剩下的就是将各个类中的队列进行统一,然后将各个线程都运行起来就可以了。我们还可以将线程的启动方法加入到一个启动窗口中去,这就可以实现视频中的样子了。

修改后的代码见网盘:

链接:pan.baidu.com/s/13DZGTR

提取码:l98f

运行之前,由于我使用的是绝对路径请将图片的路径改为你文件下的位置,否则肯定是要有问题的。

我是进林,希望与你共同进步,欢迎大家关注我~


推荐阅读:
相关文章