接上篇
V0.4:坦克类初步建好了,接下来考虑子弹,这里我曾纠结子弹到底是坦克的内部类还是兄弟类。根据面向对象的分析,子弹打出后,无论坦克是否消亡,子弹都会存在,所以子弹不是坦克的内部类,所以我们新建一个子弹类Cannonball。
在这个版本中我们只是初步构造子弹类,只编写构造方法、draw方法(绘制一颗子弹,仍然是用圆表示)和move方法(子弹的飞行)。构造方法确定子弹的初始坐标和方向,move方法根据方向来不停的运动。此时我们可以再Frame中进行一下测试,new一个子弹对象,在paint方法中调用其draw方法。
V0.5:子弹类建好了那么就要开始调用了。假设我们是按ctrl键来发射子弹,那么问题来了,我们究竟在哪里创建子弹对象?一开始,我想到的是Tank类中,按键处理的地方,如果按键为ctrl,则产生一个子弹对象,但是问题又来了,在Tank类中创建的对象,我们怎么在Frame中调用它的draw方法?毕竟在是在Tank的方法里面创建的对象,而不是Tank的成员,所以没有办法调用。或许有人说,那声明为Tank的成员呢?在我的理解来说,这不行,因为这样一旦坦克对象死亡,其发出的子弹也就死亡了,可是实际坦克游戏中,即使坦克被干掉,它之前发出的子弹也仍旧存在,所以说,声明为Tank的成员不行。
在Tank中不行,那在Frame中呢?在键盘监听器调用Tank的keyPressed方法之前,先判断按键是否为ctrl?然后get到坦克对象的x,y,还有方向来new一个子弹对象?应该可以实现,但是感觉好像跟坦克的联系没有那么密切了,无法体现出坦克是子弹的生产者这一条件,不过我比较懒,只是这样整理出这个思路,并没有动手写,有兴趣的可以试试,接下来我是看视频来验证我的思路
在视频中,是用到了持有对方引用这个方法,也就是说用的是我第一个思路,解决调用问题的办法是在Tank类中持有一个Frame的引用,这样就可以直接将new出来的子弹对象赋值给Frame中声明的子弹引用。
解决了思路,那么就开始编写代码,首先在Tank的keyPressed方法中,当按ctrl 时,调用fire方法并返回一个子弹类型的对象,为了将该对象赋值给一个Frame中的引用,需要Tank持有Frame的引用,那么在Tank中添加一个Frame类的变量,修改构造方法,增加一个Frame类型的参数,然后修改Frame类中的创建语句,将this指针传递给Tank的构造方法。此时Tank类的Frame引用就指向了游戏的Frame。
然后是在Frame中声明接收子弹对象的容器。考虑到子弹不止一个,所以我声明了一个ArrayList,然后编写了一个add方法用来添加一个子弹对象。此时再返回fire的调用处,将fire的返回值当做add的参数传递出来。酱紫算是终于将创建出来的子弹对象传递到Frame手上了,接下来就好办了,在paint中遍历ArrayList,调用每个子弹对象的draw方法。
到此为止算是把“打出一发子弹”这个事件实现了。
V0.6:在这个版本中处理几个小问题。首先是子弹是从坦克左上角打出的,这是因为坦克的坐标和子弹的坐标都是左上角点的坐标,只要修改创建子弹时的参数就行了,只是个简单的几何问题,就不多说了。
然后是在按住方向键的同时,按ctrl 键,打出子弹时,坦克会停下的问题,这是因为按下ctrl 键就激发了另一个KeyEvent ,所以原本按下方向键的KeyEvent 就被打断了,我的解决办法是不再将移动这个动作写在keyPressed方法中,每当按下一个方向键,只是确定方向和开始移动(定义一个isMoving 的布尔型变量),另外写一个move方法,根据方向和isMoving 来判断如何移动,然后添加keyReleased 方法,当松开方向键时将isMoving设置为false。
最后是添加一个炮筒,没有方向打子弹总是觉得别扭有没有,添加炮筒也很简单,就是在draw方法里面调用一个drawLine 而已,稍微有点麻烦的是炮筒的方向,这里需要根据direction 也就是坦克方向的不同来计算不同的坐标值。
到V0.6版的代码如下:
TankClient.java
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
public class TankClient {
public static void main(String[] args) {
new TankFrame("TankWar").launchFrame();
}
}
class TankFrame extends Frame {
public static final int FRAME_H = 800;
public static final int FRAME_W = 600;
private ArrayList<Cannonball> cannonballs = new ArrayList<Cannonball>();
Tank myTank = new Tank(200,200,this);
public TankFrame(String s){
super(s);
}
public void launchFrame () {
setLocation(100,100);
setSize(FRAME_H,FRAME_W);
setVisible(true);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
this.addKeyListener(new KeyMonitor());
new Thread(new PaintThread()).start();
}
public void paint(Graphics g) {
myTank.draw(g);
for(Cannonball c : cannonballs){
c.draw(g);
}
}
public void addCannonball(Cannonball c) {
cannonballs.add(c);
}
class KeyMonitor extends KeyAdapter {
public void keyPressed(KeyEvent e) {
myTank.keyPressed(e);
}
public void keyReleased(KeyEvent e) {
myTank.keyReleased(e);
}
}
class PaintThread implements Runnable {
public void run() {
while(true){
repaint();
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Tank.java
import java.awt.*;
import java.awt.event.*;
public class Tank {
private static final int X_SPEED = 5;
private static final int Y_SPEED = 5;
private int tank_x = 0;
private int tank_y = 0;
private Direction direction = Direction.Up;
private TankFrame client = null;
private boolean isMoving = false;
public enum Direction {
Right,Left,Up,Down
}
public Tank(int x, int y, TankFrame client){
this.tank_x = x;
this.tank_y = y;
this.client = client;
}
public void draw(Graphics g) {
Color c = g.getColor();
g.setColor(Color.GREEN);
g.fillOval(tank_x, tank_y, 50, 50);
int x1,x2,y1,y2;
x1 = x2 = tank_x + 25;
y1 = y2 = tank_y + 25;
switch(direction){
case Up:
y1 -= 25;
y2 -= 50;
break;
case Down:
y1 += 25;
y2 += 50;
break;
case Left:
x1 -= 25;
x2 -= 50;
break;
case Right:
x1 += 25;
x2 += 50;
break;
}
g.drawLine(x1, y1, x2, y2);
g.setColor(c);
move();
}
private void move() {
if(!isMoving) return;
switch(direction) {
case Up:
tank_y -= Y_SPEED;
break;
case Down:
tank_y += Y_SPEED;
break;
case Left:
tank_x -= X_SPEED;
break;
case Right:
tank_x += X_SPEED;
break;
}
}
public Cannonball fire(){
Cannonball c = new Cannonball(tank_x + 20,tank_y + 20,direction);
return c;
}
public void keyPressed(KeyEvent e){
int code = e.getKeyCode();
switch(code) {
case KeyEvent.VK_UP:
direction = Direction.Up;
isMoving = true;
break;
case KeyEvent.VK_DOWN:
direction = Direction.Down;
isMoving = true;
break;
case KeyEvent.VK_LEFT:
direction = Direction.Left;
isMoving = true;
break;
case KeyEvent.VK_RIGHT:
direction = Direction.Right;
isMoving = true;
break;
case KeyEvent.VK_CONTROL:
client.addCannonball(fire());
break;
}
}
public void keyReleased(KeyEvent e) {
int code = e.getKeyCode();
switch(code) {
case KeyEvent.VK_UP:
case KeyEvent.VK_DOWN:
case KeyEvent.VK_LEFT:
case KeyEvent.VK_RIGHT:
isMoving = false;
break;
}
}
}
Cannonball.java
import java.awt.*;
public class Cannonball {
private int x = 0;
private int y = 0;
private Tank.Direction direction = null;
private static final int X_SPEED = 10;
private static final int Y_SPEED = 10;
public Cannonball(int x, int y, Tank.Direction dir) {
this.x = x;
this.y = y;
this.direction = dir;
}
public void draw(Graphics g) {
Color c = g.getColor();
g.setColor(Color.BLACK);
g.fillOval(x, y, 10, 10);
g.setColor(c);
move();
}
private void move() {
switch(direction) {
case Up :
y -= Y_SPEED;
break;
case Down :
y += Y_SPEED;
break;
case Left :
x -= X_SPEED;
break;
case Right :
x += X_SPEED;
break;
}
}
}