您的位置:首页 > 编程语言 > Java开发

Java实现坦克大战(1990有木有勾起童年回忆)

2014-08-05 15:15 344 查看
笔者最近一个星期又抽时间学习了一点点Java多线程编程,结合swing绘图做了一个特别简单版的坦克大战游戏。

功能说明:

1.仅仅有一关;英雄坦克(自己)只有一条命--不过可以很好的扩展,只要把MyPanel中的SuperTank做成跟敌人坦克一样的Vector即可;

2.右侧的调速面板仅可以调整我军坦克速度;子弹速度目前不可面板自动调整;

3.敌军总共20辆坦克,同时显示的有3辆;击毁一辆会在左上角自动增加一辆,直到上限20辆;

4.目前敌军坦克的移动完全是随机的,没有加入敌军向目标攻击的概率函数控制(后续优化);

代码说明:

1.我本机是编写了三个*.java文件,当然拷贝后,完全可以直接在一个文件中粘贴并编译;

游戏截图:



/**
* 坦克大战
* 1.Created by Light on 2014-8-1  画坦克
* 2.Modified by Light on 2014-8-1 让坦克按照键盘的方向键实现移动
* 3.Modified by Light on 2014-8-1 让坦克发子弹,并且让子弹飞
* 4.Modified by Light on 2014-8-4 让敌人坦克自由行动,让子弹打到坦克时,坦克和子弹都消失,让坦克少于三个时自动增加
* 5.Modified by Light on 2014-8-4 优化敌军坦克行动代码和让敌军坦克发射子弹
* 6.Modified by Light on 2014-8-5 让敌军的坦克发射子弹也能击毁英雄坦克
*/
package com.firstversion;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;
public class TankGame extends JFrame implements ActionListener{

/**
* 不知道这是干什么用的,后续补充
*/
private static final long serialVersionUID = 1L;
MyPanel mp ;
oprationPanel mep;
JSplitPane jsplit;
JButton friend, enemy,speedUp,speedDown;
JLabel speedArea;
//主函数
public static void main(String[] args) {
// TODO Auto-generated method stub
new TankGame();
}
//坦克游戏的构造函数
public TankGame()
{
mp = new MyPanel();

mep = new oprationPanel();
jsplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
//暂时把分隔窗口收起功能注释掉,免的窗口发生变化导致坦克开出边界
//jsplit.setOneTouchExpandable(true);

friend = new JButton("友军");
friend.addActionListener(mp);
friend.setActionCommand("friend");
friend.addKeyListener(mp);
//http://blog.csdn.net/hn1232/article/details/4224863
//因在另外一个Panel上添加button导致键盘监听失效,参考上面的博文将每个Button都加了键盘监听,功能恢复了,但是现在不明白原理
enemy = new JButton("敌军");
enemy.addActionListener(mp);
enemy.setActionCommand("enemy");
enemy.addKeyListener(mp);

speedUp = new JButton("加速");
speedUp.addActionListener(this);
speedUp.addActionListener(mp);
speedUp.setActionCommand("speedUp");
speedUp.addKeyListener(mp);

speedDown = new JButton("减速");
speedDown.addActionListener(this);
speedDown.addActionListener(mp);
speedDown.setActionCommand("speedDown");
speedDown.addKeyListener(mp);

speedArea = new JLabel(String.valueOf(mp.st.getSpeed()));

//Layout
mep.add(friend);
mep.add(enemy);
mep.add(speedUp);
mep.add(speedDown);
mep.add(speedArea);

jsplit.setLeftComponent(mp);
jsplit.setRightComponent(mep);
this.add(jsplit);

//Listener set
this.addKeyListener(mp);

//启动MyPanel
Thread t = new Thread(mp);
t.start();

//设置JFrame
this.setSize(600, 450);
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocation(350, 180);
this.setVisible(true);
jsplit.setDividerLocation(0.88);

}

//写一个方法用于刷新speedArea
public void refreshSpeed()
{
speedArea.setText(String.valueOf(this.mp.st.getSpeed()));
}

@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
this.refreshSpeed();
this.repaint();
}

}

package com.firstversion;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.*;
import javax.swing.JPanel;
import java.lang.Thread;

//个人Panel,用于重写paint方便自定义
class MyPanel extends JPanel implements KeyListener,ActionListener,Runnable
{
/**
* 暂时不清楚这个是干什么用的
*/
private static final long serialVersionUID = 1L;
//英雄坦克
SuperTank st = null;
//敌军坦克,使用Vector来装敌军坦克群
int enemyNum = 3;
Vector<EnemyTank> enemyTanks = null;
//个人Panel构造方法

public MyPanel()
{
//初始化英雄坦克-->位置在底层中央,方向朝上
st = new SuperTank(250,350);
//初始化敌人坦克,并为每个坦克启动一个线程-->在上层,方向朝下
enemyTanks = new Vector<EnemyTank>();
for(int i=0;i<enemyNum;i++)
{
//初始化敌军坦克
EnemyTank et = new EnemyTank(i*100,0);
//初始化敌军坦克的类型和方向
et.setDirection(3);
et.setTankType(1);
//将敌军坦克装载Vector中管理
enemyTanks.addElement(et);
//System.out.println("装了"+(i+1)+"个");
//System.out.println("总共"+(enemyTanks.size())+"个");
Thread t = new Thread(this.enemyTanks.get(i));
t.start();
}

}

//重写paint方法
public void paint(Graphics g)
{
super.paint(g);
//设置一下背景颜色
g.fillRect(0, 0, 600, 450);
//画英雄坦克
if(this.st.isLive())
{
this.createTank(this.st.getX(),this.st.getY(),g,this.st.getTankType(),this.st.getDirection());
}

//画英雄坦克子弹,要先判断子弹是否存在(子弹集是否为空)并且是否存活
if(!(this.st.bulletVector.isEmpty()))//子弹集不为空
{
for(int i=0;i<this.st.bulletVector.size();i++)//遍历子弹集
{
Bullet b = this.st.bulletVector.get(i);
if(b.isLive())//子弹为存活状态
{
//画子弹,x和y是取得子弹当前的最新坐标参照点
g.fillOval(b.getX(), b.getY(), 4, 4);
}
}
}

//画敌军坦克 for
for(int i=0;i<enemyTanks.size();i++)
{
//System.out.println("画了"+(i+1)+"个");
//System.out.println("总共"+(enemyTanks.size())+"个");
EnemyTank et = this.enemyTanks.get(i);
if(et.isLive())
{
this.createTank(et.getX(),et.getY(),g,et.getTankType(),et.getDirection());
}
//System.out.println("画了"+(i+1)+"个");
//画敌军坦克子弹
for(int j=0;j<et.bulletVector.size();j++)
{
Bullet b = et.bulletVector.get(j);
if(b.isLive())
{
//画子弹,x和y是取得子弹当前的最新坐标参照点
g.fillOval(b.getX(), b.getY(), 4, 4);
}
}
}

}

//专门写一个方法来创建坦克-->参数:初始横坐标/初始纵坐标/画笔/坦克类型/坦克方向
public void createTank(int x,int y,Graphics g,int tankType, int direction)
{
//先根据tankType判断坦克类型
switch(tankType)
{//0-->自己; 1--> 敌军
case 0: g.setColor(Color.YELLOW); break; //暂时使用颜色来标识tankType
case 1: g.setColor(Color.CYAN); break;
}
//接着根据direction判断坦克朝向,因为坦克朝不同的方向画法可能不一样,比如炮筒的画法和旗帜的画法肯定不一样
switch(direction)
{//0-->right; 1-->left; 2-->up; 3-->down;
case 0: //认为是朝右
//画两条履带
g.fill3DRect(x, y, 60, 10, false);
g.fill3DRect(x, y+30, 60, 10, false);
//画坦克体
g.fill3DRect(x+10, y+10, 30, 20, false);
//画盖子
g.fillOval(x+18, y+13, 14, 14);
//画炮筒+炮筒头(略粗一点点)
g.drawLine(x+25, y+20, x+60, y+20);
g.drawLine(x+55, y+19, x+60, y+19);
g.drawLine(x+55, y+21, x+60, y+21);
break;
case 1://朝左-->由朝右掉头
//画两条履带
g.fill3DRect(x, y, 60, 10, false);
g.fill3DRect(x, y+30, 60, 10, false);
//画坦克体
g.fill3DRect(x+20, y+10, 30, 20, false);
//画盖子
g.fillOval(x+28, y+13, 14, 14);
//画炮筒+炮筒头(略粗一点点)
g.drawLine(x+35, y+20, x, y+20);
g.drawLine(x+5, y+19, x, y+19);
g.drawLine(x+5, y+21, x, y+21);
break;
case 2://朝上-->由朝右转头
//画两条履带
g.fill3DRect(x, y, 10, 60, false);
g.fill3DRect(x+30, y, 10, 60, false);
//画坦克体
g.fill3DRect(x+10, y+20, 20, 30, false);
//画盖子
g.fillOval(x+13, y+28, 14, 14);
//画炮筒+炮筒头(略粗一点点)
g.drawLine(x+20, y, x+20, y+35);
g.drawLine(x+19, y, x+19, y+5);
g.drawLine(x+21, y, x+21, y+5);
break;
case 3://朝下-->由朝上掉头
//画两条履带
g.fill3DRect(x, y, 10, 60, false);
g.fill3DRect(x+30, y, 10, 60, false);
//画坦克体
g.fill3DRect(x+10, y+10, 20, 30, false);
//画盖子
g.fillOval(x+13, y+18, 14, 14);
//画炮筒+炮筒头(略粗一点点)
g.drawLine(x+20, y+10, x+20, y+60);
g.drawLine(x+19, y+55, x+19, y+60);
g.drawLine(x+21, y+55, x+21, y+60);
break;
}

}

//写一个方法或者当前Panel中存活的敌军坦克数
public int getAliveTanks()
{
int aliveTankNum = 0;
for(int i=0;i<this.enemyTanks.size();i++)
{
EnemyTank et = this.enemyTanks.get(i);
if(et.isLive())
{
aliveTankNum++;
}
}
return aliveTankNum ;
}

//写一个方法判断面板中的子弹是否与坦克遭遇,遭遇则将坦克置为isLive = false
public void gotTarget(Bullet b,EnemyTank et)
{
//一定要判断子弹和坦克是否存活,不然会碰到幽灵坦克和幽灵子弹
if(b.isLive() && et.isLive())
{
//需要根据坦克的方向来判断
switch(et.getDirection())
{
case 0:
case 1:
if(b.getX()>et.getX() && b.getX() < et.getX()+56 && b.getY()>et.getY() && b.getY()<et.getY()+36)
{
b.setLive(false);//将子弹置为死亡
et.setLive(false);//将坦克置为死亡
}
break;
case 2:
case 3:
if(b.getX()>et.getX() && b.getX()<et.getX()+36 && b.getY()>et.getY() && b.getY()<et.getY()+56)
{
b.setLive(false);//将子弹置为死亡
et.setLive(false);//将坦克置为死亡
}
break;
}

}
}

//写一个方法判断英雄坦克死亡
public void isSuperDead(Bullet b,SuperTank st)
{
//先判断敌军子弹是否存活
if(b.isLive() && st.isLive())
{
//需要根据坦克的方向来判断
switch(st.getDirection())
{
case 0:
case 1:
if(b.getX()>st.getX() && b.getX() < st.getX()+56 && b.getY()>st.getY() && b.getY()<st.getY()+36)
{
b.setLive(false);//将子弹置为死亡
st.setLive(false);//将坦克置为死亡
}
break;
case 2:
case 3:
if(b.getX()>st.getX() && b.getX()<st.getX()+36 && b.getY()>st.getY() && b.getY()<st.getY()+56)
{
b.setLive(false);//将子弹置为死亡
st.setLive(false);//将坦克置为死亡
}
break;
}
}
}
//写一个方法加入坦克

public void addEnemyTank()
{
EnemyTank et = new EnemyTank(0,0);
et.setDirection(3);
et.setTankType(1);
//一定记得把坦克加入到Vector中,不然尼玛取不到,也画不出来
this.enemyTanks.add(et);
Thread t = new Thread(et);
t.start();
}

@Override //重写监听方法
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
if(e.getActionCommand().equals("friend"))
{
this.st.setTankType(0);
}else if(e.getActionCommand().equals("enemy"))
{
this.st.setTankType(1);
}else if(e.getActionCommand().equals("speedUp"))
{
this.st.setSpeed(this.st.getSpeed()+1);

}else if(e.getActionCommand().equals("speedDown"))
{
if((this.st.getSpeed()-1)>0)
{
this.st.setSpeed(this.st.getSpeed()-1);
}else{
this.st.setSpeed(1);
}

}else{
System.out.println("真神奇,你竟然能让程序跑到这个逻辑来!");
}
this.repaint();
}

@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if(e.getKeyCode()==KeyEvent.VK_S)
{
this.st.setDirection(3);//控制方向
this.st.move(this.st.getDirection());
}else if(e.getKeyCode()==KeyEvent.VK_W)
{
this.st.setDirection(2);
this.st.move(this.st.getDirection());
}else if(e.getKeyCode()==KeyEvent.VK_A)
{
this.st.setDirection(1);
this.st.move(this.st.getDirection());
}else if(e.getKeyCode()==KeyEvent.VK_D)
{
this.st.setDirection(0);
this.st.move(this.st.getDirection());
}
//判断发射子弹
if(e.getKeyCode()==KeyEvent.VK_J)
{
this.st.fair();
}

this.repaint();
}

@Override//释放键盘
public void keyReleased(KeyEvent ke) {
// TODO Auto-generated method stub

}

@Override//打印字符
public void keyTyped(KeyEvent ke) {
// TODO Auto-generated method stub

}

@Override
public void run() {
// 重写run()方法,刷新整个MyPanel
while(true)
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//System.out.println("刷新了一次!");//测试使用
//判断子弹是否击中敌军坦克
for(int i=0;i<this.st.bulletVector.size();i++)
{
Bullet b = this.st.bulletVector.get(i);
for(int j=0;j<this.enemyTanks.size();j++)
{
EnemyTank et = this.enemyTanks.get(j);
this.gotTarget(b, et);
}
}
//判断英雄坦克是否死亡-->对每辆坦克的每颗子弹进行判断
for(int i=0;i<this.enemyTanks.size();i++)
{
if(this.enemyTanks.get(i).isLive())
{
EnemyTank et = this.enemyTanks.get(i);
for(int j=0;j<et.bulletVector.size();j++)
{
if(et.bulletVector.get(j).isLive())
{
Bullet b = et.bulletVector.get(j);
this.isSuperDead(b, this.st);
}
}
}
}
//判断敌军坦克是否需要增加,少于三个存活的坦克则增加到三个,总数达到20个则不再增加
if(this.getAliveTanks()<3 && this.enemyTanks.size()<20)
{
this.addEnemyTank();
}
//敌军射击
for(int j=0;j<this.enemyTanks.size();j++)
{
if(this.enemyTanks.get(j).isLive())
{
this.enemyTanks.get(j).fair();
}
}

//重新调用repaint()
this.repaint();
}
}
}

//操作面板类
class oprationPanel extends JPanel
{

/**
*
*/
private static final long serialVersionUID = 1L;

public oprationPanel()
{

}

}

package com.firstversion;

import java.util.*;

class Tank
{
private int x=100;//坦克初始位置的横坐标
private int y=100;//坦克初始位置的纵坐标
private int direction=2; //坦克的朝向
private int speed=4;//坦克的速度
private int tankType=0;//坦克类型
private boolean isLive = true ;//坦克默认存活

Vector<Bullet> bulletVector = null;//使用Vector是为了使每个打出去的子弹都是一个线程,同时每个坦克可以打出多个子弹
//成员属性访问和设置方法群
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;
}
public int getDirection() {
return direction;
}
public void setDirection(int direction) {
this.direction = direction;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public int getTankType() {
return tankType;
}
public void setTankType(int tankType) {
this.tankType = tankType;
}
public boolean isLive() {
return isLive;
}
public void setLive(boolean isLive) {
this.isLive = isLive;
}

//坦克构造函数,很重要,对于需要在其他位置调用的成员属性,在此都需要初始化,不然会报空指针
public Tank(int x, int y)
{
this.x=x;
this.y=y;
//这句话初始化Vector一定要有的,不然会在MyPanel中调用出现空指针错误
bulletVector = new Vector<Bullet>();

}
//坦克移动的能力方法,以方向为入参
public void move(int direction)
{//0-->right; 1-->left; 2-->up; 3-->down;
switch(direction)
{
case 0: if(x+speed<455) x+=speed; break;
case 1: if(x-speed>0) x-=speed; break;
case 2: if(y-speed>0) y-=speed; break;
case 3: if(y+speed<362) y+=speed; break;
default: break;
}
//System.out.println("跑了一次!");
}

//判断当前坦克存活子弹数
public int getAliveBullets()
{
int aliveBulletNum =0 ;
for(int i=0;i<this.bulletVector.size();i++)
{
Bullet b = this.bulletVector.get(i);
if(b.isLive())
{
aliveBulletNum++;
}
}
return aliveBulletNum;
}
}

class SuperTank extends Tank
{

public SuperTank(int x,int y)
{
super(x,y);//使用父类的能够匹配(参数个数与类型完全一致)的构造函数
}
//fair
public void fair()
{//每次开火就产生一个子弹,也就是子弹类的实例对象,加入到bulletVector中的最后一位,再从最后一个取取出来启动线程
if(this.getAliveBullets()<3)
{
switch(this.getDirection())
{
case 0: bulletVector.addElement(new Bullet(this.getX()+60,this.getY()+18,this.getDirection())); break;
case 1: bulletVector.addElement(new Bullet(this.getX()-4,this.getY()+18,this.getDirection())); break;
case 2: bulletVector.addElement(new Bullet(this.getX()+18,this.getY()-4,this.getDirection())); break;
case 3: bulletVector.addElement(new Bullet(this.getX()+18,this.getY()+60,this.getDirection()));break;
}
//在每次发射动作之后,就要启动这个线程
Thread t = new Thread(bulletVector.lastElement());
t.start();
}

}
}

//敌军坦克类,因为敌军坦克需要自己走动,所以实现Runnable接口,另每一个敌军坦克对象作为一个线程
class EnemyTank extends Tank implements Runnable
{
private boolean isMoving = false ;
private int fairController = 25 ;

public int getFairController() {
return fairController;
}
public void setFairController(int fairController) {
this.fairController = fairController;
}

public boolean isMoving() {
return isMoving;
}
public void setMoving(boolean isMoving) {
this.isMoving = isMoving;
}
public EnemyTank(int x, int y) {
super(x, y);
}
//fair automatically -- fair controller
//fair
public void fair()
{//每次开火就产生一个子弹,也就是子弹类的实例对象,加入到bulletVector中的最后一位,再从最后一个取取出来启动线程
if(this.fairController>0)
{
this.fairController--;
//System.out.println("不发射子弹");
}else
{
//System.out.println("发射子弹");
switch(this.getDirection())
{
case 0: bulletVector.addElement(new Bullet(this.getX()+60,this.getY()+18,this.getDirection())); break;
case 1: bulletVector.addElement(new Bullet(this.getX()-4,this.getY()+18,this.getDirection())); break;
case 2: bulletVector.addElement(new Bullet(this.getX()+18,this.getY()-4,this.getDirection())); break;
case 3: bulletVector.addElement(new Bullet(this.getX()+18,this.getY()+60,this.getDirection()));break;
}
//在每次发射动作之后,就要启动这个线程
Thread t = new Thread(bulletVector.lastElement());
t.start();
this.setFairController(25);
}

}
@Override
public void run() {
// TODO Auto-generated method stub
int i =500;
int j =3;
while(true)
{
//休眠
try {
Thread.sleep(i);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//随机秒
i = (int)(Math.random()*5000);
//开始计时毫秒级时间
Date d = new Date();
long longtime = d.getTime();
//测试打印代码
//System.out.println(longtime);
//System.out.println(longtime+i);
//System.out.println((new Date()).getTime());

//随机方向
j = ((int)(Math.random()*100))%4;
//一定要按照随机的方向给敌军坦克设置方向,不能只定义移动方向,不然你会看到坦克横着开
this.setDirection(j);
//休眠结束时,将坦克置为移动状态
this.setMoving(true);
//在随机数期间保持运动
while(true)
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Date d2 = new Date();
//System.out.println("d2:"+d2.getTime());
if(longtime+i>d2.getTime())
{
//调用移动方法
this.move(j);
}else{
//停止时发射一个子弹
this.fair();
break;
}
}

//退出循环时,将坦克置为静止
this.setMoving(false);
//在坦克静止时,每隔2~3s发射一颗子弹
//判断线程退出
if(this.isLive()==false)
{
break;
}
}
}
}

//子弹类,作为线程处理
class Bullet implements Runnable
{
private int x;
private int y;
private int speed = 5;//妈的这个地方坑死老子了,打了子弹就是不走,最后找到初始速度尼玛默认为0了
private int direction;
private boolean isLive = true;//默认子弹是激活的

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;
}

public int getSpeed() {
return speed;
}

public void setSpeed(int speed) {
this.speed = speed;
}

public int getDirection() {
return direction;
}

public void setDirection(int direction) {
this.direction = direction;
}

public boolean isLive() {
return isLive;
}

public void setLive(boolean isLive) {
this.isLive = isLive;
}

//Bullet构造方法
public Bullet(int x, int y, int direction)
{
this.x=x;
this.y=y;
this.direction=direction;
}

@Override//重写run()方法
public void run(){
//在fair()的方法中,会调用start(),进而似的run()得以执行,所以在run()中需要包含当前子弹线程结束的逻辑判断
//需要进行当前子弹的方向的判断
while(true)
{
try {
Thread.sleep(100);//每100毫秒变化一次初始位置,这样在MyPanel中进行repaint()就可以实现让子弹飞了
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
switch(this.direction)
{
case 0: x+=this.speed; break;
case 1: x-=this.speed; break;
case 2: y-=this.speed; break;
case 3: y+=this.speed; break;
}

//System.out.println("子弹坐标:["+this.x+","+this.y+"]");//测试使用
//判断子弹到边界自动退出线程
if(x<0||y<0||x>600||y>450)
{
this.setLive(false);//将子弹状态置为非激活,作为后面在画子弹时需要判断的条件
break;
//这里不知道怎么在退出线程时,清除Vector中已经消亡的子弹;初步考虑应该在外部按照时间和子弹状态来清除子弹
}else if(this.isLive==false){
break;
}

}

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: