整体的思路和博主其他博文java实现贪吃蛇小游戏(源码+注释)和java实现2048小游戏(源码+注释)是一样的,都是利用Frame创建窗体,利用Panel添加组件,提前规划好布局,调用鼠标和键盘监听,引用图片元素。
此处的思路在于不断的刷新敌机集合和子弹集合来实现动态效果。
主函数,实现类
package ui;
//主函数实现
public class Main {
public static void main(String[] args) {
//创建窗体
GameFrame frame = new GameFrame();
//创建面板
GamePanel panel = new GamePanel(frame);
//调用开始游戏的方法启动游戏
panel.action();
//将面板加入到窗体中
frame.add(panel);
//设置窗体可见
frame.setVisible(true);
}
}
窗体类,绘制窗体
package ui;
import javax.swing.*;
//创建窗体
public class GameFrame extends JFrame {
//构造方法,初始化窗体属性
public GameFrame(){
//设置标题,来源于JFrame
setTitle("飞机大战");
//设置大小
setSize(512,768);
//设置居中
setLocationRelativeTo(null);
//设置窗体可见
//setVisible(true);
//不允许玩家修改界面大小
setResizable(false);
//设置默认的关闭选项
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
画布类,用于勾勒元素
package ui;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
//自定义游戏面板(创建画布)
public class GamePanel extends JPanel {
//定义背景图
BufferedImage bg;
//构造游戏机
Hero hero = new Hero();
//敌机集合
List<Ep> eps = new ArrayList<Ep>();
//弹药集合
List<Fire> fs = new ArrayList<Fire>();
//定义分数
int score;
//设置游戏开关
Boolean gameover=false;
//设置火力
int power = 1;
//动作函数,描绘场景内飞行物的运动状态
public void action(){
//创建线程(基本模板)
new Thread(){
public void run(){
//无线循环创建
while (true){
//判断如果游戏没有失败,则执行以下操作
if(!gameover){
//敌机进场
epEnter();
//调用敌机移动方法
epMove();
//发射子弹
shoot();
//子弹移动
fireMove();
//判断子弹是否击中敌机
shootEp();
//检测敌机是否撞到游戏机
hit();
}
//每执行一次,线程休眠一会儿
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//重绘界面
repaint();
}
}
}.start();//让线程开始运行
}
//每执行20次,释放一个敌机,创建计数器计数
int index = 0;
protected void epEnter(){
index++;
//创建敌机
if(index>=20){
//创建敌机
Ep e = new Ep();
//加入集合
eps.add(e);
//重置计数器
index = 0;
}
}
//让敌机移动
protected void epMove(){
//遍历敌机集合,依次移动
for (int i = 0; i < eps.size(); i++) {
//获取集合中敌机
Ep e = eps.get(i);
//调用敌机类中的移动方法
e.move();
}
}
//每执行20次,创建一颗子弹,创建计数器计数
int findex = 0;
protected void shoot(){
findex++;
if(findex>=20){
//根据火力判断子弹行数
//一排子弹
if(power==1){
//创建子弹
Fire fire1 = new Fire(hero.x+45,hero.y,1);
//将子弹存入子弹集合中
fs.add(fire1);
}
//两排子弹
else if(power==2){
//创建子弹
Fire fire1 = new Fire(hero.x+15,hero.y,0);
//将子弹存入子弹集合中
fs.add(fire1);
//创建子弹
Fire fire2 = new Fire(hero.x+75,hero.y,2);
//将子弹存入子弹集合中
fs.add(fire2);
}
//三排子弹
else{
//创建子弹
Fire fire1 = new Fire(hero.x+15,hero.y,0);
//将子弹存入子弹集合中
fs.add(fire1);
//创建子弹
Fire fire2 = new Fire(hero.x+75,hero.y,2);
//将子弹存入子弹集合中
fs.add(fire2);
//创建子弹
Fire fire3 = new Fire(hero.x+45,hero.y-10,1);
//将子弹存入子弹集合中
fs.add(fire3);
}
//使计数器归0
findex = 0;
}
}
//让子弹移动
protected void fireMove(){
//遍历子弹集合
for (int i = 0; i < fs.size(); i++) {
//获取每一颗子弹位置
Fire f = fs.get(i);
//依次移动每一颗子弹
f.move();
}
}
//判断子弹是否击中敌机
protected void shootEp(){
//遍历所有子弹
for (int i = 0; i < fs.size(); i++) {
//获取每一颗子弹
Fire f = fs.get(i);
//判断一颗子弹是否击中敌机
bang(f);
}
}
//判断一颗子弹是否击中敌机
protected void bang(Fire f){
for (int i = 0; i < eps.size(); i++) {
//取出每一张敌机
Ep e = eps.get(i);
//判断这个子弹是否击中敌机
if(e.shootBy(f)&&e.type!=15){
//判断游戏机机是否击中道具机
if(e.type==12){
//火力增加
power++;
//如果火力值大于三,增加血量
if(power>3){
//恢复血量
if(hero.hp<3){
hero.hp++;
}
//使游戏机血量不超过3
power = 3;
}
}
//如果敌机被子弹击中
//敌机消失
eps.remove(e);
//删除子弹
fs.remove(f);
//增加分数
score += 10;
}
}
}
//检测敌机是否撞到主机
protected void hit() {
for (int i = 0; i < eps.size(); i++) {
//获取每一个敌机
Ep e = eps.get(i);
//调用敌机的方法判断
if(e.shootBy(hero)){
//删除敌机
eps.remove(e);
//主机血量减少
hero.hp--;
//火力恢复初始值
power = 1;
//分数增加
score += 10;
//当主机血量减少到0时游戏结束
if(hero.hp==0){
gameover = true;
}
}
}
}
//构造函数
public GamePanel(GameFrame frame){
//设置背景
bg = App.getImg("/img/bg2.jpg");
//创建鼠标监听和鼠标适配器
MouseAdapter adapter = new MouseAdapter() {
//点击鼠标时会执行的代码
@Override
public void mouseClicked(MouseEvent e) {
//游戏结束时候,点击屏幕时重新开始游戏
if(gameover){
//重新初始化主机
hero = new Hero();
//重置游戏开关
gameover = false;
//分数清0
score = 0;
//清空敌机集合
eps.clear();
//随机背景图
Random random = new Random();
int index = random.nextInt(5)+1;
bg = App.getImg("/img/bg"+index+".jpg");
//重新绘制
repaint();
}
}
//确定需要监听的事件,此处监听鼠标移动事件
@Override
public void mouseMoved(MouseEvent e) {
//让游戏机的横纵坐标等于鼠标的移动坐标
//获取鼠标的横纵坐标
int mx = e.getX();
int my = e.getY();
//传递坐标
if(!gameover){
//使鼠标坐标正好位于图片中央
hero.moveToMouse(mx-114/2,my-93/2);
}
//重新绘制界面
repaint();
}
};
//将适配器加入到监听器中
addMouseListener(adapter);
addMouseMotionListener(adapter);
//使用键盘监听,创建键盘适配器
KeyListener kd = new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
}
//当键盘被按下是触发
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
//上键
if(keyCode == KeyEvent.VK_UP){
hero.y-=10;
}
//下键
else if(keyCode == KeyEvent.VK_DOWN){
hero.y+=10;
}
//左键
else if(keyCode == KeyEvent.VK_LEFT){
hero.x-=10;
}
//右键
else if(keyCode == KeyEvent.VK_RIGHT){
hero.x+=10;
}
repaint();
}
@Override
public void keyReleased(KeyEvent e) {
}
};
//将适配器加入窗体的监听器中
frame.addKeyListener(kd);
}
//画图方法
@Override
public void paint(Graphics g) {
//调用父类中的一些渲染方法
super.paint(g);
//画背景
g.drawImage(bg,0,0,null);
//画敌机
for (int i = 0; i < eps.size(); i++) {
Ep ep = eps.get(i);
g.drawImage(ep.img,ep.x,ep.y,null);
}
//画子弹
for (int i = 0; i < fs.size(); i++) {
Fire fire = fs.get(i);
g.drawImage(fire.img,fire.x,fire.y,fire.w,fire.h,null);
}
//画分数
g.setColor(Color.white);
//设置字体型号,设置加粗,设置字体大小
g.setFont(new Font("\u6977\u4F53",Font.BOLD,30));
//显示分数
g.drawString("分数:"+score,10,30);
//画游戏机
g.drawImage(hero.img,hero.x,hero.y,null);
//画游戏机血量
for (int i = 0; i < hero.hp; i++) {
g.drawImage(hero.img,380+i*35,5,30,30,null);
}
//画游戏结束
if(gameover){
//设置字体颜色为红色
g.setColor(Color.red);
//设置字体型号,设置加粗,设置字体大小
g.setFont(new Font("楷体",Font.BOLD,35));
//显示字体
g.drawString("GAMEOVER",170,300);
//设置字体颜色为绿色
g.setColor(Color.green);
//设置字体型号,设置加粗,设置字体大小
g.setFont(new Font("楷体",Font.BOLD,29));
//显示字体
g.drawString("yh提醒你点击屏幕任意位置重新开始",10,350);
}
//重新绘制界面
repaint();
}
}
飞行物类,设定飞行物特性
package ui;
import java.awt.image.BufferedImage;
//飞行物具有共同特点,故抽离成父类
public class FlyObject {
//均采用照片素材
BufferedImage img;
//位置的横坐标
int x;
//位置的纵坐标
int y;
//图片元素的宽度
int w;
//图片元素的高度
int h;
}
主机类,设定游戏时候操控的飞机属性
package ui;
import java.awt.image.BufferedImage;
//游戏机
public class Hero extends FlyObject{
//设置游戏机血量
int hp;
//构造函数
public Hero(){
//获取游戏机元素
img = App.getImg("/img/hero.png");
//确认初始位置
x = 200;
y = 500;
//获取游戏机图片元素的宽和高
w = img.getWidth();
h = img.getHeight();
//设置初始血量为3
hp = 3;
}
//根据传入参数移动相应位置
public void moveToMouse(int mx,int my){
x = mx;
y = my;
}
}
敌机类
package ui;
import javax.swing.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
//敌机类
public class Ep extends FlyObject{
//设置敌机速度
int sp;
//设置敌机类型,不同的敌机类型有不同的属性
int type;
//构造函数
public Ep(){
//引入随机数随机调用敌机
Random random = new Random();
//调用[1,15]范围内的敌机
int index = random.nextInt(15)+1;
//保存敌机类型
type = index;
//如果序号小于10,则补充前导0,实质符合图片命名规律
String path = "/img/ep" + (index<10?"0":"")+index+".png";
//根据路径调用方法类函数获取图片io流
img = App.getImg(path);
//确定敌机位置
//获取敌机照片元素宽度参数
w = img.getWidth();
//边界长减去照片宽度,防止照片越界
x = random.nextInt(512-w);
y = 0;
//设置速度
sp = 17-index;
}
//设置敌机移动方法
public void move() {
//如果敌机类型为5,则向左方倾斜移动
if(type==5){
x -= 5;
y += sp;
}
//如果敌机类型为5,则向右方倾斜移动
else if(type==6){
x += 5;
y += sp;
}
//如果是其他类型,则正常向下移动
else {
y+=sp;
}
}
//判断敌机是否被子弹击中
public boolean shootBy(Fire f) {
//获取图片元素属性,确定相应的坐标算法,判断是否满足条件,满足则被击中
Boolean hit = x <= f.x+f.w &&x>f.x-w&&y<=f.y+f.h&&y>f.y-h;
return hit;
}
//判断敌机是否被玩家机击中
public boolean shootBy(Hero f) {
//获取图片元素属性,确定相应的坐标算法,判断是否满足条件,满足则被击中
Boolean hit = x <= f.x+f.w &&x>f.x-w&&y<=f.y+f.h&&y>f.y-h;
return hit;
}
}
子弹类
package ui;
public class Fire extends FlyObject{
//子弹当前移动方向,0为左上角飞,1垂直飞,2右上角飞
int dir;
//构造方法,初始化子弹
public Fire(int hx,int hy,int dir){
//获取子弹的图片
img = App.getImg("/img/fire.png");
//确定图片的大小,此处把子弹大小缩小了4倍
w = img.getWidth()/4;
h = img.getHeight()/4;
//根据构造函数传进来的参数设置子弹的位置以及子弹的方向
x = hx;
y = hy;
this.dir=dir;
}
//子弹的移动方法
public void move() {
//左上角飞
if(dir==0){
x -= 1;
y -= 10;
}
//垂直上飞
else if(dir == 1){
y -= 10;
}
//右上角飞
else if(dir == 2){
x += 1;
y -= 10;
}
}
}
方法类
package ui;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
//处理图片的工具类
//此处我定义了两种获取图片的方法,可以对照参考
public class App {
//static可以公用所有对象都共用该方法,并且可以不依赖对象实现
public static BufferedImage getImg(String path){
//用try方法捕获异常
try {
//io流,输送数据的管道
BufferedImage img = ImageIO.read(App.class.getResource(path));
return img;
}
//异常处理,打印异常
catch (IOException e) {
e.printStackTrace();
}
//没找到则返回空
return null;
}
//此处与贪吃蛇小游戏的调用方法是一样的
public static ImageIcon getImg2(String path){
InputStream is;
//从主类文件所在的路径寻找相应路径的图片
is = App.class.getClassLoader().getResourceAsStream(path);
//用try方法捕获异常
try {
return new ImageIcon(ImageIO.read(is));
}
//异常处理,打印异常
catch (IOException e) {
e.printStackTrace();
}
//没找到则返回空
return null;
}
}