##源代码:http://download.csdn.net/download/u012234452/9583953
##QQ:1151315936
##完成后的基本样式图:
##结构图:
###父类:FlyingObject,子类:Airplane(小飞机),BigPlane(大飞机),Bullet子弹,Bee(蜜蜂),Hero(英雄机),Sky(天空).世界:World.接口:Award(奖励),Enemy(敌人)
##根据逻辑分析把大飞机,小飞机,蜜蜂,子弹,英雄机的公共属性和方法泛化到父类(FlyingObject)中去.
##让World类继承JPanel,构建出窗口,重写paint方法
#FlyingObject.java
##1.默认构造器,用于初始化
public FlyingObject() {
life = 1;
state = ACTIVE;
}
##2.有参构造器
public FlyingObject(double width,double heigth){
this();//调用无参数的构造器,必须写在第一行.
this.x = (int)(Math.random()*(480-width));
this.y = -heigth;
this.width = width;
this.heigth = heigth;
step = Math.random()*3+0.8;//初始化step为[0.8,3.8)之间的数
}
##3.重写了toString()方法可以用来测试用
public String toString() {
return x+","+y+","+width+","+heigth+","+image;
}
##4.父类中重写了paint(Graphics g)方法,方便了子类的使用,用于绘图
public void paint(Graphics g) {
g.drawImage(image, (int)x, (int)y, null);//绘制图片
}
##5.父类中重构了move方法,实现了各种飞行物的移动和播放销毁动画功能
public void move(){
if(state == ACTIVE){
y += step;
return ;
}
if(state == DEAD){
//从子类对象中获取下一张照片
BufferedImage img = nextImage();
if(img == null){
state = REMOVE;//没有照片则回收
}else{
image = img;//否则把子类的图片传给image
}
//越界则销毁
if(y>=825){
state = REMOVE;
}
}
}
##6.子类中必须有的方法,返回下一个要播放的照片引用,如果返回null表示没有可播放的照片了.(虽然只有一句,但是非常重要)
protected abstract BufferedImage nextImage();
##7.飞行物被打了一下,生命-1,当生命等于0的时候为死亡状态
public void hit(){
if(life>0){
life--;
}
if(life==0){
state = DEAD;
}
}
##8.经典算法:碰撞检测的方法,用于检测物体的位置是否在碰撞的范围内
public boolean duang(FlyingObject obj){
//this(x,y,w,h)
//obj(x,y,w,h)
double x1 = this.x - obj.width;
double x2 = this.x + this.width;
double y1 = this.y - obj.width;
double y2 = this.y + this.heigth;
return x1
##9.状态检查方法,用于返回状态
public boolean isDead(){
return state == DEAD;
}
/** 检查飞行物是否活动的 */
public boolean isActive(){
return state == ACTIVE;
}
/** 检查飞行是否可以被删除*/
public boolean canRemove(){
return state == REMOVE;
}
/** 飞行物添加"去死"方法*/
public void goDead(){
if(isActive()){
state = DEAD;
}
}
#Airplane.java
##1.获取的小飞机的资源
private static BufferedImage[] imgs;// 定义一个唯一的图片素材
// 静态代码块用于获取图片资源
static {
imgs = new BufferedImage[5];
try {
for (int i = 0; i < imgs.length; i++) {
String png = "cn/feike/shoot/airplane" + i + ".png";//获取图片路径
imgs[i] = ImageIO.read(Airplane.class.getClassLoader().getResourceAsStream(png));
}
} catch (Exception e) {
e.printStackTrace();
}
}
##2.初始化小飞机,同时初始化照片
public Airplane() {
super(49, 36);// 初始化小飞机
this.image = imgs[0];
}
##3.继承父类的抽象方法,播放下一张图片,用于销毁
protected BufferedImage nextImage() {
index++;
if(index >= imgs.length){
return null;//如果下标大于等于数组长度,无图可播,返回null.
}
return imgs[index];
}
#BigPlane.java
##1.初始化大飞机,让生命等于3(即子弹打三下才打死)
public BigPlane() {
super(69,99);//初始化大飞机
this.image = imgs[0];
life = 3;
}
#BigPlaneAward.java
##1.继承了大飞机,重写了paint方法,在飞机外面加上一个框框
public void paint(Graphics g){
super.paint(g);//重载父类的paint方法
//在大飞机外面加上一个框框
g.drawRect((int)x, (int)y, (int)width, (int)heigth);
}
##2.固定奖励,打掉返回双倍奖励
public int getAward() {
return DOUBLE_FIRE;//获取双倍奖励
}
#Hero.java
##1.初始化英雄飞机,让其在地图的下方出现
public Hero() {
this.width = 97;
this.heigth = 124;
this.x = (480-width)/2;
this.y =500;
this.image = imgs[0];
}
##2.重写了move()的无参方法,用于显示动态效果和英雄飞机的销毁效果
public void move() {
if(isActive()){
n++;
int i = n%2;//用这个i的值来控制切换的图片(i=n%2)的切换速度快于(i=n/2%2)
this.image = imgs[i];//来回切换图片,显示出动画的效果.
}
if(isDead()){
//从子类获取下一张照片
BufferedImage img = nextImage();
if(img==null){
state = REMOVE;
}else{
image = img;
}
}
}
##3.再次重写了move()有参方法,用于英雄飞机随着鼠标移动
public void move(int x,int y){
this.x = x-width/2;
this.y = y-heigth/2;
}
public Bullet[] shoot(int type){
int x = (int)(this.x+width/2-8/2);//子弹的出场的x位置
int y = (int)(this.y-30);//子弹的出场的y位置
if(type == 1){//单枪发射
return new Bullet[]{
new Bullet(x,y)//创建一颗子弹
};
}
if(type == 2){
return new Bullet[]{
new Bullet(x-30, y),//第一个子弹原出场的位子向左移动30
new Bullet(x+30,y)//第二个子弹原出场的位子向右移动30
};
}
return new Bullet[0];
}
#Bullet.java
##1.初始化子弹,因为子弹跟着英雄飞机一起走,所以需要传入两个参数.
public Bullet(int x,int y) {
this.x = x;
this.y = y;
width = 8;
heigth = 14;
this.image = img;
}
##2.重写子弹move方法,子弹是从下往上移动
public void move() {
if(state == ACTIVE){
y -= 8;
if(y<=-heigth){
state = REMOVE;
}
}
}
##3.重写了nextImage()和hit()方法,子弹撞击后直接销毁
protected BufferedImage nextImage() {
return null;
}
public void hit(){
state = REMOVE;//子弹撞击后销毁
}
#Bee.java
##1.初始化蜜蜂,蜜蜂的出场方向是随机的
public Bee() {
super(60, 50);
this.image = imgs[0];
direction = Math.random() > 0.5 ? -2 : 2;// 初始化蜜蜂的移动方向,各占50%的概率.
}
public void move() {
super.move();// y += step;即把y坐标交个父类来处理
if (state == ACTIVE) {
x += direction;
// 如果蜜蜂移动到右边边界,则反方向移动
if (x >= 480 - width) {
direction = -2;
}
// 如果蜜蜂移动到左边边界,则反方向移动
if (x < 0) {
direction = 2;
}
}
// 蜜蜂的动漫效果
n++;
int i = n % 2;// 用来控制蜜蜂的扇翅膀的速度
this.image = imgs[i];// 来回切换图片,显示出动画的效果.
}
#Sky.java
##1.初始化天空,改变了天空的移动速度,和第二种图片的高度
public Sky() {
this.x = 0;
this.y = 0;
this.width=480;
this.heigth=825;
this.image = img;
this.step = 1;//初始化图片移动的速度
y1 = -heigth;//初始化第二张图片的位置
}
##2.重写了move()方法,两张图片同时移动,一张图片移除下边界就重新定义高度再移动.
public void move() {
y++;
y1++;
if(y>=heigth){
y = -heigth;//如果第一张图移动出下边界位置,则返回顶部
}
if(y1>=heigth){
y1 = -heigth;//如果第二张图移动出下边界位置,则返回顶部
}
}
##3.重写了paint,用于绘制两张背景图片的位置.
public void paint(Graphics g) {
g.drawImage(image, (int)x, (int)y, null);
g.drawImage(image, (int)x, (int)y1, null);
}
##4.天空没有销毁图片,所以返回null
protected BufferedImage nextImage() {
return null;
}
#Award.java
##1.奖励接口,用于获取奖励
public interface Award {
int LIFE = 0;
int FIRE = 1;
int DOUBLE_FIRE = 2;
int getAward();
}
#Enemy.java
##1.敌人接口,用于获取得分
public interface Enemy {
int getScore();
}
#World.java
##1.初始化三张开始,暂停,结束照片
private static BufferedImage pause;//暂停
private static BufferedImage ready;//开始
private static BufferedImage gameOver;//结束
static{
try {
String png = "cn/feike/shoot/start.png";
ready = ImageIO.read(World.class.getClassLoader().getResourceAsStream(png));
png = "cn/feike/shoot/pause.png";
pause = ImageIO.read(World.class.getClassLoader().getResourceAsStream(png));
png = "cn/feike/shoot/gameover.png";
gameOver = ImageIO.read(World.class.getClassLoader().getResourceAsStream(png));
} catch (Exception e) {
e.printStackTrace();
}
}
##2.初始化世界中的物体
public World() {
flyingObjects = new FlyingObject[]{};//初始化飞行物数组
bullets = new Bullet[]{};//初始化子弹数组
hero = new Hero();//初始化英雄飞机
sky = new Sky();//初始化一片天空
nextTime = System.currentTimeMillis()+1000;//初始化下一秒的系统时间
}
//nextOne方法被定时(1/24秒)调用一次
public void nextOne(){
//方法调用一次,获取一下当前时间.
long now = System.currentTimeMillis();//获取当前系统时间的毫秒数
if(now>=nextTime){
nextTime = now + 1000;//控制飞行物的出场时间间隔,即每隔一秒出场一个飞行物
FlyingObject obj = randomOne();//调用randomOne方法,随机生成一个飞行物
flyingObjects = Arrays.copyOf(flyingObjects, flyingObjects.length+1);//数组扩容
flyingObjects[flyingObjects.length-1]=obj;//随机生成的飞行物放到数组中去
}
}
##4.随机出场一个飞行物,出场的概率不同
private static FlyingObject randomOne(){
int n = (int)(Math.random()*10);//[0,10)
//这个上面这个随机数的大小也可以调整不同飞行物的出场概率
switch(n){
case 0 : return new Bee();
case 1 :
case 2 : return new BigPlane();
case 3 :
case 4 : return new BigPlaneAward();
default : return new Airplane();//概率最高
}
}
##5.重写了paint(Graphics g)方法,绘制出天空的物体
public void paint(Graphics g) {
sky.paint(g);//画出天空,即背景图片
hero.paint(g);//画出英雄飞机
for(FlyingObject fly : flyingObjects){
fly.paint(g);//绘制每个飞行物
}
for(Bullet bullet : bullets){
bullet.paint(g);//绘制每个子弹
}
//添加分数,生命,子弹类型的显示
g.drawString("SCORE:"+score, 20, 30);
g.drawString("LIFE:"+life, 20, 50);
g.drawString("FIRE:"+fireType, 20, 70);
switch(state){
case PAUSE : g.drawImage(pause, 0, 0, null);
case READY : g.drawImage(ready, 0, 0, null);
case GAME_OVER : g.drawImage(gameOver, 0, 0, null);
}
}
##6.添加一个定时器和启动定时器的方法.每1/24秒一个动作
public void action(){
timer = new Timer();//初始化一个定时器
timer.schedule(new TimerTask() {
@Override
public void run() {
if(state == RUNNING){
nextOne();//每(1/24秒)生成一个飞行物
move();//各种物体的移动
shoot();//射击封装到shoot方法
duangDuang();//碰撞方法
removeObjects();//回收方法
heroLifeCircle();//英雄的声明周期
}
repaint();//重新绘制JPanel
}
}, 0,1000/24);//从0开始,间隔为(1/24)秒
//创建鼠标的监听即开启鼠标的监听
MouseAdapter l = new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
if(state == RUNNING){
int x = e.getX();
int y = e.getY();
hero.move(x,y);//传递移动的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;
}
if(state == GAME_OVER){//游戏结束所有参数重置。
score = 0;
life = 3;
fireType = 1;
hero = new Hero();
bullets = new Bullet[0];
flyingObjects = new FlyingObject[0];
state = READY;
}
}
};
//将监听器加入到当前的面板中
addMouseListener(l);
addMouseMotionListener(l);
}
##7.英雄飞机的生命周期
public void heroLifeCircle(){
//检查飞行物和英雄的碰撞
if(hero.isActive()){
for(FlyingObject plane : flyingObjects){
if(plane.isActive()&&plane.duang(hero)){
plane.goDead();
hero.goDead();
}
}
}
if(hero.canRemove()){
if(life>0){
life--;
hero = new Hero();//新生一个英雄
//清场,死了的瞬间,再碰撞英雄不死,碰撞物死.
for(FlyingObject plane : flyingObjects){
if(plane.isActive()&&plane.duang(hero)){
plane.goDead();
}
}
}else{
//游戏结束
state = GAME_OVER;
}
}
}
public void removeObjects(){
//删除掉废弃的子弹
Bullet[] ary = {};//初始化一个子弹数组
for(Bullet b : bullets){
if(b.canRemove()){
continue;//忽略掉要删除的子弹
}
ary = Arrays.copyOf(ary, ary.length+1);//数组扩容
ary[ary.length-1] = b;//把没有删掉的子弹重新添加到数组中去
}
bullets = ary;//bullets变量引用到ary数组上,抛弃原来的引用
//删除掉废弃的飞机
FlyingObject[] arr = {};//初始化一个飞行物数组
for(FlyingObject obj : flyingObjects){
if(obj.canRemove()){
continue;//忽略掉废弃的飞行物
}
arr = Arrays.copyOf(arr, arr.length+1);//数组扩容
arr[arr.length-1] = obj;把没有删掉的飞行物添加到数组中去
}
flyingObjects = arr;
}
public void duangDuang(){
for(FlyingObject plane : flyingObjects){
if(plane.isActive()){
if(shootByBullet(plane)){
plane.hit();
if(plane.isDead()){
//计分,获取奖励
if(plane instanceof Enemy){
Enemy enemy = (Enemy)plane;
int s = enemy.getScore();
score += s;
}
if(plane instanceof Award){
Award award =(Award)plane;
int awd = award.getAward();
if(awd == Award.LIFE){
life++;
}else if(awd == Award.FIRE){
fireType = 1;
}else if(awd == Award.DOUBLE_FIRE){
fireType = 2;
}
}
}
}
}
}
}
##10.检查每个子弹是否和飞行物碰撞
public boolean shootByBullet(FlyingObject plane){
for(Bullet bullet : bullets){
if(bullet.duang(plane)){
bullet.hit();
return true;
}
}
return false;
}
##11.射击控制方法被定时器定时调用,控制英雄飞机射击子弹的速度
public void shoot(){
//方法调用一次,获取一下当前时间.
long now = System.currentTimeMillis();//获取当前系统时间的毫秒数
if(now>nextShootTime){
nextShootTime=now + 500;//半秒发射一发
Bullet[] arr = hero.shoot(fireType);//子弹射击类型
bullets = Arrays.copyOf(bullets, bullets.length+arr.length);
System.arraycopy(arr, 0, bullets, bullets.length-arr.length, arr.length);
}
}
##12.各种物体的移动
private void move() {
//每个飞行物移动一下,重新绘制JPanel方法
for(FlyingObject fly : flyingObjects){
fly.move();
}
//每个子弹的移动
for(Bullet bullet : bullets){
bullet.move();
}
sky.move();//天空移动
hero.move();//英雄飞机移动
}
##13.main()方法,设置窗口大小并显示
public static void main(String[] args) {
World world = new World();//创建一个world面板
JFrame frame = new JFrame();//创建一个窗口
frame.setSize(400, 680);//窗口大小
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭窗口即关闭程序
frame.setLocationRelativeTo(null);//让窗口居中
frame.add(world);//把world面板加到JFrame窗口里去
frame.setVisible(true);//窗口可见
world.action();//启动定时器
}