呀~好久没有更新文章啦~~这次我们用Java的Swing图形界面框架写一个飞机大战小游戏
飞机大战游戏分为几个部分:
com.mr // 主目录
main
Constants // 常量
Start // 程序入口
model
Bullet // 子弹
Enemy // 敌机
Player // 玩家
service
Fresh // 刷新帧
MouseMotion // 鼠标移动事件
ScoreRecorder // 分数记录器
view
GameFrame // 窗体
GamePanel // 面板
程序目录如上图
首先来写Start.java的代码
package com.mr.main;
import com.mr.view.GameFrame;
public class Start {
public static void main(String[] args){
GameFrame gameFrame=new GameFrame();
}
}
上面的代码导入GameFrame,实例化
接下来是GameFrame中的代码
package com.mr.view;
import com.mr.service.MouseMotion;
import javax.swing.*;
import java.awt.*;
public class GameFrame extends JFrame {
private Container container;
private GamePanel panel;
private MouseMotion mm;
public static final int SPACING=15;
public GameFrame(){
setBounds(300,200,720+SPACING,960);
setTitle("FlightWar");
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
container=getContentPane();
container.removeAll();
panel=new GamePanel();
mm=new MouseMotion(panel);
addMouseMotionListener(mm);
addKeyListener(panel);
container.add(panel);
container.validate();
setVisible(true);
}
}
GameFrame里导入了javax.swing的所有类和java.awt的所有类,还有待会要写的MouseMotion鼠标移动事件监听类,GameFrame继承于JFrame,对窗体进行初始化,并实例化GamePanel主面板,添加到窗口
接下来我们要来写GamePanel的代码
package com.mr.view;
import com.mr.main.Constants;
import com.mr.model.Bullet;
import com.mr.model.Enemy;
import com.mr.model.Player;
import com.mr.service.Fresh;
import com.mr.service.ScoreRecorder;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
public class GamePanel extends JPanel implements KeyListener {
private BufferedImage img;
private Graphics2D g2;
private BufferedImage backgroundImage;
private Thread thread;
private Player player;
private final int INTERVAL;
private int timer_create_bullet;
private int timer_create_enemy;
private ArrayList<Bullet> bullets;
private ArrayList<Enemy> enemies;
private int score;
private int health;
private boolean record=false;
private int maxScore;
public GamePanel(){
img=new BufferedImage(720,960,BufferedImage.TYPE_INT_BGR);
g2=img.createGraphics();
File tmp=new File(Constants.IMAGES+"/background.png");
try {
backgroundImage=ImageIO.read(tmp);
} catch (IOException e) {
e.printStackTrace();
}
player=new Player();
INTERVAL=Fresh.INTERVAL;
timer_create_bullet=0;
score=0;
health=5;
bullets=new ArrayList<>();
enemies=new ArrayList<>();
thread=new Fresh(this);
thread.start();
}
private void paintImage(){
g2.drawImage(player.img,player.x,player.y,this);
if (!isLose()) {
timer_create_bullet+=INTERVAL;
timer_create_enemy+=INTERVAL;
if (timer_create_bullet>=125) {
timer_create_bullet=0;
Bullet newBullet=new Bullet(player.x+player.img.getWidth()/2,player.y);
bullets.add(newBullet);
}
if (timer_create_enemy>=700) {
timer_create_enemy=0;
Enemy newEnemy=new Enemy();
enemies.add(newEnemy);
}
for (Bullet bullet:bullets) {
g2.drawImage(bullet.img,bullet.x,bullet.y,this);
}
for (Enemy enemy:enemies) {
g2.drawImage(enemy.img,enemy.x,enemy.y,this);
}
g2.setColor(Color.red);
g2.setFont(new Font("simhei",Font.PLAIN,24));
g2.drawString("得分:"+String.valueOf(score),10,30);
g2.setColor(Color.blue);
g2.drawString("生命值:"+String.valueOf(health),10,60);
} else {
g2.setColor(Color.red);
g2.setFont(new Font("simhei",Font.PLAIN,24));
g2.drawString("得分:"+String.valueOf(score),10,30);
g2.setColor(Color.blue);
g2.drawString("生命值:"+String.valueOf(health),10,60);
g2.setColor(Color.green);
g2.drawString("纪录:"+String.valueOf(maxScore),10,90);
}
}
public void paint(Graphics g){
g2.drawImage(backgroundImage,0,0,720,960,this);
if (!isLose()) {
freshBullets();
freshEnemies();
collide();
} else {
if (!record) {
ScoreRecorder scoreRecorder=new ScoreRecorder();
maxScore=scoreRecorder.start(score);
}
bullets.clear();
enemies.clear();
}
paintImage();
g.drawImage(img,0,0,this);
}
private void freshBullets(){
for (int i=0;i<bullets.size();i++) {
Bullet bullet=bullets.get(i);
bullet.move();
if (bullet.y+bullet.img.getHeight()<=0){
bullets.remove(i);
i--;
}
}
}
private void collide(){
for (int i=0;i<enemies.size();i++) {
if (enemies.get(i).getBound().intersects(player.getBound())) {
enemies.remove(i);
i--;
health--;
continue;
}
for (int j=0;j<bullets.size();j++) {
Enemy enemy=enemies.get(i);
if (enemy.getBound().intersects(bullets.get(j).getBound())) {
enemy.health--;
if (enemy.health<=0) {
score+=enemy.score;
enemies.remove(i);
}
bullets.remove(j);
i--;
break;
}
}
}
}
private void freshEnemies(){
for (int i=0;i<enemies.size();i++) {
Enemy enemy=enemies.get(i);
enemy.move();
if (enemy.y>=960) {
enemies.remove(i);
i--;
}
}
}
public boolean isLose(){
return health<=0;
}
public Player getPlayer(){
return player;
}
@Override
public void keyTyped(KeyEvent event){
;
}
@Override
public void keyPressed(KeyEvent event){
;
}
@Override
public void keyReleased(KeyEvent event){
;
}
}
GamePanel是整个程序中特别重要的一个部分,代码量也很多,在这里面,我们导入了所有模型,com.mr.model包下的模型,com.mr.service下的刷新帧和分数记录器,还有我们写的常量,java.awt和javax.swing,流对象javax.imageio.ImageIO和java.io.File等,还有ArrayList数组等,后面代码都会用到。GamePanel继承自JPanel,实现了KeyListener接口,实现KeyListener接口需要写入3个事件监听方法KeyTyped,KeyPressed,KeyReleased,不过在飞机大战的示例中,我们不需要用到键盘事件,先空着用分号代替,接下来就要生命很多private变量,img主图片,g2为img对应的Graphics2D对象,将东西绘制在g2中,最后将主图片绘制于Panel中;Thread类型变量thread表示刷新帧的线程,Player模型,刷新间隔常量Interval,间隔创建子弹和敌机的timer_create_bullet和timer_create_enemy,存储子弹和敌机的ArrayList<Bullet>和ArrayList<Enemy>类型的bullets和enemies,分数score,生命health,是否已经将最高分写入文件的bool值,还有从文件读入的最高分。接下来我们来看看构造,首先创建一个长宽为720,960的BufferedImage对象作为主图片,创建g2,写一个tmp临时变量,储存File,然后尝试读入背景图片(这里IMAGES为com.mr.main.Constants下的公有静态字符串型常量,待会会讲到Constants下的代码),实例Player,从刷新帧方法读入刷新帧时间作为刷新率,初始化得分生命数组线程等。然后有一个paint方法,这个是重写自JPanel用于绘制的方法(自动调用),默认传入一个Graphics类型的变量g,我们不把东西画在g中,要画在我们自己创建的g2里,所以g2先绘制背景图,进行是否已经失败的判断,做出读写得分、清除子弹敌机等数组、检测碰撞collide(),刷新freshBullets()等等,然后运行paintImage方法,这个是我们自己写的方法,最后将img主图片画在g中即可,paintImage就绘制一些东西,代码不难理解。freshBullets和freshEnemies方法就是刷新子弹和敌机啦,collide方法就是检测碰撞,理清思路来写代码就不会太难理解或者是太乱。最后isLose判断输赢。
下面补上Constants的代码
package com.mr.main;
public class Constants {
public static final String BASEPATH=System.getProperty("user.dir");
public static final String RESOURCES=BASEPATH+"/resources";
public static final String IMAGES=RESOURCES+"/images";
public static final String DATA=RESOURCES+"/data";
public static final String SOUNDS=RESOURCES+"/sounds";
}
Constants就是一些路径的常量
然后我们来写model中的代码
首先来写bullet
package com.mr.model;
import com.mr.main.Constants;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class Bullet {
public BufferedImage img;
public int x;
public int y;
private int speed;
public Bullet(int x,int y){
File tmp=new File(Constants.IMAGES+"/bullet1.png");
try {
img=ImageIO.read(tmp);
} catch (IOException e){
e.printStackTrace();
}
this.x=x;
this.y=y;
speed=15;
}
public void move(){
y-=speed;
}
public Rectangle getBound(){
return new Rectangle(x,y,img.getWidth(),img.getHeight());
}
}
这个model里面的类代码不算太多,也不难理解,导入需要的包,声明变量并初始化,这里就包括图片和xy坐标以及移动速度等,move方法移动,getBound方法获取对应的Rectangle长方形对象,用于检测碰撞。
下面是Enemy的代码
package com.mr.model;
import com.mr.main.Constants;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;
public class Enemy {
public BufferedImage img;
public int x;
public int y;
private int speed;
public String type;
public int health;
public int score;
public Enemy(){
Random r=new Random();
int v=r.nextInt(1,10);
switch (v){
case 1: case 2: case 3: case 4: case 5:
type="small";
health=r.nextInt(1,4);
break;
case 6: case 7: case 8:
type="mid";
health=r.nextInt(4,7);
break;
case 9:
type="big";
health=r.nextInt(7,10);
break;
}
score=health;
File tmp=new File(Constants.IMAGES+"/"+type+"_enemy.png");
try {
img=ImageIO.read(tmp);
} catch (IOException e){
e.printStackTrace();
}
speed=r.nextInt(2,8);
y=-img.getHeight();
x=r.nextInt(0,720-img.getWidth());
}
public void move(){
y+=speed;
}
public Rectangle getBound(){
return new Rectangle(x,y,img.getWidth(),img.getHeight());
}
}
因为这些代码思路都差不多所以就不再详细讲解
下面是Player类
package com.mr.model;
import com.mr.main.Constants;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class Player {
public BufferedImage img;
public int x;
public int y;
private int speed;
public Player(){
File tmp=new File(Constants.IMAGES+"/myplane.png");
try {
img=ImageIO.read(tmp);
} catch (IOException e){
e.printStackTrace();
}
y=960-80-img.getHeight();
x=720/2-img.getWidth()/2;
speed=10;
}
public void checkBound(){
if (x<0) {
x=0;
} else if (x+img.getWidth()>720) {
x=720-img.getWidth();
}
}
public Rectangle getBound(){
return new Rectangle(x,y,img.getWidth(),img.getHeight());
}
}
然后我们来写刷新帧的代码
package com.mr.service;
import com.mr.view.GamePanel;
public class Fresh extends Thread {
private GamePanel panel;
public static final int INTERVAL=20;
public Fresh(GamePanel panel){
this.panel=panel;
}
public void run(){
while(true){
panel.repaint();
try {
Thread.sleep(INTERVAL);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
刷新帧继承Thread,重复执行repaint方法重绘即可
接下来是MouseMotion
package com.mr.service;
import com.mr.model.Player;
import com.mr.view.GamePanel;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
public class MouseMotion implements MouseMotionListener {
private GamePanel panel;
public MouseMotion(GamePanel panel){
this.panel=panel;
}
@Override
public void mouseDragged(MouseEvent event){
;
}
@Override
public void mouseMoved(MouseEvent event){
if (!panel.isLose()) {
Player player = panel.getPlayer();
player.x = event.getX() - player.img.getWidth() / 2;
player.checkBound();
}
}
}
这个MouseMotion要实现MouseMotionListener接口,写mouseMoved方法,如果没有失败则更新飞机位置,也就是说飞机的x坐标随着鼠标移动
最后一个小功能,实现计分效果
package com.mr.service;
import com.mr.main.Constants;
import com.mr.model.Player;
import java.io.*;
public class ScoreRecorder {
private int maxScore;
private int myScore;
private File tmp;
public void read(){
tmp=new File(Constants.DATA+"/data.txt");
if (!tmp.exists()) {
maxScore=0;
try {
tmp.createNewFile();
FileWriter fw=new FileWriter(tmp);
BufferedWriter bw=new BufferedWriter(fw);
String tmp2="0";
bw.write(tmp2);
bw.close();
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
} else {
try {
FileReader fr=new FileReader(tmp);
BufferedReader br=new BufferedReader(fr);
String tmp2=br.readLine();
maxScore=Integer.valueOf(tmp2.strip());
br.close();
fr.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public int start(int score){
myScore=score;
read();
if (myScore>maxScore) {
write();
}
return maxScore;
}
public void write(){
try {
FileWriter fw=new FileWriter(tmp);
BufferedWriter bw=new BufferedWriter(fw);
String tmp2=String.valueOf(myScore);
bw.write(tmp2);
bw.close();
fw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
计分效果用到了io输入输出流,运用了文件的读写操作
这里resources的资源目录如下,可以根据自己的实际情况修改Constants的值
LastOne!!!画上点睛之笔,写个Start程序入口就完工啦!!
package com.mr.main;
import com.mr.view.GameFrame;
public class Start {
public static void main(String[] args){
GameFrame gameFrame=new GameFrame();
}
}
喜欢的话就点赞收藏关注吧!