第一个小项目——坦克大战

坦克大战

TankWar0.1
该版本要做的很简单,建立一个窗口就可以,原始的大的类继承Frame方法,在main函数可以对原始大类进行初始化或赋值,大类中定义一个方法launch,主函数中定义类调用这个方法就完成了。

TankWar0.2
增加窗口监听器并设置不能改变窗口大小,还可以设置标题

this.setResizable(false);
this.setTitle("TankWar");

TankWar0.3
在已有的窗口内画一个坦克,需要重写paint方法,paint方法在TankClient类中会自动调用,坦克是个实心圆,用g.fillOval(位置)
遗留问题:设置三原色的代码是什么?

TankWar0.4
让坦克动起来,用线程的方法,另起一个类MoveThread(该类只在TankClient类中内部调用,所以可以控制其是private),该类实现Runnable,重写run方法,做个死循环,调用repaint(),因为该类是TankClient的内部类,所以调用repaint()会自动调用TankClient或其父类的repaint()方法,用Thread.sleep(50)控制其50毫秒重画一次。
值得注意的是该版本在paint方法画坦克的地方位置改成变量xy,用xy控制坦克坐标,控制每重画一次怎么移动。

TankWar0.41
sleep里面2000毫秒看不到闪烁,20毫秒闪烁严重,原因:重画的间隔很满的话,paint还没完成就又要重画了,比如paint一个坦克要30毫秒,20毫秒的刷新间隔,没画完一个坦克就又要开始画下一个了
该版本需要解决屏幕闪烁问题,用到的是双缓冲,即画一个虚拟屏幕,每次repaint时会先调用update再由update调用paint,重写update方法就不会自动调动paint方法,放在一张虚拟图片上,一次性画出来,就不会出现坦克画到一半就消失画下一个坦克的现象,定义Image OffScreenImage = null;拿到虚拟屏幕的画笔
Graphics gOffScreen = OffScreenImage.getGraphics();
然后paint(gOffScreen);
0.41中画了开始连续,中间正常,后面又连续的问题解决了,因为我把update中的fillRect写成来fillOval,如果是这样,前面出现实线的原因是update只重画oval区域,那段会一直刷新,不在这段范围内的上下区域就会出现实线。

TankWar0.5
该版本只需要把窗口的高度和宽度换成一个公共静态常量:

	public static int GAME_WIDTH = 800;
	public static int GAME_HEIGHT = 600;

这样的话以后要改变窗口大小只需要改一处地方
TankWar0.6
该版本需要增加上下左右四个方向键以供玩家操控,取消原来实现坦克自动移动的测试代码,用键盘监听器控制:

private class KeyMonitor extends KeyAdapter{
		public void keyPressed(KeyEvent key) {
			int k = key.getKeyCode();
			switch(k) {
			case KeyEvent.VK_UP :y-=5;break;
			case KeyEvent.VK_DOWN :y+=5;break;
			case KeyEvent.VK_LEFT :x-=5;break;
			case KeyEvent.VK_RIGHT :x+=5;break;
			}
		}
		
	}

注意key.getkeyCode方法和KeyEvent.VK_UP等方法书写记忆

TankWar0.7
该版本需要画出100辆坦克,运用面向对象思维,建一个Tank类,Tank需要完成自己画自己这件事,因为原来的那个类里面已经没有xy变量了,所以还要把键盘监听器的方法放进去,在原始类中监听器只需要调用Tank类中的方法,老师讲的在Tank中该方法名为keyPressed,其实不一定要这个名字,随便一个名字就可以,Tank中的该方法主要是为了能让方法内的代码被原始类(TankClient)中的监听器调用。

TankWar0.8-0.9
该版本新增四个方向,对Tank这个类改动很大,新增的上右,右下,下左,左上和上下左右四个方向和一个STOP建立枚举类型:

	enum direction{U,UR,R,RD,D,DL,L,LU,STOP};
	
	private direction dir = direction.STOP;

新建四个boolean类型上下左右按钮都是false,当按了上键,则上键变成true(由keyPressed控制),松开上键,上键按钮变成false(由keyReleased控制)所以在keyPressed方法中以前是控制方向,现在是控制这几个变量,再调用一个方法setDirection(keyReleased与之类似),该方法控制方向,用if语句判断上键是true其他都是false的话就方向向上,以此类推控制好8个方向,再由一个方法move控制移动,该方法要用witch,case语句,如果方向是向上,则让Y坐标减一个数,以此类推,在draw中调用move方法。
值得一提的是,以前控制移动都是x+=5;可以考虑把5换成:

public static final int X_SPEED = 5;

Y方向与之类似,只有要修改就更方便一点。
Thread.sleep(1000)是控制1秒钟重画一次,巨慢无比,改成20或50流畅很多。

TankWar1.0
该版本新增一个子弹,按照面向对象思维,新建一个Missile类,类中定义x,y,Tank.direction d;再通过这三个变量构造方法,类中需要自己画自己,定义方法public void DrawMissile(Graphics g)注意不要写成private,因为需要在TankClient类中调用该类中的DrawMissile方法,该方法中要有move方法,该方法可以是private的,move方法和Tank中的move方法一样,在Missile中不能直接说:
Tank.direction d = new Tank.direction();
因为direction是个枚举不是个类,可以说Tank.direction d ;
不要对d初始化,否则会报错。

TankWar1.1
该版本需要通过按Ctrl键实现子弹的发射,在Tank里面增加一个键case

KeyEvent.VK_CONTROL :
			tc.m = file();
			break;

意思是按这个键就调用file方法,file方法会返回一个Missile,记住Missile的三个成员变量是x,y,Tank.direction d ,以下是重点:
按下CTRL后,子弹初始化,所以一开始直接在TankClient中定义的一个子弹换成空,在画子弹前加个if(m!=null),否则会抛空指针错,现在想把file方法返回的Missile的值赋值给TankClient中的成员变量(该类会在paint中把子弹和坦克都画出来),可是直接访问不了,这种方法叫持有对方的引用,即在Tank中有一个成员变量

TankClient tc = null; 

重载构造方法:

public Tank(int x,int y ,TankClient tc) {
		this(x,y);
		this.tc = tc;
	}

在该构造方法中会对TankClient tc初始化,再有上面那段case里面的代码,通过在TankClient中初始化Tank,即调用Tank重载后的那个构造方法,就把在Tank中有file返回的m的信息的TankClient ts放到了TankClient类中,之后直接if(m!=null) m.DrawMissile(g);
就可以得到那个子弹了
子弹,坦克的宽度高度都设置成public static final int 类型方便修改,该类型可以通过Missile.width调用,一般成员变量都是要new一个对象出来才能通过” . ”调用,数学运算得出子弹中心和坦克中心的位置,在file中调用使每个子弹在坦克中心发射。坦克和子弹重画的先后顺序决定是哪个的图像覆盖哪个。
自己调出的bug:一直按一直放子弹最后会控制不住方向,后来发现是同时按了三个键及以上依次松开,往一个方向一直移动后只要按一下那个方向的按键,就会停下,按其他的都无效。

TankWar1.2
该版本解决了坦克停下来就不会放炮问题,新增了一个炮筒,用直线代替,switch case 方法可以对各个方向画线,用g.drawLine(),炮筒的方向就是放炮的方向,在file中的对Missile m的初始化里可以将dir换成ptDir,子弹就不会有STOP的方向
自己调出的bug:让坦克停的时候炮筒的方向不可能对着斜方向,我觉得是按两个方向键不可能做到同时释放,只要有一个晚释放了那么停下来时炮筒的方向就会是这个晚释放的方向。1.5最后发现是只要在一个方向上再按一个方向键同时按ctrl就会锁住那个方向。原因:自己猜测是Ctrl+↑等方向键会触发快捷键,把发射子弹案件改成F键就能解决该问题。

TankWar1.3
该版本须解决只有一个发射子弹的问题
引入List容器要注意直接 import java.util.;会出问题,因为之前已经有引入import java.awt.;而awt里面已经有List了,所以要重新引入import java.util.List和java.util.ArrayList。ArrayList在循环遍历时很快,每隔50ms要循坏画一次子弹,所以ArrayList要比LinkedList更有效率,LinkedList在增加删除元素时更有效率。改成容器之后还是还是需要构造方法去访问m,建立Tank和TankClient之间的联系。
还有个问题:一直按Ctrl会连续打很密集的点出问题,可以控制时间间隔出一个点,也可以改成抬起该键后打一个点,后者方法我决得不好,老师演示的就是这个方法,前者方法没讲。

TankWar1.4
解决炮弹不消亡问题,可以在Missile类中添加构造方法,引入TankClient类tc ,在move方法中判断如果炮弹越界,就调用:

if(x<0||y<0||x>TankClient.GAME_WIDTH||y>TankClient.GAME_HEIGHT) {
			tc.Missiles.remove(this);
		}

最后要在Tank中的file中初始化Missile时加上tc。
成员变量首字母应该小写
解决坦克出窗口问题,在Tank中的move方法中加上下列语句:

if(x<0)x=0;
		if(y<25)y=25;
		if(x>TankClient.GAME_WIDTH-Tank.WIDTH) x=TankClient.GAME_WIDTH-Tank.WIDTH;
		if(y>TankClient.GAME_HEIGHT-Tank.HEIGHT) y=TankClient.GAME_HEIGHT-Tank.HEIGHT;

TankWar1.5
该版本改动为新画了一个地方坦克,在Tank中添加了一个成员变量good,当good为true时画我,当其为false时画敌方坦克,这就需要改变构造函数,从最原始的构造函数开始改,之后调用了这个构造函数的地方需要注意一点,选中该原始函数右键会出来Open Call hierarchy可以看到该函数在哪里被调用过。

TankWar1.6
该版本需要让子弹接触到地方坦克后和坦克一起消失,并且保证之后的子弹经过以前坦克呆的那块区域不消失

TankWar1.7
该版本要建立爆炸类,用容器,在hitTank中新建爆炸,爆炸类中有数组模拟大小变化,圆模拟爆炸,构造方法可以引用TankClient tc,自己用draw方法画自己,在TankClient可以通过这个构造方法调用

TankWar1.8
该版本要画出更多敌人坦克来。建坦克容器,取消EnemyTank

for(int i = 0;i<Missiles.size();i++) {
			Missile m = Missiles.get(i);
//			m.hitTank(EnemyTank);
			m.hitTanks(Tanks);
			 m.DrawMissile(g);
		}

记住当初建坦克的时候,hitTank方法:

public boolean hitTank(Tank t) {
		if(this.getRect().intersects(t.getRect()) && t.isLive()) {
			t.setLive(false);
			this.live = false;
			Explode exp = new Explode(x,y,tc);
			tc.Explodes.add(exp);
		}
		return false;
	}

然后会在那个子弹的循环中调用该方法,意思是判断每个子弹是否打到坦克,所以当把敌方坦克换掉的时候要进行两重循环,本来可以嵌套循环解决,这里可以在上面那个循环中调用hitTanks方法,而该方法中完成对所有坦克的遍历,才能判断每个子弹和每个坦克的相遇情况。

TankWar1.9
该版本让每个坏坦克开始做无规则运动用随机数:

private static Random r = new Random(); 

这需要引入util.*,枚举类型转为数组代码:

if(!good) {
			direction []dirs = direction.values();
			int rn = r.nextInt(dirs.length);
			dir = dirs[rn];
		}

nextInt(9)是随机出0-8中一个整数

int num = r.nextInt(12)+3;

每次重画一次调用一次Tank里面的move,每调用一次让num–;当减到小于等于0时让num重新来个随机值,这样就可以实现沿着某个方向走一段路再换方向,为什么

if(dir!= direction.STOP) {
			this.ptDir=dir;
		}

放在move里面,不能放在setDirection里面?
放在后者里面坏坦克会改变不了炮筒的方向啊,从而改变不了子弹方向,在我的坦克里面按方向键会调用后者,但新建的坏坦克不会因为按方向键就换炮筒的方向,他不可以调用后者方法,只会调用move方法
在子弹类中新建成员变量mgood,和坦克类似,坏子弹和好子弹,hitTank条件判断里面通过

if(this.getRect().intersects(t.getRect()) && t.isLive() && this.mGood!= t.isGood())

控制坏坦克不能杀坏坦克
System.exit(0);比return null; 粗鲁

TankWar2.0
新建墙类

TankWar2.1
敌方坦克不能重叠,我自己思考的方法和老师讲的不一样,我觉得我的有优势一点,结局是一样的

TankWar2.2-2.8
加血条,血包,建血包类,敌人不能穿墙,我能,设置子弹颜色。


至此,该项目结束,此项目为学习马士兵老师JavaSE课程第一个练手项目做的笔记。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值