匿名内部类与碰撞检测
匿名内部类与碰撞检测
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();
注意事项:
- 如果只是简洁的继承父类,并且只需要创建一个子类对象,就采用匿名内部类
- 如果子类需要反复使用创建一组子类对象就采用普通的子类
- 匿名内部类一定是子类,一定需要有父类型时候才能使用
- 匿名内部类的最大优点就是语法简洁,在一行上继承子类并且创建了子类对象
2 鼠标事件
根据鼠标动作执行软件功能,实现利用鼠标控制软件的执行流程。
实现步骤:
- 创建鼠标事件处理类MouseAction,编写鼠标事件处理方法
- 将鼠标处理事件类创建为对象注册到发生鼠标事件到面板
- 在发生鼠标事件时候,会自动触发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 射击功能实现
- 为英雄机添加射击方法fire(),调用这个方法会(创建)射击子弹。
- 在定时任务中调用射击方法fire(),将射出的子弹添加到飞机数组中,这样子弹就出来了
4.1 射击方法
在英雄飞机添加算法:
-
业务功能是射击所以将方法名定义为fire(开火)
-
射击的结果是子弹,所以设计方法返回值是子弹类型
-
方法中创建子弹对象作为返回值,而子弹的初始位置在飞机的前方,完全可以利用当前飞机位置计算得到,不需要外部数据参与,所以方法可以没有参数
设计方法如下:
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 利用定时器实现定时射击
具体步骤:
- 重构定时器任务,定时调用英雄的fire方法获得打出的子弹对象
- 将子弹添加到当前子弹数组中
重构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 碰撞检测
先得到两个物体的切圆数据,再计算两个内切圆的圆心距离,如果圆心距离小于两个圆的半径和,就发生了碰撞:
- 在小飞机上声明方法duang(碰撞时候发出的声音)
- 方法返回值为boolean类型,true时候表示撞上了,false表示没有碰上
- 计算时需要当前飞机的数据,还需要子弹的数据,所有可以将子弹数据利用方法参数传入
- 计算时先计算出两个圆的参数(r1,x1,y1)和(r2,x2,y2)
- 利用勾股定理计算出圆心距离
- 比较圆心距离和两圆半径和,将比较结果作为返回值
代码:
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,父类型变量就可以引用多态的参数对象。
步骤:
- 将Airplane中的duang方法抽取到父类型FlyingObject中
- 将方法参数换成FlyingObject类型
- 测试
重构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;
}
}
每天对着镜子里的自己说一句:今天的你也很棒!