身无分文宅家,细发日渐稀疏。
双亲日益劳累,奈何无心寻工。
复试遥遥无期,心情惨惨戚戚。
若问此时作甚?抽烟喝酒扣叮。
---杂记
2020/3/25-2020/4/12
这些天,我不是很想复习专业课,想打游戏,但是吧,我想我说是个程序员预备役,但是还没自己写过一个小游戏,一直都是做网页开发,搞搞业务逻辑,不妨试试做个游戏??
我在开始之前其实是有思路的,游戏的效果,就是后台逻辑控制出来的,先不谈3D或者其他的复杂的,我感觉如此。就是张图片,然后有攻击力度和敌人的血量,打一下调多少血,或者没血了,敌人消失。我们把敌人图片从游戏界面删除即可。
(其实应该不是那么简单的吧。
各位大神有空可在评论区当面打我脸,本人欢迎。)
那么放在Java中这个敌人吧,我们可以抽象个类。
然后一个界面里面肯定不止有一个敌人,这是肯定的,那么我们就需要一个容器去存储在界面上有效的敌人,然后当敌人无效时(无效就是说敌人被打死,或者说敌人不在游戏界面我们打不到时),我们将无效的敌人对象删除。
ok,我们再来分析下我们的大业务
英雄机----------我们的主角
敌人--------------小型敌人(一个5分,血量为1),中型敌人(20分之一的概率一个20分,血量为4),大型敌人(满1000出现一个,且血量随着1000的出现而上涨最低为8,一个得20分).‘’奖励敌人‘’(给双倍火力,或者是加一条命)
业务流程没啥好说的,就跟微信我们玩过的一样,见到打就是,然后看最后的得分,和敌人碰到就死,敌人不会发子弹(MD要是敌人会发子弹,我这游戏水平,根本打不到1000分。我是菜鸡,自己弄得游戏也打不好,虽然我可以不死,但这不就没意思了嘛。 )
说句题外话,后面两个敌人一开始是没有的,我随便打,就没死过,除非故意。
但是加上中型还好,加上大型的之后。吼吼吼没超过5000分。。。
到目前为止
我们分析了有,英雄机,3个敌人,1个‘’奖’励敌人‘’,一共5个实体了吧
那么问题来了
请问,子弹算不算一个实体?
说是英雄机可以装子弹,但是这是游戏,它的子弹是我们给他画出来的。于是,根据找类的原则:
那么子弹也要飞的,子弹和敌人碰到,敌人也会减命的这是子弹的行为
子弹的状态是,它的飞行速度和他的图片。
OK
我们接下来再提取几个公共的功能,抽象出来一个父类。他们都会飞,自己的飞机,子弹和所有敌人都是可以飞的,除了自己的飞机,是由鼠标控制的之外,其他的都有飞行这一选项,
我们用程序模拟这些除自己的飞机之外的类飞行,就是在面板上定时移动其图片,给人感觉他就在移动一样——(是不是说破了就很无聊。。。)
注意了子弹是从下往上打的,敌人是从上往下落的,所以我们对于这个方法,我们要给他声明为抽象方法,然后我们在各自的类进行不同的实现。
对于敌人我们统一再声明个接口,里面存放,敌人生命是否已死,以及得分。这样也方便我们以后添加新的敌人。
对于‘’奖励敌人‘’也是
我们声明一个接口,然后实现该接口,方便日后新增‘’奖励敌人‘’。
import java.awt.image.BufferedImage;
public abstract class FlyingObject {
/*
* 位置x,y, 大小width length 图片image 每个对象都有这些东西,所以给抽象称父类
*/
protected BufferedImage image;
protected int width;
protected int height;
protected int x;
protected int y;
/**
* 飞行物起飞
*/
public abstract void step();
/**
* 检查是否出界
*
* @return true 越界,false不越界
*/
public abstract boolean outOfBounds();
/**
* 敌人被子弹射 this 代表敌人,bullet是子弹
*/
public boolean shootBy(Bullet bullet) {
int x1 = this.x;
int x2 = x1 + this.width;
int y1 = this.y;
int y2 = this.y + this.height;
int x = bullet.x;
int y = bullet.y;
return x > x1 && x < x2 && y > y1 && y < y2;
}
}
public interface Enemy {
// 只要是敌人就能得分,小鸡5分,Boss20分
public int getScore();
public void subtractLife();
public boolean isDie();
public int getLife();
}
public interface Award {
// 双倍火力值
public int DOUBLE_FILE = 0;
public int LIFE = 1;
// 获取奖励类型
public int getType();
}
import java.awt.image.BufferedImage;
import java.util.Random;
public class Hero extends FlyingObject {
// 命
private int life;
// 火力值
private int doubleFire;
// 图片数组
private BufferedImage[] images;
// 协助图片切换
private int index;
// 构造方法
public Hero() {
image = PlaneGame.hero1;
width = image.getWidth();
height = image.getHeight();
x = 150;
y = 400;
life = 3;
doubleFire = 0;
images = new BufferedImage[2];
images[0] = PlaneGame.hero0;
images[1] = PlaneGame.hero1;
index = 0;
}
@Override
public void step() {
// 10ms走一次
// 100ms换一张图
/*
* index++; int a=index/10; int b=a%2; image=images[b];
*/
image = images[index++ / 10 % images.length];
}
/**
* 英雄机发射子弹
*/
public Bullet[] shoot() {
int xStep = this.width / 4;
if (doubleFire > 0) {
Bullet[] bs = new Bullet[2];
bs[0] = new Bullet(x + xStep, y);
bs[1] = new Bullet(x + (3 * xStep), y);
doubleFire -= 2;
return bs;
} else if (doubleFire <= 0) {
Bullet[] bs = new Bullet[1];
bs[0] = new Bullet(x + 2 * xStep, y);
return bs;
}
return null;
}
/**
*
* @param x
* @param y
*/
public void moveTo(int x, int y) {
this.x = x - this.width / 2;
this.y = y - this.height / 2;
}
@Override
public boolean outOfBounds() {
// TODO 自动生成的方法存根
return false;
}
/**
* 英雄机加命
*/
public void addLife() {
life++;
}
/**
* 获取命
*
* @return
*/
public int getLife() {
return life;
}
/**
* 减命
*/
public void subtractLife() {
life--;
}
/**
* 英雄机加火力值
*/
public void addDoubleFire() {
doubleFire += 40;
}
public void clearDoubleFire() {
doubleFire = 0;
}
public boolean hit(FlyingObject obj) {
int x1 = obj.x - this.width / 2;
int x2 = obj.x + obj.width + this.width / 2;
int y1 = obj.y - this.height / 2;
int y2 = obj.y + obj.height + this.height / 2;
int x = this.x + this.width / 2;
int y = this.y + this.height / 2;
return x > x1 && x < x2 && y < y2 && y > y1;
}
public boolean isDie() {
// TODO 自动生成的方法存根
return life == 0;
}
}
import java.util.Random;
public class Airplane extends FlyingObject implements Enemy {
private int speed = 2;// 走步的步数
public int life = 1;
// 构造方法
public Airplane() {
image = PlaneGame.airplane;
width = image.getWidth();
height = image.getHeight();
Random rand = new Random();
// x位于0到窗口宽-敌机宽之间的随机数
x = rand.nextInt(PlaneGame.WIDTH - this.width);
y = -this.height;
}
@Override
public int getScore() {
// 打掉一个敌机得5分
life--;
return 5;
}
@Override
public void step() {
y = y + speed;// 敌机向下坠
}
public boolean isDie() {
return life <= 0;
}
public void subtractLife() {
life--;
}
@Override
public boolean outOfBounds() {
return this.y >= PlaneGame.HEIGHT;
}
@Override
public int getLife() {
// TODO 自动生成的方法存根
return life;
}
}
import java.util.Random;
public class Boss extends FlyingObject implements Enemy {
public int life;
private int speed;
public Boss() {
this.life = 5;
this.speed = 1;
image = PlaneGame.boss;
height = image.getHeight();
width = image.getWidth();
Random rand = new Random();
// x位于0到窗口宽-敌机宽之间的随机数
x = rand.nextInt(PlaneGame.WIDTH - this.width);
y = -this.height;
}
@Override
public int getScore() {
// TODO 自动生成的方法存根
return 30;
}
public boolean isDie() {
return life <= 0;
}
public void subtractLife() {
life = life - 1;
}
@Override
public void step() {
// TODO 自动生成的方法存根
y = y + speed;
}
@Override
public boolean outOfBounds() {
return this.y >= PlaneGame.HEIGHT;
}
@Override
public int getLife() {
// TODO 自动生成的方法存根
return life;
}
}
import java.util.Random;
public class BigBoss extends FlyingObject implements Enemy {
public int life = 10;
public int speed;
public BigBoss(int k) {
this.life = this.life * k;
this.speed = 1;
image = PlaneGame.bigboss;
height = image.getHeight();
width = image.getWidth();
Random rand = new Random();
// x位于0到窗口宽-敌机宽之间的随机数
x = rand.nextInt(PlaneGame.WIDTH - this.width);
y = -this.height;
}
@Override
public int getScore() {
// TODO 自动生成的方法存根
return 200;
}
@Override
public void subtractLife() {
life = life - 1;
}
@Override
public boolean isDie() {
// TODO 自动生成的方法存根
return life <= 0;
}
@Override
public int getLife() {
// TODO 自动生成的方法存根
return life;
}
@Override
public void step() {
if (y < 0) {
y = y + speed;
} else {
y = y;
}
}
@Override
public boolean outOfBounds() {
// TODO 自动生成的方法存根
return false;
}
}
import java.util.Random;
public class Bee extends FlyingObject implements Award {
// x坐标的走步步数
private int xSpeed = 1;
// y坐标走步步数
private int ySpeed = 2;
// 奖励类型
private int awardType;
public int life;
// 构造方法
public Bee() {
life = 1;
image = PlaneGame.bee;
width = image.getWidth();
height = image.getHeight();
Random rand = new Random();
// x位于0到窗口宽-敌机宽之间的随机数
x = rand.nextInt(PlaneGame.WIDTH - this.width);
y = -this.height;
awardType = rand.nextInt(2);
}
@Override
public int getType() {
life--;
return awardType;
}
public boolean isDie() {
return life == 0;
}
@Override
public void step() {
x = x + xSpeed;
y = y + ySpeed;
if (x >= PlaneGame.WIDTH - this.width) {
// 到窗口最右边,弹回来
xSpeed = -1;
}
if (x <= 0) {
// 到最左边,弹回来
xSpeed = 1;
}
}
@Override
public boolean outOfBounds() {
// TODO 自动生成的方法存根
return this.y >= PlaneGame.HEIGHT;
}
}
import java.util.Random;
public class Bullet extends FlyingObject {
// 走步步数
private int speed = 3;
// 构造方法
// 子弹的x,y根据英雄机走
public Bullet(int x, int y) {
image = PlaneGame.bullet;
width = image.getWidth();
height = image.getHeight();
this.x = x;
this.y = y;
}
@Override
public void step() {
// 子弹向上飞
y = y - speed;
}
@Override
public boolean outOfBounds() {
// TODO 自动生成的方法存根
return (this.y + this.height) <= 0;
}
public boolean isDie() {
// TODO 自动生成的方法存根
return false;
}
}
这部分不算难,只要前面的理解了,这部分的思路很清晰的
具体的业务流程在代码注释里,我写的很清楚哦
package com.jjyu.shoot;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Arrays;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class PlaneGame extends JPanel {
// 窗口宽
public static final int WIDTH = 400;
// 窗口高
public static final int HEIGHT = 654;
// 背景图
public static BufferedImage background;
// 启动图
public static BufferedImage start;
// 暂停图
public static BufferedImage pause;
// 结束
public static BufferedImage gameover;
// 敌机
public static BufferedImage airplane;
// Boss飞机
public static BufferedImage boss;
//1000分后的超大飞机
public static BufferedImage bigboss;
// 蜜蜂
public static BufferedImage bee;
// 子弹
public static BufferedImage bullet;
// 英雄机0
public static BufferedImage hero0;
// 英雄机1
public static BufferedImage hero1;
// 开始状态
public static final int START = 0;
// 运行状态
public static final int RUNNING = 1;
// 暂停状态
public static final int PAUSE = 2;
// 结束状态
public static final int GAME_OVER = 3;
// 当前默认状态
private int state = START;
// 英雄机对象
private Hero hero = new Hero();
// 敌人机数组
private FlyingObject[] flyings = {};
// 子弹数组
private Bullet[] bullets = {};
public PlaneGame() {
}
static {
// 初始化静态资源(图片)
try {
// File file=new File("");
// ImageIO.read(file);
background = ImageIO.read(PlaneGame.class
.getResource("background.png"));
start = ImageIO.read(PlaneGame.class.getResource("start.png"));
pause = ImageIO.read(PlaneGame.class.getResource("pause.png"));
gameover = ImageIO
.read(PlaneGame.class.getResource("gameover.png"));
airplane = ImageIO
.read(PlaneGame.class.getResource("airplane.png"));
bee = ImageIO.read(PlaneGame.class.getResource("bee.png"));
boss = ImageIO.read(PlaneGame.class.getResource("boss.png"));
bigboss=ImageIO.read(PlaneGame.class.getResource("bigboss.png"));
bullet = ImageIO.read(PlaneGame.class.getResource("bullet.png"));
hero0 = ImageIO.read(PlaneGame.class.getResource("hero0.png"));
hero1 = ImageIO.read(PlaneGame.class.getResource("hero1.png"));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 满1000分来个大Boss
* @return 10分之2的概率产生小蜜蜂,20分之1的概率产生大Boss,10分之7的概率产生敌机
*/
public FlyingObject nextOne() {
if(score!=0&&score%1000==0){
return new BigBoss(score/1000);
}
Random rand = new Random();
int type = rand.nextInt(200);
if (type <= 20) {
return new Bee();
} else if (type <= 30) {
return new Boss();
} else {
return new Airplane();
}
}
// 敌人入场计数器
int flyEnteredIndex = 0;
/**
* 生成敌人对象,将对象添加到flying数组中 敌人入场
*/
protected void enterAction() {
flyEnteredIndex++;
if (flyEnteredIndex % 40 == 0) {
// 400毫秒走一次
FlyingObject one = nextOne();
// 敌人数组扩容
flyings = Arrays.copyOf(flyings, flyings.length + 1);
// 将敌人放在数组最后
flyings[flyings.length - 1] = one;
}
}
/**
* 英雄机发射子弹
*/
int shootIndex = 0;
protected void shootAction() {
shootIndex++;
if (shootIndex % 30 == 0) {
// 300ms一发子弹
Bullet[] bs = hero.shoot();
bullets = Arrays.copyOf(bullets, bullets.length + bs.length);
// for(int i=0;i
// bullets[bullets.length-i-1]=bs[i];
// }
// 数组的追加
System.arraycopy(bs, 0, bullets, bullets.length - bs.length,
bs.length);
}
}
/**
* 飞行物飞,10ms走一步
*/
protected void stepAction() {
hero.step();
for (int i = 0; i < flyings.length; i++) {
flyings[i].step();
}
for (int i = 0; i < bullets.length; i++) {
bullets[i].step();
}
}
/**
* 删除越界敌人和子弹
*/
protected void outOfBoundsAction() {
// 不越界的敌人个数
int index = 0;
FlyingObject[] flyingLives = new FlyingObject[flyings.length];
for (int i = 0; i < flyings.length; i++) {
if (!flyings[i].outOfBounds()) {
flyingLives[index] = flyings[i];
index++;
}
}
// System.out.println(index);
flyings = Arrays.copyOf(flyingLives, index);
index = 0;
Bullet[] b = new Bullet[bullets.length];
for (Bullet btemp : bullets) {
if (!btemp.outOfBounds()) {
b[index] = btemp;
index++;
}
}
// System.out.println(index);
bullets = Arrays.copyOf(b, index);
}
/**
* 所有子弹与所有敌人的碰撞
*/
protected void bangAction() {
for (int i = 0; i < bullets.length; i++) {
// 获取每一个子弹
Bullet b = bullets[i];
int lenBefore = flyings.length;
// 1个子弹和所有敌人的碰撞
// 说明子弹被用啦
if (bang(b)) {
bullets[i] = bullets[bullets.length - 1];
bullets[bullets.length - 1] = b;
bullets = Arrays.copyOf(bullets, bullets.length - 1);
i--;
}
}
}
/**
* 1个子弹与所有敌人的碰撞
*
* @param b
*/
// 得分
int score = 0;
protected boolean bang(Bullet b) {
int index = -1;
for (int i = 0; i < flyings.length; i++) {
FlyingObject f = flyings[i];
if (f.shootBy(b)) {
// 获取被撞的敌人坐标
index = i;
break;
}
}
if (index != -1) {
// 获取被撞的敌人
FlyingObject one = flyings[index];
if (one instanceof Enemy) {
Enemy e = (Enemy) one;
// 玩家加分
System.out.println(e.getLife());
e.subtractLife();
System.out.println(e.getLife());
if(e.isDie()){
System.out.println(e.getLife());
score += e.getScore();
// 将被撞敌人与数组最后一个元素交换
FlyingObject t = flyings[index];
flyings[index] = flyings[flyings.length - 1];
flyings[flyings.length - 1] = t;
// 缩容
flyings = Arrays.copyOf(flyings, flyings.length - 1);
}
}
if (one instanceof Award) {
Award a = (Award) one;
int type = a.getType();
switch (type) {
case Award.LIFE:
hero.addLife();
break;
case Award.DOUBLE_FILE:
hero.addDoubleFire();
break;
}
// 将被撞敌人与数组最后一个元素交换
FlyingObject t = flyings[index];
flyings[index] = flyings[flyings.length - 1];
flyings[flyings.length - 1] = t;
// 缩容
flyings = Arrays.copyOf(flyings, flyings.length - 1);
}
return true;
}
return false;
}
/**
* 检查游戏结束
*/
protected void checkGameOverAction() {
// TODO 自动生成的方法存根
if (isGameOver()) {
state = GAME_OVER;
}
}
/**
* 是否游戏结束 返回true则游戏结束
*
* @return
*/
public boolean isGameOver() {
for (int i = 0; i < flyings.length; i++) {
FlyingObject f = flyings[i];
if (hero.hit(f)) {
hero.subtractLife();
hero.clearDoubleFire();
// 被撞后敌人数组的变化
flyings[i] = flyings[flyings.length - 1];
flyings = Arrays.copyOf(flyings, flyings.length - 1);
i--;
}
}
return hero.getLife() <= 0;
}
public void action() {
// 创建侦听器对象
MouseAdapter l = new MouseAdapter() {
/**
* 重写鼠标移动事件
*
* @param e
*/
public void mouseMoved(MouseEvent e) {
if (state == RUNNING) {
// 英雄机随便动
// 获取鼠标的x坐标
int x = e.getX();
// 获取鼠标的y坐标
int y = e.getY();
hero.moveTo(x, y);
}
}
/**
* 重写鼠标移出事件
*/
public void mouseExited(MouseEvent e) {
if (state == RUNNING)
state = PAUSE;
}
/**
* 重写鼠标移入事件
*/
public void mouseEntered(MouseEvent e) {
if (state == PAUSE) {
state = RUNNING;
}
}
public void mouseClicked(MouseEvent e) {
switch (state) {
case START:
state = RUNNING;
break;
case GAME_OVER:// 游戏结束状态前
// 所有数据归0
score = 0;
hero = new Hero();
flyings = new FlyingObject[0];
bullets = new Bullet[0];
state = START;
break;
}
}
};
// 鼠标操作事件
this.addMouseListener(l);
// 鼠标移动事件
this.addMouseMotionListener(l);
Timer timer = new Timer();
int intervel = 10;// 时间间隔,毫秒为单位
timer.schedule(new TimerTask() {
@Override
public void run() {
if (state == RUNNING) {
shootAction();// 子弹入场
// 定时干的那个事,每10个毫秒的事
enterAction();
// 飞行物定时飞
stepAction();
// 删除越界的敌人和子弹
outOfBoundsAction();
// 子弹与敌人撞
bangAction();
// 检查游戏是否结束
checkGameOverAction();
}
repaint();
}
}, intervel, intervel);
}
@Override
// g当做画笔
public void paint(Graphics g) {
// 画背景图
g.drawImage(background, 0, 0, null);
// 画英雄机
paintHero(g);
// 画子弹对象
paintBullets(g);
// 画敌人对象
paintFlyingObjects(g);
// 画分和命
paintScoreAndLife(g);
// 画状态
paintState(g);
}
/**
* 画状态
*
* @param g
*/
public void paintState(Graphics g) {
switch (state) {
case START:
g.drawImage(start, 0, 0, null);
break;
case PAUSE:
g.drawImage(pause, 0, 0, null);
break;
case GAME_OVER:
g.drawImage(gameover, 0, 0, null);
}
}
public void paintHero(Graphics g) {
// 画英雄机
g.drawImage(hero.image, hero.x, hero.y, null);
}
public void paintBullets(Graphics g) {
// 画子弹对象
for (int i = 0; i < bullets.length; i++) {
Bullet b = bullets[i];
g.drawImage(b.image, b.x, b.y, null);
}
}
public void paintFlyingObjects(Graphics g) {
// 画敌人(敌机,小蜜蜂)对象
for (int i = 0; i < flyings.length; i++) {
FlyingObject fo = flyings[i];
g.drawImage(fo.image, fo.x, fo.y, null);
}
}
/**
* 画英雄机得分和命
*
* @param g
*/
public void paintScoreAndLife(Graphics g) {
g.setColor(new Color(0xffff00));
g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 20));
g.drawString("得分: " + score, 10, 600);
g.drawString("飞机复活次数: " + hero.getLife(), 10, 580);
}
public static void main(String[] args) {
// 创建了框架,窗口
JFrame frame = new JFrame("Fly");
// 创建了面板
PlaneGame game = new PlaneGame();
// 向窗口中加入面板
frame.add(game);
// 设置窗口的大小
frame.setSize(WIDTH, HEIGHT);
// 设置是否总在顶部
frame.setAlwaysOnTop(true);
// 设置默认关闭,关闭窗口时,退出程序
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 设置相对位置为null时居中,否则在屏幕的定点
frame.setLocationRelativeTo(null);
// 1、设置窗口可见 2、尽快调用paint()方法
frame.setVisible(true);
// 启动程序执行
game.action();
}
}