匿名内部类与碰撞检测

匿名内部类与碰撞检测

1 匿名内部类

1.1 什么是匿名内部类

匿名内部类:是在一行代码上继承父类并且创建出子类实例的语法。

1.2 使用匿名内部类

匿名内部类的语法在一行代码上完成两个功能:继承父类,创建子类行对象,所以要有个前提条件:有一个可以被继承的父类型,这个父类型可以是类,抽象类,接口。

比如存在这样一个父类Bird:

public class Bird{
    int a=5;
    public void move(){
        Sustem.out.println("飞翔");
    }
}

对比普通类和匿名内部类的区别:

普通类:

class Duck extends Bird{
    public void move(){
        System.out.println("游泳")
    }
}
Duck duck=new Duck();
duck.move();

匿名内部类:

Bird bird=new Bird(){
    public void move(){
        System.out.println("游泳")
    }
};
bird.move();

注意事项:

  1. 如果只是简洁的继承父类,并且只需要创建一个子类对象,就采用匿名内部类
  2. 如果子类需要反复使用创建一组子类对象就采用普通的子类
  3. 匿名内部类一定是子类,一定需要有父类型时候才能使用
  4. 匿名内部类的最大优点就是语法简洁,在一行上继承子类并且创建了子类对象

2 鼠标事件

根据鼠标动作执行软件功能,实现利用鼠标控制软件的执行流程。

实现步骤:

  1. 创建鼠标事件处理类MouseAction,编写鼠标事件处理方法
  2. 将鼠标处理事件类创建为对象注册到发生鼠标事件到面板
  3. 在发生鼠标事件时候,会自动触发MouseAction中的事件处理方法

案例:

1.声明类 MouseAction,编写鼠标事件处理方法

package demo02;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class MouseAction extends MouseAdapter{
    /*
     *重写MouseAdaoter类中约定的鼠标移动事件方法
     *这个方法会在鼠标事件发生时候执行
     */
    public void mouseMoved(MouseEvent e){
        System.out.println("鼠标在移动")
    }
}

2.将鼠标处理事件类创建为对象注册到发生鼠标事件到面板

public void action(){
    Timer timer=new Timer();
    PaintTask task=new PaintRask();
    timer.schedule(task,1000,1000/100);
    
    //创建鼠标事件处理对象
    MouseAction action=new MouseAction();
    //将鼠标事件处理对象注册到面板,处理鼠标移动事件
    this.addMouseMotionListener(action);
}
2.2 利用内部类优化鼠标事件

利用内部类进行优化减少代码的冗余,提供编程效率

public void action(){
    Timer timer=new Timer();
    PaintTask task=new PaintRask();
    timer.schedule(task,1000,1000/100);
    //利用匿名内部类继承MouseAdapter重写mouseMoved方法
    MouseAdapter action=new MouseAdapter(){
        public void mouseMoved(MouseEvent e){
            System.out.println("鼠标移动");
        }
    };
    //将鼠标事件处理对象注册到面板,处理鼠标移动事件
    this.addMouseMotionListener(action); 

更可以进一步优化,减少action变量:

public void action(){
    Timer timer=new Timer();
    PaintTask task=new PaintRask();
    timer.schedule(task,1000,1000/100);
    
    //将鼠标事件处理对象注册到面板,处理鼠标移动事件
    this.addMouseMotionListener(new MouseAdapter(){
		public void mouseMoved(MouseEvent e){
            System.out.println("鼠标移动");
        }
    });
}

3 英雄机移动

public void action(){
    Timer timer=new Timer();
    PaintTask task=new PaintRask();
    timer.schedule(task,1000,1000/100);
    //将鼠标事件处理对象注册到面板,处理鼠标移动事件
    this.addMouseMotionListener(new MouseAdapter(){
		public void mouseMoved(MouseEvent e){
            int x=e.getX();
            int y=e.getY();
            System.out.println("鼠标移动:"+x+","+y);
        }
    });
}
3.2 将英雄移动到鼠标位置

只有将鼠标x,y位置设置给英雄机即可:

具体代码如下:

public void action(){
    Timer timer=new Timer();
    PaintTask Task=new PaintTask();
    timer.schedule(task,1000,1000/100);
    
    //将鼠标事件处理对象注册到面板,处理鼠标移动事件
     this.addMouseMotionListener(new MouseAdapter(){
		public void mouseMoved(MouseEvent e){
            int x=e.getX();
            int y=e.getY();
            System.out.println("鼠标移动:"+x+","+y);
            hero.move(x,y);
        }
    });
}
3.3 处理英雄位置偏移问题
package demo01;
public class Hero extends FlyingObject{
    
	public Hero(double x, double y){
        super(x, y, Images.hero[0], Images.hero, Images.bom);
    }
	/*
	 * 重写move方法,空方法,目的是不动
	 * 修改超类中规定的向下飞,改成不动
	 */
	public void move() {
	}
	/*
	 *将鼠标机移动到鼠标位置X,Y
	 *@param x鼠标位置x
	 *@param y鼠标位置y
	 */
	public void move(int x, int y) {
		this.x=x-width/2;
		this.y=y-height/2;
	}
}

4 射击功能实现

  1. 为英雄机添加射击方法fire(),调用这个方法会(创建)射击子弹。
  2. 在定时任务中调用射击方法fire(),将射出的子弹添加到飞机数组中,这样子弹就出来了
4.1 射击方法

在英雄飞机添加算法:

  1. 业务功能是射击所以将方法名定义为fire(开火)

  2. 射击的结果是子弹,所以设计方法返回值是子弹类型

  3. 方法中创建子弹对象作为返回值,而子弹的初始位置在飞机的前方,完全可以利用当前飞机位置计算得到,不需要外部数据参与,所以方法可以没有参数

    设计方法如下:

package demo01;
public class Hero extends FlyingObject{
    
	public Hero(double x, double y){
        super(x, y, Images.hero[0], Images.hero, Images.bom);
    }
	/*
	 * 重写move方法,空方法,目的是不动
	 * 修改超类中规定的向下飞,改成不动
	 */
	public void move() {
	}
	/*
	 *将鼠标机移动到鼠标位置X,Y
	 *@param x鼠标位置x
	 *@param y鼠标位置y
	 */
	public void move(int x, int y) {
		this.x=x-width/2;
		this.y=y-height/2;
	}
    //开火方法
    public Bullet fire(){
        double x=this.x+width/2-5;
        double y=this.y-15;
        Bullet bullet=new Bullet(x,y);
        return bullet;
    }
}

测试射击方法:

package demo01;
public class FireDemo {
	public static void main(String[] args) {
		/*
		 * 开火方法测试
		 */
		Hero hero=new Hero(200,500);
		Bullet bullet=hero.fire();
		
		System.out.println(hero);
		System.out.println(bullet);
	}
}
4.2 利用定时器实现定时射击

具体步骤:

  1. 重构定时器任务,定时调用英雄的fire方法获得打出的子弹对象
  2. 将子弹添加到当前子弹数组中

重构World中的定时器任务:

 /*
  * 添加内部类,实现定时计划任务
  * 为何使用内部类实现定时任务
  * 1.隐藏定时任务到world类中
  * 2.可以访问外部类中的数据,飞机,子弹等
  */
private class PaintTask extends TimerTask{
    public void run(){
        index++;
        //调用定时开火方法
        Bullet bullet=hero.fire();
        //扩容bullet数组,添加新子弹
        bullets=Arrays.copyOf(bullets,bullets.length+1);
        bullets[bullets.length-1]=bullet;

        createPlane();
        //执行飞机移动方法;是多态的移动方法,每个飞机都不同
        for(int i=0; i<planes.length;i++){
            planes[i].move();
        }

        for(int i=0; i<bullets.length;i++){
            bullets[i].move();
        }
        sky.move();
        repaint();	//调用重写绘制方法,这个方法会自动执行paint
    }
}

抽取fireAction方法,优化定时任务:

private class PaintTask extends TimerTask{
    public void run(){
        index++;
        fireAction();
        createPlane();
        //执行飞机移动方法;是多态的移动方法,每个飞机都不同
        for(int i=0; i<planes.length;i++){
            planes[i].move();
        }

        for(int i=0; i<bullets.length;i++){
            bullets[i].move();
        }
        sky.move();
        repaint();	//调用重写绘制方法,这个方法会自动执行paint
    }
}
public void fireAction(){
    if(index % 15==0){
        //调用定时开火方法
        Bullet bullet=hero.fire();
        //扩容bullet数组,添加新子弹
        bullets=Arrays.copyOf(bullets,bullets.length+1);
        bullets[bullets.length-1]=bullet;
    }
}

5 碰撞检测

先得到两个物体的切圆数据,再计算两个内切圆的圆心距离,如果圆心距离小于两个圆的半径和,就发生了碰撞:

  1. 在小飞机上声明方法duang(碰撞时候发出的声音)
  2. 方法返回值为boolean类型,true时候表示撞上了,false表示没有碰上
  3. 计算时需要当前飞机的数据,还需要子弹的数据,所有可以将子弹数据利用方法参数传入
  4. 计算时先计算出两个圆的参数(r1,x1,y1)和(r2,x2,y2)
  5. 利用勾股定理计算出圆心距离
  6. 比较圆心距离和两圆半径和,将比较结果作为返回值

代码:

package demo01;
public class Airplane extends Plane{
    //飞机从屏幕上方随机位置出场
    public Airplane(){
        super(Images.ariplane[0], Images.airplane, Images.bom);
        step=Math.random()*4+1.5;
    }
    //从自定位置出场
	public Airplane(double x, double y, double step) {
		//利用super()调用父类有参数构造器,复用了父类中构造器算法
		super(x, y, Images.airplane[0], Images.airplane, Images.bom);
        this.step=step;
	}
    public boolean duang(Bullet bu) {
		Airplane p=this;
		//计算小飞机的内切圆数据
		double r1=Math.min(p.width, p.height)/2;
		double x1=p.x+p.width/2;
		double y1=p.y+p.height/2;
		//计算子弹的内切圆数据
		double r2=Math.min(bu.width, bu.height)/2;
		double x2=bu.x+bu.width/2;
		double y2=bu.y+bu.height/2;
		//利用勾股定理计算圆心距离
		double a=y2-y1;
		double b=x2-x1;
		double c=Math.sqrt(a*a + b*b);
		//如果圆心距离小于半径和就表示两个圆相交 就是发生了碰撞
		System.out.println(c+","+(r1+r2));
		return c<r1+r2;
    }
}

测试:

package demo02;
public class DuangDemo {
	public static void main(String[] args) {
		/*
		 * 测试碰撞检测算法
		 */
		Airplane airplane=new Airplane(20,50,5);
		Bullet bullet=new Bullet(20,200);

		for(int i=0; i<30; i++) {
			System.out.println(airplane);
			System.out.println(bullet);
			//duang方法是从父类继承的方法
			//方法可以接收多态参数,可以接收bullet

			System.out.println(airplane.duang(bullet));
			airplane.move();
		}
	}
}
5.2 多态碰撞

将方法泛化抽取到父类,实现全部子类继承此方法。再将方法参数定义为父类型FlyingObject,父类型变量就可以引用多态的参数对象。

步骤:

  1. 将Airplane中的duang方法抽取到父类型FlyingObject中
  2. 将方法参数换成FlyingObject类型
  3. 测试

重构Airplane

package demo02;
public class Airplane extends Plane{
    //飞机从屏幕上方随机位置出场
    public Airplane(){
        super(Images.ariplane[0], Images.airplane, Images.bom);
        step=Math.random()*4+1.5;
    }
    //从自定位置出场
	public Airplane(double x, double y, double step) {
		//利用super()调用父类有参数构造器,复用了父类中构造器算法
		super(x, y, Images.airplane[0], Images.airplane, Images.bom);
        this.step=step;
	}

重构FlyingObject

package demo02;
import java,awt.Graphics;
import javax.swing.ImageIcon;
/*
 * 父类中定义从子类抽取的属性和方法
 * 这种抽取方式称为“泛化”
 */
public abstract class FlyingObject {
	 protected double x;
	 protected double y;
	 protected double width;
	 protected double height;
     protected double step;
     protected ImageIcon image;
    
    //当前对象动画帧,如果没有:如子弹、天空,则此属性保持null
    protected ImageIcon[] images;
    
    //爆炸效果动画帧,如果没有保持null
    protected ImageIcon[] bom;
    
    //动画帧播放计数器,%数组长度得到播放动画帧的位置
    protected int index=0;
    
    //无参数构造器,减少子类的编译错误
    public FlyingObject(){
    }
    //根据位置初始化x,y,image,images,bom
    public FlyingObject(double x, double y, 
                        ImageIcon image, ImageIcon[] Images, ImageIcon[] bom) {
		                      //当前图片        全部动画帧       爆炸图片                             
		this.x=x;
		this.y=y;
		this.image=image;
		this.images=Images;
		this.bom=bom;
		width=image.getIconWidth();
		height=image.getIconHeight();
	}
  
    public abstract void move();
    
    //动画帧播放方法
    public void nextImage(){
        //没有动画帧时候,不播放动画帧图片
        if(images==null){
            return;
        }
        //System.out.println(index +","+(index % images.length));//打桩
		image=images[index++/30 % images.length];//除以30等于调慢30倍
    }
        
    public void paint(Graphics g){
        nextImage();//换动画帧,然后再绘制
        image.paintIcon(null,g,(int)x, (int)y);
    }
    
     public boolean duang(FlyingObject bu) {
		FlyingObject p=this;
		//计算小飞机的内切圆数据
		double r1=Math.min(p.width, p.height)/2;
		double x1=p.x+p.width/2;
		double y1=p.y+p.height/2;
		//计算子弹的内切圆数据
		double r2=Math.min(bu.width, bu.height)/2;
		double x2=bu.x+bu.width/2;
		double y2=bu.y+bu.height/2;
		//利用勾股定理计算圆心距离
		double a=y2-y1;
		double b=x2-x1;
		double c=Math.sqrt(a*a + b*b);
		//如果圆心距离小于半径和就表示两个圆相交 就是发生了碰撞
		//System.out.println(c+","+(r1+r2));
		return c<r1+r2;
    }
	@Override
	public String toString() {
        String className=getClass().getName();
		return className+" [x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + "]";
	}
}

测试:

package demo02;
public class DuangDemo {
	public static void main(String[] args) {
		/*
		 * 测试碰撞检测算法
		 */
		Airplane airplane=new Airplane(20,50,5);
		Bullet bullet=new Bullet(20,200);

		for(int i=0; i<30; i++) {
			System.out.println(airplane);
			System.out.println(bullet);
			//duang方法是从父类继承的方法
			//方法可以接收多态参数,可以接收bullet

			System.out.println(airplane.duang(bullet));
			airplane.move();
		}
	}
}
5.3 检测每个子弹碰到每个飞机

添加hitDetection()方法。

/*
  * 添加内部类,实现定时计划任务
  * 为何使用内部类实现定时任务
  * 1.隐藏定时任务到world类中
  * 2.可以访问外部类中的数据,飞机,子弹等
  */
private class PaintTask extends TimerTask{
    public void run(){
        index++;
        fireAction();
        createPlane();
        //执行飞机移动方法;是多态的移动方法,每个飞机都不同
        for(int i=0; i<planes.length;i++){
            planes[i].move();
        }

        for(int i=0; i<bullets.length;i++){
            bullets[i].move();
        }
        hitDetection();
        sky.move();
        repaint();	//调用重写绘制方法,这个方法会自动执行paint
    }
}
public void hitDetection() {
		for(int i=0; i<bullets.length; i++) {
			Bullet bullet=bullets[i];

			for(int j=0; j<planes.length; j++) {
				FlyingObject plane=planes[j];
				
				if(plane.duang(bullets)) {
					System.out.println(bullet+"打到"+plane);
                }
           }
      }
}
public void fireAction(){
    if(index % 15==0){
        //调用定时开火方法
        Bullet bullet=hero.fire();
        //扩容bullet数组,添加新子弹
        bullets=Arrays.copyOf(bullets,bullets.length+1);
        bullets[bullets.length-1]=bullet;
    }
}
posted @ 2021-03-02 23:06  指尖上的未来  阅读(60)  评论(0)    收藏  举报