飞机大战
这个让我想起,许多培训机构都会有一个项目“飞机大战”,读书那会儿也浏览了该代码;下载图片资源,发现这个资源与我读书时看得不一样,但可以使用,资源重整了一下
ShootGame射击游戏
1.给6个对象类添加图片属性,共有的读取图片属性在FlyingObject父类里面提取。
- 分别打开子弹、天空、英雄机、小敌机、大敌机、蜜蜂6个子类。
- Bullet、Sky类里面最上面添加子弹图片的属性,设为静态的,以及给image赋值放在静态块中,但是静态块中先不赋值。
- Hero类里面最上面添加子弹图片的属性,设为静态的,以及给image赋值放在静态块中,但是静态块中也先不赋值。
- 打开FlyingObject父类,读图片的行为子类共有的,放在父类里面,完成loadImage()方法。
- Bullet、Sky、Hero里面完成静态块里面图片的赋值。
- Airplane、BigAirplane、Bee类里面最上面添加子弹图片的属性,设为静态的,以及在静态块中给image。
2.ShootGame中—窗体的宽(512)、高(768)做成常量(适合)。
- World类里声明窗口宽、高为常量,主方法里面窗体大小修改为WIDTH,HEIGHT。
- Sky修改构造方法里面super(参数)改为World.WIDTH,World.HEIGHT、y1的坐标-this.height
- FlyingObject修改构造方法的x坐标World.WIDTH-this.width
3.画对象
3.1、获取对象图片
想画对象得先得到对象的图片,而我们分析每个对象都能得到图片,意味着得图片的行为,为所有派生类所共有的。
所以将得图片的行为设计在超类FlyingObject中,每个对象得到图片的行为都是不一样的(在派生类中需要重写getImage()),所以设计为抽象方法。
---行为不一样原因?看步骤3.3。
3.2、需要在派生类分别进行一一重写getImage()。
- 射击游戏分别找6个对象列,进行重写。
- 重写之前先分析对象的状态。步骤3.3
3.2.1、天空Sky,直接返回image
3.2.2、子弹Bullet
(1)若活着,直接返回image
(2)若死了,则删除
---3.3.1步骤看对象的三种状态
3.2.3、英雄机Hero:
(1)若活着呢,返回images[0]和images[1]切换
(2)若死了呢,游戏结束了,不需要考虑图片
3.2.4、小敌机Airplane:
(1)若活着呢,直接返回images[0]
(2)若死了呢,images[1]到images[4]轮换,4后删除
3.2.5、大敌机BigAirplane:
(1)若活着呢,直接返回images[0]
(2)若死了呢,images[1]到images[4]轮换,4后删除
3.2.6、小蜜蜂Bee:
(1)若活着呢,直接返回images[0]
(2)若死了呢,images[1]到images[4]轮换,4后删除
//所以每个对象得到图片的行为都不一样,回到步骤3.1、FlyingObject中设计
//抽象方法getImage()。
//所有子类重写getImage()方法完成后到步骤3.4继续操作。
3.3、对象状态
3.3.1、分为三种
---活着的
---死了的(还没有删除,有爆破效果)
---删除的
回到3.2.3步骤。。。
3.3.2、获取图片时需要考虑对象的状态
因为需要在不同状态下得到不同的图片,所以需要设计对象的状态。
而每个对象都有状态,所以将状态设计在超类FlyingObject中,状态在实际项目中一般都设计为常量,后面不需要修改。
---在FlyingObject中设计LIFE、DEAD、REMOVE,state表示当前状态,四种属性。
因为每个对象都需要去判断它当前所属的状态,所以判断状态的行为为共有的, 需要设计在超类中。
又因每个对象判断状态的行为都是一样的,所以设计为普通方法。
---在FlyingObject中设计isLife(),isDead(),isRemove()三个方法。
//继续进行步骤3.2分别6个对象进行重写getImage()方法。
3.4、开始绘制
准备好图片之后可以开画了,每个对象都有画的功能,所以将画的行为
设计在超类FlyingObject中,每个对象画的行为都是一样的,所以设计为普通方法。
---在FlyingObject中设计paintObject()画笔方法。
3.5、是否需要重画
画对象的图片,除了天空都是一张图(敌人虽然不是一个但是每次画出来一个都一样),所以不需要重写。
天空Sky需要画两张图,y和y1两个坐标,需要重写超类的paintObject()。
----在Sky中重写paintObject(),图片画两次。
//完成后继续操作步骤3.6
3.6、画的行为写好之后,则需要在窗口上调用画笔方法。
想调用画笔方法,我们需要重写面板里面的画笔方法paint(),paint()方法并不需要
在主方法里调用,就因为它是自带的方法。
调用之前action()方法里面代码全部删除。
----在World类中重写paint()。
//所有完成,运行测试,背景图片、英雄机、子弹可以正常处理,3种敌人没出现,因为
//父类3种敌人构造方法里面,设置敌人坐标为负的,这样在窗体外,因此改为正的测试,测试完
//再调回负的。
太空入侵者
《电玩游戏Java实战DIY》中 "项目30 太空入侵者——太空\项目31 太空入侵者——外星人\项目32太空入侵者——复仇\项目33太空入侵者——生死较量"选中这个看一下,基本和“飞机大战”一样,可以复用
30 太空入侵者——太空
创 建 太 空 ,并 制 造 太 空 船 。
30.1 绘制天空Space
绘制一个黑色纯色背景
public class Space extends FlyingObject{
public Space(int width, int height) {
super(WIDTH, HEIGHT,0,0);
}
/**
* 重写 paintObject() 画对象
*/
public void paintObject(Graphics g) {
g.drawImage(getImage(), x, y, null);
}
@Override
public BufferedImage getImage() {
return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
}
}
30.2 绘制太空船Ship
获取hero0、hero1图片绘制,使用两个看起来有变化
public class Ship extends FlyingObject {
//添加图片属性,英雄机图片两张
private static BufferedImage[] heros;
public Ship() {
super(48, 69, 226, 562);
heros = new BufferedImage[2];
for (int i = 0; i < heros.length; i++) {
heros[i] = ReadResource.loadImage("hero" + i + ".png");
}
}
@Override
public BufferedImage getImage() {
return heros[0];
}
/**
* 英雄机移动
*
* @param x 坐标x
* @param y 坐标y
*/
public void move(int x, int y) {
//英雄机的x = 鼠标的x - this.width/2;
this.x = x - this.width / 2;
this.y = y - this.height / 2;
}
}
30.3 抽象共有对象和共有方法
共有对象
public abstract class FlyingObject {
protected int width; //宽
protected int height; //高
protected int x; //x坐标
protected int y; //y坐标
public FlyingObject(int width, int height) {
this.width = width;
this.height = height;
}
public FlyingObject(int width, int height, int x, int y) {
this.width = width;
this.height = height;
this.x = x;
this.y = y;
}
/**
* 画对象 g:画笔
*/
public void paintObject(Graphics g) {
/*
* Graphics绘制图像的一个类,理解为一个画笔
* Graphics对象封装了Java支持的基本呈现操作所需的状态信息。
*/
//参数1代表img传图片,谁调就是谁的图片,x、y为对象的坐标,null用不到,不用问。
g.drawImage(getImage(), x, y, null);
}
/**
* 获取图片
*/
public abstract BufferedImage getImage();
}
共有方法
public class ReadResource {
private static final String PATH = "/res/img/";
/**
* 调整bufferedimage大小
*
* @param fileName BufferedImage 图片名称
* @return BufferedImage 返回新image
*/
public static BufferedImage loadImage(String fileName) {
try {
BufferedImage img =
ImageIO.read(ReadResource.class.getResource(PATH + fileName));
return img;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
}
/**
* 调整bufferedimage大小
*
* @param source BufferedImage 原始image
* @param targetW int 目标宽
* @param targetH int 目标高
* @param flag boolean 是否同比例调整
* @return BufferedImage 返回新image
*/
public static BufferedImage resizeBufferedImage(BufferedImage source, int targetW, int targetH, boolean flag) {
int type = source.getType();
BufferedImage target = null;
double sx = (double) targetW / source.getWidth();
double sy = (double) targetH / source.getHeight();
if (flag && sx > sy) {
sx = sy;
targetW = (int) (sx * source.getWidth());
} else if (flag && sx <= sy) {
sy = sx;
targetH = (int) (sy * source.getHeight());
}
if (type == BufferedImage.TYPE_CUSTOM) { // handmade
ColorModel cm = source.getColorModel();
WritableRaster raster = cm.createCompatibleWritableRaster(targetW, targetH);
boolean alphaPremultiplied = cm.isAlphaPremultiplied();
target = new BufferedImage(cm, raster, alphaPremultiplied, null);
} else {
target = new BufferedImage(targetW, targetH, type);
}
Graphics2D g = target.createGraphics();
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.drawRenderedImage(source, AffineTransform.getScaleInstance(sx, sy));
g.dispose();
return target;
}
/**
* 调整bufferedimage大小
*
* @param source BufferedImage 原始image
* @return BufferedImage 返回新image
*/
public static BufferedImage resizeBufferedImageByHalf(BufferedImage source) {
return resizeBufferedImage(source, source.getWidth() / 2, source.getHeight() / 2, true);
}
}
30.4 添加到战场SpaceDestroyers
public class SpaceDestroyers extends JPanel implements MouseMotionListener, MouseListener {
public static final int WIDTH = 500; //窗口的宽
public static final int HEIGHT = 700; //窗口的高
private Space sky = new Space(); //太空背景
private Ship ship = new Ship(); //飞船
public SpaceDestroyers() {
addMouseMotionListener(this);
addMouseListener(this);
}
@Override
public void mouseDragged(MouseEvent event) {
}
@Override
public void mouseMoved(MouseEvent event) {
int x = event.getX();
int y = event.getY();
ship.move(x, y);
repaint();
}
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) { //鼠标移入事件
}
@Override
public void mouseExited(MouseEvent e) { //鼠标移出事件
}
}
30.5 运行效果
游戏入口RunGame
public class RunGame {
public static void main(String[] args) {
JFrame frame = new JFrame();
SpaceDestroyers space = new SpaceDestroyers();;
frame.add(space);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(space.WIDTH, space.HEIGHT);
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
}
}
31 太空入侵者——外星人
31.1 生成敌机Bee
1》父类添加“生、死”状态,和绘制坐标变化step()
2》被子弹击中爆炸图片播放
public class Bee extends FlyingObject {
private static BufferedImage[] bees;
static {
bees = new BufferedImage[5];
bees[0] = ReadResource.loadImage("bee1.png");
for (int i = 1; i < bees.length; i++) {
bees[i] = ReadResource.loadImage("bom" + i + ".png");
}
}
private int xSpeed; //x坐标移动速度
private int ySpeed; //y坐标移动速度
private int deadIndex = 1; // 死了,爆炸开始的下标
public Bee() {
super(48, 41);
xSpeed = 1;
ySpeed = 2;
}
@Override
public void step() {
x += xSpeed; //x坐标移动速度(或左或又)
y += ySpeed; //y坐标移动速度
if (x <= 0 || x >= WIDTH) {
xSpeed *= -1; //切换方向
}
}
@Override
public BufferedImage getImage() { //每10毫秒走一次
if (isLife()) { //若活着呢
return bees[0]; //返回images[0]
} else if (isDead()) {
BufferedImage img = bees[deadIndex++]; //从第2张开始轮转
if (deadIndex == bees.length) { //若到最后一张了
state = REMOVE; //则修改当前状态为删除状态
}
return img;
}
return null;
}
@Override
public boolean outOfBounds() {
//蜜蜂越界 = 蜜蜂的y >= 窗体的高
return this.y >= HEIGHT;
}
}
31.2 将敌机添加到战场SpaceDestroyers
private FlyingObject[] enemies = {}; //敌机(小蜜蜂)数组
private int enterindex = 0; //敌人数组角标
// 游戏状态
public static final int START = 0; //开始状态
public static final int RUNNING = 1; //运行状态
public static final int PAUSE = 2; //暂停状态
public static final int GAME_OVER = 3; //结束状态
public int state = START;// 默认状态
//生成敌人进行储存
public void populateEneraies() {//每10毫秒
enterindex++;
if (enterindex % 40 == 0) {//限制敌人的出场速率,每400毫秒走一次
//获取敌人对象
FlyingObject obj = generateEnemy();
//将敌人数组进行扩容
enemies = Arrays.copyOf(enemies, enemies.length + 1);
//将敌人对象添加到数组中
enemies[enemies.length - 1] = obj;
//System.out.println("敌人数量:"+enemies.length);
}
}
//生成敌人(蜜蜂)对象;可以增加敌人种类如Boss等
public FlyingObject generateEnemy() {
return new Bee();
}
@Override
public void paint(Graphics g) {
sky.paintObject(g); // 绘制太空背景
ship.paintObject(g); // 绘制飞船
for (int i = 0; i < enemies.length; i++) { //遍历所有敌人
enemies[i].paintObject(g); //绘制敌人
}
}
//定时器
public void action() {
java.util.Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if (state == RUNNING) {
stepAction(); //飞行物移动
repaint(); //重绘
}
}
}, 10, 10);
}
//飞行物移动
public void stepAction() {
populateEneraies(); //生成敌机
sky.step();
for (int i = 0; i < enemies.length; i++) {
enemies[i].step();
}
}
- java.util.Timer定时器自动生成敌机
- repaint() 绘制放到定时器,并添加游戏状态
31.2 添加子弹Bullet
public class Bullet extends FlyingObject {
private static BufferedImage bullet; //添加子弹图片
static {
bullet = ReadResource.loadImage("bullet.png");
}
private int speed; //移动速度
public Bullet(int x, int y) {
super(8, 20, x, y);
speed = 3;
}
@Override
public void step() {
y -= speed;
}
@Override
public BufferedImage getImage() {
if (isLife()) { //活着的
return bullet; // 直接返回image
} else if (isDead()) { // 死了的
state = REMOVE; // 将状态修改为删除状态
}
return null; // 死了返回空
}
@Override
public boolean outOfBounds() {
//子弹越界 = 子弹的y <= 负的自身的高度
return this.y <= -this.height;
}
}
31.3 为Ship添加子弹Bullet的射击shoot()
//生成子弹对象,因为子弹是由英雄机发射出来
public Bullet[] shoot() {//1发 2发 或 4发
//判断子弹的火力,是单倍火力还是双倍火力
if (fire > 0) {//双倍火力
Bullet[] bs = new Bullet[2];
//在英雄机1/4的位置
bs[0] = new Bullet(this.x + this.width * 1 / 4, this.y - 20);
//在英雄机2/4的位置
bs[1] = new Bullet(this.x + this.width * 3 / 4, this.y - 20);
fire -= 2;//每生成一次双别火力,火力值减2
return bs;
} else {//单倍火力
Bullet[] bs = new Bullet[1];
/*
* 单倍火力,一颗子弹,子弹位置在英雄机的正中间
* 子弹的x = 1/2的英雄机的宽 + 英雄机的x;
* 子弹的y = 英雄机的y - 子弹的高度;
*/
bs[0] = new Bullet(this.x + this.width / 2, this.y - 20);
return bs;
}
}
31.4 将子弹添加到战场SpaceDestroyers
//生成子弹对象进行储存
public void shootAction() {
//限制子弹的出场速率,该速率要比敌人稍快
shootindex++;
if (shootindex % 30 == 0) {
//获取子弹对象
Bullet[] bs = ship.shoot();
//将子弹数组进行扩容
bullets = Arrays.copyOf(bullets, bullets.length + bs.length);
//将子弹对象添加到数组中
System.arraycopy(bs, 0, bullets, bullets.length - bs.length, bs.length);
//System.out.println(bullets.length);
}
}
@Override
public void paint(Graphics g) {
sky.paintObject(g); // 绘制太空背景
ship.paintObject(g); // 绘制飞船
for (int i = 0; i < enemies.length; i++) { //遍历所有敌人
enemies[i].paintObject(g); //绘制敌人
}
for (int i = 0; i < bullets.length; i++) { //遍历所有子弹
bullets[i].paintObject(g); //画子弹
}
}
//定时器
public void action() {
java.util.Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if (state == RUNNING) {
stepAction(); //飞行物移动
repaint(); //重绘
}
}
}, 10, 10);
}
//飞行物移动
public void stepAction() {
populateEneraies(); //生成敌机
shootAction(); //存储子弹,ship射击子弹
sky.step();
for (int i = 0; i < enemies.length; i++) {
enemies[i].step();
}
for (int i = 0; i < bullets.length; i++) {
bullets[i].step();
}
}
31.5 子弹与敌机撞上、飞船与敌机撞上
父类FlyingObject添加碰撞逻辑判断
/**
* 碰撞逻辑判断
*/
//other---代表子弹、(或者英雄机)
//this---表示敌人
public boolean hit(FlyingObject other) {
int x1 = this.x - other.width;
int x2 = this.x + this.width;
int y1 = this.y - other.height;
int y2 = this.y + this.height;
int x = other.x;
int y = other.y;
return x >= x1 && x <= x2 && y >= y1 && y <= y2;
}
/**
* 飞行物去死
*/
public void goDead() {
state = DEAD;
}
在战场SpaceDestroyers判断撞击,设为Dead状态
//子弹与敌人碰撞
public void bulletBangAction() {
//遍历获取所有子弹
for (int i = 0; i < bullets.length; i++) {
Bullet b = bullets[i];
//遍历获取所有的敌人
for (int j = 0; j < enemies.length; j++) {
FlyingObject f = enemies[j];
//具体碰撞条件
if (b.isLife() && f.isLife() && f.hit(b)) {//条件满足,代表撞上了
//敌人去死
f.goDead();
//子弹去死
b.goDead();
}
}
}
}
//飞船与敌人碰撞
public void shipBangAction() {
//遍历所有敌人
for (int j = 0; j < enemies.length; j++) {
//获取所有敌人
FlyingObject f = enemies[j];
if (ship.isLife() && f.isLife() && f.hit(ship)) {
//敌人去死
f.goDead();
//英雄机减命
ship.substractLife();
//英雄机火力值归0
ship.clearFire();
}
}
}
子弹与敌机goDead()后,在获取绘制图片getImage()时走到移除REMOVE状态;在定时器java.util.Timer添加清除
@Override
public BufferedImage getImage() {
if (isLife()) {
return bullet;
} else if (isDead()) {
state = REMOVE; // 将状态修改为删除状态
}
return null;
}
@Override
public BufferedImage getImage() { //每10毫秒走一次
if (isLife()) {
return bees[0]; //返回images[0]
} else if (isDead()) {
BufferedImage img = bees[deadIndex++]; //从第2张开始轮转
if (deadIndex == bees.length) { //若到最后一张了
state = REMOVE; //则修改当前状态为删除状态
}
return img;
}
return null;
}
31.6 在SpaceDestroyers战场清除越界和已移除的飞行物
//定时器
public void action() {
java.util.Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if (state == RUNNING) {
stepAction(); //飞行物移动
cleanFlyingAction();
repaint(); //重绘
}
}
}, 10, 10);
}
//删除越界和死亡移除的飞行物(敌机、子弹)
public void cleanFlyingAction() {
bulletBangAction();//子弹与敌人碰撞
shipBangAction();//英雄机与敌人碰撞
int index = 0; //计数
//1、删除越界和死亡移除的敌人
FlyingObject[] enemiesLife =
new FlyingObject[enemies.length];
for (int i = 0; i < enemies.length; i++) {
FlyingObject f = enemies[i];
if (!f.isRemove() && !f.outOfBounds()) {
//将不越界且没有死亡移除的敌人添加到数组中
enemiesLife[index] = f;
index++;
}
}
enemies = Arrays.copyOf(enemiesLife, index);
//2、删除越界和死亡移除的子弹
index = 0;//计数
Bullet[] bulletsLife = new Bullet[bullets.length];
for (int i = 0; i < bullets.length; i++) {
Bullet b = bullets[i];
if (!b.isRemove() && !b.outOfBounds()) {
bulletsLife[index] = b;
index++;
}
}
bullets = Arrays.copyOf(bulletsLife, index);
}
显示游戏命条数和分数
添加得分接口
/*
* 得分接口;可为不同敌机实现的不同得分
*/
public interface Enemey {
public int getScore();
}
敌机继承接口public class Bee extends FlyingObject implements Enemey
子弹与敌人碰撞增加分数【score += ((Enemey) f).getScore()】和火力【ship.addFire(score)】,增加50分增加40发火力
paint(Graphics g)显示游戏命条数和分数
//绘制分数和命
//设置字体
g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 18));
//设置颜色
g.setColor(new Color(255, 200, 0));
g.drawString("分数:" + score, 10, 25);
g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 18));
g.setColor(new Color(255, 0, 0));
g.drawString("命数:" + ship.getLife(), 10, 55);
最后游戏结束判断
检测飞船生命为零游戏结束
//检测游戏结束
public void checkGameOver() {
if (ship.getLife() <= 0) {
state = GAME_OVER;
}
}
paint(Graphics g)显示游戏状态
//绘制游戏状态
g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 36));
String strState = "";
switch (state) {
case START:
g.setColor(new Color(0, 13, 255));
strState = "点击开始游戏";
break;
case PAUSE:
g.setColor(new Color(255, 200, 0));
strState = "暂停游戏";
break;
case GAME_OVER:
g.setColor(new Color(255, 0, 0));
strState = "GAME OVER";
break;
}
g.drawString(strState, (WIDTH - (36 * strState.length())) / 2, HEIGHT / 2);
游戏结束状态转变处理
@Override
public void mouseClicked(MouseEvent event) {
switch (state) {
case START:
case PAUSE:
int x = event.getX();
int y = event.getY();
ship.move(x, y);
state = RUNNING;
break;
case RUNNING:
state = PAUSE;
break;
case GAME_OVER:
state = START;
score = 0;
sky = new Space();
ship = new Ship();
enemies = new FlyingObject[0];
bullets = new Bullet[0];
break;
}
}
自行查看一下"32 太空入侵者——复仇\33 太空入侵者——生死较量" ,就是敌机添加子弹和飞船放大招