飞机大战
游戏需求
- 用户通过鼠标操作一架英雄机,英雄机自动发射子弹,攻击自上而下飞行的敌机,击落敌机获得奖励,不同敌机奖励不同,英雄机生命归零则死亡,游戏结束。
-
界面信息:
- 得分信息、英雄机剩余生命信息、英雄机、敌机、子弹、天空图
- 初始界面,用户点击界面,游戏开始,鼠标移至窗口外面,游戏暂停,英雄机死亡后,显示Game Over界面,再次点击鼠标,恢复初始界面
-
敌机及奖励类型:
- 小敌机:击落得1分
- 大敌机:击落得3分
- 小蜜蜂:击落奖励(英雄机获得40火力值或者1条生命)
- 敌机被击落后,显示爆炸效果后消失
-
英雄机:
- 初始生命值:3
- 初始火力值:1
- 攻击模式:单发/双发,默认单发,击落小蜜蜂后,切换为双发攻击模式
- 其它:
开发文档
需求分析
fields/method | World | Sky | Hero | Bullet | BigAirplane | Airplane | Bee |
---|---|---|---|---|---|---|---|
WIDTH | √ | ||||||
HEIGTH | √ | ||||||
START | √ | ||||||
RUNNNING | √ | ||||||
PAUSE | √ | ||||||
GAME_OVER | √ | ||||||
score | √ | ||||||
maxScore | √ | ||||||
LIFE | √ | √ | √ | √ | √ | ||
DEAD | √ | √ | √ | ||||
REMOVE | √ | √ | √ | √ | √ | ||
width | √ | √ | √ | √ | √ | √ | |
heigth | √ | √ | √ | √ | √ | √ | |
x | √ | √ | √ | √ | √ | √ | |
y | √ | √ | √ | √ | √ | √ | |
speed | √ | √ | √ | √ | √ | ||
√ | √ | √ | |||||
y1 | √ | ||||||
life | √ | ||||||
fire | √ | ||||||
xspeed | √ | ||||||
awardType | √ | ||||||
getImage() | √ | √ | √ | √ | √ | √ | |
生成对象 | √ | √ | √ | √ | √ | ||
移动 | √ | √ | √ | √ | √ | ||
得分 | √ | √ | |||||
奖励 | √ | ||||||
碰撞 | √ | √ |
超纲代码
主窗口
/**
* 飞机大战,世界窗口
*/
private static final long serialVersionUID = 1L;
public static final int WIDTH = 400;
public static final int HEIDHT = 800;
public static void main(String[] args) {
JFrame frame = new JFrame();
World world = new World();
frame.add(world);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(WIDTH, HEIDHT);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
world.action();// 运行游戏入口
}
private void action() {
// TODO Auto-generated method stub
}
画界面
@Override
public void paint(Graphics g) {
g.drawImage(sky.getImages(), sky.x, sky.y, null);// 将天空图片画到界面上
}
}
获取图片文件
public static BufferedImage readImage(String fileName) {
try {
BufferedImage img = ImageIO.read(FlyingObject.class.getResource(fileName));
return img;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
}
游戏入口(监听器、定时器)
/**
* 游戏入口
*/
public void action() {
// 监听器
MouseAdapter msAdapter = new MouseAdapter() {
};
this.addMouseListener(msAdapter);
this.addMouseMotionListener(msAdapter);
// 定时器
Timer timer = new Timer();
int intervel = 10;// 定时周期,单位毫秒
timer.schedule(new TimerTask() {
@Override
public void run() {// 要运行的程序
}
}, intervel, intervel);
}
开发步骤
创建对象类
-
创建World类,画窗口(复制超纲代码)
- import JFrame+JPanel
- 设计World类继承JPanel
- 复制粘贴main()方法中的所有代码
- 修改frame.setSize()中的参数为常量WIDTH,HEIGHT
- 创建了6个对象类,设计FlyingObject超类,6个对象类继承超类
- FlyingObject定义共用属性,6个对象定义特定属性
基础数据初始化
- 给FlyingObject设计两个构造方法,6个派生类分别调用
对象 | 宽 | 高 | x | y | speed |
---|---|---|---|---|---|
Sky | 400 | 800 | 0 | 0 | 1 |
Hero | 97 | 139 | 140 | 400 | |
Bullet | 8 | 20 | x | y | 3 |
BigAirplane | 66 | 89 | 随机 | -高 | 2 |
Bee | 60 | 51 | 随机 | -高 | 2 |
Airplane | 48 | 50 | 随机 | -高 | 2 |
设计Images图片工具类
- 静态块
画对象:
- 想画对象需要去获取对象的图片,每个对象都得获取图片, 意味着获取图片行为为共有行为,所以设计在超类FlyingObject中, 每个对象获取图片的行为都是不一样的,所以设计为抽象方法 —-在FlyingObject中设计抽象方法getImage()用于获取对象的图片
- 获取图片时需要考虑对象的状态,因为在不同状态下获取的图片是不同的, 每个对象都有状态,意味着状态为共有属性,所以设计在FlyingObject超类中, 状态一般都设计为常量,同时设计变量state来表示当前状态 —-在FlyingObject中设计LIFE、DEAD、REMOVE常量,state变量表示当前状态 有了状态常量还不够,还需要去判断对象的状态,每个对象都得判断, 意味着判断状态为共有行为,所以设计在超类FlyingObject中, 每个对象判断状态的行为都是一样的,所以设计为普通方法 —-在FlyingObject中设计isLife()、isDead()、isRemove()来判断对象的状态
-
重写getImage()获取对象图片:
- 天空Sky: 返回sky图片即可
-
子弹Bullet:
- 若活着的,直接返回bullet图片即可
- 若死了的,直接删除(不返回图片)
-
英雄机Hero:
- 若活着的,返回heros[0]和heros[1]来回切换
-
小敌机Airplane:
- 若活着的,返回airs[0]即可
- 若死了的,依次返回airs[1]到airs[4],4后删除
-
大敌机BigAirplane:
- 若活着的,返回bairs[0]即可
- 若死了的,依次返回bairs[1]到bairs[4],4后删除
-
小蜜蜂Bee:
- 若活着的,返回bees[0]即可
- 若死了的,依次返回bees[1]到bees[4],4后删除
- 图片有了,就可以开画了,在World类中重写paint()画方法
英雄机随鼠标移动
- 英雄机随着鼠标移动为英雄机的行为,所以在Hero中设计moveTo()来实现英雄机随着鼠标移动
- 英雄机随着鼠标动为事件触发的,所以在侦听器中重写mouseMoved()鼠标移动事件 在mouseMoved()中: 获取鼠标的x和y坐标,调用Hero的moveTo()实现英雄机随着鼠标移动
敌机/子弹入场:enterAction()
- 随机生成一个敌机,构建getEnemy()方法,用于生成敌机,进行延时生成处理,0.4秒生成一个
- 对敌人数组进行扩容,并将生成的敌机插入数组中
-
构建getBullet()方法,用于生成子弹,根据英雄机的火力值,确认单双发,火力值>0,说明击落过小蜜蜂,获得双发火力,延时处理,0.3秒生成一个
-
对子弹数组进行扩容,并将子弹插入数组中
敌机和子弹移动:stepAction()
- 飞行物移动为派生类所共有的行为,所以在FlyingObject类中设计step()实现飞行物移动,默认自上而下移动,派生类不同的自行重写
- 飞行物移动为定时发生的,所以在run()中调用stepAction()实现飞行物移动 在stepAction()中:天空动,遍历敌人敌人动,遍历子弹子弹动
画分和画命:
- 在Hero中设计getLife()获取英雄机的命数
- 在World类的paint()中: 画分和画命
碰撞检测:checkHitAction()
- 设计EnemyScore得分接口,Airplane和BigAirplane实现接口 设计EnemyAward奖励接口,Bee实现接口
- 在FlyingObject中设计isHit()检测碰撞、goDead()飞行物去死,在Hero中设计addLife()增命、addFire()增火力,在Hero中设计subtractLife()英雄机减命、clearFire()清空火力值
-
子弹与敌人的碰撞:
- 遍历子弹得子弹,遍历敌人得敌人,判断若都活着并且撞上了:敌人去死、子弹去死 判断若敌人能得分,则强转为得分接口, 则玩家得分 判断若敌人为奖励,则强转为奖励接口, 则获取奖励类型,最后根据不同的奖励类型来获取不同的奖励
-
英雄机与敌人的碰撞
- 遍历敌人得敌人,判断若都活着并且撞上了:敌人去死、英雄机减命、英雄机清空火力值
删除越界和remove对象:deleteAction()
- 在FlyingObject中设计isOutside()检测敌机是否越界 在Bullet中重写isOutside()检测子弹是否越界
- 遍历所有敌人/子弹,判断若敌人/子弹越界了或者处于remove状态,则将最后一个元素赋值到越界位置,缩容
检测游戏结束:checkGameOverAction()
- 判断若英雄机的命数<=0,则表示游戏结束,…
画游戏状态控制
- 在World中设计START、RUNNING、PAUSE、GAME_OVER状态常量,usestatus变量表示当前状态 在Images中设计start、pause、gameover状态图片,静态块中初始化 在World的paint()中,设置在不同的状态下画不同的状态图
- 设置那一堆action为仅在运行状态下执行 设置英雄机随着鼠标移动为仅在运行状态下执行
- 重写mouseClicked()鼠标点击事件: 启动时变运行,游戏结束时先清理现场再变启动 重写mouseExited()鼠标移出事件: 运行变暂停 重写mouseEntered()鼠标移入事件: 暂停变运行
扩展需求列表
更多需求改动,详见
https://gitee.com/qiaobilong/shoot
(内附源码哦)
版权声明:本文为QBL_user原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。