前期回顾
Java Swing 经典小游戏《飞机大战》———— (一)获取素材,创建窗口,添加滚动背景,双缓冲
Java Swing 经典小游戏《飞机大战》———— (二)玩家 移动 与 子弹
Java Swing 经典小游戏《飞机大战》———— (三)敌机 敌机发射 与 种类生成
Java Swing 经典小游戏《飞机大战》———— (四)碰撞检测 游戏状态与得分 玩家升级
(一)效果展示
以上边框是作者为了调试碰撞检测添加实际上没有
(二)代码实现
1.碰撞检测
首先来做碰撞检测部分
先介绍一下碰撞检测,我们可以用一个矩形来表示物体的位置,如:
然后对物体碰撞的检测其实也就是看矩形的交集
但是这样就会出现一点问题,如这样:
碰撞了,又好像没有碰撞
原因是矩形碰撞了,但是实际上的飞机图像并没有碰撞(也就是玩家并不会知道撞机了)
这怎么办呢?
个人思考如下:(如果大家有更好的办法可以告诉我)
观察到这样子的情况是小部分,而且其碰撞的面积也很小,因此我们给他一个阈值
具体如下:
我们计算橙色部分的面积,然后看他在两个矩形中的占比
那么如何衡量呢?取平均值,和还是差?
都不是,个人认为应该取最大值,毕竟有一个已经快完全撞上了,肯定应该先考虑他嘛
所以来实现一下
在GameWin的主循环里检测
import java.awt.*;
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class GameWin extends JFrame {
int width = 500;
int height = 700;
int score = 0;
Image offScreenImage;
Bg bg = new Bg();
Player player = new Player(this);
List<Bullet> bullets = new ArrayList<>();
List<Enamy> enamies = new ArrayList<>();
int TimeStamp = 0,lstBoss = -100,Bossnum = 0,lstFire = -15;
boolean autofire = false;
//直接用java.awt中的API计算
int GetArea(Rectangle r1,Rectangle r2) {
Rectangle insect = r1.intersection(r2); // 获取相交的矩形
return insect.height * insect.width;
}
boolean collision(Rectangle a,Rectangle b) {
if (!a.intersects(b)) return false; //先检测有无交集
int area = GetArea(a, b);
int areaa = a.width * a.height;
int areab = b.width * b.height;
if (Math.max(area * 1.0 / areaa,area * 1.0 / areab) <= 0.5) return false; // 如果超过一半 就返回碰撞
return true;
}
void launch() {
this.setVisible(true);
this.setSize(width,height);
this.setLocationRelativeTo(null);
this.setTitle("飞机大战");
setDefaultCloseOperation(EXIT_ON_CLOSE);
this.addKeyListener(new MyKeyListener());
while (true) {
TimeStamp ++;
{
int num;
if (enamies.size() < 10 && enamies.size() >= 3) {
num = (int)(Math.random() * ((10 - enamies.size()) - 1)) + 1;
} else if(enamies.size() < 3) {
num = (int)(Math.random() * ((10 - enamies.size()) - 3)) + 3;
} else {
num = 0;
}
for(int i = 1;i <= num;i ++) {
double rd = Math.random();
int level;
if (rd <= 0.7) level = 0;
else if(rd <= 0.95 || (TimeStamp - lstBoss) <= 1500 || Bossnum != 0) level = 1;
else {
level = 2;
lstBoss = TimeStamp;
Bossnum ++;
}
this.enamies.add(new Enamy((int)(Math.random() * (450 - 20)) + 20,(int)(Math.random() * (25 - 0)) + 0,level));
}
}
{
for(Bullet bl: bullets) { // 以子弹为外层,因为只要被一个物体碰到了就会消失,后面可以break掉
if (bl.move_dir == 1) { // 如果是乡下发射的
if (collision(player.rect,bl.rect) && !bl.delete) { // 与玩家的飞机做检测
bl.delete = true;
player.hp -= bl.level + 1;
break;
}
} else {
for(Enamy en: enamies) {
// 看有没有击落敌机
if (collision(en.rect,bl.rect) && !bl.delete && !en.delete) {
en.hp -= bl.level + 1;
bl.delete = true;
break;
}
}
}
}
// 敌机是否碰到了玩家
for(Enamy en: enamies) {
if (collision(en.rect,this.player.rect) && !en.delete) {
// 执行一些操作
}
}
}
{
if (this.autofire && this.TimeStamp - this.lstFire >= 15){
this.lstFire = this.TimeStamp;
player.Fire();
}
for(Enamy en: enamies) {
if (en.state == 12 && this.TimeStamp - en.lstFire >= 15){
en.lstFire = this.TimeStamp;
en.Fire(bullets);
}
}
}
}
repaint();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class MyKeyListener implements KeyListener {
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if (GameState != 1) return ;
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
player.movedir = -1;
} else if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
player.movedir = 1;
} else if(e.getKeyCode() == KeyEvent.VK_SPACE) {
player.Fire();
} else if(e.getKeyCode() == KeyEvent.VK_E) {
autofire = !autofire;
}
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
if (GameState != 1) return ;
if (e.getKeyCode() == KeyEvent.VK_LEFT) player.movedir = 0;
else if(e.getKeyCode() == KeyEvent.VK_RIGHT) player.movedir = 0;
}
}
@Override
public void paint(Graphics g) {
// TODO Auto-generated method stub
offScreenImage = this.createImage(width,height);
Graphics gImage = offScreenImage.getGraphics();
bg.paintSelf(gImage);
player.paintSelf(gImage);
for(Iterator<Bullet> it = bullets.iterator();it.hasNext();) {
Bullet bl = it.next();
if (bl.delete) it.remove();
else bl.paintSelf(gImage);
}
for(Iterator<Enamy> it = enamies.iterator();it.hasNext();) {
Enamy en = it.next();
if (en.delete) {
if (en.state == 12) Bossnum --;
if (en.rect.y < 700) this.score += Utils.EnamyScore[en.state - 10];
it.remove();
}
else en.paintSelf(gImage);
}
g.drawImage(offScreenImage,0,0,null);
}
public static void main(String[] args) throws Exception {
GameWin gameWin = new GameWin();
gameWin.launch();
}
}
2.游戏状态
写代码的时候发现有敌机或子弹碰到玩家,我们不知到应该干什么了
其实这个时候游戏就失败了,我们应该切换页面了
新建一个GameState
变量标记游戏状态
然后再主循环与paint函数中检测
GameWin.java
import java.awt.*;
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class GameWin extends JFrame {
int width = 500;
int height = 700;
int score = 0;
Image offScreenImage;
Bg bg = new Bg();
Player player = new Player(this);
List<Bullet> bullets = new ArrayList<>();
List<Enamy> enamies = new ArrayList<>();
int TimeStamp = 0,lstBoss = -100,Bossnum = 0,lstFire = -15;
/*
* 0: 等待开始
* 1: 游戏进行中
* 2: 游戏结束 玩家胜利
* 3:游戏结束 玩家失败
*/
int GameState = 0;
boolean autofire = false;
int GetArea(Rectangle r1,Rectangle r2) {
Rectangle insect = r1.intersection(r2);
return insect.height * insect.width;
}
boolean collision(Rectangle a,Rectangle b) {
if (!a.intersects(b)) return false;
int area = GetArea(a, b);
int areaa = a.width * a.height;
int areab = b.width * b.height;
if (Math.max(area * 1.0 / areaa,area * 1.0 / areab) <= 0.5) return false;
return true;
}
void launch() {
this.setVisible(true);
this.setSize(width,height);
this.setLocationRelativeTo(null);
this.setTitle("飞机大战");
setDefaultCloseOperation(EXIT_ON_CLOSE);
this.addKeyListener(new MyKeyListener());
while (true) {
switch(GameState) {
case 1:
TimeStamp ++;
{
int num;
if (enamies.size() < 10 && enamies.size() >= 3) {
num = (int)(Math.random() * ((10 - enamies.size()) - 1)) + 1;
} else if(enamies.size() < 3) {
num = (int)(Math.random() * ((10 - enamies.size()) - 3)) + 3;
} else {
num = 0;
}
for(int i = 1;i <= num;i ++) {
double rd = Math.random();
int level;
if (rd <= 0.7) level = 0;
else if(rd <= 0.95 || (TimeStamp - lstBoss) <= 1500 || Bossnum != 0) level = 1;
else {
level = 2;
lstBoss = TimeStamp;
Bossnum ++;
}
this.enamies.add(new Enamy((int)(Math.random() * (450 - 20)) + 20,(int)(Math.random() * (25 - 0)) + 0,level));
}
}
{
for(Bullet bl: bullets) {
if (bl.move_dir == 1) {
if (collision(player.rect,bl.rect) && !bl.delete) {
bl.delete = true;
player.hp -= bl.level + 1;
break;
}
} else {
for(Enamy en: enamies) {
if (collision(en.rect,bl.rect) && !bl.delete && !en.delete) {
en.hp -= bl.level + 1;
bl.delete = true;
break;
}
}
}
}
for(Enamy en: enamies) {
if (collision(en.rect,this.player.rect) && !en.delete) {
GameState = 3;
}
}
}
{
if (this.autofire && this.TimeStamp - this.lstFire >= 15){
this.lstFire = this.TimeStamp;
player.Fire();
}
for(Enamy en: enamies) {
if (en.state == 12 && this.TimeStamp - en.lstFire >= 15){
en.lstFire = this.TimeStamp;
en.Fire(bullets);
}
}
}
break;
case 2:
break;
case 3:
break;
}
if (player.hp <= 0) GameState = 3; // 别忘了这个,玩家可能被子弹射死哦
repaint();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class MyKeyListener implements KeyListener {
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if (GameState != 1) return ;
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
player.movedir = -1;
} else if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
player.movedir = 1;
} else if(e.getKeyCode() == KeyEvent.VK_SPACE) {
player.Fire();
} else if(e.getKeyCode() == KeyEvent.VK_E) {
autofire = !autofire;
}
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
if (GameState != 1) return ;
if (e.getKeyCode() == KeyEvent.VK_LEFT) player.movedir = 0;
else if(e.getKeyCode() == KeyEvent.VK_RIGHT) player.movedir = 0;
}
}
@Override
public void paint(Graphics g) {
// TODO Auto-generated method stub
offScreenImage = this.createImage(width,height);
Graphics gImage = offScreenImage.getGraphics();
switch(GameState) {
case 0:
break;
case 1:
bg.paintSelf(gImage);
player.paintSelf(gImage);
for(Iterator<Bullet> it = bullets.iterator();it.hasNext();) {
Bullet bl = it.next();
if (bl.delete) it.remove();
else bl.paintSelf(gImage);
}
for(Iterator<Enamy> it = enamies.iterator();it.hasNext();) {
Enamy en = it.next();
if (en.delete) {
if (en.state == 12) Bossnum --;
if (en.rect.y < 700) this.score += Utils.EnamyScore[en.state - 10];
it.remove();
}
else en.paintSelf(gImage);
}
gImage.setFont(new Font("黑体",Font.BOLD,25));
gImage.setColor(Color.red);
break;
case 2:
break;
case 3:
break;
}
g.drawImage(offScreenImage,0,0,null);
}
public static void main(String[] args) throws Exception {
GameWin gameWin = new GameWin();
gameWin.launch();
}
}
3.游戏得分
还记得我们之前创建了3种状态的玩家飞机吗?现在我们只能一直玩0级当然是不行的
于是我们需要有一个衡量升级的标准————得分
先规定一下得分规则
击落飞机 | 得分 |
---|---|
小怪 | 10 |
中怪 | 30 |
Boss | 50 |
还有升级规则
分数 | 等级 |
---|---|
0~100 | 0级 |
100~600 | 1级 |
600~1000 | 3级 |
1000以上 | 胜利 |
在代码中实现一下
GameWin.java
import java.awt.*;
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class GameWin extends JFrame {
int width = 500;
int height = 700;
int score = 0;
Image offScreenImage;
Bg bg = new Bg();
Player player = new Player(this);
List<Bullet> bullets = new ArrayList<>();
List<Enamy> enamies = new ArrayList<>();
int TimeStamp = 0,lstBoss = -100,Bossnum = 0,lstFire = -15;
/*
* 1: 游戏进行中
* 2: 游戏结束 玩家胜利
* 3:游戏结束 玩家失败
*/
int GameState = 0;
boolean autofire = false;
int GetArea(Rectangle r1,Rectangle r2) {
Rectangle insect = r1.intersection(r2);
return insect.height * insect.width;
}
boolean collision(Rectangle a,Rectangle b) {
if (!a.intersects(b)) return false;
int area = GetArea(a, b);
int areaa = a.width * a.height;
int areab = b.width * b.height;
if (Math.max(area * 1.0 / areaa,area * 1.0 / areab) <= 0.5) return false;
return true;
}
void launch() {
this.setVisible(true);
this.setSize(width,height);
this.setLocationRelativeTo(null);
this.setTitle("飞机大战");
setDefaultCloseOperation(EXIT_ON_CLOSE);
this.addKeyListener(new MyKeyListener());
while (true) {
switch(GameState) {
case 1:
TimeStamp ++;
{
int num;
if (enamies.size() < 10 && enamies.size() >= 3) {
num = (int)(Math.random() * ((10 - enamies.size()) - 1)) + 1;
} else if(enamies.size() < 3) {
num = (int)(Math.random() * ((10 - enamies.size()) - 3)) + 3;
} else {
num = 0;
}
for(int i = 1;i <= num;i ++) {
double rd = Math.random();
int level;
if (rd <= 0.7) level = 0;
else if(rd <= 0.95 || (TimeStamp - lstBoss) <= 1500 || Bossnum != 0) level = 1;
else {
level = 2;
lstBoss = TimeStamp;
Bossnum ++;
}
this.enamies.add(new Enamy((int)(Math.random() * (450 - 20)) + 20,(int)(Math.random() * (25 - 0)) + 0,level));
}
}
{
for(Bullet bl: bullets) {
if (bl.move_dir == 1) {
if (collision(player.rect,bl.rect) && !bl.delete) {
bl.delete = true;
player.hp -= bl.level + 1;
break;
}
} else {
for(Enamy en: enamies) {
if (collision(en.rect,bl.rect) && !bl.delete && !en.delete) {
en.hp -= bl.level + 1;
bl.delete = true;
break;
}
}
}
}
for(Enamy en: enamies) {
if (collision(en.rect,this.player.rect) && !en.delete) {
GameState = 3;
}
}
}
{
if (this.autofire && this.TimeStamp - this.lstFire >= 15){
this.lstFire = this.TimeStamp;
player.Fire();
}
for(Enamy en: enamies) {
if (en.state == 12 && this.TimeStamp - en.lstFire >= 15){
en.lstFire = this.TimeStamp;
en.Fire(bullets);
}
}
}
break;
case 2:
break;
case 3:
break;
}
if (player.hp <= 0) GameState = 3;
else if(this.score >= 100 && this.score <= 600 && this.player.state != 1) this.player.Upper();
else if(this.score > 600 && this.score < 1000 && this.player.state != 2) this.player.Upper();
else if(this.score >= 1000) {
GameState = 2;
}
repaint();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class MyKeyListener implements KeyListener {
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if (GameState != 1) return ;
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
player.movedir = -1;
} else if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
player.movedir = 1;
} else if(e.getKeyCode() == KeyEvent.VK_SPACE) {
player.Fire();
} else if(e.getKeyCode() == KeyEvent.VK_E) {
autofire = !autofire;
}
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
if (GameState != 1) return ;
if (e.getKeyCode() == KeyEvent.VK_LEFT) player.movedir = 0;
else if(e.getKeyCode() == KeyEvent.VK_RIGHT) player.movedir = 0;
}
}
@Override
public void paint(Graphics g) {
// TODO Auto-generated method stub
offScreenImage = this.createImage(width,height);
Graphics gImage = offScreenImage.getGraphics();
switch(GameState) {
case 0:
break;
case 1:
bg.paintSelf(gImage);
player.paintSelf(gImage);
for(Iterator<Bullet> it = bullets.iterator();it.hasNext();) {
Bullet bl = it.next();
if (bl.delete) it.remove();
else bl.paintSelf(gImage);
}
for(Iterator<Enamy> it = enamies.iterator();it.hasNext();) {
Enamy en = it.next();
if (en.delete) {
if (en.state == 12) Bossnum --;
if (en.rect.y < 700) this.score += Utils.EnamyScore[en.state - 10]; // 注意这里要 -10 作者RE了很多次才发现
it.remove();
}
else en.paintSelf(gImage);
}
gImage.setFont(new Font("黑体",Font.BOLD,25));
gImage.setColor(Color.red);
gImage.drawString("得分:" + this.score, 10,60); // 把得分绘制出来
break;
case 2:
break;
case 3:
break;
}
g.drawImage(offScreenImage,0,0,null);
}
public static void main(String[] args) throws Exception {
GameWin gameWin = new GameWin();
gameWin.launch();
}
}
然后再在Player.java
中实现Upper()
import java.awt.*;
public class Player extends Plane{
/*
* 0 不动 1 向右 -1 向左
*/
int movedir;
GameWin frame;
Player(GameWin frame) {
this.frame = frame;
this.state = 0;
this.hp = 10;
this.movedir = 0;
this.img = Utils.Plane[state];
this.rect = new Rectangle(225,625,50,50);
}
void Upper() {
if (this.state == 2) return ; // 好习惯 ++
this.state = this.state + 1; // 升级
this.hp += 5 * this.state; // 给点血量补给
this.img = Utils.Plane[state]; // 重新加载造型
}
void Fire() {
if (this.state == 0) {
this.frame.bullets.add(new Bullet(rect.x + rect.width / 2,rect.y,-1, 0));
} else if (this.state == 1) {
this.frame.bullets.add(new Bullet(rect.x,rect.y,-1, 0));
this.frame.bullets.add(new Bullet(rect.x + rect.width - 5,rect.y,-1, 0));
} else {
this.frame.bullets.add(new Bullet(rect.x,rect.y,-1, 1));
this.frame.bullets.add(new Bullet(rect.x + rect.width - 5,rect.y,-1, 1));
}
}
@Override
void paintSelf(Graphics g) {
// TODO Auto-generated method stub
int x = this.rect.x;
int y = this.rect.y;
x = Math.min(Math.max(15,x + this.movedir * 2 * ((this.state + 1) / 2 + 1)),440);
this.rect.setLocation(x,y);
super.paintSelf(g);
}
}
- 实现了碰撞检测
- 实现了游戏状态标记
- 实现了玩家造型的升级
下期预告
添加游戏胜利/失败以及说明游戏规则的开始界面 完成游戏
最后 撰写不易,做人,一定要善良; 点赞,好人,有好福
关注我,收藏 点赞 评论文章 更新速度更快哦