在做一个飞机大战前,我们首先要确定我们需要建几个类,这里我们为飞机,子弹,物品建了类。因为它们有自己的属性和方法,方便重复创建对象并区分。飞机大战的窗体界面和监听器是必不可少的类,然后为了画面的重绘以及图像的绘制,我们专门创建了一个类继承容器,在这个类上写程序来确定如何去画这个游戏界面,最后我们还为子弹,飞机,背景分别创建了线程类,来控制他们参数(比如坐标)的修改。
游戏规则:
1、通过上、下、左、右按键控制飞机移动。
2、按下空格键发射子弹进行攻击。
3、敌机会对你进行攻击,击落敌机将获取积分。
4、打中急救包可回复生命值并获取积分。
5、游戏结束获取分数和排名。
搭好了框架之后我们就试着让它们具体实现吧。我们首先让键盘的上下左右键控制飞机移动。
加键盘监听器的基本步骤:
PlaneJpanel cp = new PlaneJpanel();
cp.addKeyListener(listener);
cp.requestFocus();
public class Listener implements KeyListener, ActionListener,MouseListener
public void keyPressed(KeyEvent e) {
// System.out.println("keyPressed");
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
bluePlane.y -= 20;
if (bluePlane.y < 0)
bluePlane.y = 0;
// System.out.println("y-");
break;
case KeyEvent.VK_DOWN:
bluePlane.y += 20;
if (bluePlane.y > 900)
bluePlane.y = 900;
break;
case KeyEvent.VK_LEFT:
bluePlane.x -= 20;
if (bluePlane.x < 10)
bluePlane.x = 10;
break;
case KeyEvent.VK_RIGHT:
bluePlane.x += 20;
if (bluePlane.x > 980)
bluePlane.x = 980;
break;
case KeyEvent.VK_SPACE:
// System.out.println("发射子弹");
addBulletMe();
break;
}
}
然后我们改写画板重绘方法,在容器类的构造方法中加载飞机照片的地址(注意游戏中的照片尽量都用ps处理为背景透明),然后在重绘方法中画出飞机,在线程中调用repaint()方法即可。
img=new ImageIcon("E:\\workspace\\mayifan\\src\\com\\myf\\plane1102\\BluePlane.png");
if(bluePlane.life>0)
buffgr.drawImage(img.getImage(),bluePlane.x-bluePlane.size/2, bluePlane.y-bluePlane.size/2, bluePlane.size , bluePlane.size ,null);
连续背景的绘制思路是我们在画布中画两张图(需要上下两侧颜色衔接良好的图片),他们首尾相接,一张图的截止是另一张图的开始,坐标循环变化,类似于滚动的形式在画布上画出,它们坐标的修改需要线程来控制。那么什么是双缓冲呢?这样理解,我们先画出背景,然后再画出飞机,然后在下一次进入方法时画出背景覆盖飞机,再画出飞机,这会导致飞机的闪烁。解决这个问题的办法就是在后台建立一个显示缓冲区,我们在后台定义一个画布,获取画笔,先把背景和飞机等全部画好,再一次性画在我们要显示的地方,这样就解决了屏闪问题。
连续背景:
buffgr.drawImage(img1.getImage(),0, tb3.BackGroundY, tb3.xMax , tb3.yMax-tb3.BackGroundY ,null);
buffgr.drawImage(img1.getImage(),0, 0, tb3.xMax , tb3.BackGroundY ,null);
public void run()
{
xMax=cp.getWidth();
yMax=cp.getHeight();
while(true)
{
BackGroundY+=100;
if(BackGroundY>=yMax)
BackGroundY=0;
try{
Thread.sleep(200);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
双缓冲:
private Image iBuffer;
public void paint(Graphics g)
{
super.paint(g);
if(iBuffer==null)
{
iBuffer=createImage(cp.getWidth(),cp.getHeight());
buffgr=iBuffer.getGraphics();
}
buffgr.drawImage(img1.getImage(),0, tb3.BackGroundY, tb3.xMax , tb3.yMax-tb3.BackGroundY ,null);
buffgr.drawImage(img1.getImage(),0, 0, tb3.xMax , tb3.BackGroundY ,null);
if(bluePlane.life>0)
buffgr.drawImage(img.getImage(),bluePlane.x-bluePlane.size/2, bluePlane.y-bluePlane.size/2, bluePlane.size , bluePlane.size ,null);
buffgr.drawImage(img4.getImage(),Listener.bulletsMe.get(i).x,Listener.bulletsMe.get(i).y,25,25,null);
g.drawImage(iBuffer, 0, 0, cp.getWidth(),cp.getHeight(),null);
}
}
}
我们在按下空格后发射子弹,这里我们获取飞机头部的坐标,然后创建子弹对象,给与子弹初始位置,速度,攻击力,类别等属性,这些属性在子弹类的构造方法中都有实现,然后我们把子弹存入ArrayList即可,在子弹线程里修改子弹坐标,画板上重绘画出子弹,它就运动起来了。这里贴出子弹类和创建方法,子弹的位置修改通过独立的子弹线程实现,子弹的重绘即和飞机一起画在缓冲区,这里不赘述。
public class Bullet {
public int x=400,y=50;
public int harm=10;
public int type=0;//0:我方。1:对方
public Bullet(int x,int y,int harm,int type){
this.x=x;
this.y=y;
this.harm=harm;
this.type=type;
}
}
public void addBulletMe() {
int num = 1 + (int) (Math.random() * 20);
Bullet bullet = new Bullet(bluePlane.x - 15, bluePlane.y - 110, num, 0);
bulletsMe.add(bullet);
}
我们在开始初始化一些敌机,随机给它们初始坐标,范围是屏幕后方,在敌机线程里修改它们的坐标,在游戏开始一段时间后,它们会出现在画面里,发射子弹的方式和我方飞机相同,区别是它们的子弹是自动添加,添加的方法写在线程里,然后在子弹的类型上也要和我方飞机加以区分。同样,敌机也有它的队列来存放对象。思路和创建子弹很接近。
for(int i=0;i<7;i++)
{
int rand1 = -(int) (Math.random() * 1000);
int rand = 200 + (int) (Math.random() * 800);
Plane PaperPlane=new Plane(rand,rand1,50,50,2,0);
PaperPlaneArray.add(PaperPlane);
}
public class Plane {
public int x=400;
public int y=400;
public int size=30;
public int life=100;
public int vy=10;
public int ExplodeFlag=0;
public Plane(int x,int y ,int size,int life,int vy,int ExplodeFlag)
{
this.x=x;
this.y=y;
this.size=size;
this.life=life;
this.vy=vy;
this.ExplodeFlag=ExplodeFlag;
}
}
定义一个Goods类,并定义物品的初始化属性,线程内修改位置坐标,飞机可以射击它们并触发诸如回血的效果。到这里,你会发现,其实子弹,飞机,物品的实现思路如出一辙。先是创建对象,赋予一些初始化属性,再在线程修改参数,最后再画板上画出。触发的特效的实现方法即:在线程修改物品坐标后做一个判断,依次取出我方子弹对象,判断子弹和物品的距离是否在一定范围内,如果是,则把子弹和物品的坐标修改到很小或很大(即屏幕坐标以外,实现在屏幕上消失的效果,然后修改诸如生命值之类的属性即可。子弹识别敌机位置的方法也是同理。我们没有销毁对象,只是把它们挪到了显示区外面罢了。
for(int i=0;i<3;i++)
{
int rand4 = -(int) (Math.random() * 1000);
int rand5 = 200 + (int) (Math.random() * 800);
Goods goods= new Goods(rand5,rand4,60,1);
goodsArray.add(goods);
}
public class Goods {
public int size;
public int x;
public int y;
public int type; //1:急救包
public Goods(int x, int y , int size ,int type)
{
this.x=x;
this.y=y;
this.size=size;
this.type=type;
}
}
游戏结束后显示本局分数,历史最高分和排名。我们通过IO流的方式在TXT文件获取历史分数,存放到数组,和本局分数比较,确定排名并插入本局分数,然后再存入本地。
public void result() {
JFrame jf2 = new JFrame();
jf2.setSize(100, 150);
jf2.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
jf2.setLocationRelativeTo(null);
FlowLayout flow = new FlowLayout();
jf2.setLayout(flow);
String str = String.valueOf(score);
JLabel jb = new JLabel("本局得分:" + str);
JLabel jb2 = new JLabel("历史最高分:" + firstScore);
JLabel jb3 = new JLabel("您的排名:" + rank);
jf2.add(jb);
jf2.add(jb2);
jf2.add(jb3);
jf2.setVisible(true);
}
public void InPut() {
int n = 100;
int[] arr1 = new int[n];
int[] arr2 = new int[n];
try {
File file = new File("E:\\workspace\\mayifan\\src\\com\\myf\\plane1102\\data.txt"); // 存放数组数据的文件
BufferedReader in = new BufferedReader(new FileReader(file)); // 把字节转为字符,然后可以read
// ,字符缓冲输入流
String line; // 行数据
// 逐行读取,并将每个数据放入到数组中
String[] temp = null;
while ((line = in.readLine()) != null) {
temp = line.split("\t");
for (int j = 0; j < temp.length; j++) {
arr2[j] = (int) Double.parseDouble(temp[j]);
}
}
in.close();
// 显示读出的数据
for (int i = 0; i < temp.length; i++) {
//System.out.print(arr2[i] + "\t");
}
for (int i = 0; i < temp.length; i++) {
if (score >= arr2[i]) {
for (int j = temp.length - 1; j >= i; j--) {
arr2[j + 1] = arr2[j];
}
arr2[i] = score;
rank = i + 1;
break;
}
}
firstScore = arr2[0];
}
FileWriter out = new FileWriter(file); // 文件写入流
for (int i = 0; i < temp.length + 1; i++) {
out.write(arr2[i] + "\t");
}
out.close();
} catch (IOException e) {
e.getStackTrace();
}
}
到这里基本的游戏框架都已经搭建完了,然后就逐渐实现一些细节就可以了,比如子弹击中敌机添加动画效果;实现大小,种类,攻击力不同的飞机出现在画面;添加更多有趣的功能道具;设置不同的关卡(我通过在线程内计时,实现飞机坐标和攻击力的定时更新实现不同关卡的更新和游戏难度的变化);显示出血量条和积分值;设置游戏的的功能按键,比如“重新开始”。读者都可以自己去实现,我在这里不一一详解,给大家自由发挥的空间。最后附上几张游戏的效果图。