花了一个星期的时间把马士兵老师讲的那个单机版坦克大战编好了,在编写过程中经常出错,由于对debug使用还不是很熟练,每次修复bug都要花很长时间,现在终于编好了个demo,给大家分享下
编TankWar,主要功能有 1.能够四处移动、2.-能够打击敌人 3.敌人能够移动 4.能够模拟爆炸 5.能够产生障碍
编程过程中的思想:
* 1.首先先new出一个frame,并设置大小,位置,
* 2.用户不能改变窗口大小,监听窗口关闭
* 3.画出一个子弹,设置大小和颜色
* 4.让坦克动起来,将a位置改变为变量,b启动线程不断重画 ,c每次重画改变Tank位置
* 5. 使用双缓冲消除闪烁现象,将所有东西画在虚拟图片上,一次性显示出来
* 6.添加键盘监听器类KeyMonitor,针对不同的键改变坦克的位置,与重画线程结合产生不同方向运动,
* 7.将坦克单独包装成类,
* 8.让主战坦克向8个方向行走,
* 9主战坦克向8个方向行走,并处理键抬起的消息
* 10.添加子弹类
* 11.根据主战坦克的方向和位置,打出子弹
* 12.解决坦克停下也能打出炮弹的问题—画出炮筒,步骤一:Tank类增加新的属性ptDir
步骤二:每次move后根据Tank新的方向确定炮筒的方向
步骤三:将炮筒用直线的形式表现出来
* 13.打出多发炮弹 步骤一:使用容器装炮弹,步骤二:每当抬起Ctrl键就往容器中加入新的炮弹,步骤三:逐一画出每一发炮弹
* 14.解决炮弹不消亡的问题,解决坦克出界的问题。步骤一:加入控制炮弹生死的量bLive(Missle)
* 步骤二:当炮弹已经死去就不需要对其重画,步骤三:当炮弹飞出边界就死亡,步骤四:当炮弹死亡就从容器中去除
*15 画一辆敌人的坦克,步骤一:加入区别敌我的量good,步骤二:根据敌我的不同设置不同的颜色
* 步骤三:更新Tank的构造函数,加入good,步骤四:TankClient中new 出敌人的坦克并画出
*16 一颗子弹击中敌人坦克 步骤一:Missle中加入hitTank(Tank)方法,返回布尔类型;步骤二:碰撞检测的辅助类Rectangle
*步骤三:为Tank和Missle都加入getRect方法 步骤四:当击中敌人坦克时,坦克被打死,子弹也死去
*步骤五:增加控制Tank生死的量live 步骤六:如果死去就不画了
*17 加入爆炸:步骤一:添加爆炸类,步骤二:爆炸应存在于集合类中,步骤三:击毙一辆坦克后应产生爆炸
*18添加多辆坦克,步骤一:用容器来装敌人的Tank,步骤二:向容器中装入多辆敌人Tank,步骤三:画出来
*19.让敌军坦克更加智能,步骤一:让敌军坦克动起来,步骤二:让敌军坦克向随机方向移动,步骤三:让敌军坦克向随机方向移动随机的步骤
* 步骤四:让敌军坦克发射炮弹 步骤五:敌军炮火不能太猛烈
*20添加两堵墙
*21坦克不能互相穿越
*22超级炮弹
*23,主战坦克的生命值
*24 画出主坦克的血块
这个程序总共五个类:
先发TankClient.java这个类
1 package cn.shaoyangjiang.com;
2 import java.awt.*;
3 import java.awt.event.KeyAdapter;
4 import java.awt.event.KeyEvent;
5 import java.awt.event.WindowAdapter;
6 import java.awt.event.WindowEvent;
7 import java.util.List;
8 import java.util.ArrayList;
9
10 public class TankClient extends Frame{
11 //游戏的屏幕宽度
12 public static final int GAME_WIDTH = 800;
13 public static final int GAME_HEIGHT = 600;
14
15 Tank myTank = new Tank(50,200,true,Tank.Driection.STOP,this);
16 //设置两堵墙
17 Wall w1 = new Wall(100, 200, 20, 150, this), w2 = new Wall(300, 100, 300, 20, this);
18 //泛型,new出一个容器放子弹,爆炸,坦克
19 List<Missile> missiles = new ArrayList<Missile>();
20 List<Explode> explodes = new ArrayList<Explode>();
21 List<Tank> tanks = new ArrayList<Tank>();
22
23 Image offScreenImage = null;
24
25 //绘制容器 ,画坦克和子弹
26 public void paint(Graphics g){
27 //当坏坦克数量小于3时,再出来5辆坦克
28 if(tanks.size()<3){
29 for(int i=0; i <5;i++){
30 tanks.add(new Tank(50 + 40*(i+1),50,false ,Tank.Driection.D,this));
31 }
32 }
33 //这句是用来测试missiles里可以放多少子弹
34 g.drawString("missiles count:" + missiles.size(), 10, 50);
35 g.drawString("explodes count:" + explodes.size(), 10, 70);
36 g.drawString("tanks count" + tanks.size(), 10, 90);
37 g.drawString("tank life" + myTank.getLife(),10,110);
38
39 //挨个画ArrayList容器里的子弹,逐一画出每一发炮弹
40 for( int i=0; i<missiles.size();i++){
41 Missile m = missiles.get(i);
42 //TankClient里面的每发子弹都打tanks
43 m.hitTanks(tanks);
44 m.hitTank(myTank);
45 m.hitWall(w1);
46 m.hitWall(w2);
47 m.draw(g);
48 }
49
50 for(int i = 0; i < explodes.size(); i++){
51 Explode e = explodes.get(i);
52 e.draw(g);
53 }
54
55 for(int i = 0;i < tanks.size();i++){
56 Tank t = tanks.get(i);
57 t.collidesWithWall(w1);
58 t.collidesWithWall(w2);
59 t.collidesWithTanks(tanks);
60 t.draw(g);
61 }
62 //画出自己的坦克
63 myTank.draw(g);
64 w1.draw(g);
65 w2.draw(g);
66 }
67
68 // 使用双缓冲消除闪烁现象,将所有东西画在虚拟图片上,一次性显示出来
69 public void update(Graphics g) {
70 if(offScreenImage == null) {
71 offScreenImage = this.createImage(GAME_WIDTH, GAME_HEIGHT);
72 }
73 Graphics gOffScreen = offScreenImage.getGraphics();
74 Color c = gOffScreen.getColor();
75 gOffScreen.setColor(Color.GREEN);
76 gOffScreen.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
77 gOffScreen.setColor(c);
78 paint(gOffScreen);
79 g.drawImage(offScreenImage, 0, 0, null);
80 }
81
82 public void lauchFrame(){
83 //增加坏坦克
84 for(int i=0; i <12;i++){
85 tanks.add(new Tank(50 + 40*(i+1),50,false ,Tank.Driection.D,this));
86 }
87
88 this.setTitle("邵洋江 vs tank");
89 this.setSize(GAME_WIDTH, GAME_HEIGHT);
90 this.setLocation(100, 100);
91 this.setBackground(Color.GREEN);
92 //用户不能改变窗口大小
93 this.setResizable(false);
94 this.setVisible(true);
95 this.addWindowListener( new WindowAdapter() {
96 public void windowClosing(WindowEvent e) {
97 System.exit(0);
98 }
99
100 });
101 this.addKeyListener(new KeyMonitor());
102 //4.启动线程
103 new Thread(new PaintThread()).start();
104 }
105
106 public static void main(String[] args) {
107 TankClient tc = new TankClient();
108 tc.lauchFrame();
109 }
110
111 //每100毫秒重画一次
112 private class PaintThread implements Runnable{
113 public void run() {
114 while(true){
115 repaint();
116 try {
117 Thread.sleep(50);
118 } catch (InterruptedException e) {
119 e.printStackTrace();
120 }
121 }
122
123 }
124 }
125
126 //6让坦克动起来,添加键盘监听器类KeyMonitor,针对不同的键改变坦克的位置,与重画线程结合产生不同方向运动,
127 private class KeyMonitor extends KeyAdapter{
128 //按键按下
129 public void keyPressed(KeyEvent e) {
130 myTank.keyPressed(e);
131 }
132 //按键弹起
133 public void keyReleased(KeyEvent e){
134 myTank.keyReleased(e);
135 }
136 }
137
138
139 }
第二个类是Tank.java类
package cn.shaoyangjiang.com;
//可以方便的实现new出100辆坦克
/*
* 建立Tank类
为Tank类添加成员变量x y
添加draw方法,使Tank类独立控制自己的画法
添加Tank类处理按键的方法
根据Tank类修改TankClient类
*/
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class Tank {
//定义坦克移动常量
private int x,y;
private int oldx,oldy;
public static final int XSPEED = 5;
public static final int YSPEED = 5;
public static final int WIDTH = 30;
public static final int HEIGHT = 30;
//定义四个按键状态
private boolean bL = false, bU = false, bR = false,bD = false;
//添加代表方向的枚举
enum Driection {L,LU,U,RU,R,RD,D,LD,STOP};
//指定一个初始的状态
private Driection dir = Driection.STOP;
//指定炮筒的初始位置
private Driection ptDir = Driection.D;
//持有对象的应用
TankClient tc;
//区分是自己的坦克还是敌人的坦克
private boolean good;
//坦克的生命值
private int life = 100;
//new出血块
private BloodBar bb = new BloodBar();
//这个方法是为其他类访问private的life
public int getLife() {
return life;
}
public void setLife(int life) {
this.life = life;
}
public boolean isGood() {
return good;
}
public void setGood(boolean good) {
this.good = good;
}
//增加控制Tank生死的量live
private boolean live = true;
private static Random r = new Random();
private int step = r.nextInt(12) + 3;
public Tank(int x, int y, boolean good){
this.x = x;
this.y = y;
this.oldx = x;
this.oldy = y;
this.good=good;
}
//方法的覆盖
public Tank(int x, int y,boolean good, Driection dir,TankClient tc) {
this(x,y,good);
this.dir = dir;
this.tc = tc;
}
//graphics指画笔,这里是画坦克
public void draw(Graphics g){
//如果坦克死了就不画了
if(!live){
if(!good){
tc.tanks.remove(this);
}
return;
}
//如果是好的就画出血块
if(good){
bb.draw(g);
}
//取出前景色
Color c = g.getColor();
if(good) g.setColor(Color.RED);
else g.setColor(Color.BLUE);
//小坦克,前两个参数指定坐标,第三个是宽度,第四个是高度。
g.fillOval(x, y , WIDTH, HEIGHT);
//设回前景色
g.setColor(c);
//画一根线代表炮筒,并确定方向
switch(ptDir) {
case L:
g.drawLine(x + Tank.WIDTH/2, y + Tank.HEIGHT/2, x, y + Tank.HEIGHT/2);
break;
case LU:
g.drawLine(x + Tank.WIDTH/2, y + Tank.HEIGHT/2, x, y);
break;
case U:
g.drawLine(x + Tank.WIDTH/2, y + Tank.HEIGHT/2, x + Tank.WIDTH/2, y);
break;
case RU:
g.drawLine(x + Tank.WIDTH/2, y + Tank.HEIGHT/2, x + Tank.WIDTH, y);
break;
case R:
g.drawLine(x + Tank.WIDTH/2, y + Tank.HEIGHT/2, x + Tank.WIDTH, y + Tank.HEIGHT/2);
break;
case RD:
g.drawLine(x + Tank.WIDTH/2, y + Tank.HEIGHT/2, x + Tank.WIDTH, y + Tank.HEIGHT);
break;
case D:
g.drawLine(x + Tank.WIDTH/2, y + Tank.HEIGHT/2, x + Tank.WIDTH/2, y + Tank.HEIGHT);
break;
case LD:
g.drawLine(x + Tank.WIDTH/2, y + Tank.HEIGHT/2, x, y + Tank.HEIGHT);
break;
}
move();
}
//坦克回到原来一步
private void stay(){
x = oldx;
y = oldy;
}
//坦克移动的方法
void move() {
//把坦克的当前位置给oldx,oldy
this.oldx = x;
this.oldy = y;
switch(dir) {
case L :
x = x - XSPEED;
break;
case LU :
x = x - XSPEED;
y = y - YSPEED;
break;
case U :
y = y - YSPEED;
break;
case RU :
x = x + XSPEED;
y = y - YSPEED;
break;
case R :
x = x + XSPEED;
break;
case RD :
x = x + XSPEED;
y = y + YSPEED;
break;
case D :
y = y + YSPEED;
break;
case LD :
x = x - XSPEED;
y = y + YSPEED;
break;
case STOP :
break;
}
//当坦克的方向改变时,炮筒的方向也改变。
if(this.dir!= Tank.Driection.STOP) {
this.ptDir = this.dir;
}
//防止坦克出界
if(x < 0) x = 0;
if(y < 30) y = 30;
if(x + Tank.WIDTH > TankClient.GAME_WIDTH) x = TankClient.GAME_WIDTH - Tank.WIDTH;
if(y + Tank.HEIGHT > TankClient.GAME_HEIGHT) y = TankClient.GAME_HEIGHT - Tank.HEIGHT;
if(!good){
//把enum(枚举)里的方向转化为数组
Driection[] dirs = Driection.values();
if(step == 0){
step = r.nextInt(12)+3;
//随机产生一个数
int rn = r.nextInt(dirs.length);
dir = dirs[rn];
}
step--;
if(r.nextInt(40) > 36) this.fire();
}
}
//6让坦克动起来,添加键盘监听器类KeyMonitor,针对不同的键改变坦克的位置,与重画线程结合产生不同方向运动,
//根据按键状态改变坦克的方向
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
switch(key){
case KeyEvent.VK_LEFT:
bL = true;
break;
case KeyEvent.VK_RIGHT:
bR = true;
break;
case KeyEvent.VK_UP:
bU = true;
break;
case KeyEvent.VK_DOWN:
bD = true;
break;
}
locationDircation();
}
//按键释放时,坦克停止
public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();
switch(key){
//按下F2重新出来自己的好坦克
case KeyEvent.VK_F2:
if(!this.live){
this.live = true;
life = 100;
}
break;
//当ctrl弹起来的时候发出子弹
case KeyEvent.VK_CONTROL:
fire();
break;
case KeyEvent.VK_LEFT:
bL = false;
break;
case KeyEvent.VK_RIGHT:
bR = false;
break;
case KeyEvent.VK_UP:
bU = false;
break;
case KeyEvent.VK_DOWN:
bD = false;
break;
case KeyEvent.VK_A:
superfire();
break;
}
locationDircation();
}
//确定坦克的位置
void locationDircation(){
if( bL && !bU && !bR && !bD ) dir = Driection.L;
else if( bL && bU && !bR && !bD ) dir = Driection.LU;
else if( !bL && bU && !bR && !bD ) dir = Driection.U;
else if( !bL && bU && bR && !bD ) dir = Driection.RU;
else if( !bL && !bU && bR && !bD ) dir = Driection.R;
else if( !bL && !bU && bR && bD ) dir = Driection.RD;
else if( !bL && !bU && !bR && bD ) dir = Driection.D;
else if( bL && !bU && !bR && bD ) dir = Driection.LD;
else if( !bL && !bU && !bR && !bD ) dir = Driection.STOP;
}
//打出子弹的方法
public Missile fire() {
if(!live) return null;
int x = this.x + Tank.WIDTH/2 -Missile.WIDTH/2;
int y = this.y + Tank.HEIGHT/2 - Missile.HEIGHT/2;
//根据炮筒的方向
Missile m = new Missile(x,y ,good,ptDir,this.tc);
//往容器里装子弹
tc.missiles.add(m);
return m;
}
//根据dir(有八个方向)的方向发出子弹
public Missile fire(Driection dir) {
if(!live) return null;
int x = this.x + Tank.WIDTH/2 - Missile.WIDTH/2;
int y = this.y + Tank.HEIGHT/2 - Missile.HEIGHT/2;
//根据dir的方向
Missile m = new Missile(x, y, good, dir, this.tc);
tc.missiles.add(m);
return m;
}
//碰撞检测的辅助类Rectangle,并加入getRect方法
public Rectangle getRect() {
return new Rectangle(x,y,WIDTH,HEIGHT);
}
//坦克不能穿越墙
public boolean collidesWithWall(Wall w) {
if(this.live && this.getRect().intersects(w.getRect())) {
//坦克回到上一步停住
this.stay();
this.dir = Driection.STOP;
return true;
}
return false;
}
//坦克不能互相穿越
public boolean collidesWithTanks(java.util.List<Tank> tanks) {
for(int i=0; i<tanks.size(); i++) {
Tank t = tanks.get(i);
if(this != t) {
if(this.live && t.isLive() && this.getRect().intersects(t.getRect())) {
this.stay();
t.stay();
return true;
}
}
}
return false;
}
//按八个方向打子弹的方法
public void superfire(){
Driection[] dirs = Driection.values();
for(int i =0;i < 8;i ++){
fire(dirs[i]);
}
}
//血块
private class BloodBar {
public void draw(Graphics g) {
Color c = g.getColor();
g.setColor(Color.RED);
g.drawRect(x, y-10, WIDTH, 10);
int w = WIDTH * life/100 ;
g.fillRect(x, y-10, w, 10);
g.setColor(c);
}
}
public boolean isLive() {
return live;
}
public void setLive(boolean live) {
this.live = live;
}
}
第三个类是子弹类,Missile.java类
1 package cn.shaoyangjiang.com;
2 import java.awt.*;
3 import java.util.List;
4
5 /*
6 * 子弹的类
7 *
8 */
9 public class Missile {
10
11 public static final int XSPEED = 10;
12 public static final int YSPEED = 10;
13 public static final int WIDTH = 10;
14 public static final int HEIGHT = 10;
15
16 int x,y;
17 Tank.Driection dir;
18 private boolean live = true;
19 private TankClient tc;
20 private boolean good;
21
22 public Missile(int x, int y,Tank.Driection dir) {
23 this.x = x;
24 this.y = y;
25 this.dir = dir;
26 }
27
28 //方法的重载
29 public Missile(int x, int y,boolean good,Tank.Driection dir,TankClient tc){
30 this(x,y,dir);
31 this.good = good;
32 this.tc = tc;
33 }
34 //绘制容器 ,画出一个子弹,设置大小和颜色
35 public void draw(Graphics g){
36 if(!live){
37 tc.missiles.remove(this);
38 return;
39 }
40 //取出前景色
41 Color c = g.getColor();
42 g.setColor(Color.BLACK);
43 //小坦克,前两个参数指定坐标,第三个是宽度,第四个是高度。
44 g.fillOval(x, y , WIDTH, HEIGHT);
45 //设回前景色
46 g.setColor(c);
47 move();
48 }
49
50 //坦克移动的方法
51 private void move() {
52 switch(dir) {
53 case L :
54 x = x - XSPEED;
55 break;
56 case LU :
57 x = x - XSPEED;
58 y = y - YSPEED;
59 break;
60 case U :
61 y = y - YSPEED;
62 break;
63 case RU :
64 x = x + XSPEED;
65 y = y - YSPEED;
66 break;
67 case R :
68 x = x + XSPEED;
69 break;
70 case RD :
71 x = x + XSPEED;
72 y = y + YSPEED;
73 break;
74 case D :
75 y = y + YSPEED;
76 break;
77 case LD :
78 x = x - XSPEED;
79 y = y + YSPEED;
80 break;
81 case STOP :
82 break;
83 }
84 //当子弹飞出边界的时候,把子弹从容器里移除
85 if(x < 0 || y < 0 || x > TankClient.GAME_WIDTH || y > TankClient.GAME_HEIGHT){
86 live = false ;
87
88 }
89 }
90
91 public boolean isLive() {
92 return live;
93 }
94
95 //碰撞检测的辅助类Rectangle,并加入getRect方法
96 public Rectangle getRect() {
97 return new Rectangle(x,y,WIDTH,HEIGHT);
98 }
99
100 //Missle中加入hitTank(Tank)方法,返回布尔类型
101 public boolean hitTank(Tank t){
102 //当击中敌人坦克时,坦克被打死,子弹也死去
103 if(this.live && this.getRect().intersects(t.getRect()) && t.isLive() && this.good != t.isGood()) {
104 //如果坦克是好的,当击中坦克的时候,生命值减20,当生命值小于0的时候,坦克死去,坏坦克打一下就死
105 if(t.isGood()) {
106 t.setLife(t.getLife()-20);
107 if(t.getLife() <= 0) t.setLive(false);
108 } else {
109 t.setLive(false);
110 }
111 this.live = false;
112 //当打中坦克时,new出爆炸。
113 Explode e = new Explode(x,y,tc);
114 //坦克里加入爆炸
115 tc.explodes.add(e);
116 return true;
117 }
118 return false;
119 }
120
121 //打一系列tank的方法
122 public boolean hitTanks(List<Tank> tanks){
123 for(int i = 0;i < tanks.size();i++){
124 if(hitTank(tanks.get(i))){
125 return true;
126 }
127 }
128 return false;
129 }
130
131 //子弹打击墙,当子弹和墙相交时,子弹死去
132 public boolean hitWall(Wall w) {
133 if(this.live && this.getRect().intersects(w.getRect())) {
134 this.live = false;
135 return true;
136 }
137 return false;
138 }
139 }
第四个类是爆炸类,Explode.java
1 package cn.shaoyangjiang.com;
2
3 import java.awt.*;
4 //添加爆炸类
5 public class Explode {
6
7 int x,y;
8 //设置圆的直径,给爆炸做效果用
9 int[] diameter = {4,7,12,18,26,32,49,30,14,6};
10 //申明爆炸是否活着
11 private boolean live = true;
12 int step = 0;
13 private TankClient tc;
14
15 Explode(int x,int y,TankClient tc){
16 this.x = x;
17 this.y = y;
18 this.tc = tc;
19 }
20
21 public void draw(Graphics g){
22 //如果爆炸死了,移除爆炸
23 if(!live) {
24 tc.explodes.remove(this);
25 return;
26 }
27 //判断是否到了最后一部
28 if(step ==diameter.length){
29 live = false;
30 step = 0;
31 return;
32 }
33
34 Color c = g.getColor();
35 g.setColor(Color.ORANGE);
36 g.fillOval(x, y, diameter[step],diameter[step]);
37 g.setColor(c);
38
39 step++;
40
41 }
42 }
第五个类是墙壁类,Wall.java
1 package cn.shaoyangjiang.com;
2 import java.awt.*;
3 //墙这个类
4 public class Wall {
5 int x, y, w, h;
6 TankClient tc ;
7
8 public Wall(int x, int y, int w, int h, TankClient tc) {
9 this.x = x;
10 this.y = y;
11 this.w = w;
12 this.h = h;
13 this.tc = tc;
14 }
15
16 //画墙这个方法
17 public void draw(Graphics g) {
18 g.fillRect(x, y, w, h);
19 }
20 //碰撞检测的辅助类Rectangle,并加入getRect方法
21 public Rectangle getRect() {
22 return new Rectangle(x, y, w, h);
23 }
24 }
演示效果图: