2018.8.8
前言:学习Java也有好长一段时间了,前几天搞了一下线程游戏,借此写篇博客留点东西供日后回来学习,也希望能帮到刚入门线程的小伙伴。
一、先看看游戏界面:有点不美观,23333。暂时还没有太多的时间去考虑细节,红色的为飞机的血量,没有血量的飞机是玩家的,hhh无敌有点作弊。
二、具体实现:
其实这个就是根据那个多线程弹跳小球改过来了。我本来想用多线程来实现,但无奈空指针错误多得我改不过来。(期待大佬指正)所以我就用了简单的循环+队列数组和一个单线程。循环主要是产生随机的敌机,线程用来控制飞机的绘制和移动。为了更好的理解游戏的结构,我新建了四个类,分别是主界面、监听、产生飞机、产生子弹。每个类干好自己的事情就好了,需要共用的数据就传一下,emmm就是这么简单。在写的时候可能会遇到空指针的错误,这是类对象在传递的时候的错误,简单的说就是当年需要用这个对象的时候,但这个对象还没有被传递过来,如果使用它就会产生空指针错误,所以线程的启动时间需要特别注意!给各位朋友的建议就是在所有数据都到位后在启动绘制飞机的线程,这样百分百不会产生空指针错误。
三、详细代码分析:
为了更方便管理我们的代码,我们将代码分成四个部分:1、主界面类 ; 2、负责画飞机的类 ; 3、负责画子弹的类; 4、监听器类;详细说明已在代码加注释。
1.主界面类:
package com.Lzh_20180727;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import com.zn.ArrayList0720.*; //自定义的数组队列,用系统自带的会发生空指针异常
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.Image;
;public class MainFrame extends JFrame implements Runnable{
public static void main(String[] args) {
MainFrame mf = new MainFrame();
mf.Init();
}
public ArrayList<Ball>list = new ArrayList();//存储小球的
public ArrayList<DrawBullet>bulletlist = new ArrayList();//存储子弹的
public volatile boolean StopFlag = false;//是否开始游戏的标志
public volatile boolean PauseFlag = true;//是否处于游戏暂停状态
public int Count = 0;
public JLabel s ,Time;//游戏剩余时间
//BallAI ballai = new BallAI(this,5);//将窗体和要生成的小球数量传过去
public void Init()
{
this.setTitle("飞机大战2`.0");
this.setSize(1400, 1000);
this.setResizable(false);
this.setLocationRelativeTo(null);
this.getContentPane().setBackground(Color.black);
this.setDefaultCloseOperation(3);
String type[]={"启动","暂停","恢复","停止"};
this.setLayout(null);
AllListener AL = new AllListener(StopFlag,PauseFlag,this,bulletlist);//建立监听器对象
ImageIcon IMG = new ImageIcon("src\\1285.gif");
JLabel score = new JLabel();
score.setIcon(IMG);
score.setBounds(950, 20, 150, 50);
score.setFont(new Font("宋体",Font.BOLD,30));
this.add(score);
ImageIcon MMG = new ImageIcon("src\\1313.gif");
JLabel time = new JLabel();
time.setIcon(MMG);
time.setBounds(60, 20, 150, 50);
time.setFont(new Font("宋体",Font.BOLD,30));
this.add(time);
Time = new JLabel();
Time.setBounds(250,20, 50, 50);
Time.setFont(new Font("宋体",Font.BOLD,30));
this.add(Time);
s = new JLabel("0");
s.setText(Count+" ");
s.setBounds(1150, 20, 150, 50);
s.setFont(new Font("宋体",Font.BOLD,30));
this.add(s);
for(int i=0;i<type.length;i++)
{
JButton button = new JButton(type[i]);
button.setFont(new Font("宋体",Font.BOLD,30));
button.setBounds(300+i*160, 20, 150, 50);
this.add(button);
button.addActionListener(AL);
}
this.setVisible(true);
Graphics g = this.getGraphics();
ImageIcon MG = new ImageIcon("src\\飞机2.png");
Image mg = MG.getImage();
Ball ball = new Ball(30,50,10,5,6,10,Color.black,this,g,list,10,mg);
ball.Craeat_Ball();
AllListener LL = new AllListener(StopFlag,PauseFlag,this,bulletlist);
this.addMouseListener(LL);
this.addMouseMotionListener(LL);//由于游戏是鼠标左键开火和拖拉操作飞机,故需要添加两种监听器
}
public void run() { //绘制飞机的线程
//System.out.println("画球线程启动!StopFlag:"+StopFlag);
while(StopFlag)
{
if(PauseFlag)
{
BufferedImage b = new BufferedImage(this.getWidth(),this.getHeight(),BufferedImage.TYPE_INT_RGB);
Graphics pan = b.getGraphics();
ImageIcon image = new ImageIcon("src\\boom.gif");
Image img = image.getImage();
for(int t=120;t>=0;t--)//倒计时,时间结束后GameOver
{
Time.setText(t+"");
}
pan.drawImage(img, 0, 0, this.getWidth(), this.getHeight(), null);
pan.fillRect(list.get(0).x-3*list.get(0).r, list.get(0).y-2*list.get(0).r,
this.list.get(0).blood*10, 10);//飞机当前的生命值
this.list.get(0).drawBall(pan);
this.list.get(0).moveBall();
//this.list.get(0).Iscrash(0);
for(int i=1;i<this.list.size();i++)
{
//list.get(i).clearBall();
pan.setColor(Color.red);
pan.fillRect(list.get(i).x-3*list.get(i).r, list.get(i).y-2*list.get(i).r,
list.get(i).blood*10, 10);
list.get(i).drawBall(pan);
list.get(i).moveBall();
//list.get(i).Iscrash(i);
if(list.get(i).blood<=0)//生命值为0的飞机要移除掉
{
list.remove(i);
}
}
if(list.size()==1)
{
Graphics g = this.getGraphics();
ImageIcon MG = new ImageIcon("src\\飞机2.png");
Image mg = MG.getImage();
Ball ball = new Ball(30,50,10,5,6,10,Color.black,this,g,list,10,mg);
ball.Craeat_Ball();
}
for(int j=0;j<this.bulletlist.size();j++)//绘制子弹
{
if(this.bulletlist.get(j).y==0)//到达边界的子弹要移除,否则内存容易溢出
{
this.bulletlist.remove(j);
break;
}
this.bulletlist.get(j).drawbullet(pan);//绘制子弹
this.bulletlist.get(j).movebullet();//移动子弹
this.bulletlist.get(j).Ishit(); //子弹是否击中飞机
}
Graphics g = this.getGraphics();
g.drawImage(b, 0, 150, null);//次画布
try { //异常抛出
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
2.负责保存飞机信息的类:
package com.Lzh_20180727;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import com.zn.ArrayList0720.*;
import java.util.Random;
import javax.swing.ImageIcon;
public class Ball {
public int x=30,y=30,r=5,speedX=5,speedY=7,num=0;//初始化一些数据,防止空指针错误
public int blood =0;//飞机的生命值
private Color c = Color.black;//这个没用到
private Image img ;//飞机的图片对象
public MainFrame mf;//将主界面传过来
private Graphics g;//绘制需要的画笔类
private ArrayList<Ball>list;//存储飞机所有信息的队列数组
private Random random = new Random();//随机数,主要用于产生敌机的位置和生命值
public Ball(int x, int y, int r, int speedX, int speedY,int num, Color c, MainFrame mf,
Graphics g, ArrayList<Ball> list,int blood,Image img) {//构造函数
super();
this.x = x;
this.y = y;
this.r = r;
this.speedX = speedX;
this.speedY = speedY;
this.c = c;
this.mf = mf;
this.g = g;
this.list = list;
this.num = num;
this.blood = blood;
this.img = img ;
}
// public Ball(int x,int y,int r,int speedX,int speedY)
// {
// this.x = x;
// this.y = y;
// this.r = r;
// this.speedX = speedX;
// this.speedY = speedY;
// }
public void drawBall(Graphics gg)
{
// int i = random.nextInt(255);
// int j = random.nextInt(255);
// int k = random.nextInt(255);
// g.setColor(new Color(i,j,k));//设不设置颜色都无所谓了,因为我们画的是图片
gg.setColor(Color.blue);
// gg.fillOval(x-r, y-r, 2*r, 2*r);
gg.drawImage(img, x-r, y-r, null);//绘制飞机
}
public void moveBall()//飞机移动的方法
{
if(x+r>=mf.getWidth())//到达右边界
{
speedX*=-1;
}
else if(x+r<0)
{
speedX*=-1;
}
if(y+r>=mf.getHeight())
{
speedY*=-1;
}
else if(y+r<0)
{
speedY*=-1;
}
x+=speedX;
y+=speedY;
}
public void clearBall()//清除飞机的,现在不需要了,用remove就好了。
{
g.setColor(Color.black);
g.fillOval(x-r-speedX, y-r-speedY, 2*r, 2*r);
}
// public void Iscrash(int i)//此方法原来是判断飞机间是否碰撞的,但现在判断子弹和飞机,故注释
// {
// for(int j=0;j<mf.list.size();j++)
// {
// if(i==j) continue;
//
// int DistanceX=x-mf.list.get(j).x;
// int DistanceY=y-mf.list.get(j).y;
// int RadiiI=r/2;
// int RadiiJ=mf.list.get(j).r/2;
// //两个小球之间的距离小于两者的半径和
// if((DistanceX*DistanceX+DistanceY*DistanceY)<=((RadiiI+RadiiJ)*(RadiiI+RadiiJ))) {
// if(x>mf.list.get(j).x) {
// speedX*=1;
// blood-=1;
// }
// else
// {
// speedX*=-1;
// blood-=1;
// }
// if(y>mf.list.get(j).y) {
// speedY*=1;
// blood-=1;
// }
// else
// {
// speedY*=-1;
// blood-=1;
// }
//
// }
//
// }
// }
public void Craeat_Ball()//生成绘制飞机所需要的信息,并存入队列数组
{
ImageIcon image = new ImageIcon("src\\飞机1.png");
Image img = image.getImage();
Ball myball = new Ball(400,400,30,0,0,num,Color.red,mf,g,list,0,img);
list.add(myball);
System.out.println("生成球的方法被调用!");
for(int i=0;i<num;i++)
{
int x = random.nextInt(mf.getWidth());
int y = random.nextInt(mf.getHeight());
int r = 25;
int b = random.nextInt(10)+5;
int speedX = 0;
int speedY = 2;
int or = random.nextInt(10)+2;
ImageIcon iimage = new ImageIcon("src\\飞机"+or+".png");
Image iimg = iimage.getImage();
Ball ball = new Ball(x,y,r,speedX,speedY,num,Color.BLUE,mf,g,list,b,iimg);
list.add(ball); //添加到队列数组里面去。
}
}
}
3.绘制子弹类:
package com.Lzh_20180727;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import com.zn.ArrayList0720.*;
import java.util.Random;
import javax.swing.JOptionPane;
public class DrawBullet {
//public ArrayList<DrawBullet>bulletlist;
public int x,y,r,speedX,speedY; //定义绘制子弹需要的信息
public Color c ;
private Random random = new Random();
public boolean IsFire = false;是否开火的标志
public MainFrame mf;
private Image img ;
public DrawBullet(int x,int y,int r,int speedX,int speedY,Color c,MainFrame mf ,
Image img )//构造函数,传入需要的数据
{
this.mf = mf ;
this.x = x;
this.y = y;
this.r = r;
this.speedX = speedX;
this.speedY = speedY;
this.c = c;
this.img = img;
}
public void drawbullet(Graphics gg)//根据拿到的信息绘制子弹
{
//gg.setColor(Color.red);
// gg.fillOval(x, y, 2*r, 2*r);
gg.drawImage(img, x+10, y-50, 40,80,null);
}
public void movebullet()//子弹移动
{
// if(x+r>=mf.getWidth())
// {
// speedX*=-1;
// }
// else if(x+r<0)
// {
// speedX*=-1;
// }
// if(y+r>=mf.getHeight())
// {
// speedY*=-1;
// }
// else if(y+r<0)
// {
// speedY*=-1;
// }
y+=speedY;
}
public void Ishit()//子弹是否击中飞机,遍历所有的子弹和飞机,计算两者的距离,小于某个值则碰撞,飞机生命值减少。
{
for(int i=1;i<mf.list.size();i++)
{
for(int j=0;j<mf.bulletlist.size();j++)
{
int DistanceX = mf.list.get(i).x - mf.bulletlist.get(j).x;//算出两个点间的x
int DistanceY = mf.list.get(i).y - mf.bulletlist.get(j).y;
int DistanceR = mf.list.get(i).r + mf.bulletlist.get(j).r+6;
if(DistanceX*DistanceX + DistanceY*DistanceY <= DistanceR*DistanceR )
{
// System.out.println("碰撞!");
mf.list.get(i).blood -=1;
mf.Count++;
mf.s.setText(mf.Count+"");
if(mf.Count==100)
{
String msg = String.format("恭喜通过!");
JOptionPane.showMessageDialog(mf, msg);
}
System.out.println("COUNG:"+mf.Count);
mf.bulletlist.remove(j);
}
}
}
}
}
4.监听器类:
package com.Lzh_20180727;
import java.awt.Color;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import com.zn.ArrayList0720.*;
import javax.swing.ImageIcon;
public class AllListener extends MouseAdapter implements ActionListener {
public volatile boolean StopFlag ,PauseFlag;
public MainFrame mf ;
public ArrayList<DrawBullet>bulletlist;
private boolean Isfire = false;
public AllListener( boolean StopFlag, boolean PauseFlag,MainFrame mf,ArrayList<DrawBullet>bulletlist)
{
this.StopFlag = StopFlag;
this.PauseFlag = PauseFlag;
this.mf = mf;
this.bulletlist = bulletlist;
}
public void actionPerformed(ActionEvent e)
{
if(e.getActionCommand().equals("启动"))
{
mf.StopFlag = true;
System.out.println("启动!"+StopFlag);
Thread t = new Thread(mf);
t.start();
}
else if(e.getActionCommand().equals("暂停"))
{
mf.PauseFlag = false;
}
else if(e.getActionCommand().equals("恢复"))
{
mf.PauseFlag = true;
}
else if(e.getActionCommand().equals("停止"))
{
mf.StopFlag = false;
}
}
public void mousePressed(MouseEvent e) {
// bulletlist.clear();
Isfire = true;
int x = mf.list.get(0).x+5;
int y = mf.list.get(0).y-30;
Isfire = true;
ImageIcon IMG = new ImageIcon("src\\bullet2.png");
Image img = IMG.getImage();
DrawBullet mybullet = new DrawBullet(x,y,5,0,-10,Color.red,mf,img);
bulletlist.add(mybullet);
}
public void mouseReleased(MouseEvent e) {
Isfire = false;
}
public void mouseMoved(MouseEvent e){
int x = e.getX();
int y = e.getY()-150;
mf.list.get(0).x = x-mf.list.get(0).r;
mf.list.get(0).y = y-mf.list.get(0).r;
}
public void mouseDragged(MouseEvent e){
}
}
我有罪啊。。8.8就写的博客现在才完成。。请鞭策我。。。下次绝对不懒了!!