一、预备知识
1、java绘图机制
2、多线程基础
二、实现过程
- Tank类(抽象出坦克的共有属性比如横纵坐标、方向、子弹对象及子弹数量,构造器只有横纵坐标并提供相应的getter和setter方法;共有方法比如坦克移动、创建子弹)
@SuppressWarnings("all") public class Tank { private int x; //坦克横坐标 private int y; //坦克纵坐标 private int direct;//表示方向(0-上, 1-右, 2-下, 3-左) private int type; Bullet bullet = null; //定义一个子弹对象 private int bulletNum = 1; //坦克的子弹数量-->给子类去设置 private boolean isTankAlive = true; public boolean isTankAlive() { return isTankAlive; } public void setTankAlive(boolean tankAlive) { isTankAlive = tankAlive; } public int getBulletNum() { return bulletNum; } public void setBulletNum(int bulletNum) { this.bulletNum = bulletNum; } //再给个速度 private int tank_speed = 1; public int getTank_speed() { return tank_speed; } public void setTank_speed(int tank_speed) { this.tank_speed = tank_speed; } //在父类中添加根据坦克方向创建子弹的方法 //子弹的初始坐标就是坦克炮筒的终点坐标 public Bullet makeBullet (){ switch (getDirect()) { case 0 : //上 bullet = new Bullet(getX() + 20, getY(), 0); break; case 1 : //右 bullet = new Bullet(getX() + 60, getY() + 20, 1); break; case 2 : //下 bullet = new Bullet(getX() + 20, getY() + 60, 2); break; case 3 : //左 bullet = new Bullet(getX(), getY() + 20, 3); break; } return bullet; } //上右下左移动方法 public void moveUp() { y -= tank_speed; } public void moveRight() { x += tank_speed; }public void moveDown() { y += tank_speed; }public void moveLeft() { x -= tank_speed; } public int getDirect() { return direct; } public void setDirect(int direct) { this.direct = direct; } public Tank(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } }
- Bullet类(把它做成一个线程,在其run方法中实现子弹移动及销毁)
@SuppressWarnings("all")
//把子弹做成线程
class Bullet implements Runnable
{
//给子弹设置它自身的坐标
//并根据坦克的方向进行初始化
private int x;//子弹横坐标
private int y;//子弹纵坐标
private int direct = 0; //子弹方向
private int speed = 5; //子弹速度
private boolean isBulletAlive = true; //子弹是否还存活
public int getX()
{
return x;
}
public int getY()
{
return y;
}
public int getDirect()
{
return direct;
}
public int getSpeed()
{
return speed;
}
public void setBulletAlive(boolean live)
{
isBulletAlive = live;
}
public boolean getBulletAlive()
{
return isBulletAlive;
}
public Bullet(int x, int y, int direct)
{
this.x = x;
this.y = y;
this.direct = direct;
}
@Override
public void run() //射击
{
while (true) {
//休眠->以便看到效果
try {
Thread.sleep(50);
}
catch (InterruptedException e) {
e.printStackTrace();
}
//根据方向改变x, y坐标
//代码简单就不用封装为方法了
switch (direct)
{
case 0 : //上
y -= speed;
break;
case 1 : //右
x += speed;
break;
case 2 : //下
y += speed;
break;
case 3 : //左
x -= speed;
break;
}
//测试, 输出x,y 坐标
//System.out.println("子弹 x= " + x + " y= " + y);
//如果
//1.子弹移动到面板边界,则销毁(即退出循环->结束线程)
//2.子弹击中敌方坦克, 也销毁
//注意条件的判断方式->先列出正确的条件 再取反
if (!(x >= 0 && x <= 1000 && y >= 0 && y <= 750 && isBulletAlive == true))
{
isBulletAlive = false;
System.out.println("子弹已销毁..");
break;
}
}
}
}
- Bomb类(实现爆炸效果)
public class Bomb
{
int x, y;//炸弹坐标
int life = 18; //炸弹生命周期
boolean isLive = true;
public Bomb(int x, int y)
{
this.x = x;
this.y = y;
}
//减少生命值
public void lifeDown()
{
if(life > 0) {
life--;
}
else {
isLive = false;
}
}
}
- Hero类(我方坦克,继承Tank类)
@SuppressWarnings("all")
//自己的坦克
public class Hero extends Tank
{
//定义一个Vector, 用来存放我方子弹
Vector<Bullet> bullet_hero = new Vector<Bullet>();
public Hero(int x, int y)
{
super(x, y);
}
//把创建一个Bullet对象和启动Bullet线程 封装到shotEnemy方法
public void shotEnemy()
{
//如果我方坦克发射的子弹大于5颗, 就不再创建新的子弹了
setBulletNum(5);
if (bullet_hero.size() > getBulletNum()) {
return;
}
//根据当前Hero对象的位置和方向, 创建一个Bullet对象
//-->调用父类方法
bullet = makeBullet();
bullet_hero.add(bullet); //把我方子弹加入集合
new Thread(bullet).start();//启动Bullet线程
}
}
Enemy类(敌方坦克,同样继承Tank类, 做成线程)
@SuppressWarnings("all")
public class Enemy extends Tank implements Runnable
{
//定义一个Vector, 用来存放敌方子弹
private Vector<Bullet> bullet_enemy = new Vector<Bullet>();
public Vector<Bullet> getBullet_enemy()
{
return bullet_enemy;
}
public Enemy(int x, int y)
{
super(x, y);
}
@Override
public void run()
{
while (isTankAlive()) {
setBulletNum(2);//如果敌方坦克的子弹为2颗
if(bullet_enemy.size() <= getBulletNum()){
//判断敌方坦克的方向来创建子弹对象-->调用父类方法
bullet = makeBullet();
bullet_enemy.add(bullet); //把敌方子弹加入集合
new Thread(bullet).start();//启动该Bullet线程
}
//生成一个1~3的随机整数(即不往上打),表示敌方坦克的方向
int direct_random = (int) (1 + Math.random() * 3);
System.out.println("随机方向:" + direct_random);
System.out.println("敌方坐标(" + getX() + "," + getY() + ")");
setDirect(direct_random);
System.out.println("实际方向: " + getDirect());
//设置敌方坦克的随机速度
int speed_random = (int)(Math.random() * 3 + 1);
setTank_speed(speed_random);
switch (getDirect())
{
case 0 : //上
//让坦克保持一个方向走30步
for (int i = 0; i < 30; i++) {
if(getY() > 0) { //控制敌方坦克移动范围
moveUp();
}
try {
Thread.sleep(50);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 1 : //右
for (int i = 0; i < 30; i++) {
if(getX() + 60 < 1000) { //控制敌方坦克移动范围
moveRight();
}
try {
Thread.sleep(50);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 2 : //下
for (int i = 0; i < 30; i++) {
if(getY() + 60 < 750) { //控制敌方坦克移动范围
moveDown();
}
try {
Thread.sleep(50);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 3 : //左
for (int i = 0; i < 30; i++) {
if(getX() > 0) { //控制敌方坦克移动范围
moveLeft();
}
try {
Thread.sleep(50);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
}
}
}
- MyPanel类(用于绘制坦克、子弹、爆炸,做成线程-->这样才能看到子弹行进)
@SuppressWarnings("all")
//坦克大战的绘图区域
//1.为了监听键盘事件,实现KeyListener
//2.为了看到子弹前进的效果->MyPanel要一直repaint->把MyPanel也做成一个线程
public class MyPanel extends JPanel implements KeyListener,Runnable
{
//定义我的坦克
Hero hero = null;
//定义敌方坦克,放入到vector集合
Vector<Enemy> enemyTanks = new Vector<Enemy>();
int enemyTankNum = 3;
//定义一个Vector, 用于存放炸弹
//当敌人击中坦克时, 就加入一个Bomb对象到bombs
Vector<Bomb> bombs = new Vector<Bomb>();
//定义三张炸弹图片, 用于`显示爆炸效果
Image image1 = null;
Image image2 = null;
Image image3 = null;
public MyPanel()
{
//初始化自己的坦克
hero = new Hero(100, 200);
hero.setTank_speed(10);//设置我方坦克速度
//初始化敌方的坦克
for(int i = 0; i < enemyTankNum; i++)
{
Enemy enemy = new Enemy(200 * (i + 1), 0);
enemy.setDirect(2);//设置敌方坦克方向朝下
//在此处创建敌方坦克的一颗子弹, 并加入到bullet_enemy 集合
Bullet b = new Bullet(enemy.getX() + 20, enemy.getY() + 60, enemy.getDirect());
enemy.getBullet_enemy().add(b);//加入到bullet_enemy 集合
new Thread(b).start(); //启动敌方坦克子弹
enemyTanks.add(enemy);
new Thread(enemy).start(); //启动敌方坦克
}
//初始化图片对象
image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_1.gif"));
image2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_2.gif"));
image3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_3.gif"));
}
@Override
public void paint(Graphics g)
{
super.paint(g);
//填充矩形,默认是黑色
g.fillRect(0, 0, 1000, 750);
//画出我方坦克-->封装到方法
if(hero.isTankAlive()) {
drawTank(hero.getX(), hero.getY(), g, hero.getDirect(), 1);
}
//遍历我方子弹集合, 画出我方坦克发射的子弹
for(int i = 0; i < hero.bullet_hero.size(); i++){
Bullet bullet = hero.bullet_hero.get(i);
if (bullet != null && bullet.getBulletAlive() == true) {
g.setColor(Color.yellow);
g.fillOval(bullet.getX(), bullet.getY(), 5, 5);
}
else { //当我方子弹不存活时就将它移除
hero.bullet_hero.remove(bullet);
}
}
//如果bombs集合中有对象, 就画出
for(int i = 0; i < bombs.size(); i++)
{
Bomb bomb = bombs.get(i); //取出炸弹对象
//根据当前bomb的life值去画出对应的图片
if (bomb.life > 12) {
g.drawImage(image1, bomb.x, bomb.y, 60, 60, this);
}
else if (bomb.life > 6) {
g.drawImage(image2, bomb.x, bomb.y, 60, 60, this);
}
else{
g.drawImage(image3, bomb.x, bomb.y, 60, 60, this);
}
//让炸弹的生命值减少
bomb.lifeDown(); //配合出现动态效果
//如果life为0, 就从bombs集合中删除
if (bomb.life == 0) {
bombs.remove(bomb);
}
}
//画出敌方坦克, 遍历Vecror
for(int i = 0; i < enemyTanks.size(); i++)
{
//取出坦克
Enemy enemy = enemyTanks.get(i);
//调用drawTank方法
//如果当前敌方坦克还存活,才去画
if (enemy.isTankAlive() == true) {
drawTank(enemy.getX(), enemy.getY(), g, enemy.getDirect(), 0);
//画出敌方坦克的子弹
for (int j = 0; j < enemy.getBullet_enemy().size(); j++) {
Bullet b = enemy.getBullet_enemy().get(j);//取出敌方子弹
if (b.getBulletAlive() == true) {
g.setColor(Color.cyan);
g.fillOval(b.getX(), b.getY(), 5, 5);
}
else {
enemy.getBullet_enemy().remove(b);//移除敌方子弹
}
}
}
else{
//当敌方坦克不存活了, 就移除它
enemyTanks.remove(enemy);
}
}
}
//画出坦克-->封装到方法
/**
*
* @param x 坦克的左上角横坐标
* @param y 坦克的左上角纵坐标
* @param g 画笔
* @param direct 坦克的方向
* @param type 坦克的类型
*/
public void drawTank(int x, int y, Graphics g, int direct, int type)
{
//根据不同类的坦克设置颜色
switch (type)
{
case 0 : //敌人的坦克
g.setColor(Color.cyan);//青色
break;
case 1 : //我们的坦克
g.setColor(Color.yellow);
break;
}
//根据坦克的方向来绘制对应形状的坦克
//direct 表示方向(0-上, 1-右, 2-下, 3-左
switch (direct)
{
case 0 : //向上的坦克
g.fill3DRect(x, y, 10, 60, false);//左履带
g.fill3DRect(x + 30, y, 10, 60, false);//右履带
g.fill3DRect(x + 10, y + 10, 20, 40, false);//机身
g.fillOval(x + 10, y + 20, 20, 20);//圆盖子
g.drawLine(x + 20, y + 30, x + 20, y);//炮筒
break;
case 1 : //向右的坦克
g.fill3DRect(x, y, 60, 10, false);//左履带
g.fill3DRect(x, y + 30, 60, 10, false);//右履带
g.fill3DRect(x + 10, y + 10, 40, 20, false);//机身
g.fillOval(x + 20, y + 10, 20, 20);//圆盖子
g.drawLine(x + 30, y + 20, x + 60, y+20);//炮筒
break;
case 2 : //向下的坦克
g.fill3DRect(x, y, 10, 60, false);//左履带
g.fill3DRect(x + 30, y, 10, 60, false);//右履带
g.fill3DRect(x + 10, y + 10, 20, 40, false);//机身
g.fillOval(x + 10, y + 20, 20, 20);//圆盖子
g.drawLine(x + 20, y + 30, x + 20, y+60);//炮筒
break;
case 3 : //向左的坦克
g.fill3DRect(x, y, 60, 10, false);//左履带
g.fill3DRect(x, y + 30, 60, 10, false);//右履带
g.fill3DRect(x + 10, y + 10, 40, 20, false);//机身
g.fillOval(x + 20, y + 10, 20, 20);//圆盖子
g.drawLine(x + 30, y + 20, x, y+20);//炮筒
break;
default:
System.out.println("其他情况暂时没有...");
}
}
@Override
public void keyTyped(KeyEvent e)
{
}
@Override
public void keyPressed(KeyEvent e)
{
//处理wdsa键按下的情况
if(e.getKeyCode() == KeyEvent.VK_W)//按下w键
{
//向上
hero.setDirect(0);
if(hero.getY() > 0) { //控制我方坦克移动范围
hero.moveUp();
}
}
else if(e.getKeyCode() == KeyEvent.VK_D)//按下d键
{
//向右
hero.setDirect(1);
if(hero.getX() + 60 < 1000) {//控制我方坦克移动范围
hero.moveRight();
}
}
else if(e.getKeyCode() == KeyEvent.VK_S)//按下s键
{
//向下
hero.setDirect(2);
if(hero.getY() + 60 < 750) {//控制我方坦克移动范围
hero.moveDown();
}
}
else if(e.getKeyCode() == KeyEvent.VK_A)//按下a键
{
//向左
hero.setDirect(3);
if(hero.getX() > 0) { //控制我方坦克移动范围
hero.moveLeft();
}
}
//如果用户按下J键, 发射子弹
if(e.getKeyCode() == KeyEvent.VK_J)
{
System.out.println("用户按下J键, 发射子弹...");
hero.shotEnemy();
}
this.repaint();//面板重绘
}
@Override
public void keyReleased(KeyEvent e)
{
}
//敌方坦克被击中
//什么时候判断坦克是否被击中呢?? -->面板重绘时
//alt + shift + R -->批量更改变量名
public void hitted(Bullet b, Tank t)
{
switch (t.getDirect()) {
case 0: //上
case 2: //下
if (b.getX() >= t.getX() && b.getX() <= t.getX() + 40
&& b.getY() >= t.getY() && b.getY() <= t.getY() +60)
{
b.setBulletAlive(false);
t.setTankAlive(false);
//创建一个Bomb对象, 加入到bombs集合中
Bomb bomb = new Bomb(t.getX(), t.getY());
bombs.add(bomb);
}
break;
case 1: //右
if (b.getX() >= t.getX() && b.getX() <= t.getX() + 60
&& b.getY() >= t.getY() && b.getY() <= t.getY() + 40)
{
b.setBulletAlive(false);
t.setTankAlive(false);
//创建一个Bomb对象, 加入到bombs集合中
Bomb bomb = new Bomb(t.getX(), t.getY());
bombs.add(bomb);
}
case 3: //左
break;
}
}
//判断敌方坦克是否被击中
public void hitEnemy(){
//如果我方发射的子弹还存活->不为空且没有碰到边界
//注意: 这里如果用 逻辑与-> & 就会报空指针异常
//遍历我方子弹集合 bullet_hero,
//并调用hitted方法,判断敌方坦克是否被击中
for (int j = 0; j < hero.bullet_hero.size(); j++) {
Bullet bullet = hero.bullet_hero.get(j);
if (bullet != null && bullet.getBulletAlive()) {
//遍历敌方坦克
for (int i = 0; i < enemyTanks.size(); i++) {
Enemy e = enemyTanks.get(i);
//调用hitted方法,判断坦克是否被击中
hitted(bullet, e);
}
}
}
}
//判断我方坦克是否被击中
public void hitHero(){
//遍历敌方子弹集合 bullet_enemy,
//并调用hitted方法,判断我方坦克是否被击中
for (int i = 0; i < enemyTanks.size(); i++) {
Enemy enemy = enemyTanks.get(i);
for(int j = 0; j < enemy.getBullet_enemy().size(); j++){
Bullet bullet = enemy.getBullet_enemy().get(j);
if (bullet != null && bullet.getBulletAlive()) {
hitted(bullet, hero);
}
}
}
}
//使MyPanel不断重绘->这样才能显示子弹的行进
@Override
public void run()
{
while (true) {
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
e.printStackTrace();
}
//判断坦克是否被击中
hitEnemy();
hitHero();
this.repaint();
}
}
}
- TankGame类(相当于一个窗口,在此主方法启动MyPanel线程)
public class TankGame04 extends JFrame
{
//定义MyPanel
MyPanel mp = null;
public static void main(String[] args)
{
TankGame04 tankGame01 = new TankGame04();
}
//构造器
public TankGame04()
{
mp = new MyPanel();
this.add(mp);
this.setSize(1050, 900);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
this.addKeyListener(mp);//监听面板发生的键盘事件
//启功MyPanel mp 线程
Thread thread_mp = new Thread(mp);
thread_mp.start();
}
}