绘制飞机大战
1 绘制飞机大战
1.1 Swing API
Swing 是Java内嵌桌面应用GUI界面API,Java 8也内嵌类Java FX API,是新一代的GUI API。
1.2创建窗口
使用步骤:
package demo01;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Demo01{
public static void main(String[] args){
/*
*测试Swing 窗口
*/
JFrame frame=new JFrame();//框架
JPanel panel=new JPanel();//面板
frame.add(panel);//面板安装到窗口框中
frame.setSize(400,700);//面板大小
frame.setLocationRelativeTo(null);//位置居中
frame.setDefaultCloseOperation(JFrame,EXIT_ON_CLOSE);//关闭窗口功能
frame.setVisible(true);//显示窗口
}
}
1.3 绘制图片
- 利用imagelcon读取图片素材到内存图片对象
- 将内存图片对象绘制到面板上
- 将面板显示到窗口中
从网站下载素材,然后图片素材放到images文件夹,再将素材图片复制到项目中的images文件夹。
要在面板上显示图片,需要继承JPanel类,本质上就是继承JPanel中全部属性和方法。
package demo01;
import java.awt.Graphics;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class MyPanel extends JPanel{
ImageIcon icon=new ImageIcon("images/airplane0.png");
/*
* 重写paint方法,JPanel中定义类paint方法,这里重写以后
* 就修改了父类中定义paint方法,修改为自定义绘制
* 如;绘制图片
*/
public void paint(Graphics g) {
//g 是绘图API,相当于面板上的画笔
//paintIcon是ImageIcon类型提供绘图方法
//执行这个方法就可以把图片画出来
//参数this没有用途
//参数g是画笔对象,表示绘制图片的面板
//10,10是出现在面板上的位置
icon.paintIcon(this, g, 10, 10);
}
public static void main(String[] args) {
JFrame frame=new JFrame();
MyPanel panel=new MyPanel();
frame.add(panel);
frame.setSize(400,700);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
1.4 绘制更多图片
package demo02;
import java.awt.Graphics;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class MyPanel extends JPanel{
ImageIcon icon1=new ImageIcon("images/airplane0.png");
ImageIcon icon2=new ImageIcon("images/bigplane0.png");
ImageIcon icon3=new ImageIcon("images/hero0.png");
/*
* 重写paint方法,JPanel中定义类paint方法,这里重写以后
* 就修改了父类中定义paint方法,修改为自定义绘制
* 如;绘制图片
*/
public void paint(Graphics g){
//g 是绘图API,相当于面板上的画笔
//paintIcon是ImageIcon类型提供绘图方法
//执行这个方法就可以把图片画出来
//参数this没有用途
//参数g是画笔对象,表示绘制图片的面板
//10,10是出现在面板上的位置
icon1.paintIcon(this,g,10,10);
icon2.paintIcon(this,g,200,30);
icon3.paintIcon(this,g,200,400);
}
public static void main(String[] args){
JFrame frame=new JFrame();
MyPanel panel=new MyPanel();
frame.add(panel);
frame.setSize(400,700);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
1.5 加载每个飞机的图片
由于image属性的值每个飞行物都不同,所以其初始化代码写在子类的构造器中。这样在创建对象时候就可以初始化为不同的图片了。
代码:
public class FlyingObject {
double x;
double y;
double width;
double height;
double step;
ImageIcon image;
public FlyingObject(double x, double y, double width, double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public void move(){
y+=step;
}
@Override
public String toString() {
String className=getClass().getName();
return className+" [x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + "]";
}
}
public class Airplane extends FlyingObject{
public Airplane(double x, double y, double width, double height, double step) {
//利用super()调用父类有参数构造器,复用了父类中构造器算法
super(x,y,width,height);
this.step = step;
image=new ImageIcon("images/airplane0.png");
}
}
public class Bee extends FlyingObject{
public Bee(double x, double y, double width, double height, double step){
super(x,y,width,height);
this.step=step;
this.image=new ImageIcon("images/bee0.png");
}
/**
*重写父类型move方法修改为斜向飞行
*/
public void move(){
//调用父类型方法,复用父类型定义的算法
super.move();//向下飞行
x++;
}
}
public class Bigplane extends FlyingObject{
public Bigplane(double x, double y, double width, double height, double step) {
//利用super()调用父类有参数构造器,复用了父类中构造器算法
super(x,y,width,height);
this.step = step;
this.image=new ImageIcon("images/bigairplane0.png");
}
}
public class Sky extends FlyingObject{
public Sky(double x, double y, double width, double height, double step) {
//利用super()调用父类有参数构造器,复用了父类中构造器算法
super(x,y,width,height);
this.step = step;
this.image=new ImageIcon("images/background.png");
}
}
public class Bullet extends FlyingObject{
public Bullet(double x, double y, double width, double height, double step) {
//利用super()调用父类有参数构造器,复用了父类中构造器算法
super(x,y,width,height);
this.step = step;
this.image=new ImageIcon("images/bullet.png");
}
/**
*重写继承与超类的move方法,作用就是修改了超类move行为
*超类是向下移动,修改为向上移动
*/
public void move(){
y-=step;
}
}
public class Hero extends FlyingObject{
public Hero(double x, double y, double height, double width) {
super(x,y,width,height);
this.image=new ImageIcon("images/hero0.png");
}
/*
* 重写move方法,空方法,目的是不动
* 修改超类中规定的向下飞,改成不动
*/
public void move() {
}
/*
*将鼠标机移动到鼠标位置X,Y
*@param x鼠标位置x
*@param y鼠标位置y
*/
public void move(int x, int y) {
this.x=x;
this.y=y;
}
}
测试程序:
public class Demo1 {
public static void main(String[] args) {
/*
* 测试图片加载结果
*/
Airplane p1=new Airplane(10,10,1.5);//宽高去掉了
//getImageLoadStatus()返回结果为8表示图片加载成功
System.out.println(p1.image.getImageLoadStatus());;//8
Bigplane p2=new Bigplane(100,100,2.3);
System.out.println(p2.image.getImageLoadStatus());//8
Bee bee=new Bee(10,10,2.4);
System.out.println(bee.image.getImageLoadStatus());//8
Bullet bullet=new Bullet(100,100);
System.out.println(bullet.image.getImageLoadStatus());//8
Hero hero=new Hero(100,100);
System.out.println(hero.image.getImageLoadStatus());//8
Sky sky=new Sky();
System.out.println(sky.image.getImageLoadStatus());//8
}
}
1.6 飞机大战的“世界”
设计一个类叫World,代表飞机大战中的“世界”,飞机大战的世界包含全部飞机,子弹,天空以及英雄。
完美步骤:
- World类继承JPanel
- 在main方法中创建窗口,窗口添加World对象
- 给World添加构造器,初始化天空,飞机,子弹,英雄等
- 重写paint方法绘制天空,飞机,子弹,英雄等
package demo05;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class World extends JPanel{
/*
* 添加飞机大战世界中的物体
*/
Airplane[] airplanes;
Bigplane[] bigplanes;
Bullet[]bullets;
Sky sky;
Hero hero;
/*
* 利用构造器初始化世界中每个物体
*/
public World(){
airplanes=new Airplane[2];//创建两个元素的数组
airplanes[0]=new Airplane(10,10,1.5);//宽高去掉了50,40
airplanes[1]=new Airplane(10,100,1.5);
//x,y 宽 高 速度
bigplanes=new Bigplane[2];
bigplanes[0]=new Bigplane(100,20,2);
bigplanes[1]=new Bigplane(100,220,2);
bullets =new Bullet[2];
bullets[0]=new Bullet(200,400,4);
bullets[1]=new Bullet(200,350,4);
sky=new Sky(0,0,400,700,0.8);
hero=new Hero(200,500,200,200);
}
public void paint(Graphics g) {
sky.image.paintIcon(this.g.(int)sky.x,(int)sky.y);
hero.image.paintIcon(this.g.(int)hero.x,(int)hero.y);
bullets[0].image,paintIcon(this.g,(int)bullets[0].x,(int)bullets[0].y);
bullets[1].image,paintIcon(this.g,(int)bullets[1].x,(int)bullets[1].y);
airplanes[0].image,paintIcon(this.g,(int)airplanes[0].x,(int)airplanes[0].y);
airplanes[1].image,paintIcon(this.g,(int)airplanes[1].x,(int)airplanes[1].y);
bigplanes[0].image,paintIcon(this.g,(int)bigplanes[0].x,(int)bigplanes[0].y);
bigplanes[1].image,paintIcon(this.g,(int)bigplanes[1].x,(int)bigplanes[1].y);
}
public static void main(String[] args) {
JFrame frame=new JFrame();
World world=new World();
frame.add(world);
frame.setSize(400,700);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
1.7抽取绘制方法
解决冗余:
实现步骤:
- 在FlyingObject类中定义绘制方法paint,这个方法将被全体子类继承复用
- 重构World中的paint方法,调用每个对象的paint方法,这样就可以简化World编码
- 重构World的paint方法利用for循环遍历数组元素,这样数组元素再多都能处理了
FlyingObject.java
package demo05;
import java,awt.Graphics;
import javax.swing.ImageIcon;
/*
* 父类中定义从子类抽取的属性和方法
* 这种抽取方式称为“泛化”
*/
public class FlyingObject {
double x;
double y;
double width;
double height;
double step;
ImageIcon image;
public FlyingObject(double x, double y, double width, double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public void move(){
y+=step;
}
public void paint(Graphics g){
image.paintIcon(null,g,(int)x, (int)y);
}
@Override
public String toString() {
String className=getClass().getName();
return className+" [x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + "]";
}
}
World.java
package demo05;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class World extends JPanel{
/*
* 添加飞机大战世界中的物体
*/
Airplane[] airplanes;
Bigplane[] bigplanes;
Bullet[]bullets;
Sky sky;
Hero hero;
/*
* 利用构造器初始化世界中每个物体
*/
public World(){
airplanes=new Airplane[2];//创建两个元素的数组
airplanes[0]=new Airplane(10,10,1.5);//宽高去掉了50,40
airplanes[1]=new Airplane(10,100,1.5);
//x,y 宽 高 速度
bigplanes=new Bigplane[2];
bigplanes[0]=new Bigplane(100,20,2);//宽高去掉了100,200
bigplanes[1]=new Bigplane(100,220,2);
bullets =new Bullet[6];
bullets[0]=new Bullet(200,400,4);//宽高去掉了10,10
bullets[1]=new Bullet(200,350,4);
bullets[2]=new Bullet(200,300,4);
bullets[3]=new Bullet(200,250,4);
bullets[4]=new Bullet(200,200,4);
bullets[5]=new Bullet(200,150,4);
sky=new Sky(0,0,400,700,0.8);
hero=new Hero(200,500,200,200);
}
public void paint(Graphics g) {
sky.paint(g);
hero.paint(g);
for(int i=0; i<bullets.length; i++) {
//i=0 1 2 3 4 5
bullets[i].paint(g);
}
//调用每个飞机的多态方法,实现多态的绘制
for(int i=0; i<planes.length; i++) {
airplanes[i].paint(g);
}
for(int i=0; i<planes.length; i++) {
bigplanes[i].paint(g);
}
}
public static void main(String[] args) {
JFrame frame=new JFrame();
World world=new World();
frame.add(world);
frame.setSize(400,700);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
2 定时器与内部类
利用定时器和内部类配合就可以实现飞行图片的运动。
2.1 定时器
利用定时器可以定时执行方法,其中被定时执行的方法称为“定时任务”。
三个概念:
- 定时器对象,是执行定时任务的对象
- 包含被执行方法的任务对象
- 将被执行任务作为计划添加到定时器
案例:
public class TimerDemo {
public static void main(String[] args) {
Timer timer=new Timer();
MyTask task=new MyTask();//被执行的任务
timer.schedule(task, 1000,1000);//为定时器提交执行计划;延迟一秒以后执行第一次,之后每间隔一秒再执行
}
}
class MyTask extends TimerTask{
public void run() {
//定义被定时器执行的任务
System.out.println("Hello World!");
}
}
2.2 内部类
什么是内部类:在类内部定义的类,称为内部类,内部类按照定义位置可以分为:
- 成员内部类:在类体中定义,比较常用
- 局部内部类:在方法中定义,不常用
- 匿名内部类:在方法中使用匿名内部类语法声明,常用
内部类优点:
- 可以隐藏类的定义
- 可以共享外部的属性、方法
何时使用内部类:
- 如果需要隐藏类的定义
- 或者内部类需要共享外部的属性或者方法
内部类注意事项:
- 如果不需要如上特性,请勿使用
- 内部类一般只在类的内部使用,不会在类的外部使用
案例:
public class Demo{
public static void main(String[] args){
Foo foo=new Foo();
foo.demo();
}
}
class Foo{//Foo 叫外部类
int a=9;
class Koo{//内部类
public void test(){
System.out.println(a);//内部类使用外部类定义的属性a
}
}
public void demo(){
Koo koo=new Koo();//创建内部类对象
koo.test();
}
}
2.3 飞机移动
实现步骤:
- 创建定时任务内部类,执行飞行的move()方法移动每个飞机,移动后执行repaint()方法,repaint()方法会自动执行paint方法绘制整个面板。
- World类添加action方法,在action方法中启动定时器
- 在main方法中显示窗口后调用action方法,也就是显示窗口后启动定时器
代码:
package demo07;
import java.awt.Graphics;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class World extends JPanel{
/*
* 添加飞机大战世界中的物体
*/
Airplane[] airplanes;
Bigplane[] bigplanes;
Bullet[]bullets;
Sky sky;
Hero hero;
/*
* 利用构造器初始化世界中每个物体
*/
public World(){
airplanes=new Airplane[2];//创建两个元素的数组
airplanes[0]=new Airplane(10,10,1.5);//宽高去掉了50,40
airplanes[1]=new Airplane(10,100,1.5);
//x,y 宽 高 速度
bigplanes=new Bigplane[2];
bigplanes[0]=new Bigplane(100,20,2);//宽高去掉了100,200
bigplanes[1]=new Bigplane(100,220,2);
bullets =new Bullet[6];
bullets[0]=new Bullet(200,400,4);//宽高去掉了10,10
bullets[1]=new Bullet(200,350,4);
bullets[2]=new Bullet(200,300,4);
bullets[3]=new Bullet(200,250,4);
bullets[4]=new Bullet(200,200,4);
bullets[5]=new Bullet(200,150,4);
sky=new Sky(0,0,400,700,0.8);
hero=new Hero(200,500,200,200);
}
public void paint(Graphics g) {
sky.paint(g);
hero.paint(g);
for(int i=0; i<bullets.length; i++) {
//i=0 1 2 3 4 5
bullets[i].paint(g);
}
//调用每个飞机的多态方法,实现多态的绘制
for(int i=0; i<planes.length; i++) {
airplanes[i].paint(g);
}
for(int i=0; i<planes.length; i++) {
bigplanes[i].paint(g);
}
}
public static void main(String[] args) {
JFrame frame=new JFrame();
World world=new World();
frame.add(world);
frame.setSize(400,700);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
//调用action方法启动定时器
world.action();
}
/*
* 添加内部类,实现定时计划任务
* 为何使用内部类实现定时任务
* 1,隐藏定时任务到world类中
* 2,可以访问外部类中的数据,飞机,子弹等
*/
class PaintTask extends TimerTask{
public void run(){//执行飞机移动方法;是多态的移动方法,每个飞机都不同
for(int i=0; i<airplanes.length; i++){
airplanes[i].move();
}
for(int i=0; i<bigplanes.length; i++){
bigplanes[i].move();
}
for(int i=0; i<bullets.length; i++){
bullets[i].move();
}
sky.move();
repaint(); //调用重写绘制方法,这个方法会自动执行paint
}
}
public void action(){//启动方法
Timer timer=new Timer();
PaintTask task=new PaintTask();//定时器任务
timer.schedule(task,1000,1000/100);//规定计划每间隔多少时间执行
}
}
2.4 连续的天空
利用两张图片反复不断交替滚动播放,可以实现连续不断的天空背景。
实现步骤:
- 为了控制两张图片的运动,在原有的一个图片纵坐标y基础上扩展一个新属性y0,代表第二张图片的纵坐标位置
- 初始这两个纵坐标,一个是当前面板背景,一个是屏幕之外准备入场
- 移动时候,两个坐标同时运动
- 每当一个背景图片坐标移动到屏幕之外就其坐标返回到屏幕上方准备入场的位置
- 用两个纵坐标绘制两个图片
代码:
package demo08;
import java.awt.Graphics;
import javax.swing.ImageIcon;
public class Sky extends FlyingObject{
double yo;
public Sky(double x, double y, double width, double height, double step) {
super(x,y,width,height);
this.step =step;
this.image=new ImageIcon("images/background.png");
x=0;
y=0;
yo=-image.getIconHeight();//照片的高度
height=image.getIconHeight(); //天空的高度(照片)
width=image.getIconWidth();//天空的宽度(照片)
System.out.println("y="+y+" y0="+yo+" width="+width+" height="+height);
}
public void move() {
y+=step;
yo+=step;//(两个坐标都在移动)
if(y>=height) {
System.out.println("第一个照片返回:y="+y);
y=-height;
System.out.println("y="+y+" y0="+yo+" width="+width+" height="+height);
}
if(yo>=height) {
System.out.println("第二个照片返回:y0="+yo);
yo=-height;
System.out.println("y="+y+" y0="+yo+" width="+width+" height="+height);
}
}
public void paint(Graphics g) {//改写父类中的方法
image.paintIcon(null, g, (int)x, (int)y);
image.paintIcon(null, g, (int)x, (int)yo);//对天空做修改,对两张照片进行处理
}
}
package demo08;
public class SkyTest{
public static void main(String[] args){
/*
*测试连续的天空
*/
Sky sky=new Sky(0,0,400,700,0.8);
for(int i=0; i<5000; i++){
sky.move();
}
}
}
每天对着镜子里的自己说一句:今天的你也很棒!