ITPub博客

首页 > 数据库 > 数据库开发技术 > 快来看,新手实战必备!单机版坦克大战分步实现项目源码~

快来看,新手实战必备!单机版坦克大战分步实现项目源码~

原创 数据库开发技术 作者:Finally_m 时间:2022-03-29 08:03:00 0 删除 编辑
点击上方蓝字关注我们

很多java初学者,在学习一段时间java后,都想写一个项目练练手来检测一下学习成果,而坦克大战就是一个非常好的试手项目。坦克大战本身就是一个非常经典的单机项目,其次使用java来实现,技术点几乎能够涉及到java所有的基础知识。

那么千锋老谢就从最开始手把手的教你实现坦克大战,简单明了,步骤详细,绝对让你写出自己的坦克大战,同时加深对java知识的理解。那我们就开始吧。

1. 窗体绘制

JFrame是指一个计算机语言-java的GUI程序的基本思路是以JFrame为基础,它是屏幕上window的对象,能够最大化、最小化、关闭。

1.1 创建窗口

  • 创建类继承JFrame

  • 创建启动方法launch()

    • setTitle() 设置标题

    • setSize(width, height); 设置宽高

    • setLocationRelativeTo(null); 设置位置,null为居中

    • setResizable(); 是否可拉伸

    • setVisible(); 是否可见

    • setDefaultCloseOperation(3); 1,2,3 (点差号,关闭程序)

package com.qf;
import javax.swing.JFrame;
public class GamePanel extends JFrame {
   private static final long serialVersionUID = 7471238471259660459L;
   
   //窗口宽高
   int width = 800;
   int height = 610;
   
   public void launch() {
       //设置窗口标题
       setTitle("千锋坦克大战");
       //设置宽高
       setSize(width, height);
       //居中
       setLocationRelativeTo(null);
       //差掉窗口,结束
       setDefaultCloseOperation(3);
       //窗口不可拉伸
       setResizable(false);
       //可见
       setVisible(true);
   }
   
   public static void main(String[] args) {
       GamePanel gp = new GamePanel();
       gp.launch();
   }
}

1.2 为窗口上色

  • 重写paint()方法,参数g为图形

    • 设置图形颜色

    • 设置图形大小

package com.qf;

import java.awt.Color;
import java.awt.Graphics;

import javax.swing.JFrame;

public class GamePanel extends JFrame {
   
   private static final long serialVersionUID = 7471238471259660459L;
   
   //窗口宽高
   int width = 800;
   int height = 610;
   
   //获取画笔(为窗口上色)
   @Override
   public void paint(Graphics g) {
       //设置颜色
       g.setColor(Color.GRAY);
       //填充窗口
       g.fillRect(0, 0, width, height);
       
   }
   
   public void launch() {
       setTitle("千锋坦克大战");
       //设置宽高
       setSize(width, height);
       //居中
       setLocationRelativeTo(null);
       //差掉窗口,结束
       setDefaultCloseOperation(3);
       //窗口不可拉伸
       setResizable(false);
       //可见
       setVisible(true);
   }
   
   public static void main(String[] args) {
       GamePanel gp = new GamePanel();
       gp.launch();
   }

}

1.3 添加选项文字

  • 设置字体颜色,不要和背景色一样

  • 设置字体,字体大小

  • 添加文字,及文字位置

package com.qf;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;

import javax.swing.JFrame;

public class GamePanel extends JFrame {
   
   private static final long serialVersionUID = 7471238471259660459L;
   
   //窗口宽高
   int width = 800;
   int height = 610;
   
   //获取画笔
   @Override
   public void paint(Graphics g) {
       g.setColor(Color.GRAY);
       g.fillRect(0, 0, width, height);
       
       //添加游戏选项
       g.setColor(Color.BLUE);
       g.setFont(new Font("仿宋", Font.BOLD, 50));
       g.drawString("选择游戏模式", 220, 100);
       g.drawString("单人模式", 220, 200);
       g.drawString("双人模式", 220, 300);
       
   }
   
   public void launch() {
       setTitle("千锋坦克大战");
       //设置宽高
       setSize(width, height);
       //居中
       setLocationRelativeTo(null);
       //差掉窗口,结束
       setDefaultCloseOperation(3);
       //窗口不可拉伸
       setResizable(false);
       //可见
       setVisible(true);
   }
   
   public static void main(String[] args) {
       GamePanel gp = new GamePanel();
       gp.launch();
   }

}

1.4 为窗口添加键盘事件

  • 添加内部类,实现KeyAdapter类,重写keyPressed方法,监听按钮

class KeyMonitor extends KeyAdapter {
   
   @Override
   public void keyPressed(KeyEvent e) {
       System.out.println(e.getKeyChar());
   }
       
}
  • 窗口添加键盘监视器

this.addKeyListener(new GamePanel.KeyMonitor());
package com.qf;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.JFrame;

public class GamePanel extends JFrame {
   
   private static final long serialVersionUID = 7471238471259660459L;
   
   //窗口宽高
   int width = 800;
   int height = 610;
   
   //获取画笔
   @Override
   public void paint(Graphics g) {
       g.setColor(Color.GRAY);
       g.fillRect(0, 0, width, height);
       
       //添加游戏选项
       g.setColor(Color.BLUE);
       g.setFont(new Font("仿宋", Font.BOLD, 50));
       g.drawString("选择游戏模式", 220, 100);
       g.drawString("单人模式", 220, 200);
       g.drawString("双人模式", 220, 300);
       
   }
   
   //添加内部类,实现KeyAdapter类,重写keyPressed方法
   class KeyMonitor extends KeyAdapter {
       
       @Override
       public void keyPressed(KeyEvent e) {
           System.out.println(e.getKeyChar());
       }
       
   }
   
   public void launch() {
       setTitle("千锋坦克大战");
       //设置宽高
       setSize(width, height);
       //居中
       setLocationRelativeTo(null);
       //差掉窗口,结束
       setDefaultCloseOperation(3);
       //窗口不可拉伸
       setResizable(false);
       //可见
       setVisible(true);
       
       this.addKeyListener(new GamePanel.KeyMonitor());
   }
   
   public static void main(String[] args) {
       GamePanel gp = new GamePanel();
       gp.launch();
   }

}

1.5 键盘控制选择游戏模式

  • 添加指针图片

    • 在项目文件夹下创建文件夹images,关于项目的图片,插件都放在这个文件夹内

  • 创建图片对象

Image select = Toolkit.getDefaultToolkit().getImage("images/mytank_right.gif");
  • 设置初始纵坐标为150

    • int y = 150;

  • 在paint()方法,添加此图片

    • g.drawImage(select, 160, y, null);

  • 在launch()方法中重绘图形(每隔30毫秒)

    • //重绘

        while(true) {
           repaint();  
           try {
               Thread.sleep(25);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
  • 重写键盘事件,1选择单人模式,2选择双人模式

//添加内部类,实现KeyAdapter类,重写keyPressed方法

    class KeyMonitor extends KeyAdapter {
       
       @Override
       public void keyPressed(KeyEvent e) {
           int key = e.getKeyCode();
           switch(key) {
               case KeyEvent.VK_1 :
                   y = 150;
                   break;
               case KeyEvent.VK_2 :
                   y = 250;
                   break;
               case KeyEvent.VK_ENTER :
                   break;
               default:
                   break;
           }
       }
       
   }
  • 定义选择相应模式后,点击回车键,显示响应窗口

    • 定义模式state 0:未选择,1:单人 2:双人

      //模式state 0:未选择,1:单人 2:双人

      int state = 0;

      int a = 0;

    • 重写点击事件

    //添加内部类,实现KeyAdapter类,重写keyPressed方法

class KeyMonitor extends KeyAdapter {
       
       @Override
       public void keyPressed(KeyEvent e) {
           int key = e.getKeyCode();
           switch(key) {
               case KeyEvent.VK_1 :
                   a = 1; // 单人
                   y = 150;
                   break;
               case KeyEvent.VK_2 :
                   a = 2; //双人
                   y = 250;
                   break;
               case KeyEvent.VK_ENTER :
                   state = a;
                   break;
               default:
                   break;
           }
       }
       
   }
    • 写入图形判断

       if (state == 0 ) {
           g.drawString("选择游戏模式", 220, 100);
           g.drawString("单人模式", 220, 200);
           g.drawString("双人模式", 220, 300);
           
           //绘制指针
           g.drawImage(select, 160, y, null);
       } else if (state ==1 || state ==2) {
           g.drawString("游戏开始", 220, 100);
           if (state ==1) {
               g.drawString("单人模式", 220, 200);
           } else if(state ==2) {
               g.drawString("双人模式", 220, 200);
           }
       }
    • 整体代码

 package com.qf;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.JFrame;

public class GamePanel extends JFrame {
   
   private static final long serialVersionUID = 7471238471259660459L;
   
   //指针图片
   Image select = Toolkit.getDefaultToolkit().getImage("images/mytank_right.gif");
   int y =150; //纵坐标
   
   //模式state    0:未选择,1:单人   2:双人
   int state = 0;
   int a = 0;
   
   
   //窗口宽高
   int width = 800;
   int height = 610;
   
   //获取画笔
   @Override
   public void paint(Graphics g) {
       g.setColor(Color.GRAY);
       g.fillRect(0, 0, width, height);
       
       //添加游戏选项
       g.setColor(Color.BLUE);
       g.setFont(new Font("仿宋", Font.BOLD, 50));
       
       if (state == 0 ) {
           g.drawString("选择游戏模式", 220, 100);
           g.drawString("单人模式", 220, 200);
           g.drawString("双人模式", 220, 300);
           
           //绘制指针
           g.drawImage(select, 160, y, null);
       } else if (state ==1 || state ==2) {
           g.drawString("游戏开始", 220, 100);
           if (state ==1) {
               g.drawString("单人模式", 220, 200);
           } else if(state ==2) {
               g.drawString("双人模式", 220, 200);
           }
       }
       
       
   }
   
   //添加内部类,实现KeyAdapter类,重写keyPressed方法
   class KeyMonitor extends KeyAdapter {
       
       @Override
       public void keyPressed(KeyEvent e) {
           int key = e.getKeyCode();
           switch(key) {
               case KeyEvent.VK_1 :
                   a = 1; // 单人
                   y = 150;
                   break;
               case KeyEvent.VK_2 :
                   a = 2; //双人
                   y = 250;
                   break;
               case KeyEvent.VK_ENTER :
                   state = a;
                   break;
               default:
                   break;
           }
       }
       
   }
   
   public void launch() {
       setTitle("千锋坦克大战");
       //设置宽高
       setSize(width, height);
       //居中
       setLocationRelativeTo(null);
       //差掉窗口,结束
       setDefaultCloseOperation(3);
       //窗口不可拉伸
       setResizable(false);
       //可见
       setVisible(true);
       
       this.addKeyListener(new GamePanel.KeyMonitor());
       
       //重绘
       while(true) {
           repaint();  
           try {
               Thread.sleep(25);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
       
   }
   
   public static void main(String[] args) {
       GamePanel gp = new GamePanel();
       gp.launch();
   }

}

1.6 解决闪频

  • 创建一个图片

    • //解决闪动问题

    Image offScreemImage = null;

  • 重写paint()方法

    • 创建一个和弹出窗口宽高相同的图片,

    • 获取该图片的图形对象,把所有内容添加到该图片中

    //创建和容器一样大小的Image图片

if(offScreemImage == null) {
           offScreemImage = this.createImage(width,height);
       }
       
       //获该图片的图形
       Graphics gImage = offScreemImage.getGraphics();
       
       gImage.setColor(Color.GRAY);
       gImage.fillRect(0, 0, width, height);
       
       //添加游戏选项
       gImage.setColor(Color.BLUE);
       gImage.setFont(new Font("仿宋", Font.BOLD, 50));
       
       if (state == 0 ) {
           gImage.drawString("选择游戏模式", 220, 100);
           gImage.drawString("单人模式", 220, 200);
           gImage.drawString("双人模式", 220, 300);
           
           //绘制指针
           gImage.drawImage(select, 160, y, null);
       } else if (state ==1 || state ==2) {
           gImage.drawString("游戏开始", 220, 100);
           if (state ==1) {
               gImage.drawString("单人模式", 220, 200);
           } else if(state ==2) {
               gImage.drawString("双人模式", 220, 200);
           }
       }
  • 把该图片写入到窗口中

    • g.drawImage(offScreemImage, 0, 0,null);

package com.qf;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.JFrame;

public class GamePanel extends JFrame {
   
   private static final long serialVersionUID = 7471238471259660459L;
   
   //解决闪动问题
   Image offScreemImage = null;
   
   //指针图片
   Image select = Toolkit.getDefaultToolkit().getImage("images/mytank_right.gif");
   int y =150; //纵坐标
   
   //模式state    0:未选择,1:单人   2:双人
   int state = 0;
   int a = 0;
   
   //窗口宽高
   int width = 800;
   int height = 610;
   
   //获取画笔
   @Override
   public void paint(Graphics g) {
       
       //创建和容器一样大小的Image图片
       if(offScreemImage == null) {
           offScreemImage = this.createImage(width,height);
       }
       
       //获该图片的图形
       Graphics gImage = offScreemImage.getGraphics();
       
       gImage.setColor(Color.GRAY);
       gImage.fillRect(0, 0, width, height);
       
       //添加游戏选项
       gImage.setColor(Color.BLUE);
       gImage.setFont(new Font("仿宋", Font.BOLD, 50));
       
       if (state == 0 ) {
           gImage.drawString("选择游戏模式", 220, 100);
           gImage.drawString("单人模式", 220, 200);
           gImage.drawString("双人模式", 220, 300);
           
           //绘制指针
           gImage.drawImage(select, 160, y, null);
       } else if (state ==1 || state ==2) {
           gImage.drawString("游戏开始", 220, 100);
           if (state ==1) {
               gImage.drawString("单人模式", 220, 200);
           } else if(state ==2) {
               gImage.drawString("双人模式", 220, 200);
           }
       }
       
       g.drawImage(offScreemImage, 0, 0,null);
       
   }
   
   //添加内部类,实现KeyAdapter类,重写keyPressed方法
   class KeyMonitor extends KeyAdapter {
       
       @Override
       public void keyPressed(KeyEvent e) {
           int key = e.getKeyCode();
           switch(key) {
               case KeyEvent.VK_1 :
                   a = 1; // 单人
                   y = 150;
                   break;
               case KeyEvent.VK_2 :
                   a = 2; //双人
                   y = 250;
                   break;
               case KeyEvent.VK_ENTER :
                   state = a;
                   break;
               default:
                   break;
           }
       }
       
   }
   
   public void launch() {
       setTitle("千锋坦克大战");
       //设置宽高
       setSize(width, height);
       //居中
       setLocationRelativeTo(null);
       //差掉窗口,结束
       setDefaultCloseOperation(3);
       //窗口不可拉伸
       setResizable(false);
       //可见
       setVisible(true);
       
       this.addKeyListener(new GamePanel.KeyMonitor());
       
       //重绘
       while(true) {
           repaint();  
           try {
               Thread.sleep(25);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
       
   }
   
   public static void main(String[] args) {
       GamePanel gp = new GamePanel();
       gp.launch();
   }

}

2. 键盘控制坦克移动

2.1 添加游戏类父类

  • 属性

    • 图片

    • 位置(坐标)

    • 面板

  • 方法

    • 构造方法

    • 在图形中构建自己

    • 获取自身矩形(用于比较中弹)

package com.qf;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Toolkit;

//游戏父类
public abstract class GameObject {

       //图片
       public Image img;
       
       //位置(坐标) 纵坐标  竖坐标
       public int x;
       public int y;
       //在哪个面板
       public GamePanel gamePanel;
       
       //有参构造器  img参数为字符串,图片路径
       public GameObject(String img, int x, int y, GamePanel gamePanel) {
               super();
               this.img = Toolkit.getDefaultToolkit().getImage(img);
               this.x = x;
               this.y = y;
               this.gamePanel = gamePanel;
       }
       
       //画自己
       public abstract void paintSelf(Graphics g);
       
       //获取自身矩形
       public abstract Rectangle getRec();
       
}

2.2 添加坦克类

  • 尺寸(宽高)

  • 速度

  • 方向

  • 不同方向图片

package com.qf;

import java.awt.Point;
import java.awt.Toolkit;

//坦克父类
public abstract class Tank extends GameObject {
   
   //尺寸
   public int width = 40;
   public int height = 50;
   
   //速度
   public int speed = 3;
   
   //方向
   public Direction direction = Direction.UP;
   
   //四个方向图片
   public String upImg;
   public String leftImg;
   public String rightImg;
   public String downImg;
   
   public Tank(String img, int x, int y, GamePanel gamePanel,
           String upImg, String leftImg, String rightImg, String downImg) {
       super(img, x, y, gamePanel);
       this.upImg = upImg;
       this.leftImg = leftImg;
       this.rightImg = rightImg;
       this.downImg = downImg;
   }
   
}

2.3 添加玩家类

package com.qf;

import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;

//玩家
public class PlayerOne extends Tank {

   public PlayerOne(String img, int x, int y, GamePanel gamePanel, String upImg, String leftImg, String rightImg,
           String downImg) {
       super(img, x, y, gamePanel, upImg, leftImg, rightImg, downImg);
   }

   @Override
   public void paintSelf(Graphics g) {
       g.drawImage(img, x, y, null);
   }

   @Override
   public Rectangle getRec() {
       return new Rectangle(x, y, width, height);
   }

}

2.4 在窗口中添加玩家

  • 创建玩家对象

    • //添加玩家一到面板

        PlayerOne playerOne = new PlayerOne("images/mytank_up.gif", 125, 510, 
                       this,
                       "images/mytank_up.gif",
                       "images/mytank_left.gif",
                       "images/mytank_right.gif",
                       "images/mytank_down.gif");
  • 把玩家对象添加到图形中

    playerOne.paintSelf(g);

package com.qf;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.JFrame;

/*
* 已解决闪动
*/
public class GamePanel extends JFrame {
   
   private static final long serialVersionUID = -9194828502749643640L;
   
   //解决闪动问题
   Image offScreemImage = null;
   
   //窗口长宽
   int width = 800;
   int height = 610;
   
   //指针图片
   Image select = Toolkit.getDefaultToolkit().getImage("images/mytank.gif");
   int y =150; //纵坐标
   
   //定义游戏模式 0:游戏未开始   1:单人模式   2:双人模式
   int state = 0;
   
   int a = 0; //根据输入的键值,修改a值,再赋值给state
   

   //添加玩家一到面板
   PlayerOne playerOne = new PlayerOne("images/mytank_up.gif", 125, 510,
           this,
           "images/mytank_up.gif",
           "images/mytank_left.gif",
           "images/mytank_right.gif",
           "images/mytank_down.gif");
   
   
   //窗口的启动方法
   public void launch() {
       setTitle("千锋坦克大战");
       //设置宽高
       setSize(width, height);
       //居中
       setLocationRelativeTo(null);
       //差掉窗口,结束
       setDefaultCloseOperation(3);
       //窗口不可拉伸
       setResizable(false);
       //可见
       setVisible(true);
       
       //添加键盘监视器
       this.addKeyListener(new GamePanel.KeyMonitor());
       
       //重绘
       while(true) {
           repaint();  
           try {
               Thread.sleep(25);  //间隔时间
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           
       }
       
   }
   
   //画  图形
   @Override
   public void paint(Graphics g) {
       //创建和容器一样大小的Image图片
       if(offScreemImage == null) {
           offScreemImage = this.createImage(width,height);
       }
       
       //获的该图片的画笔
       Graphics gImage = offScreemImage.getGraphics();
       
       
       //把窗口背景色设置为灰色
       gImage.setColor(Color.GRAY);
       //把这个背景色填充整个窗口
       gImage.fillRect(0, 0, width, height);
       
       //设置颜色
       gImage.setColor(Color.BLUE);
       //设置字体
       gImage.setFont(new Font("仿宋", Font.BOLD, 50));
       
       if(state == 0 ) {
           //添加文字
           gImage.drawString("选择游戏模式", 220, 100);
           gImage.drawString("单人游戏", 220, 200);
           gImage.drawString("双人游戏", 220, 300);
           //绘制指针
           gImage.drawImage(select, 160, y, null);
           
       } else if (state == 1 || state == 2) {
           gImage.drawString("游戏开始", 220, 100);
           if(state == 1) {
               gImage.drawString("单人游戏", 220, 200);
           } else {
               gImage.drawString("双人游戏", 220, 200);
           }
           
           //添加玩家
           playerOne.paintSelf(gImage);
       }
       
       g.drawImage(offScreemImage, 0, 0,null);
   }
   
   //键盘监视器,需要添加到窗口中
   class KeyMonitor extends KeyAdapter {
       
       //按下键盘的回调方法
       @Override
       public void keyPressed(KeyEvent e) {
           //返回按钮的值
           int key = e.getKeyChar();
           
           switch(key) {
               case KeyEvent.VK_1 :
                   a = 1;
                   y = 150;
                   break;
               case KeyEvent.VK_2 :
                   a = 2;
                   y = 250;
                   break;
               case KeyEvent.VK_ENTER :
                   state = a;
                   break;
               default:
                   
           }
       }
   }
   
   public static void main(String[] args) {
       GamePanel gp = new GamePanel();
       gp.launch();
   }

}

2.5使用键盘控制坦克移动

  • 在坦克类(Tank)中添加上下左右四个方法

    • 不同方向设置不同图片

    • 不同的方向

    //移动方法

    public void leftward() {
       x -=speed;
       direction = Direction.LEFT;
       setImg(leftImg);
   }
   
   public void rightward() {
       x +=speed;
       direction = Direction.RIGHT;
       setImg(rightImg);
   }
   
   public void upward() {
       y -=speed;
       direction = Direction.UP;
       setImg(upImg);
   }
   
   public void downward() {
       y +=speed;
       direction = Direction.DOWN;
       setImg(downImg);
   }
   
   public void setImg(String img) {
       this.image = Toolkit.getDefaultToolkit().getImage(img);
   }
  • 在PlayerOne类中按钮点击,松开方法,及移动方法

    • W上,A左,S下,D右

    boolean left, right, up, down;

    //键盘按下去方法

public void keyPressed(KeyEvent e) {
       int key = e.getKeyCode();
       switch(key) {
           case KeyEvent.VK_A : left=true; break;
           case KeyEvent.VK_S : down=true; break;
           case KeyEvent.VK_D : right=true; break;
           case KeyEvent.VK_W : up=true; break;
       }
   }
   
   //键盘松开方法
   public void keyReleased(KeyEvent e) {
       int key = e.getKeyCode();
       switch(key) {
           case KeyEvent.VK_A : left=false; break;
           case KeyEvent.VK_S : down=false; break;
           case KeyEvent.VK_D : right=false; break;
           case KeyEvent.VK_W : up=false; break;
       }
   }
    • //移动的方法,每次在绘制的时候移动

 public void move() {
       if(left) {
           leftward();
       } else if (right) {
           rightward();
       } else if (up) {
           upward();
       } else if(down) {
           downward();
       }
   }

   @Override
   public void paintSelf(Graphics g) {
       g.drawImage(image, x, y, null);
       
       //每次绘图的时候,根据最新的属性,绘图
       move();
   }
  • 在主窗口类中,添加对A,S,D,W按键的监控

    //键盘监控

    class KeyMonitor extends KeyAdapter {
               
               //点击键盘的回调方法
               @Override
               public void keyPressed(KeyEvent e) {
                       int key = e.getKeyChar();
                       switch(key) {
                               case KeyEvent.VK_1:
                                       y = 150;  
                                       a = 1;
                                       break;
                               case KeyEvent.VK_2:
                                       y = 250;  
                                       a = 2;
                                       break;
                               case KeyEvent.VK_ENTER :  //点击回车,选择相应的模式
                                       state = a;
                               default:
                                       playerOne.keyPressed(e);   //这里
                       }
               }
               
               @Override
               public void keyReleased(KeyEvent e) {  //监听按钮的松开事件
                       playerOne.keyReleased(e);  //这里
               }
       }

3. 坦克发射子弹

3.1 创建子弹类

  • 属性

    • 尺寸

    • 速度

    • 方向

  • //尺寸

        int width = 10;

        int height =10;

        //速度

        int speed = 7;

        //方向,方向和坦克方向一致

        Direction direction;

  • 方法

    • 左移,右移,下移,上移

    • 根据坦克方向不同,移动方向不同

    • //移动方法

 public void leftward() {
       x -=speed;
   }
   
   public void rightward() {
       x +=speed;
   }
   
   public void upward() {
       y -=speed;
   }
   
   public void downward() {
       y +=speed;
   }
   
   //根据方向移动,绘制自己时,调用
   public void go() {
       switch(direction) {
       case LEFT:  leftward(); break;
       case DOWN:  downward(); break;
       case RIGHT: rightward();break;
       case UP:    upward();   break;
       }
   }

全部代码:

 package com.qf;

import java.awt.Graphics;
import java.awt.Rectangle;

public class Bullet extends GameObject {

   //尺寸
   int width = 10;
   int height =10;
   //速度
   int speed = 7;
   //方向,方向和坦克方向一致
   Direction direction;
   
   //移动方法
   public void leftward() {
       x -=speed;
   }
   
   public void rightward() {
       x +=speed;
   }
   
   public void upward() {
       y -=speed;
   }
   
   public void downward() {
       y +=speed;
   }
   
   //绘制自己时,调用
   public void go() {
       switch(direction) {
       case LEFT:  leftward(); break;
       case DOWN:  downward(); break;
       case RIGHT: rightward();break;
       case UP:    upward();   break;
       }
   }
   
   
   
   public Bullet(String imageUrl, int x, int y, GamePanal gamePanal,Direction direction) {
       super(imageUrl, x, y, gamePanal);
       this.direction = direction;
   }

   @Override
   public void paintSelf(Graphics g) {
       g.drawImage(image, x, y, null);
       
       //绘制子弹时,可以移动
       go();
   }

   @Override
   public Rectangle getRec() {
       return new Rectangle(x, y, width, height);
   }
   
}

3.2 实现坦克发射子弹

  • 在坦克类中

    • 获取坦克的头部坐标

       public Point getHeadPoint() {
               switch(direction) {
                        case LEFT:        return new Point(x, y+height/2);
                        case DOWN: return new Point(x+width/2, y+height);
                        case RIGHT: return new Point(x+width, y+height/2);
                        case UP: return new Point(x+width/2, y);
                        default: return null;
               }
       }
    • 添加发射方法

      • 根据坦克头坐标,及坦克方向创建子弹

      • 把创建的子弹添加到面板中创建子弹集合中

        //发射子弹

        public void attack() {
               //获取头坐标
               Point point = getHeadPoint();
               //创建子弹
               Bullet bullet = new Bullet("images/bullet.gif", point.x, point.y,
                               this.gamePanal, this.direction);
               //可以一次按很多次发射,把产生的子弹存储在面板的子弹集合中
               this.gamePanal.bulletList.add(bullet);
       }

//子弹集合 在面板类中定义

List<Bullet> bulletList = new ArrayList<Bullet>();
  //绘制玩家
   playerOne.paintSelf(gImage);
           
   //绘制子弹  在面板类中操作
   for(Bullet bullet : bulletList) {
       bullet.paintSelf(gImage);
   }
  • 在PlayerOne类中添加键盘事件

    • 当点击空格按钮,就会调用射击方法

    //键盘按下去方法

    public void keyPressed(KeyEvent e) {
       int key = e.getKeyCode();
       switch(key) {
           case KeyEvent.VK_A : left=true; break;
           case KeyEvent.VK_S : down=true; break;
           case KeyEvent.VK_D : right=true; break;
           case KeyEvent.VK_W : up=true; break;
           case KeyEvent.VK_SPACE: attack();           //添加发送子弹方法
       }
   }

3.3 添加子弹冷却时间

  • 在坦克类中添加冷却状态,冷却时间

        //冷却状态

        boolean attackCoolDown = true;

        //冷却时间

        int attackCoolTime = 1000;

  • 创建冷却线程

        //冷却线程

   class AttackCool extends Thread {
       
       public void run() {
           attackCoolDown = false;
           try {
               Thread.sleep(attackCoolTime);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           attackCoolDown = true;
       }
       
   }
  • 修改发射方法,每发射一次,启动冷却线程

        //发射子弹

    public void attack() {
       if (attackCoolDown) {
           //获取头坐标
           Point point = getHeadPoint();
           //创建子弹
           Bullet bullet = new Bullet("images/bullet.gif", point.x, point.y,
                   this.gamePanal, this.direction);
           //可以一次按很多次发射,把产生的子弹存储在面板的子弹集合中
           this.gamePanal.bulletList.add(bullet);
           
           //启动冷却线程
           new AttackCool().start();
       }
   }

4. 敌方坦克

4.1 随机添加敌方坦克

  • 添加敌方坦克类

        //敌方坦克

public class Bot extends Tank{

   public Bot(String imageUrl, int x, int y, GamePanal gamePanal, String upImg, String leftImg, String rightImg,
           String downImg) {
       super(imageUrl, x, y, gamePanal, upImg, leftImg, rightImg, downImg);
   }

   @Override
   public void paintSelf(Graphics g) {
       g.drawImage(image, x, y, null);
   }

   @Override
   public Rectangle getRec() {
       return new Rectangle(x, y, width, height);
   }

}
  • 批量把敌方坦克图片添加到面板上

    • 把敌方坦克的图片添加到项目中(4个方向各一个)

    • 在面板中创建敌方坦克集合

    • 面板每重新加载100次,添加一个敌方坦克,敌方坦克最多有10各

        //重绘次数

        int count =0;

        //敌方坦克个数

        int enemyCount = 0;

  • 在重新绘制循环中,添加敌方坦克

        //重新载入窗口

                while(true) {
                       
                       if(count%100 ==1 && enemyCount<5 ) {
                               //添加敌方坦克
                               Random r = new Random();
                               int rnum = r.nextInt(800);
                               botList.add(new Bot("images/enemy_up.png", rnum, 110,
                                               this, "images/enemy_up.png", "images/enemy_left.png",
                                               "images/enemy_right.png", "images/enemy_down.gif"));
                               enemyCount++;
                       }
                       
                       this.repaint();
                       try {
                               Thread.sleep(25);
                       } catch (InterruptedException e) {
                               e.printStackTrace();
                       }
                       
               }

在开始游戏加载面板中显示敌方坦克并对count自增(paint方法)

                if (state ==0) {
                       //220离左边的距离,  100离上面的距离
                       gImage.drawString("选择游戏模式", 220, 100);
                       gImage.drawString("单人模式", 220, 200);
                       gImage.drawString("双人模式", 220, 300);
                       //画入图像
                       gImage.drawImage(select, 140, y, null);
               } else {
                       gImage.drawString("开始游戏", 220, 100);
                       if(state == 1) {
                               gImage.drawString("单人模式", 220, 200);
                       } else if (state == 2) {
                               gImage.drawString("双人模式", 220, 200);
                       }
                       
                       //绘制玩家
                       playerOne.paintSelf(gImage);
                       
                       //绘制子弹
                       for(Bullet bullet : bulletList) {
                               bullet.paintSelf(gImage);
                       }
                       
                       //绘制敌方坦克
                       for(Bot bot : botList) {
                               bot.paintSelf(gImage);
                       }
                       
                       count++;
               }

4.2 敌方坦克随机移动

  • 随机生成方向(Bot)

        //随机生成个数,表示方向

       public Direction getRandomDirection() {
               Random random = new Random();
               int rnum = random.nextInt(4); //0-3
               switch(rnum) {
                       case 0 : return Direction.LEFT;
                       case 1 : return Direction.RIGHT;
                       case 2 : return Direction.UP;
                       case 3 : return Direction.DOWN;
                       default: return null;
               }
               
       }
  • 添加运动方法,每20次,转化一次方向(Bot)

        //每间隔20,转换方向一次

        int moveTime = 20;

        //行走方法

    public void go() {
       if (moveTime >=20) {
           direction = getRandomDirection();
           moveTime=0;
       } else {
           moveTime++;
       }
       
       switch(direction) {
           case LEFT:  leftward(); break;
           case DOWN:  downward(); break;
           case RIGHT: rightward();break;
           case UP:    upward();   break;
       }
       
   }

    //勿忘把重绘方法添加到绘制自己方法中

    @Override
   public void paintSelf(Graphics g) {
       g.drawImage(image, x, y, null);
       
       go();
   }

4.3 敌方坦克射击

  • 创建敌方子弹类,直接继承Bullet即可

package com.qf;

import java.awt.Graphics;
import java.awt.Rectangle;

//敌方子弹类
public class EnemyBullet extends Bullet {

   public EnemyBullet(String imageUrl, int x, int y, GamePanal gamePanal, Direction direction) {
       super(imageUrl, x, y, gamePanal, direction);
   }
   

   @Override
   public void paintSelf(Graphics g) {
       g.drawImage(image, x, y, null);
       
       //在图形绘制自己时移动
       go();
   }

   @Override
   public Rectangle getRec() {
       return new Rectangle(x, y, width, height);
   }

}
  • 在机器人坦克类中添加攻击发射方法

    • 机器人自行发射,把该方法添加到go()方法中

    //攻击类,添加到go方法中

    public void attack() {
       Point p = getHeadPoint();
       Random random = new Random();
       int num = random.nextInt(100); // [0,99]
       if(num <4) {  //4%设计概率,可自行调节发射频率   0,1,2,3
           this.gamePanal.bulletList.add(new EnemyBullet("images/bullet.gif",
                   p.x, p.y, this.gamePanal, this.direction));
       }
   }

//机器人坦克自行行走

    public void go() {
       
       attack();  // 添加到这里,边走边发射子弹
       
       if (moveTime>=20) {
           direction = getRandomDirection();
           moveTime =0;
       } else {
           moveTime++;
       }
       
       switch(direction) {
           case LEFT : leftward(); break;
           case RIGHT : rightward(); break;
           case DOWN : downward(); break;
           case UP : upward(); break;
       }
       
   }

5. 碰撞检测

5.1 我方子弹(Bullet)和敌方坦克(Bot)碰撞

  • 在面板类中定义要删除/消失的子弹(GamePanal)

  • 在每次重绘时,把删除的子弹从绘制子弹集合中删除

        //碰撞后要删除的子弹

        List<Bullet> removeList = new ArrayList<Bullet>();
       //绘制子弹
       for(Bullet bullet : bulletList) {
               bullet.paintSelf(gImage);
       }
                       
       //去除要删除的子弹
       bulletList.removeAll(removeList);
  • 在子弹类中添加碰撞方法(Bullet)

    • 在绘制每个子弹时,把当前子弹对象和所有计算机坦克比较是否碰撞

    • 该方法要添加到绘制方法中

        //碰撞 添加到paintSelf方法中,每次绘制时,检测

        public void hitBot() {
               List<Bot> bots = this.gamePanal.botList;
               for(Bot bot : bots) {
                       if (this.getRec().intersects(bot.getRec())) {
                               this.gamePanal.botList.remove(bot);   // 坦克消失
                               this.gamePanal.removeList.add(this);  //子弹消失
                               break;
                       }
               }
       }
       @Override
       public void paintSelf(Graphics g) {
               g.drawImage(image, x, y, null);
               
               //在图形绘制自己时移动
               go();
               
               //每次绘制时,检测碰撞
               hitBot();
       }

5.2 敌方子弹和我方坦克碰撞

  • 添加我方坦克集合(为后续扩展使用)

    • 把玩家都添加到此集合中,在选择游戏模式时添加

  • 玩家的绘制,修改成遍历该集合

    • 不要忘记把原绘制玩家代码删除

        //玩家坦克集合

List<Tank> playerList = new ArrayList<Tank>();
       //键盘监控
       class KeyMonitor extends KeyAdapter {
               
               //点击键盘的回调方法
               @Override
               public void keyPressed(KeyEvent e) {
                       int key = e.getKeyChar();
                       switch(key) {
                               case KeyEvent.VK_1:
                                       y = 150;  
                                       a = 1;
                                       break;
                               case KeyEvent.VK_2:
                                       y = 250;  
                                       a = 2;
                                       break;
                               case KeyEvent.VK_ENTER :  //点击回车,选择相应的模式
                                       state = a;
                                       
                                       //添加玩家,双人待做
                                       playerList.add(playerOne);     //----这里添加
                               default:
                                       playerOne.keyPressed(e);   //坦克的移动
                       }
               }
               //绘制玩家
               //        playerOne.paintSelf(gImage);
               for(Tank player : playerList) {
                       player.paintSelf(gImage);
               }
  • 在敌方子弹类中添加碰撞方法

    • 该方法添加到绘制方法中

       //碰撞 添加到paintSelf方法中,每次绘制时,检测

        public void hitPlayer() {
               List<Tank> tanks = this.gamePanel.playerList;
               for(Tank t : tanks) {
                       if (this.getRec().intersects(t.getRec())) {
                               this.gamePanel.playerList.remove(t);   // 玩家坦克消失
                               this.gamePanel.removeList.add(this);  //子弹消失
                               break;
                       }
               }
       }
       @Override
       public void paintSelf(Graphics g) {
               g.drawImage(image, x, y, null);
               
               //在图形绘制自己时移动
               go();
               
               //检测碰撞
               hitPlayer();
       }

5.3 与围墙的碰撞检测

5.3.1面板中添加围墙

  • 添加围墙类

package com.qf;

import java.awt.Graphics;
import java.awt.Rectangle;

public class Wall extends GameObject {
   
   //围墙尺寸
   int length = 60;

   public Wall(String imageUrl, int x, int y, GamePanel gamePanal) {
       super(imageUrl, x, y, gamePanel);
   }

   @Override
   public void paintSelf(Graphics g) {
       g.drawImage(image, x, y, null);
   }

   @Override
   public Rectangle getRec() {
       return new Rectangle(x, y, length, length);
   }

}
在面板中添加围墙集合,在lunch方法中添加围墙,然后把集合绘制到面板中
//围墙列表
List<Wall> wallList = new ArrayList<Wall>();
//在lunch方法中

//添加围墙
       for(int i = 0; i<14; i++) {
           wallList.add(new Wall("images/wall.png", i*60, 170, this));
       }
       wallList.add(new Wall("images/wall.png", 305, 560, this));
       wallList.add(new Wall("images/wall.png", 305, 500, this));
       wallList.add(new Wall("images/wall.png", 365, 500, this));
       wallList.add(new Wall("images/wall.png", 425, 500, this));
       wallList.add(new Wall("images/wall.png", 425, 560, this));
   //在paint方法中绘制围墙
   for(Wall wall : wallList) {
       wall.paintSelf(gImage);
   }

5.3.2 添加围墙和子弹碰撞检测

  • 子弹类添加和围墙碰撞方法(Bullet)

    • 把该方法添加到go()方法中,让敌方子弹也可以和围墙碰撞

    //子弹和围墙碰撞,添加到go方法中,让敌方子弹也可以击破围墙

    public void hitWall() {
       List<Wall> walls = this.gamePanal.wallList;
       for(Wall wall : walls) {
           if (this.getRec().intersects(wall.getRec())) {
               this.gamePanal.wallList.remove(wall);   // 围墙消失
               this.gamePanal.removeList.add(this);  //子弹消失
               break;
           }
       }
   }
       public void go() {
               switch(direction) {
                       case LEFT : leftward(); break;
                       case RIGHT : rightward(); break;
                       case DOWN : downward(); break;
                       case UP : upward(); break;
               }
               hitWall();       //---------------添加到这里
       }

5.3.3 坦克和围墙碰撞

  • 当坦克和围墙碰撞时,坦克无法移动

  • 在坦克类中添加围墙碰撞方法

    • 然后在移动类中判断是否会和围墙碰撞,如果碰撞则不移动

        //与围墙碰撞检测

                public boolean hitWall(int x, int y) {
                       List<Wall> wallList = this.gamePanal.wallList;
                       //坦克下一步将要移动后的矩形
                       Rectangle next = new Rectangle(x, y, width, height);
                       
                       for(Wall wall : wallList) {
                               if(next.intersects(wall.getRec())) {
                                       return true;
                               }
                       }
                       return false;
               }
  • 修改所有移动的方法

    //移动方法 左移

  public void leftward() {
           if(!hitWall(x-speed, y)) {
               x -=speed;
           }
           direction = Direction.LEFT;
           setImg(leftImg);
       }
       
        //移动方法   右移
       public void rightward() {
           if(!hitWall(x+speed, y)) {
               x +=speed;
           }
           
           direction = Direction.RIGHT;
           setImg(rightImg);
       }
       
        //移动方法   上移
       public void upward() {
           if(!hitWall(x, y-speed)) {
               y -=speed;
           }
           direction = Direction.UP;
           setImg(upImg);
       }
       
        //移动方法   下移
       public void downward() {
           if(!hitWall(x, y+speed)) {
               y +=speed;
           }
           
           direction = Direction.DOWN;
           setImg(downImg);
       }

5.4 判断坦克边缘的碰撞

  • 坦克类中添加边缘碰撞判断(Tank)

    • 该判断添加到移动方法中

        //判断和边缘的碰撞

        public boolean moveToBorder(int x,int y) {
           if(x<0) {
               return true;
           } else if (x+width > this.gamePanal.width) {
               return true;
           } else if (y <0) {
               return true;
           } else if (y+height > this.gamePanal.height) {
               return true;
           }
           return false;
       }
      //移动方法   左移
       public void leftward() {
           if(!hitWall(x-speed, y) && !moveToBorder(x-speed, y)) {
               x -=speed;
           }
           direction = Direction.LEFT;
           setImg(leftImg);
       }
       
        //移动方法   右移
       public void rightward() {
           if(!hitWall(x+speed, y) && !moveToBorder(x+speed, y)) {
               x +=speed;
           }
           
           direction = Direction.RIGHT;
           setImg(rightImg);
       }
       
        //移动方法   上移
       public void upward() {
           if(!hitWall(x, y-speed)&&!moveToBorder(x, y-speed)) {
               y -=speed;
           }
           direction = Direction.UP;
           setImg(upImg);
       }
       
        //移动方法   下移
       public void downward() {
           if(!hitWall(x, y+speed)&&!moveToBorder(x, y+speed)) {
               y +=speed;
           }
           
           direction = Direction.DOWN;
           setImg(downImg);
       }

5.5判断子弹是否出界

  • 判断子弹坐标是否出界,如果出界则删除

    • 该方法添加到go方法中

        //如果子弹出界,该方法添加在go方法中,敌人子弹也可以删除 删除子弹,节省资源

    public void moveToBorder() {
       if(x<0 || x+width > this.gamePanel.width) {
           this.gamePanel.removeList.add(this);
       }

       if(y<0 || y+height > this.gamePanel.height) {
           this.gamePanel.removeList.add(this);
       }

   }

   //子弹的移动是根据方向来的
   public void go() {
       switch(direction) {
           case LEFT : leftward(); break;
           case RIGHT : rightward(); break;
           case DOWN : downward(); break;
           case UP : upward(); break;
       }
       hitWall(); //子弹撞墙
       moveToBorder(); //子弹出界删除
   }

6. 添加基地

6.1 创建并添加基地

  • 创建基地类

package com.qf;

import java.awt.Graphics;
import java.awt.Rectangle;

//基地类
public class Base extends GameObject {
   
   int length = 60;

   public Base(String imageUrl, int x, int y, gamePanel gamePanal) {
       super(imageUrl, x, y, gamePanal);
   }

   @Override
   public void paintSelf(Graphics g) {
       g.drawImage(image, x, y, null);
       
   }

   @Override
   public Rectangle getRec() {
       return new Rectangle(x, y, length, length);
   }

}
在面板中添加基地
//基地集合
List<Base> baseList = new ArrayList<Base>();

//在lunch方法添加创建一个基地
Base base = new Base("images/base.png", 365, 560, this);
baseList.add(base);

//绘制基地
for(Base base : baseList) {
   base.paintSelf(gImage);
}

6.2 子弹与基地的碰撞测试

  • 在子弹类中添加碰撞方法

    • 该方法添加到go方法中

        //与基地碰撞方法

    public void hitBase() {
       List<Base> bases = this.gamePanel.baseList;
       for(Base base : bases) {
           if (this.getRec().intersects(base.getRec())) {
               this.gamePanel.baseList.remove(base);   // 基地消失
               this.gamePanel.removeList.add(this);  //子弹消失
               break;
           }
       }
   }
   public void go() {
       switch(direction) {
           case LEFT : leftward(); break;
           case RIGHT : rightward(); break;
           case DOWN : downward(); break;
           case UP : upward(); break;
       }
       hitWall(); //子弹撞墙
       moveToBorder(); //子弹出界删除
       hitBase(); //---销毁基地
   }

7. 游戏规则

  • 我方胜利

    • 把所有的敌人干倒

  • 机器赢了

    • 玩家坦克被干掉

    • 基地被倒了

        //判断规则,在每次重绘时

//玩家赢了
if(botList.size() == 0 && enemyCount == 3) {
   state = 3;
}
           
//机器赢了
if((playerList.size()==0&&(state ==1 || state ==2))  
  || baseList.size() ==0) {
   state = 4;
}

   //获取画笔
   @Override
   public void paint(Graphics g) {
       
       if(offScreemImage==null) {
           offScreemImage = this.createImage(width, height);
       }
       
       Graphics gImage = offScreemImage.getGraphics();
       
       //设置图形的颜色
       gImage.setColor(Color.GRAY);
       //把设置的颜色,填充整个矩形
       gImage.fillRect(0, 0, width, height);
       
       //重新设置字体颜色
       gImage.setColor(Color.BLUE);
       gImage.setFont(new Font("楷体", Font.BOLD, 50));
       
       if (state ==0) {
           //220离左边的距离,  100离上面的距离
           gImage.drawString("选择游戏模式", 220, 100);
           gImage.drawString("单人模式", 220, 200);
           gImage.drawString("双人模式", 220, 300);
           //画入图像
           gImage.drawImage(select, 140, y, null);
       } else if (state == 1 || state ==2){
           gImage.drawString("开始游戏", 220, 100);
           if(state == 1) {
               gImage.drawString("单人模式", 220, 200);
               
           } else if (state == 2) {
               gImage.drawString("双人模式", 220, 200);
           }
           
           //绘制玩家
   //      playerOne.paintSelf(gImage);
           
           //绘制玩家列表
           for(Tank tank : playerList) {
               tank.paintSelf(gImage);
           }
           
           //绘制子弹
           for(Bullet bullet : bulletList) {
               bullet.paintSelf(gImage);
           }
           //删除发生碰撞的子弹
           bulletList.removeAll(removeList);
           
           //绘制机器人坦克
           for (Bot bot : botList) {
               bot.paintSelf(gImage);
           }
           
           //绘制城墙
           for (Wall wall : wallList) {
               wall.paintSelf(gImage);
           }
           
           //绘制基地
           for (Base base : baseList) {
               base.paintSelf(gImage);
           }
           
           count++;
       } else if(state ==3) {
           gImage.drawString("我赢了", 220, 100);
       } else if(state == 4) {
           gImage.drawString("机器赢了", 220, 100);
       }
       
       //把重新绘制的图片放到窗口中(从左上角开始放)
       g.drawImage(offScreemImage, 0, 0, null);
   }

关注公众号,回复【坦克】获取项目资料

学“废”的同学,评论区回复“666”!

精选第一名,可以领取办公鼠标垫一个哦!

点击【阅读原文】,全套入门Java资料免费学!


原文作者:Java架构栈;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2021-04-13

  • 博文量
    573
  • 访问量
    243823