github地址:
坦克的开火使用策略模式,定义两个策略:
发射一颗子弹
四面发射四颗子弹
FireStrategy接口:
public interface FireStrategy extends Serializable {
void fire(Tank t);
}
DefaultFireStrategy类:
public class DefaultFireStrategy implements FireStrategy {
@Override
public void fire(Tank t) {
int bX = t.x + Tank.WIDTH/2 - Bullet.WIDTH/2;
int bY = t.y + Tank.HEIGHT/2 - Bullet.HEIGHT/2;
new Bullet(bX, bY, t.dir, t.group);
if(t.group == Group.GOOD) new Thread(()->new Audio("audio/tank_fire.wav").play()).start();
}
}
FourDirFireStrategy类:
public class FourDirFireStrategy implements FireStrategy {
@Override
public void fire(Tank t) {
int bX = t.x + Tank.WIDTH/2 - Bullet.WIDTH/2;
int bY = t.y + Tank.HEIGHT/2 - Bullet.HEIGHT/2;
Dir[] dirs = Dir.values();
for(Dir dir : dirs) {
new Bullet(bX, bY, dir, t.group);
}
if(t.group == Group.GOOD) new Thread(()->new Audio("audio/tank_fire.wav").play()).start();
}
}
将策略名作为字符串存储在property中,在需要使用某种策略时通过反射加载。
加门面思想:之前每个坦克子弹类都持有界面类的引用,现在将所有坦克子弹直接放到gamemodel类之中,不在直接与界面打交道,gamemodel起一个门面的作用(Model和View分离),让GameModel持有所有对象的引用,当其他类之间需要打交道时,都通过GameModel来沟通。
public abstract class GameObject implements Serializable {
public int x, y;
public abstract void paint(Graphics g);
public abstract int getWidth();
public abstract int getHeight();
}
中介者调停:将所有游戏里面的画面类如墙、子弹、坦克、爆炸类都抽象出一个基类gameobject类,在中介者类中定义一个list存储基类gameobject类,在每次添加墙、子弹、坦克、爆炸类时直接丢进list,在使用时循环对gameobject类的子类进行类型判断,便可知道是哪一个具体的类。
public class GameModel {
private static final GameModel INSTANCE = new GameModel();
static {
INSTANCE.init();
}
Tank myTank;
// List<Bullet> bullets = new ArrayList<>();
// List<Tank> tanks = new ArrayList<>();
// List<Explode> explodes = new ArrayList<>();
ColliderChain chain = new ColliderChain();
private List<GameObject> objects = new ArrayList<>();
public static GameModel getInstance() {
return INSTANCE;
}
private GameModel() {}
private void init() {
// 初始化主战坦克
myTank = new Tank(200, 400, Dir.DOWN, Group.GOOD);
int initTankCount = Integer.parseInt((String) PropertyMgr.get("initTankCount"));
// 初始化敌方坦克
for (int i = 0; i < initTankCount; i++) {
new Tank(50 + i * 80, 200, Dir.DOWN, Group.BAD);
}
// 初始化墙
add(new Wall(150, 150, 200, 50));
add(new Wall(550, 150, 200, 50));
add(new Wall(300, 300, 50, 200));
add(new Wall(550, 300, 50, 200));
}
public void add(GameObject go) {
this.objects.add(go);
}
public void remove(GameObject go) {
this.objects.remove(go);
}
public void paint(Graphics g) {
Color c = g.getColor();
g.setColor(Color.WHITE);
// g.drawString("子弹的数量:" + bullets.size(), 10, 60);
// g.drawString("敌人的数量:" + tanks.size(), 10, 80);
// g.drawString("爆炸的数量:" + explodes.size(), 10, 100);
g.setColor(c);
myTank.paint(g);
for (int i = 0; i < objects.size(); i++) {
objects.get(i).paint(g);
}
// 互相碰撞
for (int i = 0; i < objects.size(); i++) {
for (int j = i + 1; j < objects.size(); j++) { // Comparator.compare(o1,o2)
GameObject o1 = objects.get(i);
GameObject o2 = objects.get(j);
// for
chain.collide(o1, o2);
}
}
// for (int i = 0; i < bullets.size(); i++) {
// for (int j = 0; j < tanks.size(); j++)
// bullets.get(i).collideWith(tanks.get(j));
// }
}
public Tank getMainTank() {
return myTank;
}
public void save() {
File f = new File("c:/mashibing/tank.data");
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(f));
oos.writeObject(myTank);
oos.writeObject(objects);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public void load() {
File f = new File("c:/mashibing/tank.data");
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));
myTank = (Tank)ois.readObject();
objects = (List)ois.readObject();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
在判断类与类之间碰撞检测时,此时我们不知道类是子弹还是坦克还是墙,定义一个Collider接口,分别定义不同的类实现判断碰撞的方法,如BulletTankCollider类负责子弹和坦克的碰撞检测,TankTankCollider类负责坦克和坦克的碰撞检测。
Collider接口:
public interface Collider {
boolean collide(GameObject o1, GameObject o2);
}
BulletTankCollider类:
public class BulletTankCollider implements Collider {
@Override
public boolean collide(GameObject o1, GameObject o2) {
if(o1 instanceof Bullet && o2 instanceof Tank) {
Bullet b = (Bullet)o1;
Tank t = (Tank)o2;
//TODO copy code from method collideWith
if(b.group == t.getGroup()) return true;
if(b.rect.intersects(t.rect)) {
t.die();
b.die();
int eX = t.getX() + Tank.WIDTH/2 - Explode.WIDTH/2;
int eY = t.getY() + Tank.HEIGHT/2 - Explode.HEIGHT/2;
new Explode(eX, eY);
return false;
}
} else if (o1 instanceof Tank && o2 instanceof Bullet) {
return collide(o2, o1);
}
return true;
}
}
TankTankCollider类:
public class TankTankCollider implements Collider {
@Override
public boolean collide(GameObject o1, GameObject o2) {
if(o1 instanceof Tank && o2 instanceof Tank) {
Tank t1 = (Tank)o1;
Tank t2 = (Tank)o2;
if(t1.getRect().intersects(t2.getRect())) {
t1.back();
t2.back();
}
}
return true;
}
}
定义一个责任链ColliderChain类也实现Collider接口,里面集成所有的责任类,定义一个list放BulletTankCollider类、TankTankCollider类等,在初始化的时候写好责任链的顺序,在需要判断碰撞检测责任的时候,循环遍历所有责任类,并调用对应类的碰撞检测方法。
public class ColliderChain implements Collider {
private List<Collider> colliders = new LinkedList<>();
public ColliderChain() {
add(new BulletTankCollider());
add(new TankTankCollider());
add(new BulletWallCollider());
add(new TankWallCollider());
}
public void add(Collider c) {
colliders.add(c);
}
public boolean collide(GameObject o1, GameObject o2) {
for(int i=0; i<colliders.size(); i++) {
if(!colliders.get(i).collide(o1, o2)) {
return false;
}
}
return true;
}
}
当需要添加坦克和墙碰撞或者子弹与墙(木墙和钢墙)碰撞检测时,只需要实现Collider接口重新定义一个类,并完善类中的碰撞检测方法,最后定义不同物体之间的碰撞检测责任链顺序,即可完成任意物体之间的碰撞检测,也不需要修改原有代码,只需添加新的类。
如果我们想在坦克吃了道具后能够发射带尾巴特效的子弹,或者生成带防护罩的坦克,或者说我们希望坦克带一个血条,这时我们可以用到装饰器模式。

实际上就是抽象出一个抽象类,然后继承抽象类实例出一个具体的类,类里面有实现特效子弹和防护罩坦克的方法,而这些方法也是其他已经实现的方法组合而成,许多个方法组和成一个方法,不再需要考虑内部实现细节,只需最终结果生成,在外界看到的就是一个具体生成方法,起到装饰的作用。
GODecorator类:
public abstract class GODecorator extends GameObject {
GameObject go;
public GODecorator(GameObject go) {
this.go = go;
}
@Override
public abstract void paint(Graphics g);
}
RectDecorator类:
public class RectDecorator extends GODecorator {
public RectDecorator(GameObject go) {
super(go);
}
@Override
public void paint(Graphics g) {
this.x = go.x;
this.y = go.y;
go.paint(g);
Color c = g.getColor();
g.setColor(Color.WHITE);
g.drawRect(super.go.x, super.go.y, super.go.getWidth()+2, super.go.getHeight()+2);
g.setColor(c);
}
@Override
public int getWidth() {
return super.go.getWidth();
}
@Override
public int getHeight() {
return super.go.getHeight();
}
}
TailDecorator类:
public class TailDecorator extends GODecorator {
public TailDecorator(GameObject go) {
super(go);
}
@Override
public void paint(Graphics g) {
this.x = go.x;
this.y = go.y;
go.paint(g);
Color c = g.getColor();
g.setColor(Color.WHITE);
g.drawLine(go.x, go.y, go.x + getWidth(), go.y + getHeight());
g.setColor(c);
}
@Override
public int getWidth() {
return super.go.getWidth();
}
@Override
public int getHeight() {
return super.go.getHeight();
}
}
将我方坦克开火视为一个事件源,其他坦克作为观察者
坦克存盘,并且继续游戏使用到备忘录模式