简介
恰逢六一便去学并写了段有关童年回忆的小游戏:大鱼吃小鱼
大鱼吃完小鱼后可以使得自身变大,变大之后便可以吃掉原本比自己大的小鱼
最经典的是Popcap Games发布的吞食鱼版本
游戏设计
基于这个游戏的理解
我们对游戏做出一下假设
状态
假设游戏有五个状态
状态0 | 状态1 | 状态2 | 状态3 | 状态4 |
初始页面 | 游戏页面 | 失败页面 | 通关页面 | 暂停页面 |
状态0通过点击任意位置进入状态1
状态1被大鱼吃掉后进入状态2
状态1达到指定分数后进入状态3
状态1点击空格键后进入状态4
状态4点击空格键后进入状态1
角色
此处主要分为两大类
一类是敌人,一类是玩家
敌方鱼
敌方鱼有三种一种是我们初始可以吃的一种是我们要吃到一定的鱼的数目后才可以升级
越高级的鱼越多分数
怪物 | 图片 | 等级 | 分数 |
小海马 | ![]() | 1 | 1 |
大嘴鱼 | ![]() | 2 | 20 |
灯笼鱼 | | 3 | 40 |
BOSS
Boss是大鲨鱼类似吞食鱼的大鲨鱼在出来之前会有感叹号提示,玩家必须提前躲避
怪物 | 图片 | 等级 | 分数 |
大鲨鱼 | ![]() | max(不可杀死) | 0 |
Player
玩家初始会在中间生成,但生命此处定义初始只有一次,在分数足够后被撞击可以用分数抵消
玩家 | 图片 | 初始等级 | 分数 |
大眼鱼 | ![]() | 1 | 0 |
游戏实现
首先是初始界面
界面实现
也就是state0
核心就是如何把字输送到图像上
public static void drawWord(Graphics g, String str, Color color, int size, int x, int y) {
g.setColor(color);
g.setFont(new Font("宋体", Font.BOLD, size));
g.drawString(str, x, y);
}
有了这个我们顺便也就可以把游戏的所有状态到达的情况显示出来了
public class Bg {
void paintSelf(Graphics g, int fishLevel) {
g.drawImage(GameUtils.bgimg, 0, 0, null);
switch (GameWin.state) {
case 0:
GameUtils.drawWord(g, "开始", Color.red, 80, 600, 400);
break;
case 1:
GameUtils.drawWord(g, "积分" + GameUtils.count, Color.ORANGE, 50, 200, 120);
GameUtils.drawWord(g, "难度" + GameUtils.level, Color.ORANGE, 50, 600, 120);
GameUtils.drawWord(g, "等级" + fishLevel, Color.ORANGE, 50, 1000, 120);
break;
case 2:
GameUtils.drawWord(g, "积分" + GameUtils.count, Color.ORANGE, 50, 200, 120);
GameUtils.drawWord(g, "难度" + GameUtils.level, Color.ORANGE, 50, 600, 120);
GameUtils.drawWord(g, "等级" + fishLevel, Color.ORANGE, 50, 1000, 120);
GameUtils.drawWord(g, "失败", Color.red, 80, 580, 450);
break;
case 3:
GameUtils.drawWord(g, "积分" + GameUtils.count, Color.ORANGE, 50, 200, 120);
GameUtils.drawWord(g, "难度" + GameUtils.level, Color.ORANGE, 50, 600, 120);
GameUtils.drawWord(g, "等级" + fishLevel, Color.ORANGE, 50, 1000, 120);
GameUtils.drawWord(g, "胜利了!", Color.ORANGE, 80, 580, 450);
case 4:
break;
default:
}
}
}
工具类实现
图像导入
//背景图
public static Image bgimg = Toolkit.getDefaultToolkit().createImage("src/images/sea.jpg");
//敌方鱼类
public static Image enamyl_1img = Toolkit.getDefaultToolkit().createImage("src/images/enemyFish/fish1_r.gif");
public static Image enamyr_1img = Toolkit.getDefaultToolkit().createImage("src/images/enemyFish/fish1_l.gif");
public static Image enamyl_2img = Toolkit.getDefaultToolkit().createImage("src/images/enemyFish/fish2_r.png");
public static Image enamyr_2img = Toolkit.getDefaultToolkit().createImage("src/images/enemyFish/fish2_l.png");
public static Image enamyl_3img = Toolkit.getDefaultToolkit().createImage("src/images/enemyFish/fish3_r.gif");
public static Image enamyr_3img = Toolkit.getDefaultToolkit().createImage("src/images/enemyFish/fish3_l.gif");
public static Image bossimg = Toolkit.getDefaultToolkit().createImage("src/images/enemyFish/boss.gif");
//我方鱼类
public static Image MyFishimg_L = Toolkit.getDefaultToolkit().createImage("src/images/myFish/myfish_left.gif");
public static Image MyFishimg_R = Toolkit.getDefaultToolkit().createImage("src/images/myFish/myfish_right.gif");
除此之外,我们将玩家所获得的分数方向移动、关卡信息敌方鱼的集合也存储在这里面
//方向
static boolean UP = false;
static boolean DOWN = false;
static boolean LEFT = false;
static boolean RIGHT = false;
//分数
static int count = 0;
//关卡等级
static int level = 0;
//敌方鱼类集合
public static List<Enamy> EnamyList = new ArrayList<>();
核心逻辑的实现
窗口类主要实现的是敌方的生成与我方的生成
敌方
通过对时间的间隔的控制来达到控制鱼群的生成速度
通过Math.random()生成的随机数的大小判断鱼的方向
random=Math.random();
//敌方鱼生成switch (GameUtils.level) {
case 4:
if(time%60==0){
if(random>0){
boss=new Boss();
isboss=true;
}
}
case 3:
case 2:
if (time % 30 == 0) {
if (random < 0.5) {
enamy = new Enamy_3_L();
} else {
enamy = new Enamy_3_R();
}
GameUtils.EnamyList.add(enamy);
}
case 1:
if (time % 40 == 0) {
if (random > 0.5) {
enamy = new Enamy_2_L();
} else {
enamy = new Enamy_2_R();
}
GameUtils.EnamyList.add(enamy);
}
case 0:
//
if(time%60==0){
if(random>0){
boss=new Boss();
isboss=true;
}
}
//
if (time % 240 == 0) {
if (random > 0.5) {
enamy = new Enamy_2_L();
} else {
enamy = new Enamy_2_R();
}
GameUtils.EnamyList.add(enamy);
}
if (time % 10 == 0) {
if (random < 0.5) {
enamy = new Enamy_1_L();
} else {
enamy = new Enamy_1_R();
}
GameUtils.EnamyList.add(enamy);
}
default:
}
我方鱼的生成直接实例化角色类Fish即可
碰撞检测
判断角色之间的矩形框是否有重合
for(Enamy enamy:GameUtils.EnamyList)
{
//碰撞检测
enamy.x = enamy.x + enamy.speed * enamy.dir;
if (isboss) {
if (boss.getRec().intersects(enamy.getRec())) {
enamy.x = -200;
enamy.y = -200;
}
if (boss.getRec().intersects(fish.getRec())) {
if (GameUtils.count <= 0) {
fish.x = 700;
fish.y = 500;
state = 2;
} else {
GameUtils.count -= 100;
}
}
}
if (fish.getRec().intersects(enamy.getRec())) {
if (fish.level >= enamy.type) {
enamy.x = -200;
enamy.y = -200;
GameUtils.count = GameUtils.count + enamy.count;
} else {
if (GameUtils.count <= 0) {
fish.x = 700;
fish.y = 500;
state = 2;
} else {
GameUtils.count -= 100;
}
}
}
}
玩家的移动机制
W/⬆️:向上移动
S/⬇️:向下移动
A/⬅️:向左移动
D/➡️:向右移动
在键盘处添加监听
if(state==0&&e.getKeyCode()==10){
state=1;
repaint();
}
if((e.getKeyCode()==87||e.getKeyCode()==38)){
GameUtils.UP=true;
}
if((e.getKeyCode()==83||e.getKeyCode()==40)){
GameUtils.DOWN=true;
}
if(e.getKeyCode()==65||e.getKeyCode()==37){
GameUtils.LEFT=true;
}
if(e.getKeyCode()==68||e.getKeyCode()==39){
GameUtils.RIGHT=true;
}
暂停机制
在键盘监听处添加
if(e.getKeyCode()==32){
switch (state){
case 1:
state=4;
GameUtils.drawWord(getGraphics(),"游戏暂停",Color.RED,120,620,360);
break;
case 4:
state=1;
break;
default:
}
}
重新开始机制
void reGame(){
GameUtils.EnamyList.clear();
time=0;
isboss=false;
fish.x=700;
fish.y=500;
fish.level=1;
boss=null;
}
在键盘监听处添加
if(e.getKeyCode()==82){
reGame();
state=1;
}
等级机制
此处定义为玩家初始为1级玩家可以食用等级笑语等于自己的鱼
玩家到达一定等级后游戏难度升级
if(GameUtils.count<5){
GameUtils.level=0;
fish.level=1;
}
else if(GameUtils.count<=15){
GameUtils.level=1;
}
else if(GameUtils.count<=50){
GameUtils.level=2;
fish.level=2;
}
else if(GameUtils.count<=150){
GameUtils.level=3;
fish.level=3;
}
else if(GameUtils.count<=300){
GameUtils.level=4;
fish.level=3;
}
else if(GameUtils.count>300) {
state=3;
}
缺陷
1.在游戏运行时间越长由于贴图的鱼过多导致资源浪费,
①可以考虑将鱼存储在一个对象池中,每一次游到边界或被吃到就回归到池子中
②或者考虑发生上述时间直接把该鱼从List中删除
2.可以做一些等级进度条,随着鱼吃的数目进度条随之变化