一篇文章做到从零开始编写一个飞机大战小游戏
老样子,先看成品
文章前半部分是过程和思路,结尾有完整代码
正常情况下:
分数达到特定值(100)之后:
好的,看完了成品接下来正式来写一个飞机大战的小游戏
ui的创建
我们先让ui窗体继承JFrame
public void ShowUi(){
setTitle("飞机大战");
setSize(500,800);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭窗体自动关闭游戏
setVisible(true);//可视化
setLocationRelativeTo(null);
setResizable(false);//不可改变尺寸
addKeyListener(gameListener);//监听器添加
gameListener.gameUi=this;//让监听器监听本窗体
startGame();
}
接着在主函数里面创建对象调用showui方法
public static void main(String[] args) {
new GameUi().ShowUi();
}
窗体创建好了之后就可以来写游戏最主要的东西——飞机
创建MyAir类
定义大小高度宽度血量等参数,然后创建我方飞机、敌方飞机和boss的构造方法
//敌机的构造方法
public MyAir(int x,int y,double speedX,double speedY,int width ,int height){
this.x=x;
this.y=y;
this.speedX=speedX;
this.speedY=speedY;
this.width=width;
this.height=height;
life=5;
color=Color.cyan;
isMyAir=false;
isBoss=false;
}
//BOSS的构造方法
public MyAir(int x,int y,double speedX,double speedY,int width ,int height,int life){
this.x=x;
this.y=y;
this.speedX=speedX;
this.speedY=speedY;
this.width=width;
this.height=height;
this.life=life;
color=Color.cyan;
isMyAir=false;
isBoss=true;
}
//我机
public MyAir(){
x=200;
y=700;
speedX=0;
speedY=0;
width=50;
height=70;
life=100;
color=Color.red;
isMyAir=true;
isBoss=false;
}
有了构造方法之后就可以准备绘制飞机了
绘制飞机
public void draw(Graphics g)
{
// 在MyAir的draw方法中添加:
if (isMyAir) {
g.setColor(Color.WHITE);
g.drawString("HP: " + life, x, y - 10);
}
g.setColor(Color.WHITE);
g.drawString("HP: " + life, x, y - 10);
g.setColor(color);
g.fillRect(x,y,width,height);
}
由于构造方法内部自己有颜色参数,所以不用额外设置敌机的颜色。
接着就可以准备绘制飞机的线程了
创建绘制飞机的线程
由于要使用多线程模式来创建飞机,所以我们使用Runnable接口来实现。
后面需要设置boss出现的时机,所以构造方法里有游戏阶段的参数
我们先在新的绘制线程类里传入参数,然后重写run方法
GameUi gameUi;
Graphics g;
PaintThread(GameUi gameUi, GameState gameState){
this.gameUi=gameUi;
g=gameUi.getGraphics();
this.gameState=gameState;
}
//重写run方法
public void run() {
while (true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程休眠,用来绘制图片和子弹用
//加入缓冲区防止图像卡顿闪烁
BufferedImage bufferedImage = new BufferedImage(500, 800, BufferedImage.TYPE_INT_ARGB);
Graphics imgGra = bufferedImage.getGraphics();
// 绘制背景
imgGra.setColor(Color.black);
imgGra.fillRect(0, 0, 500, 800);
// 绘制玩家飞机
gameUi.myAir.draw(imgGra);
gameUi.myAir.fire();
gameUi.myAir.MyAirMove();
// 模拟得分逻辑
imgGra.drawString("分数:"+gameState.getScore(),50,100);
成功绘制出飞机之后我们就可以考虑如何让飞机绘制出来以及开火了,这里我们使用fire方法让飞机随着线程的运行自动开火
接着回到MyAir类
在这个类里面我们创建开火的方法和移动的方法
由于我方飞机是使用键盘控制的,而敌方飞机是从屏幕最上方自动生成的,因此move的方法需要有两种判断条件
public void MyAirMove(){
if(x>=500-width-10){
x=500-width-10;
}
if(x<=0+width/2){
x=0+width/2;
}
if(y>=800-height){
y=800-height;
}
if(y<=0)
{
y=0;
}
x+=speedX;
y+=speedY;
}
public void emMove(){
if(x<=0||x>=500-width){
speedX=-speedX;
}
y+=speedY;
x+=speedX;
}
public void fire(){
long currentTime = System.currentTimeMillis();//获取当前的时间戳
int n=1;
// 添加子弹时加锁
synchronized (bullets) { // 锁住当前飞机的 bullets 列表
if (isMyAir && currentTime - lastFireTime > fireInterval) {
n = 3;//让飞机开火不要太频繁,有一个间歇
for (int i = 0; i < n; i++) {
bullets.add(new Bullet(x + i * width / 2, y));
}
lastFireTime = currentTime;
}
if (isMyAir == false&&!isBoss) {
bullets.add(new Bullet(x + width / 2, y, 0, (int) speedY));
}
if (isBoss&& currentTime - lastFireTime > 50){
for (int i = 0; i < 10; i++) {
bullets.add(new Bullet(x + i*width/8, y+height-50, i*3, 10));
}
lastFireTime = currentTime;
}
}
}
有了开火和绘制之后,我们需要有一个线程自动生成敌方飞机,因为我方飞机只需要生成一个,所以可以用draw方法直接绘制,但是敌方飞机有许多个,所以我们需要使用动态数组存储敌方飞机的数据
新建一个AutoCreateAir类
接着implement Runnable,然后我们继续重写run方法
不过我们可以先把自动产生飞机的类封装起来
//生产敌机
private void generateNormalEnemies() {
Random random=new Random();
if(!gameState.isBossSpawned()){// 确保锁对象是 emAirs,判断boss是否生成
int x = random.nextInt(490) + 5;
int y = random.nextInt(100);
int speedX =0;
int speedY = random.nextInt(5) + 1;
int width = random.nextInt(10) + 10;
int height = random.nextInt(10) + 10;
MyAir emAir = new MyAir(x, y, speedX, speedY, width, height);
gameUI.emAirs.add(emAir);
System.out.println("敌机工厂生产敌机 " + gameUI.emAirs.size());
}
}
//生成boss
private void checkBossSpawnCondition() {
gameState.spawnBoss();
synchronized (gameUI.emAirs) {
int x=100;
int y=50;
int speedX=10;
int speedY=0;
int width=150;
int height=170;
int life=1000;
MyAir emAir = new MyAir(x, y, speedX, speedY, width, height,life);
gameUI.emAirs.add(emAir);
gameState.setBossSpawned(true);
}
System.out.println(">>> Boss 已生成!");
}
有了这两个方法之后,我们只需要在run方法里面直接调用就可以实现自动产生飞机了。
public void run() {
while (true){
if (gameState.getScore() >= 100 && !gameState.isBossSpawned()) {
checkBossSpawnCondition();
gameState.setBossSpawned(true);
System.out.println(gameState.isBossSpawned());
}
if(gameState.getScore()>=600){
gameState.setBossSpawned(false);
gameState.defeatBoss();
}
generateNormalEnemies();
try { Thread.sleep(1000); }
catch (InterruptedException e) { e.printStackTrace(); }
}
}
我们使用分数和boss是否产生作为判断,实现分数小于一百的时候产生小飞机,分数大于一百的时候产生大飞机,击败boss之后分数刚好达到六百,于是分数清零,boss产生的设置变为false,重新下一轮循环。
这里我取了个巧,因为我不清楚该如何boss死亡的一瞬间作为时间点来更改boss是否存活的bollean值,所以我直接以分数作为判断条件
感兴趣的小伙伴可以自己加工一下
由于boss比较特殊,所以我们需要增加游戏阶段类来写入boss的参数和一些细节。
public class GameState {
private final AtomicInteger score = new AtomicInteger(0);
private volatile boolean bossSpawned = false;
private final AtomicBoolean bossAlive = new AtomicBoolean(false); // 新增:BOSS存活状态
// 新增 BOSS 状态管理
public void spawnBoss() {
bossAlive.set(true);
}
public void defeatBoss() {
bossAlive.set(false);
score.set(0);
// 可选:重置分数
}
public boolean isBossAlive() {
return bossAlive.get();
}
public void addScore(int points) {
score.addAndGet(points);
}
public int getScore() {
return score.get();
}
public boolean isBossSpawned() {
return bossSpawned;
}
public void setBossSpawned(boolean spawned) {
bossSpawned=spawned;
}
}
三个全局变量的讲解
值得注意的是,代码里面的全局变量分别用了不同的修饰词,例如final AtomicInteger, volatile boolean ,final AtomicBoolean ,这三个修饰词对于初学者来说十分陌生,我来讲解一下
- AtomicInteger: 是 Java 提供的一个原子类,用于在多线程环境下对整数进行原子操作。它的主要特点是通过无锁的方式实现高并发场景下的安全性,避免了传统锁(如 synchronized)可能带来的性能开销。
- volatile:是一个Java关键字,主要用于确保变量在多线程环境下的可见性。它用于修饰变量,确保线程对变量的修改能立即被其他线程看到。
- AtomicBoolean: 是类似 AtomicInteger 的原子类,用于在多线程环境下对布尔值进行原子操作。AtomicBoolean 提供了一种线程安全的方式来进行布尔值的读写操作。常见的方法包括 set(设置值)、get(获取值)以及 compareAndSet(比较并设置值)等。
由于原子类有内置的方法,而且线程安全性更高,更加方便,因此我们使用了原子类作为参数
游戏阶段的参数写好了之后就只剩下最后四步了。
我们现在来加一个监听器(其实应该先加,不过现在也来得及)
添加监听器
由于我们只需要控制飞机的前后左右,因此我们只需要继承键盘监听器即可。
由于要在监听器里操纵到窗体里的飞机,所以我们需要一个飞机对象和监听器对象
我们在gameui里面加入以下内容:
//创建一个监听器对象
GameListener gameListener=new GameListener();
//创建一个我方飞机对象
MyAir myAir=new MyAir();
//创建敌方飞机队列
ArrayList<MyAir>emAirs=new ArrayList<>();
然后根据按下键盘飞机移动,松开键盘飞机移动停止的逻辑在键盘监听器下写下以下判断逻辑:
public class GameListener implements KeyListener {
public GameUi gameUi;
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
//按下时将对应方向的速度设置一下
int keyCode =e.getKeyCode();
MyAir myAir=gameUi.myAir;
switch (keyCode){
case KeyEvent.VK_LEFT :
myAir.speedX=-10;
break;
case KeyEvent.VK_RIGHT :
myAir.speedX=10;
break;
case KeyEvent.VK_UP :
myAir.speedY=-10;
break;
case KeyEvent.VK_DOWN :
myAir.speedY=10;
break;
case KeyEvent.VK_SPACE:
myAir.fire();//按空格主动开火,这是项目早期的逻辑,已经优化成了自动开火
break;
}
}
@Override
public void keyReleased(KeyEvent e) {
//松开时将对应方向的速度设置一下
int keyCode =e.getKeyCode();
MyAir myAir=gameUi.myAir;
switch (keyCode){
case KeyEvent.VK_LEFT :
myAir.speedX=0;
break;
case KeyEvent.VK_RIGHT :
myAir.speedX=0;
break;
case KeyEvent.VK_UP :
myAir.speedY=0;
break;
case KeyEvent.VK_DOWN :
myAir.speedY=0;
break;
}
}
监听器设置完了之后我们现在可以去写一个敌方飞机开火的类来设置敌方飞机自动开火:
敌方飞机自动开火:
public class EmAirFire implements Runnable{
GameUi gameUi;
public EmAirFire(GameUi gameUi){
this.gameUi=gameUi;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (gameUi.emAirs) { // 加锁保护敌机列表
for (MyAir emAir : gameUi.emAirs) {
synchronized (emAir.bullets) { // 加锁保护子弹列表
emAir.fire();
}
}
}
}
}
}
由于修改飞机的时候会有可能出现飞机队列正在使用移动或者绘制方法的情况,所以我们需要创建synchronize同步代码块来锁住敌方飞机队列保证不会出现一边绘制一边移除队列或者其他情况的异常错误。
编写子弹类
类似于飞机类,写了飞机类后我们可以类比着写子弹类,用来完善上面的开火逻辑
int x,y;
double speedX,speedY;
int width,height;
int life;
Color color;
private GameState gameState;
//我机子弹的构造方法
public Bullet(int x,int y){
this.x=x;
this.y=y;
this.width=6;
this.height=10;
this.speedX=10;
this.speedY=-10;
this.life=1;
this.color=Color.yellow;
}
//BOSS的子弹构造方法
public Bullet(int x, int y, int speedX, int speedY, GameState gameState){
this.gameState = gameState;
this.x=x;
this.y=y;
this.width=6;
this.height=6;
this.speedX=speedX;
this.speedY=20;
this.life=1;
this.color=Color.green;
}
//敌机的子弹
public Bullet(int x,int y,int speedX,int speedY){
this.x=x;
this.y=y;
this.width=6;
this.height=6;
this.speedX=speedX;
this.speedY=7;
this.life=1;
this.color=Color.green;
}
public void draw(Graphics g)
{
if (life==0){
return;
}else {
g.setColor(color);
g.fillOval(x,y,width,height);
move();
}
}
//玩家和敌机子弹
public void move(){
if(x<=0||life==0 ){
life=0;
speedY=0;
}
if(y<=0||life==0){
life=0;
speedY=0;
}
y+=speedY;
// 自动销毁出界子弹
if(x < 0 || x > 500 || y < 0 || y > 800) {
life = 0;
}
}
//BOSS子弹
public void move1(){
if(x<=0||life==0 ){
life=0;
speedY=0;
}
if(y<=0||life==0){
life=0;
speedY=0;
}
y+=speedY;
x+=speedX;
// 自动销毁出界子弹
if(x < 0 || x > 500 || y < 0 || y > 800) {
life = 0;
}
}
子弹类和飞机类一样,有自己的参数,有移动和绘制方法,基本逻辑如上。
最后,也是本节最不好理解的地方,子弹与飞机的碰撞逻辑,飞机与飞机的碰撞逻辑:
子弹与飞机的碰撞逻辑
// 处理玩家子弹
synchronized (gameUi.myAir.bullets) {
Iterator<Bullet> myBulletIter = gameUi.myAir.bullets.iterator();
while (myBulletIter.hasNext()) {
Bullet bullet = myBulletIter.next();
bullet.draw(imgGra);
bullet.move();
if (bullet.life == 0) {
myBulletIter.remove();
}
}
}
// 处理敌机及碰撞检测
synchronized (gameUi.emAirs) {
Iterator<MyAir> emAirIter = gameUi.emAirs.iterator();
while (emAirIter.hasNext()) {
MyAir emAir = emAirIter.next();
// 绘制敌机
emAir.draw(imgGra);
emAir.emMove();
// 处理敌机子弹
synchronized (emAir.bullets) {
Iterator<Bullet> emBulletIter = emAir.bullets.iterator();
while (emBulletIter.hasNext()) {
Bullet bullet = emBulletIter.next();
bullet.draw(imgGra);
bullet.move();
bullet.move1();
// 敌机子弹 vs 玩家
if (bullet.x < gameUi.myAir.x + gameUi.myAir.width &&
bullet.x + bullet.width > gameUi.myAir.x &&
bullet.y < gameUi.myAir.y + gameUi.myAir.height &&
bullet.y + bullet.height > gameUi.myAir.y) {
gameUi.myAir.life -= 5;
bullet.life = 0;
if (gameUi.myAir.life <= 0) {
JOptionPane.showMessageDialog(gameUi, "Game Over!");
System.exit(0);
}
}
if (bullet.life == 0) {
emBulletIter.remove();
}
}
}
// 玩家子弹 vs 敌机
synchronized (gameUi.myAir.bullets) {
boolean enemyDestroyed = false; // 新增标志位
Iterator<Bullet> myBulletIter = gameUi.myAir.bullets.iterator();
while (myBulletIter.hasNext()) {
Bullet bullet = myBulletIter.next();
if (bullet.x < emAir.x + emAir.width &&
bullet.x + bullet.width > emAir.x &&
bullet.y < emAir.y + emAir.height &&
bullet.y + bullet.height > emAir.y) {
emAir.life -= 10;
bullet.life = 0;
myBulletIter.remove();
gameState.addScore(5); // 唯一分数修改入口
if (emAir.life <= 0) {
emAirIter.remove(); // 直接通过迭代器移除
enemyDestroyed = true;// 标记敌机已被销毁
break; // 跳出子弹循环
}
}
}
if (enemyDestroyed) {
continue; // 跳过后续碰撞检测,处理下一个敌机 }
}
// 玩家飞机 vs 敌机
if (gameUi.myAir.getX() < emAir.x + emAir.width &&
gameUi.myAir.getX() + gameUi.myAir.width > emAir.x &&
gameUi.myAir.getY() < emAir.y + emAir.height &&
gameUi.myAir.getY() + gameUi.myAir.height > emAir.y) {
gameUi.myAir.life -= 5;
emAir.life -= 5;
gameState.addScore(5);
if (emAir.life <= 0) {
emAirIter.remove();
}
if (gameUi.myAir.life <= 0) {
JOptionPane.showMessageDialog(gameUi, "Game Over!");
System.exit(0);
}
}
}
}
// 绘制到窗口
g.drawImage(bufferedImage, 0, 0, null);
}
}
}
在上述代码里面,我们任然要使用同步锁的方法保护队列防止子弹打到飞机的时候出现异常错误,比如一边死亡要移除另一边又要进行移动或者其他方法。
子弹和飞机的碰撞逻辑如下:
假如子弹的坐标为x,那么只要子弹的x小于飞机的x加宽度或者子弹的x加自己的宽度小于飞机宽度,那么就说明子弹打到了飞机,也就是子弹在飞机内部。y也是同理,只要能证明子弹的y在飞机的内部,同样也说明打到了飞机
飞机和飞机的碰撞逻辑同上,只要能判断我方飞机在敌方飞机内部,那么就说明我方飞机与敌方飞机碰撞。
注意:我使用了迭代器遍历子弹和敌机列表,保证移除飞机和子弹的时候不会报错和异常,这是一种安全的方法,大家可以试试。
以上,完毕!
以下,源代码!
public class GameUi extends JFrame {
//创建一个监听器对象
GameListener gameListener=new GameListener();
//创建一个我方飞机对象
MyAir myAir=new MyAir();
//创建敌方飞机队列
ArrayList<MyAir>emAirs=new ArrayList<>();
public void ShowUi(){
setTitle("飞机大战");
setSize(500,800);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
setLocationRelativeTo(null);
setResizable(false);//不可改变尺寸
addKeyListener(gameListener);
gameListener.gameUi=this;
startGame();
}
public void startGame()
{
GameState sharedState = new GameState();
//启动线程,绘制移动线程
PaintThread paintThread=new PaintThread(this,sharedState);
Thread thread=new Thread(paintThread);
thread.start();
//自动创造敌机的线程
AutoCreateAir autoCreateAir=new AutoCreateAir(this,sharedState);
Thread thread1=new Thread(autoCreateAir);
thread1.start();
EmAirFire emAirFire=new EmAirFire(this);
Thread thread2=new Thread(emAirFire);
thread2.start();
}
public static void main(String[] args) {
new GameUi().ShowUi();
}
}
public class GameListener implements KeyListener {
public GameUi gameUi;
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
//按下时将对应方向的速度设置一下
int keyCode =e.getKeyCode();
MyAir myAir=gameUi.myAir;
switch (keyCode){
case KeyEvent.VK_LEFT :
myAir.speedX=-10;
break;
case KeyEvent.VK_RIGHT :
myAir.speedX=10;
break;
case KeyEvent.VK_UP :
myAir.speedY=-10;
break;
case KeyEvent.VK_DOWN :
myAir.speedY=10;
break;
case KeyEvent.VK_SPACE:
myAir.fire();
break;
}
}
@Override
public void keyReleased(KeyEvent e) {
//松开时将对应方向的速度设置一下
int keyCode =e.getKeyCode();
MyAir myAir=gameUi.myAir;
switch (keyCode){
case KeyEvent.VK_LEFT :
myAir.speedX=0;
break;
case KeyEvent.VK_RIGHT :
myAir.speedX=0;
break;
case KeyEvent.VK_UP :
myAir.speedY=0;
break;
case KeyEvent.VK_DOWN :
myAir.speedY=0;
break;
}
}
}
public class AutoCreateAir implements Runnable{
private GameState gameState;
GameUi gameUI;
PaintThread paintThread;
private Boss currentBoss; // BOSS实例(需要定义Boss类)
int score;
public AutoCreateAir(GameUi gameUi, GameState gameState){
this.gameUI=gameUi;
this.gameState = gameState;
}
@Override
public void run() {
while (true){
if (gameState.getScore() >= 100 && !gameState.isBossSpawned()) {
checkBossSpawnCondition();
gameState.setBossSpawned(true);
System.out.println(gameState.isBossSpawned());
}
if(gameState.getScore()>=600){
gameState.setBossSpawned(false);
gameState.defeatBoss();
}
generateNormalEnemies();
try { Thread.sleep(1000); }
catch (InterruptedException e) { e.printStackTrace(); }
}
}
//生成boss
private void checkBossSpawnCondition() {
gameState.spawnBoss();
synchronized (gameUI.emAirs) {
int x=100;
int y=50;
int speedX=10;
int speedY=0;
int width=150;
int height=170;
int life=1000;
MyAir emAir = new MyAir(x, y, speedX, speedY, width, height,life);
gameUI.emAirs.add(emAir);
gameState.setBossSpawned(true);
}
System.out.println(">>> Boss 已生成!");
}
//生产敌机
private void generateNormalEnemies() {
Random random=new Random();
if(!gameState.isBossSpawned()){// 确保锁对象是 emAirs,判断boss是否生成
int x = random.nextInt(490) + 5;
int y = random.nextInt(100);
int speedX =0;
int speedY = random.nextInt(5) + 1;
int width = random.nextInt(10) + 10;
int height = random.nextInt(10) + 10;
MyAir emAir = new MyAir(x, y, speedX, speedY, width, height);
gameUI.emAirs.add(emAir);
System.out.println("敌机工厂生产敌机 " + gameUI.emAirs.size());
}
}
}
public class GameState {
private final AtomicInteger score = new AtomicInteger(0);
private volatile boolean bossSpawned = false;
private final AtomicBoolean bossAlive = new AtomicBoolean(false); // 新增:BOSS存活状态
// 新增 BOSS 状态管理
public void spawnBoss() {
bossAlive.set(true);
}
public void defeatBoss() {
bossAlive.set(false);
score.set(0);
// 可选:重置分数
}
public boolean isBossAlive() {
return bossAlive.get();
}
public void addScore(int points) {
score.addAndGet(points);
}
public int getScore() {
return score.get();
}
public boolean isBossSpawned() {
return bossSpawned;
}
public void setBossSpawned(boolean spawned) {
bossSpawned=spawned;
}
}
public class MyAir {
int x,y;
double speedX,speedY;
int width,height;
int life;
Color color;
ArrayList<Bullet>bullets=new ArrayList<>();
boolean isMyAir;
boolean isBoss;
private long lastFireTime;
private GameState gameState;
private final long fireInterval = 200; // 这里设置为500毫秒,可以根据需求调整
//敌机的构造方法
public MyAir(int x,int y,double speedX,double speedY,int width ,int height){
this.x=x;
this.y=y;
this.speedX=speedX;
this.speedY=speedY;
this.width=width;
this.height=height;
life=5;
color=Color.cyan;
isMyAir=false;
isBoss=false;
}
//BOSS的构造方法
public MyAir(int x,int y,double speedX,double speedY,int width ,int height,int life){
this.x=x;
this.y=y;
this.speedX=speedX;
this.speedY=speedY;
this.width=width;
this.height=height;
this.life=life;
color=Color.cyan;
isMyAir=false;
isBoss=true;
}
//我机
public MyAir(){
x=200;
y=700;
speedX=0;
speedY=0;
width=50;
height=70;
life=100;
color=Color.red;
isMyAir=true;
isBoss=false;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public double getSpeedX() {
return speedX;
}
public void setSpeedX(double speedX) {
this.speedX = speedX;
}
public double getSpeedY() {
return speedY;
}
public void setSpeedY(double speedY) {
this.speedY = speedY;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getLife() {
return life;
}
public void setLife(int life) {
this.life = life;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
//绘制方法
public void draw(Graphics g)
{
// 在MyAir的draw方法中添加:
if (isMyAir) {
g.setColor(Color.WHITE);
g.drawString("HP: " + life, x, y - 10);
}
g.setColor(Color.WHITE);
g.drawString("HP: " + life, x, y - 10);
g.setColor(color);
g.fillRect(x,y,width,height);
}
public void MyAirMove(){
if(x>=500-width-10){
x=500-width-10;
}
if(x<=0+width/2){
x=0+width/2;
}
if(y>=800-height){
y=800-height;
}
if(y<=0)
{
y=0;
}
x+=speedX;
y+=speedY;
}
public void emMove(){
if(x<=0||x>=500-width){
speedX=-speedX;
}
y+=speedY;
x+=speedX;
}
public void fire(){
long currentTime = System.currentTimeMillis();
int n=1;
// 添加子弹时加锁
synchronized (bullets) { // 锁住当前飞机的 bullets 列表
if (isMyAir && currentTime - lastFireTime > fireInterval) {
n = 3;
for (int i = 0; i < n; i++) {
bullets.add(new Bullet(x + i * width / 2, y));
}
lastFireTime = currentTime;
}
if (isMyAir == false&&!isBoss) {
bullets.add(new Bullet(x + width / 2, y, 0, (int) speedY));
}
if (isBoss&& currentTime - lastFireTime > 50){
for (int i = 0; i < 10; i++) {
bullets.add(new Bullet(x + i*width/8, y+height-50, i*3, 10));
}
lastFireTime = currentTime;
}
}
}
}
public class EmAirFire implements Runnable{
GameUi gameUi;
public EmAirFire(GameUi gameUi){
this.gameUi=gameUi;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (gameUi.emAirs) { // 加锁保护敌机列表
for (MyAir emAir : gameUi.emAirs) {
synchronized (emAir.bullets) { // 加锁保护子弹列表
emAir.fire();
}
}
}
}
}
}
public class PaintThread implements Runnable{
private GameState gameState;
GameUi gameUi;
Graphics g;
PaintThread(GameUi gameUi, GameState gameState){
this.gameUi=gameUi;
g=gameUi.getGraphics();
this.gameState=gameState;
}
public void run() {
while (true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
BufferedImage bufferedImage = new BufferedImage(500, 800, BufferedImage.TYPE_INT_ARGB);
Graphics imgGra = bufferedImage.getGraphics();
// 绘制背景
imgGra.setColor(Color.black);
imgGra.fillRect(0, 0, 500, 800);
// 绘制玩家飞机
gameUi.myAir.draw(imgGra);
gameUi.myAir.fire();
gameUi.myAir.MyAirMove();
// 模拟得分逻辑
imgGra.drawString("分数:"+gameState.getScore(),50,100);
// 处理玩家子弹
synchronized (gameUi.myAir.bullets) {
Iterator<Bullet> myBulletIter = gameUi.myAir.bullets.iterator();
while (myBulletIter.hasNext()) {
Bullet bullet = myBulletIter.next();
bullet.draw(imgGra);
bullet.move();
if (bullet.life == 0) {
myBulletIter.remove();
}
}
}
// 处理敌机及碰撞检测
synchronized (gameUi.emAirs) {
Iterator<MyAir> emAirIter = gameUi.emAirs.iterator();
while (emAirIter.hasNext()) {
MyAir emAir = emAirIter.next();
// 绘制敌机
emAir.draw(imgGra);
emAir.emMove();
// 处理敌机子弹
synchronized (emAir.bullets) {
Iterator<Bullet> emBulletIter = emAir.bullets.iterator();
while (emBulletIter.hasNext()) {
Bullet bullet = emBulletIter.next();
bullet.draw(imgGra);
bullet.move();
bullet.move1();
// 敌机子弹 vs 玩家
if (bullet.x < gameUi.myAir.x + gameUi.myAir.width &&
bullet.x + bullet.width > gameUi.myAir.x &&
bullet.y < gameUi.myAir.y + gameUi.myAir.height &&
bullet.y + bullet.height > gameUi.myAir.y) {
gameUi.myAir.life -= 5;
bullet.life = 0;
if (gameUi.myAir.life <= 0) {
JOptionPane.showMessageDialog(gameUi, "Game Over!");
System.exit(0);
}
}
if (bullet.life == 0) {
emBulletIter.remove();
}
}
}
// 玩家子弹 vs 敌机
synchronized (gameUi.myAir.bullets) {
boolean enemyDestroyed = false; // 新增标志位
Iterator<Bullet> myBulletIter = gameUi.myAir.bullets.iterator();
while (myBulletIter.hasNext()) {
Bullet bullet = myBulletIter.next();
if (bullet.x < emAir.x + emAir.width &&
bullet.x + bullet.width > emAir.x &&
bullet.y < emAir.y + emAir.height &&
bullet.y + bullet.height > emAir.y) {
emAir.life -= 10;
bullet.life = 0;
myBulletIter.remove();
gameState.addScore(5); // 唯一分数修改入口
if (emAir.life <= 0) {
emAirIter.remove(); // 直接通过迭代器移除
enemyDestroyed = true;// 标记敌机已被销毁
break; // 跳出子弹循环
}
}
}
if (enemyDestroyed) {
continue; // 跳过后续碰撞检测,处理下一个敌机 }
}
// 玩家飞机 vs 敌机
if (gameUi.myAir.getX() < emAir.x + emAir.width &&
gameUi.myAir.getX() + gameUi.myAir.width > emAir.x &&
gameUi.myAir.getY() < emAir.y + emAir.height &&
gameUi.myAir.getY() + gameUi.myAir.height > emAir.y) {
gameUi.myAir.life -= 5;
emAir.life -= 5;
gameState.addScore(5);
if (emAir.life <= 0) {
emAirIter.remove();
}
if (gameUi.myAir.life <= 0) {
JOptionPane.showMessageDialog(gameUi, "Game Over!");
System.exit(0);
}
}
}
}
// 绘制到窗口
g.drawImage(bufferedImage, 0, 0, null);
}
}
}
}
public class Bullet {
int x,y;
double speedX,speedY;
int width,height;
int life;
Color color;
private GameState gameState;
//我机的构造方法
public Bullet(int x,int y){
this.x=x;
this.y=y;
this.width=6;
this.height=10;
this.speedX=10;
this.speedY=-10;
this.life=1;
this.color=Color.yellow;
}
//BOSS的子弹构造方法
public Bullet(int x, int y, int speedX, int speedY, GameState gameState){
this.gameState = gameState;
this.x=x;
this.y=y;
this.width=6;
this.height=6;
this.speedX=speedX;
this.speedY=20;
this.life=1;
this.color=Color.green;
}
//敌机的子弹
public Bullet(int x,int y,int speedX,int speedY){
this.x=x;
this.y=y;
this.width=6;
this.height=6;
this.speedX=speedX;
this.speedY=7;
this.life=1;
this.color=Color.green;
}
public void draw(Graphics g)
{
if (life==0){
return;
}else {
g.setColor(color);
g.fillOval(x,y,width,height);
move();
}
}
//玩家和敌机子弹
public void move(){
if(x<=0||life==0 ){
life=0;
speedY=0;
}
if(y<=0||life==0){
life=0;
speedY=0;
}
y+=speedY;
// 自动销毁出界子弹
if(x < 0 || x > 500 || y < 0 || y > 800) {
life = 0;
}
}
//BOSS子弹
public void move1(){
if(x<=0||life==0 ){
life=0;
speedY=0;
}
if(y<=0||life==0){
life=0;
speedY=0;
}
y+=speedY;
x+=speedX;
// 自动销毁出界子弹
if(x < 0 || x > 500 || y < 0 || y > 800) {
life = 0;
}
}
}
其实本篇“教程”并不完善,不如说其实只是代码地注释和讲解并不是教程,讲解的逻辑也有些混乱。
作为一个初学者写这篇文章我也感到了一些吃力。
但是如果这篇文章有帮助到正在学习java的你,那么请点赞收藏评论一下吧!也算是对我的鼓励了,非常感谢看到文章的结尾!