小游戏项目2 - SkyFight 空战
-
新建项目 新建image包 复制图片到包中
-
打包图片 : 创建Image(图片工具类)类 , 把图片封装成对象 , 以便接下来调用.
//Image类 //先把图片的地址封装为一个具体地址对象, static URL shellURL = Image.class.getResource("/image/shell.png"); //把图片封装成一个对象,必须通过它的地址来封装 static ImageIcon shell = new ImageIcon(shellURL); //变量用static修饰后,在使用时可以 类名.属性名 调用了,static用到的东西都得要static的,所以shellURL也要用static修饰
-
创建窗体 : 创建startGame(开始游戏类)类, 在类中main方法中创建窗体并初始化.
//startGame类 //创建一个窗体 JFrame jf = new JFrame(); //给窗体加一个标题 jf.setTitle("SkyFight"); //获得屏幕的宽高 int width = Toolkit.getDefaultToolkit().getScreenSize.width; int height = Toolkit.getDefaultToolkit().getScreenSize.height; //设置窗体的弹出位置和大小,参数-位置的X,Y 窗体的宽和高 jf.setBounds((width-x)/2,(height-y)/2,x,y);//窗体出现在屏幕中间 //设置窗体大小不可变 jf.setResizable(false); //关闭窗体的同时,关闭程序 jf.setDefaultCloseOperation(EXIT_ON_CLOSE); //让窗体可见 jf.setVisible(true);
-
绘制图案 : 创建 gamePanel(游戏面板) 类, 继承 JPanel类 , 它是一个透明的面板, 我们所有的内容都在这个面板上, 然后把面板添加到窗体中 , 在面板中绘制背景,飞机和子弹(静态). 在面板类构造器里初始化飞机和子弹的位置 (调用初始化游戏的方法). 用数组定义炮弹坐标.
//游戏面板类 继承JPanel public class gamePanel extends JPanel { //定义飞机坐标 int planeX,planeY; //子弹的X轴坐标 int []shellX = new int[10]; //子弹的Y轴坐标 int []shellY = new int[10]; //--初始化游戏方法-- public void init(){ //初始化飞机坐标 planeX = 300; planeY = 400; //初始化子弹的坐标,10个子弹在一处 100,100 for(int i = 0;i < 10 ; i++){ shellX[i] = 100; shellY[i] = 100; } } //--构造器-- public gamePanel(){ init(); } //重写paintComponent方法,用来在面板中绘制图案 @Override protected void paintComponent(Graphics g) {//参数:相当于画笔 super.paintComponent(g); //给面板设置一个背景颜色 this.setBackground(new Color(r,g,b)); //给面板设置背景图片 Image.sky.paintIcon(this,g,0,0);//参数:当前面板,画笔,坐标xy Image.plane.paintIcon(this,g,planeX,planeY);//绘制出飞机 //绘制10个子弹 for(int i = 0; i < 10; i++){ Image.shell.paintComponent(this,g,shellX[i],shellY[i]); } } } //startGame类 gamePanel gp = new gamePanel();//创建面板对象 jf.add(gp);//把面板gp添加到窗体jf中
-
空格暂停效果 : 加入键盘监听, 在gamePanel类的构造器中加入KeyListener方法, 参数为(new KeyAdapter对象) , 用e.getKeyCode()方法获得键位值, 别忘了要把焦点放在面板上.
//gamePanel类----- //创建属性isStart游戏是否开始,默认false boolean isStart = false; //构造器中加入键盘监听 this.setFocusable(true);//把焦点放在面板上 this.addKeyListener(new KeyAdapter()); @Override public void keyPressed(KeyEvent e) { //键盘按下监听 super.keyPressed(e); int keyCode = e.getKeyCode(); //获取键位值 System.out.println(keyCode); if(keyCode == 32){//监听到按空格 //点击空格后,游戏的状态相反 isStart = !isStart; //调用repaint方法,重新执行paintComponent方法,相当于刷新游戏 repaint(); } //在paintComponent方法中------ //当游戏暂停时,面板中打印一行文字 if(isStart==false){ g.setColor(new Color(r,g,b)); g.setFont(new Font( "黑体", Font.BOLD, 40)); g.drawString("点击空格开始游戏", 0 ,0);//绘制文字 }
-
让飞机动起来: 定义上下左右变量和定时器, 监听键盘的上下左右给变量赋值, 在再监听器的KeyReleased方法中把4个变量的值赋值回false,实现松开按键飞机停下 . 实现飞机移动要靠定时器, 创建定时器, 要在程序启动时初始化, 所以它要在构造器中, 参数如下. 在方向变量true时改变XY的值实现对象移动 , 加入isStart是true的条件判断, 调用repaint方法刷新画面, 最后启动定时器.
//在类中定义4个布尔类型变量 上下左右 ,来控制是否激活飞机向各个方向移动. boolean up,down,left,rignt; //在监听器中添加对上下左右的监听 if(keyCode==KeyEvent.VK_UP){//KeyEvent.VK_UP是封装好的获得键位方法 up = true; } if(keyCode==KeyEnent.VK_DOWN){ down = true; }... //抬起按键后再赋值为false @Override public void keyReleased(KeyEvent e) { super.keyReleased(e); int keyCode = e.getKeyCode();//获取键位值 if (keyCode == KeyEvent.VK_UP){ up = false; } if (keyCode == KeyEvent.VK_DOWN){ down = false; }... //先在类中定义一个定时器 Timer timer; //程序启动时初始化,在构造器中加入定时器 //初始化定时器: 每50毫秒执行一下ActionPerformed中的逻辑 timer = new Timer(50,new ActionListener(){ @Override public void actionPerformed(ActionEvent e){ if(isStart==true){ //游戏运行时才能移动 if(up==true){ planeX -= 7; } if(down==true){ planeX += 7; }... repaint();//刷新画面 } } }); //定时器一定要启动 timer.start();
-
让炮弹动起来, 创建炮弹角度的数组, 在初始化方法中,初始化炮弹的位置和角度, 并随机给角度赋值. 在定时器方法中通过循环让炮弹按随机数组中的角度移动 , 当炮弹移动出面板时,在反方向出现: 当X值到达最右边610时,X赋值为0,以此类推
//类中创建炮弹角度数组 int [] degrees = new [10]; //在init()方法中,初始化炮弹的位置和角度(随机) for(int i = 0; i< 10 ; i++){ //10个炮弹 shellX[i] = 100; shellY[i] = 100; //炮弹初始位置 /*炮弹的移动角度为0~360之间,也就是0~2PI之间. random的取值范围: Math.random() --> [0.0 ~ 1.0) 如果要[0~5]的整数 Math.random()*6 --> [0.0 ~ 6.0) (int)(Math.random()*6) --> [0 ~ 5] 如果要[0 ~ 2PI] 如下: */ degrees[i] = (int)(Math.random()*2*Math.PI); } //在定时器的actionPerformed方法中添加循环 //炮弹按着角度移动 for(int i = 0 ; i < 10 ; i++){ shellX[i] += Math.cos(degrees[i])*7; shellY[i] += Math.sin(degrees[i])*7; //让弹移动出面板时,在反方向出现 if(shellX[i] > 610){ shell[i] = 0; } if(shellX[i] < 0){ shell[i] = 610; }... }
-
碰撞检测: Rectangle方法把对象封装成矩形, intersects方法碰撞检测, 两个对象碰撞了就返回true. 如果是true,那么就在绘制方法中显示 '游戏结束' . 在类中创建布尔类型变量表示飞机生死的状态, 飞机死了后,要让画面停止 : 要在定时器方法中的 if 语句中加上一个条件, '游戏开始' && '飞机没死' . 有这两个条件游戏才能进行.
//在类中创建布尔类型变量表示飞机生死的状态,默认false boolean isDie = false; /*在定时器方法中 把飞机和炮弹封装为矩形,用intersects方法来检测碰撞,返回布尔类型变量 封装矩形对象时要传入四个参数: 1.飞机的X轴坐标 2.飞机的Y轴坐标 3.矩形的款 4.高 */ boolean flag = new Rectangle(planeX, planeY, 64, 64).intersects(new Rectangle(shellX[i], shellY[i], 64,64)); if(flag){ //如果碰撞 isDie = true; //飞机死 } //在paintComponent方法中添加绘制文字 if(isDie){ //如果飞机死了 g.drawString("飞机死了,重新开始",100,100); //绘制文字 } //到这里运行游戏,飞机死了后还能移动,所以要在定时器方法中的 if 语句中加上一个条件 @Override public void actionPerformed(ActionEvent e) { //键盘控制飞机 当游戏开始并飞机没死的时候 飞机和炮弹能动 if (isStart&&!isDie) { if (up) { //向上为Y轴减 planeY -= 10; } if (down) { planeY += 10; }
-
飞机死了后,按空格重新游戏 : 在键盘监听空格的方法中, 加一个条件判断, 如果飞机是活的, 那就暂停(和原来一样), 如果飞机是死的, 那就调用 初始化方法 重开游戏 , 在把飞机变活.
if(keyCode==KeyEvent.VK_SPACE){ if(isDie){ //如果飞机时死的 init(); //初始化游戏 isDie = false; //再把状态改过来 }else{ isStart = !isStart;//飞机活着就是暂停 } }
-
玩了几秒 : 定义开始和结束时间, 开始时间在构造方法开头, 结束时间在碰撞检测方法中. 两个变量的差就是你游玩的时间.
//在类中创建开始和结束时间 long startTime, endTime; //在构造器中给startTime赋值 public GamePanel(){ //获取游戏开始时间 startTime = System.currentTimeMillis(); } //在碰撞检测方法中给endTime赋值 //碰撞检测, boolean flag = new Rectangle(planeX,planeY,50,50).intersects(new Rectangle(shellX[i],shellY[i],50,50)); //如果flag为true, if (flag){ isDie = true; isStart = false; endTime = System.currentTimeMillis();//--结束游戏的时间 } //显示游玩时间 //在paintComponent方法中添加 if(isDie){ g.drawString("游戏结束,你游玩了" + (endTime - startTime)/1000 + "秒",100,100); }
-
解决飞机死了后重新开始游戏时间累加的问题 : 把给开始时间赋值的代码移动到初始化方法中.
-
给飞机添加死后爆炸效果 : 在paintComponent方法中, 换一个图片对象.
//如果飞机死了,飞机plane 就变成 爆炸bang. if (isDie){ Image.bang.paintIcon(this,g,planeX,planeY);//-----爆炸图片 }else { Image.plane.paintIcon(this,g,planeX,planeY);//-----飞机图片 }