javaoop-day04-ShootGame

Airplane

package com.shoot;

/**
 * 敌机的编写 继承自FlyingObject 还要实现Enemy
 */

public class Airplane extends FlyingObject implements Enemy {
    //敌机向下移动的速度
    private int speed = 2;

    //添加构造器
    public Airplane() {
        image = ShootGame.airplane;
        height = image.getHeight();
        width = image.getWidth();
        //x的出现范围 0~ShootGame.WIDTH - this.width
        x = (int) (Math.random() * (ShootGame.WIDTH - this.width));
        y = -height;
    }

    //向下移动
    @Override
    public void step() {
        y += speed;
    }

    //敌机越界 true表示越界 false表示未越界
    @Override
    public boolean outOfBounds() {
        return y > ShootGame.HEIGHT;
    }

    //每个敌机五分
    @Override
    public int getScore() {
        return 5;
    }
}

Award

package com.shoot;

/**
 * 奖励接口: Award, 有两种奖励
 *     第一种是双倍火力
 *     第二种是生命值奖励
 */

public interface Award {
    //将两种奖励设置为两个常量
    int DOUBLE_FIRE = 0;
    int LIFE = 1;

    //定义一个抽象方法,用于获取奖励类型
    int getType();
}

Bee

package com.shoot;

/**
 * 小蜜蜂类型: 继承FlyingObject  实现 Award
 */

public class Bee extends FlyingObject implements Award {
    //bee的速度,在x,y轴的上
    private int xSpeed = 3;
    private int ySpeed = 2;

    public Bee() {
        image = ShootGame.bee;
        height = image.getHeight();
        width = image.getWidth();
        //x的数值应该在0-ShootGame之间
        x = x = (int) (Math.random() * (ShootGame.WIDTH - width));
        //y的数值应该在-height之间
        y = -height;
    }

    //bee身上的奖励在1,0之间
    @Override
    public int getType() {
        return (int) (Math.random() * 2);
    }

    @Override
    public void step() {
        y += ySpeed;
        x += xSpeed;
        //当小蜜蜂到达最右边时
        if (x > ShootGame.WIDTH - width) {
            //x轴的速度改为 -3
            xSpeed = -xSpeed;
        }
        //当小蜜蜂到达最左边时
        if (x <= 0) {
            //x轴的速度改为 3
            xSpeed = -xSpeed;
        }
    }

    @Override
    public boolean outOfBounds() {
        return this.y > ShootGame.HEIGHT;
    }
}

Bullet

package com.shoot;

/**
 * 子弹类型 :继承FlyingObject
 */

public class Bullet extends FlyingObject {
    //子弹每一次移动多少
    private int speed = 2;

    //添加子弹的构造器,进行初始化 设置形参 参数为子弹的x,y坐标
    //形参是由英雄机决定的
    public Bullet(int x, int y) {
        image = ShootGame.bullet;
        this.x = x;
        this.y = y;
        width = image.getWidth();
        height = image.getHeight();
    }


    //子弹上移 y值减少
    @Override
    public void step() {
        y -= speed;
    }

    //子弹是否越界
    //ture表示越界  false表示未越界

    @Override
    public boolean outOfBounds() {
        return this.y < -this.height;
    }
}

Enemy

package com.shoot;

/**
 * 定义接口:Enemy 敌人  敌机身上有五分
 */

public interface Enemy {
    //获取分数的功能
    int getScore();
}

FlyingObject

package com.shoot;

import java.awt.image.BufferedImage;

/**
 *   定义所有飞行物的父类型,flyingObject  抽象类和不抽象类都可以、
 *    共同特征:
 *       image x y width height
 *    共同行为:
 *       移动:step() 抽象方法
 *       是否越界: boolean outOfBounds()
 *
 *     分析发现:敌机和bee都会与子弹撞击,因此将该行为提取,放入父类中
 *         shootBy(Bullet bullet)
 */

public abstract class FlyingObject {
    protected BufferedImage image;//图片
    protected int x; //图片左上角x坐标
    protected int y;//图片左上角y坐标
    protected int width;//图片的宽
    protected int height;//图片的高

    public abstract void step();

    public abstract boolean outOfBounds();

    //敌机和bee是否被子弹击中  因此子弹是形参
    // true 表示击中 false 表示未击中
    public boolean shootBy(Bullet bullet) {
        //获取传入的子弹的坐标
        int x = bullet.x;
        int y = bullet.y;
        //获取飞行物的x范围
        int xMin = this.x;
        int xMax = this.x + this.height;
        //判断子弹是否在飞行物x范围之内
        int yMin = this.y;
        int yMax = this.y + this.height;
        //子弹的x,y在飞行物的x,y范围之内,就是击中
        return x > xMin && x < xMax && y > yMin && y < yMax;
    }
}

Hero

package com.shoot;

import java.awt.image.BufferedImage;

/**
 * 英雄机的编写: 继承FlyingObject
 */

public class Hero extends FlyingObject {
    //火力值属性  为零时单倍火力  大于零双倍火力
    private int doubleFire;
    private int life;

    //图片数组,使用两张图片 模拟英雄机在飞
    private BufferedImage[] images;
    //定义一个变量 用来协助英雄机的两张图片切换,100ms切换一次
    private int index;

    //添加构造器
    public Hero() {
        //初始位置  火力 hp
        x = 150;
        y = 430;
        doubleFire = 0;
        life = 3;

        image = ShootGame.hero0;
        width = image.getWidth();
        height = image.getHeight();
        images = new BufferedImage[]{ShootGame.hero0, ShootGame.hero1};
        index = 0;
    }

    /*hero的飞行: 就是两张图片切换的模拟效果
       注意 该方法是10ms调用一次,但是想要100ms执行一次
        调用次数    图片数组下标   index++/10   index++/10%2
          10ms         0            1             0
          20ms         0            2
          ...
          90ms         0            9             1
          110ms        1            10
          120ms        1            11
          100ms        1            12
          ...
          190ms        1            19
          200ms        0            20             2
          ......
     */
    @Override
    public void step() {
        image = images[index++ / 10 % 2];
    }

    // hero永不越界 ,越界就是游戏暂停
    @Override
    public boolean outOfBounds() {
        return false;
    }

    /*
       英雄机随着鼠标的位置而变化
       @param x 鼠标的x坐标
       @param y 鼠标的y坐标
     */
    public void moveTo(int x, int y) {
        //hero的x ,y坐标
        this.x = x - width / 2;
        this.y = y - height / 2;
    }

    //英雄机发射子弹
    public Bullet[] shoot() {
        Bullet[] bullets = null;
        int xwidth = width / 4; //hero的1/4宽度
        if (doubleFire > 0) {
            //两发子弹
            bullets = new Bullet[2];
            bullets[0] = new Bullet(x + xwidth, y - 20);
            bullets[1] = new Bullet(x + 3 * xwidth, y - 20);
            //随发射次数减少火力值  (弹夹数方式) 双倍奖励后最多发射20次子弹
            doubleFire -= 2;
        } else {
            //一发子弹
            bullets = new Bullet[1];
            bullets[0] = new Bullet(x + 2 * xwidth-3, y - 20);
        }
        return bullets;
    }

    /**
     * 判断英雄机是否被飞行物obj 击中
     *
     * @param obj
     * @return
     */

    public boolean hit(FlyingObject obj) {
        //获取飞行物的左上角的x,y坐标
        int ox = obj.x;
        int oy = obj.y;
        //获取飞行物的宽和高
        int ow = obj.width;
        int oh = obj.height;

        //范围 以英雄机以为参照物
        return ox > this.x - obj.width && ox < this.x + this.width &&
                oy > this.y - obj.height && oy < this.y + this.height;
    }

    public void subLife() {
        life--;
    }

    public int addLife() {
        return life;
    }

    //绘制时,需要获取多少条命  判断撞击后 减命判定是否结束
    public int getLife() {
        return life;
    }

    //修改撞机之后的火力值 恢复单倍状态
    public void setDoubleFire(int doubleFire) {
        this.doubleFire = doubleFire;
    }

    // 获取双倍火力的奖励
    public void getDoubleFire() {
        doubleFire += 40;

    }


}

ShootGame

package com.shoot;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;

/**
 * 定义一个游戏窗口类型:
 * 1.继承自SWING的JPanel类型
 */
public class ShootGame extends JPanel {
    //设置将窗口的宽高设置成常量
    public static final int WIDTH = 400;
    public static final int HEIGHT = 650;

    //面板上应该要一个英雄机对象
    protected Hero hero = new Hero();
    //bee和敌机
    protected FlyingObject[] flyings = {};
    //子弹
    protected Bullet[] bullets = {};
    //添加成员变量 积分器,hp
    protected int scores = 0;
    protected int lifes = hero.getLife();
    //定义游戏的状态属性和常量
    private int state;  //该变量,用于记录当前游戏的状态值
    private static final int READY = 0;
    private static final int RUNNING = 1;
    public static final int PAUSE = 2;
    public static final int GAMEOVER = 3;

    //定义图片的静态属性
    public static BufferedImage airplane; //敌机
    public static BufferedImage background; //背景
    public static BufferedImage bee; //蜜蜂
    public static BufferedImage bullet; //子弹
    public static BufferedImage gameover; //游戏结束画面
    public static BufferedImage hero0; //英雄机1
    public static BufferedImage hero1; //英雄机2
    public static BufferedImage pause; //暂停画面
    public static BufferedImage start; //游戏启动画面

    static {
        try {
            airplane = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("airplane.png"));
            background = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("background.png"));
            bee = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("bee.png"));
            bullet = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("bullet.png"));
            gameover = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("gameover.png"));
            hero0 = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("hero0.png"));
            hero1 = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("hero1.png"));
            pause = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("pause.png"));
            start = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("start.png"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //重写面板的基本绘制功能: g 可以理解为是一个画笔
    @Override
    public void paint(Graphics g) {
        //绘制背景图片
        g.drawImage(background, 0, 0, null);
        //绘制hero
        g.drawImage(hero.image, hero.x, hero.y, null);
        //绘制飞行物
        paintFlying(g);
        //绘制子弹
        paintBullet(g);
        //绘制分数和生命
        paintScoreAndLife(g);
        //绘制状态
        paintState(g);

    }

    private void paintState(Graphics g) {
        switch (state) {
            case READY:
                g.drawImage(ShootGame.start, 0, 0, null);
                break;
            case PAUSE:
                g.drawImage(ShootGame.pause, 0, 0, null);
                break;
            case GAMEOVER:
                g.drawImage(ShootGame.gameover, 0, 0, null);
                break;
        }
    }

    private void paintScoreAndLife(Graphics g) {
        g.setColor(Color.red);
        //new Font("字体名称",字体样式,字体大小)
        g.setFont(new Font("楷体", Font.BOLD, 20));
        g.drawString("Scores:" + scores, 10, 30);
        g.drawString("LIFES:" + hero.getLife(), 10, 50);

    }

    private void paintBullet(Graphics g) {
        //遍历子弹
        for (Bullet bullet : bullets) {
            g.drawImage(bullet.image, bullet.x, bullet.y, null);
        }
    }

    private void paintFlying(Graphics g) {
        //遍历Flyings
        for (FlyingObject flying : flyings) {
            g.drawImage(flying.image, flying.x, flying.y, null);
        }
    }

    //定义一个定时器成员变量
    private Timer timer;
    private long interval = 10;

    //游戏启动方法
    public void action() {
        //鼠标监听器
        MouseAdapter mouseAdapter = new MouseAdapter() {
            //鼠标移动操作
            @Override
            public void mouseMoved(MouseEvent e) {
                if (state == RUNNING) {
                    //从e(鼠标事件)对象上可以获取鼠标在屏幕上的x,y的坐标
                    int x = e.getX();
                    int y = e.getY();
                    //调用英雄机的移动方法,跟随鼠标
                    hero.moveTo(x, y);
                }
            }

            @Override
            public void mouseExited(MouseEvent e) {
                if (state == RUNNING) {
                    state = PAUSE;
                }
            }

            @Override
            public void mouseEntered(MouseEvent e) {
                if (state == PAUSE) {
                    state = RUNNING;
                }
            }

            //重写鼠标点击事件
            @Override
            public void mouseClicked(MouseEvent e) {
                if (state == READY) {
                    state = RUNNING;
                }
            }
        };
        //将鼠标监听对象与面板关联
        this.addMouseListener(mouseAdapter);  //鼠标监听
        this.addMouseMotionListener(mouseAdapter);  //鼠标滚轮监听

        //初始化定时器
        timer = new Timer();
        //调用定时器的调度计划schedule(TimerTask tt, long delay, long period);
        //第一个参数:定时任务,第二个参数:延迟时间,第三个参数,周期 注意:单位是 毫秒
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                if (state == RUNNING) {
                    enterAction();//飞行物入场
                    stepAction();//所有飞行物开始移动,包括英雄机
                    shootAction();//子弹入场
                    bangAction(); //子弹和敌人碰撞
                    checkOutOfBounds();//没有被击中的敌人和子弹越界处理
                    checkGameOver();//检查游戏结束
                }
                //调用面板自带的重新绘制功能,每10毫秒绘制一次
                repaint();
            }
        }, interval, interval);
    }

    private void checkGameOver() {
        if (isGameOver()) {
            state = GAMEOVER;
        }
    }

    private boolean isGameOver() {
        int index = -1;//记录被撞击的飞行物的下标
        //英雄机与敌人的碰撞
        for (int i = 0; i < flyings.length; i++) {
            FlyingObject flying = flyings[i];
            if (hero.hit(flying)) {
                index = i;
                break;
            }
        }
        //不等于-1 说明hero和敌人相撞,hero恢复默认状态
        if (index != -1) {
            hero.setDoubleFire(0);
            hero.subLife();

            //删除相撞的敌人
            flyings[index] = flyings[flyings.length - 1];
            //缩容
            flyings = Arrays.copyOf(flyings, flyings.length - 1);
        }
        return hero.getLife() <= 0;
    }

    /**
     * 敌人和子弹越界处理
     * 思路: 将不越界的元素,copy到一个新的数组,然后使用面板上的flyings指向新数组
     */

    private void checkOutOfBounds() {
        //敌机越界处理
        int index = 0;
        //创建一个新数组
        FlyingObject[] newflyings = new FlyingObject[flyings.length];
        for (FlyingObject flying : flyings) {
            if (!flying.outOfBounds()) {
                newflyings[index] = flying;
                index++;  //可以用来统计没有越界的个数,以及为新数组提供从左到右依次挨着的下标.
                //将没有越界的元素的地址依次从左到右放入新的数组
            }
        }
        //对新数组进行缩容,缩容后的新地址直接赋值给面板上的flyings
        flyings = Arrays.copyOf(newflyings, index);

        //子弹越界处理
        int index1 = 0;
        //一个子弹的新数组
        Bullet[] newbullets = new Bullet[bullets.length];
        for (Bullet bullet : bullets) {
            if (!bullet.outOfBounds()) {
                newbullets[index1] = bullet;
                index1++;
            }
        }
        //新数组缩容并赋值给面板
        bullets = Arrays.copyOf(newbullets, index1);
    }

    /**
     * 子弹与敌人(bee+敌机)碰撞
     */
    private void bangAction() {
        //遍历子弹数组,查看每一发子弹是否集中敌人
        for (Bullet bullet1 : bullets) {
            //用于检查一个子弹是否击中敌人
            bang(bullet1);
        }
    }

    /**
     * 用于检查一个子弹是否击中敌人
     */
    public void bang(Bullet bullet) {
        //定义一个变量,记录被击中敌人的下标
        int index = -1;
        //遍历每一个敌人是否被子弹击中
        for (int i = 0; i < flyings.length; i++) {
            //获取一个具体的敌人
            FlyingObject flying = flyings[i];
            if (flying.shootBy((bullet))) {
                index = i;
                //如果一个敌人被击中,需要处理该敌人的其他操作,不需要继续向后检查其他敌人是否被击中
                //因为10ms之后该方法还会继续调用进行再次检查,所以该循环可以打断跳出。
                break;
            }
        }
        //当检查到一个敌人被击中
        if (index != -1) {
            //如果index不等于-1,说明该敌人被击中
            //1.从数组中移除该敌人
            FlyingObject flying = flyings[index];
            //判断敌人变量
            if (flying instanceof Enemy) {
                //强转
                scores += ((Enemy) flying).getScore();
            } else {
                //不是敌机就是bee
                Award award = (Award) flying;
                int type = award.getType();
                switch (type) {
                    case Award.LIFE:
                        lifes += 1;
                        break;
                    case Award.DOUBLE_FIRE:
                        hero.getDoubleFire();
                        break;
                }
            }
            //删除被击中的敌人,将该敌人与敌人数组中最后一个元素交换位置,不交换
            //直接将最后一个元素覆盖一下被击中的元素即可。最后位置上的元素就没用了,直接缩容
            flyings[index] = flyings[flyings.length - 1];
            //缩容之后产生一个新数组,需要将新数组的地址赋值到面板的飞行物数组变量里
            flyings = Arrays.copyOf(flyings, flyings.length - 1);
        }
    }

    /**
     * hero射击行为
     * 频率过高 改成300ms
     */
    int bulletIndex = 0;

    private void shootAction() {
        bulletIndex++; //10ms执行一次
        if (bulletIndex % 30 == 0) { //如果bulletIndex是30的倍数,说明过了3·00ms
            Bullet[] bs1 = hero.shoot();
            //对面板上的子弹扩容
            bullets = Arrays.copyOf(bullets, bullets.length + bs1.length);
            //新子弹放入数组中
            /*
               System.arraycopy(srcArr,srcPos,desArr,desPos,length).
               作用:将一个数组中的元素拷贝到另外一个数组中
                 srcArr:源数组。即要被拷贝/复制的数组
                 srcPos:源数组中的下标。表示从该下标开始复制,源数组中的元素
                 desPos:目标数组。即要拷贝到哪个数组中。
                 desArr:目标数组中的下标。表示从该下标开始存储,向后存储
                 length:要拷贝的个数。
             */
            System.arraycopy(bs1, 0, bullets, bullets.length - bs1.length, bs1.length);

//        使用for循环方式实现
//            for(int i= bs1.length-1;i>=0;i--){
//                将bs1中的最后一个元素放在多出来的位置的第一个位置
//                bullets[bullets.length-1-i] =bs1[i];
//            }
        }
    }

    /**
     * 所有飞行物移动
     */
    private void stepAction() {
        //hero移动
        hero.step();
        //遍历飞行物数组
        for (FlyingObject flying : flyings) {
            flying.step();
        }
        //遍历子弹数组
        for (Bullet bullet : bullets) {
            bullet.step();
        }
    }

    /**
     * 创建一个方法,用于控制飞行物(bee和敌机)的入场
     * 如果随机数是0 是bee
     * 其他数字都是敌机
     */

    public FlyingObject nextOne() {
        int num = (int) (Math.random() * 20);
        if (num < 2) {
            return new Bee();
        } else {
            return new Airplane();
        }
    }

    /*
       定义一个方法,用于控制飞行物(bee和敌机)的入场
        10ms频率太高  控制为400ms出现一个飞行物
     */
    int flyingsIndex = 0;

    private void enterAction() {
        flyingsIndex++; //10m执行一次
        if (flyingsIndex % 40 == 0) { //如果flyingsIndex是40的倍数,说明过了400ms
            //创建一个敌机对象
            FlyingObject obj = nextOne();
            //将敌机对象放入数组中
            flyings = Arrays.copyOf(flyings, flyings.length + 1);
            flyings[flyings.length - 1] = obj;
        }
    }

    public static void main(String[] args) {
        //创建窗口对象
        JFrame frame = new JFrame("飞机大战");
        //创建JPanel对象
        ShootGame game = new ShootGame(); //面板
        //将面板嵌入窗口
        frame.add(game);

        //对窗口进行一些简单的设置
        frame.setSize(WIDTH, HEIGHT); //设置宽与高
        frame.setAlwaysOnTop(true);  //将游戏界面设置到屏幕的最上面
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//窗口关闭,即程序结束
        frame.setLocationRelativeTo(null);//界面居中
        frame.setVisible(true);//窗口可见,并尽快调用绘制paint()功能

        //启动游戏功能
        game.action();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值