【引言】
90、00后的童年是游戏机,当时的飞机大战,一度风靡全球。
一、游戏设计
实现飞机大战游戏是一个很有趣的项目,可以帮助你学习和练习 Java 编程的各种方面,包括面向对象设计、图形界面编程等。下面是一个详细的分析,涵盖了设计步骤思路和具体功能:
1、设计步骤思路:
-
游戏框架搭建:
- 创建一个 Java 项目。
- 使用 Java GUI 库(如Swing或JavaFX)来构建游戏界面。
-
设计游戏元素:
- 飞机:玩家飞机和敌方飞机,分别有不同的属性(速度、生命值等)。
- 子弹:玩家飞机和敌方飞机发射的子弹。
-
游戏逻辑设计:
- 碰撞检测:检测飞机和子弹之间的碰撞。
- 得分系统:根据击败敌机数量计算得分。
-
用户交互:
- 键盘控制:玩家通过键盘控制飞机移动和发射子弹。
- 鼠标控制:可以使用鼠标来控制飞机或者发射子弹。
-
游戏流程:
- 游戏开始界面:包括开始游戏和退出游戏选项。
- 游戏进行界面:显示飞机、敌机、得分等信息。
- 游戏结束界面:显示得分和重新开始游戏选项。
2、具体功能:
-
飞机移动:
- 玩家飞机随着键盘操作左右移动,避开敌机和敌方子弹。
- 敌机在屏幕顶部生成并向下移动,玩家需要躲避它们的攻击。
-
发射子弹:
- 玩家飞机可以发射子弹,击中敌机可以得分。
- 敌机也可以发射子弹,玩家需要躲避或击毁它们。
-
碰撞检测:
- 当玩家子弹击中敌机或者敌机子弹击中玩家飞机时,进行碰撞检测,减少生命值或者销毁飞机。
-
得分计算:
- 根据击败敌机数量来计算得分,可以在界面上显示当前得分。
-
游戏状态管理:
- 包括游戏开始、进行中和结束等状态的管理,切换不同界面和逻辑。
-
音效和动画:
- 添加背景音乐、射击音效和爆炸动画等,增强游戏体验。
-
游戏结束条件:
- 当玩家飞机生命值耗尽或者敌机到达底部时,游戏结束,显示得分和重新开始选项。
-
优化和扩展:
- 优化游戏性能,处理大量敌机和子弹的情况。
- 可以扩展游戏内容,如增加不同类型的敌机、道具等。
通过这些步骤和功能,你可以逐步实现一个简单但完整的飞机大战游戏。
二、功能设计
1.1、飞机大类
1.2 构造器
public class Plane extends PlaneWarObject {
public double speed = 10;// 速度
public boolean left, up, right, down;
public int blood;// 血量
public int level;// 等级
public int type;// 等级
public int score = 0;// 积分
/**
* 无参构造
*/
public Plane() {
super();
}
public Plane(PlaneWarClient pwc, boolean good) {
this.fire = false;
this.x = (Constant.GAME_WIDTH - width) / 2;
this.y = Constant.GAME_HEIGHT - height;
this.img = ImageUtil.images.get("myPlane_01_01");
this.width = img.getWidth(null);
this.height = img.getHeight(null);
this.pwc = pwc;
this.good = good;
this.blood = Constant.MYPLANE_MAX_BOOLD;
this.level = 1;
this.type = 1;
}
/**
* 带参构造
*/
public Plane(int x, int y, Image img, int width, int height) {
super();
this.x = x;
this.y = y;
this.img = img;
this.width = width;
this.height = height;
}
public Plane(int x, int y, String imageName) {
super();
this.x = x;
this.y = y;
this.img = ImageUtil.images.get(imageName);
this.width = img.getWidth(null);
this.height = img.getHeight(null);
}
public Plane(int x, int y, Image img) {
super();
this.x = x;
this.y = y;
this.img = img;
this.width = img.getWidth(null);
this.height = img.getHeight(null);
}
}
1.3 内部方法
* 是否开火
*/
public boolean fire;
/**
* 我方飞机发子弹的方法
*/
public void fire() {
// pwc.musics.add(mu);
new MusicUtil("fire3").start();
Missile missile = new Missile(pwc, this.x, this.y, "myPlane_missile_0" + type + "_0" + level, good);
missile.x += (this.width - missile.width) / 2;
missile.y -= height;
pwc.missiles.add(missile);
}
boolean superFire;
/**
* 超级子弹
*/
public void superFire() {
if (superFireCount > 0) {
int num = 24;
for (int i = 1; i <= num; i++) {
Missile missile = new Missile(pwc, this.x, this.y, "myPlane_missile_super", 6, good);
int r = (int) (Math.sqrt(width * width + height * height) / 2);
int theta = 360 * i / num;
missile.setTheta(theta);
missile.x = (int) (missile.x + (width / 2 + r * Math.sin(Math.toRadians(theta)) - missile.width / 2));
missile.y = (int) (missile.y
- ((r * Math.cos(Math.toRadians(theta)) - height / 2 + missile.height / 2)));
pwc.missiles.add(missile);
}
superFireCount--;
}
superFire = false;
}
/**
* 判断是否存活
*/
public boolean live = true;
@Override
public void move() {
if (left) {
x -= speed;
}
if (right) {
x += speed;
}
if (up) {
y -= speed;
}
if (down) {
y += speed;
}
outOfBounds();
if (fire)
fire();
if (superFire)
superFire();
}
@Override
public void draw(Graphics g) {
img = ImageUtil.images.get("myPlane_0" + type + "_0" + level);
if (blood <= 0 && live) {
live = false;
Explode ex = new Explode(pwc, x, y);
ex.x += (width - ex.width) / 2;
ex.y += (height - ex.height) / 2;
pwc.explodes.add(ex);
pwc.enemyPlanes.clear();
pwc.missiles.clear();
pwc.items.clear();
}
if (live) {
g.drawImage(img, x, y, null);
move();
}
drawBloodAndScore(g);
}
/**
* 画血条和积分
*
* @param g
*/
private void drawBloodAndScore(Graphics g) {
Image bloodImg = ImageUtil.images.get("myBlood");
Image blood_blank = ImageUtil.images.get("myBlood_blank");
Image scoreImg = ImageUtil.images.get("score");
int i = 0;
g.drawImage(bloodImg, 10, 40, null);
int num = (int) (((double) ((bloodImg.getWidth(null)) - 56) / (Constant.MYPLANE_MAX_BOOLD))
* (Constant.MYPLANE_MAX_BOOLD - blood) / blood_blank.getWidth(null));
for (int j = 0; j < num; j++) {
g.drawImage(blood_blank, 10 + bloodImg.getWidth(null) - blood_blank.getWidth(null) * (j + 1), 40 + 14,
null);
}
// 画积分
g.drawImage(ImageUtil.images.get("score"), 10, 40 + bloodImg.getHeight(null) + 12, null);
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.setColor(Color.WHITE);
g.drawString(score + "", 10 + scoreImg.getWidth(null) + 10, 40 + bloodImg.getHeight(null) + 50);
}
/**
* 大招剩余次数
*/
public int superFireCount = 0;
/**
* 按下键盘的方法
*
* @param e
*/
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_A:
left = true;
break;
case KeyEvent.VK_S:
down = true;
break;
case KeyEvent.VK_D:
right = true;
break;
case KeyEvent.VK_W:
up = true;
break;
case KeyEvent.VK_J:// 发子弹
superFire = false;
fire = true;
break;
case KeyEvent.VK_SPACE:// 发超级子弹
fire = false;
superFire = true;
break;
}
}
/**
* 松开键盘的方法
*
* @param e
* 键盘事件
*/
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_A:
left = false;
break;
case KeyEvent.VK_S:
down = false;
break;
case KeyEvent.VK_D:
right = false;
break;
case KeyEvent.VK_W:
up = false;
break;
case KeyEvent.VK_J:// 发子弹
fire = false;
break;
}
}
2、敌人出场
创建敌人对象过程中是随机的,有可能是小敌机、大敌机、也有可能是小道具;
工厂方法都是生产对象的;
若方法操作仅仅与参数相关而与对象无关时用静态方法,方法分为工具方法和工厂方法,工厂方法返回值类型为对象。
3、子弹击打方法接口
public class Missile extends PlaneWarObject {
boolean live;
int speed;
int type;
public Missile() {
super();
}
public Missile(PlaneWarClient pwc, int x, int y, String imageName,boolean good) {
this.live = true;
this.x = x;
this.y = y;
this.img = ImageUtil.images.get(imageName);
this.width = img.getWidth(null);
this.height = img.getWidth(null);
this.pwc = pwc;
this.speed=10;
this.good=good;
}
public Missile(PlaneWarClient pwc, int x, int y, String imageName,int type,boolean good) {
this(pwc, x, y, imageName,good);
this.type=type;
}
public void setTheta(int theta) {
this.theta = theta;
}
private int theta;
/**
* 子弹击打飞机的方法
*/
public boolean hitPlane(Plane p){
if(this.getRectangle().intersects(p.getRectangle())&&this.good!=p.isGood()&&p.live){
this.live=false;
if(p.level>=1){
p.blood-= 10*p.level;
}else{
p.blood-= 20;
}
pwc.missiles.remove(this);
return true;
}else{
return false;
}
}
/**
* 子弹击打飞机的方法
*/
public boolean hitPlanes(List<EnemyPlane> enemyPlanes){
for (EnemyPlane enemyPlane : enemyPlanes) {
if(this.hitPlane(enemyPlane)){
return true;
}
}
return false;
}
/**
* 子弹出界
*/
private void outOfBounds() {
if ((x >= (Constant.GAME_WIDTH-width) || x <= 0) || (y >= (Constant.GAME_HEIGHT - height) || y <= 0)) {
this.live = false;
this.pwc.missiles.remove(this);
}
}
}
4、子弹类型
@Override
public void move() {
switch (type) {
case 100:// 水平平移
if (x >= (Constant.GAME_WIDTH) || x <= 0 - width) {
speed = -speed;
}
x += speed;
break;
case 1:// 水平平移
x += speed * 3;
if (x >= (Constant.GAME_WIDTH - width)) {
speed = -speed;
}
break;
case 2:// 竖直平移
y += speed * 3;
break;
case 3:// 正弦线
x = (int) (center.x - width + (center.x - width) * Math.sin(theta));
theta += speed / 10;
y += speed * 10;
break;
case 4:// 余弦线
x = (int) (center.x - width + (center.x - width) * Math.cos(theta));
theta += speed / 10;
y += speed * 10;
break;
case 5:// 双曲线
x = (int) (center.x - width + 50 * 1 / Math.cos(theta));
y = (int) (center.y - height + 50 * Math.sin(theta) / Math.cos(theta));
theta += speed / 20;
break;
case 6:// 星形线
x = (int) (center.x - width / 2 + 200 * Math.pow(Math.cos(theta), 3));
y = (int) (center.y + 200 * Math.pow(Math.sin(theta), 3)) - 200;
theta += speed / 20;
break;
case 7:// 心形线
x = (int) (center.x + r * (2 * Math.cos(theta + Math.PI / 2) + Math.cos(2 * theta + Math.PI / 2)));
y = (int) (center.y + r * (2 * Math.sin(theta + Math.PI / 2) + Math.sin(2 * theta + Math.PI / 2)));
theta += speed;
break;
}
if (type!=100&&random.nextInt(1000) > 995 && live) {
fire();
}
if(type==100&&random.nextInt(1000)>990&&live){
fire();
}
}
三、效果展示
1、游戏
2、拾取道具
3、游戏结束