Java小玩意:黄金矿工
这个小项目没有什么技术难点,但是还是比较适合像我一样的小白来练手用。
一、 先看效果
二、分析
这个小项目主要有6大Java类:主类(GameWin)、背景类(Bg)、“线”类(Line)、金块类(Gold)、石头类(Rock)以及金块和石头类的父类(Object)。
其中,金块类(Gold)中又分:普通金块(Gold)、小金块(GoldMini)、大金块(GoldPlus)。由于三种金块的行为和逻辑一样,所以“大金块”和“小金块”都继承自“普通金块”。
三、实现
1. 背景类(Bg.java)
import java.awt.*;
public class Bg {
// 关卡数
static int level = 1;
// 目标得分
int goal = (level * 500) + 550;
// 总分
static int count = 0;
// 药水数量
static int waterNum = 3;
// 药水状态
static boolean waterFlag = false;
// 开始时间
long startTime;
// 结束时间
long endTime;
// 药水价格
int price = (int) (Math.random() * 10);
// 是否进入商店 f不购买
boolean shop = false;
// 载入图片
Image bg = Toolkit.getDefaultToolkit().getImage("imgs/bg.jpg");
Image bg1 = Toolkit.getDefaultToolkit().getImage("imgs/bg1.jpg");
Image peo = Toolkit.getDefaultToolkit().getImage("imgs/peo.png");
Image water = Toolkit.getDefaultToolkit().getImage("imgs/water.png");
// 绘制
void paintSelf(Graphics g) {
g.drawImage(bg, 0, 200, null);
g.drawImage(bg1, 0, 0, null);
switch (GameWin.state) {
case 0:
drawWord(g, 80, Color.black, "按鼠标右键开始", 100, 400);
drawWord(g, 60, Color.red, "游戏规则:", 100, 500);
drawWord(g, 30, Color.black, "点击鼠标左键放出抓钩", 100, 560);
drawWord(g, 30, Color.black, "抓住金块或石头可以点击鼠标右键", 100, 620);
drawWord(g, 30, Color.black, "使用“大力神药”快速收回金块或者爆破石头", 100, 680);
break;
case 1:
g.drawImage(peo, 310, 50, null);
drawWord(g, 30, Color.black, "分数:" + count, 30, 150);
g.drawImage(water, 550, 40, null);
drawWord(g, 30, Color.black, "×" + waterNum, 610, 90);
// 关卡数&目标得分
drawWord(g, 20, Color.black, "关卡:" + level, 30, 60);
drawWord(g, 30, Color.black, "目标:" + goal, 30, 115);
// 实时赋值
endTime = System.currentTimeMillis();
long time = 20 - (endTime - startTime) / 1000;
drawWord(g, 30, Color.black, "时间:" + (time > 0 ? time : 0), 520, 150);
break;
case 2:
g.drawImage(water, 330, 380, null);
drawWord(g, 30, Color.black, "价格:" + price, 300, 500);
drawWord(g, 30, Color.black, "是否购买“大力神药”?", 280, 550);
drawWord(g, 30, Color.black, "点击鼠标左键确认购买,右键退出商店", 100, 700);
if (shop) {
count = count - price;
waterNum++;
shop = false;
GameWin.state = 1;
startTime = System.currentTimeMillis();
};
break;
case 3:
drawWord(g, 80, Color.cyan, "失败", 300, 350);
drawWord(g, 80, Color.black, "分数:" + count, 200, 450);
drawWord(g, 60, Color.black, "点击鼠标左键重新开始", 90, 550);
break;
case 4:
drawWord(g, 80, Color.red, "恭喜过关", 250, 350);
drawWord(g, 80, Color.black, "分数:" + count, 200, 450);
drawWord(g, 60, Color.black, "点击鼠标左键重新开始", 90, 550);
break;
default:
};
};
// t倒计时完成,f未完成倒计时
boolean gameTime() {
long time = (endTime - startTime) / 1000;
if (time >= 20) {
return true;
};
return false;
};
// 重置元素
void reGame() {
// 关卡数
level = 1;
// 目标得分
goal = (level * 500) + 550;
// 总分
count = 0;
// 药水数量
waterNum = 3;
// 药水状态
waterFlag = false;
};
// 绘制字符串
public static void drawWord(Graphics g, int size, Color color, String str, int x, int y) {
g.setColor(color);
g.setFont(new Font("宋体", Font.BOLD, size));
g.drawString(str, x, y);
};
};
“背景类”中处理所有显示在窗口上的内容,包括背景、文字等信息
但是有一点需要注意:
// 载入图片
Image bg = Toolkit.getDefaultToolkit().getImage("imgs/bg.jpg");
Image bg1 = Toolkit.getDefaultToolkit().getImage("imgs/bg1.jpg");
Image peo = Toolkit.getDefaultToolkit().getImage("imgs/peo.png");
Image water = Toolkit.getDefaultToolkit().getImage("imgs/water.png");
使用这种方法导入图片,在导出 jar包
时,并不能将图片资源一起打包,如果运行jar包,需要将图片等资源放在和代码文件相同的相对路径下。
2. “线”(Line.java)
这个“线”就是游戏画面中左右摇摆的红色线。
import java.awt.*;
public class Line {
// 起点坐标
int x = 380;
int y = 180;
// 终点坐标
int endx = 500;
int endy = 500;
// 线段长度
double length = 100;
double MIN_length = 100;
double MAX_length = 750;
double n = 0;
// 方向
int dir = 1;
// 状态: 0摇摆,1抓取,2回收,3抓取返回
int state = 0;
// 钩爪
Image hook = Toolkit.getDefaultToolkit().getImage("imgs/hook.png");
GameWin frame;
Line(GameWin frame) {
this.frame = frame;
};
// 判断是否抓取成功
void logic() {
for (Object obj : this.frame.objectList) {
if (endx > obj.x && endx < obj.x + obj.width && endy > obj.y && endy < obj.y + obj.height) {
state = 3;
obj.flag = true;
};
};
};
// 绘制方法
void lines(Graphics g) {
endx = (int) (x + length * Math.cos(n * Math.PI));
endy = (int) (y + length * Math.sin(n * Math.PI));
g.setColor(Color.red);
g.drawLine(x - 1, y - 1, endx - 1, endy);
g.drawLine(x, y, endx, endy);
g.drawLine(x + 1, y + 1, endx + 1, endy);
g.drawImage(hook, endx - 36, endy - 2, null);
};
void paintSelf(Graphics g) {
logic();
switch (state) {
case 0:
if (n < 0.1) {
dir = 1;
}; else if (n > 0.9) {
dir = -1;
};
n = n + 0.02 * dir;
lines(g);
break;
case 1:
if (length < MAX_length) {
length = length + 50;
lines(g);
}; else {
state = 2;
};
break;
case 2:
if (length > MIN_length) {
length = length - 50;
lines(g);
}; else {
state = 0;
};
break;
case 3:
int m = 1;
if (length > MIN_length) {
length = length - 20;
lines(g);
for (Object obj : this.frame.objectList) {
if (obj.flag) {
m = obj.m;
obj.x = endx - obj.getWidth() / 2;
obj.y = endy;
if (length <= MIN_length) {
obj.x = -150;
obj.y = -150;
obj.flag = false;
Bg.waterFlag = false;
// 加分
Bg.count += obj.count;
state = 0;
};
if (Bg.waterFlag) {
if (obj.type == 1) {
m = 1;
};
if (obj.type == 2) {
obj.x = -150;
obj.y = -150;
obj.flag = false;
Bg.waterFlag = false;
state = 2;
};
};
};
};
};
try {
Thread.sleep(m);
}; catch (InterruptedException e) {
e.printStackTrace();
};
break;
default:
};
};
// 重置线
void reGame() {
n = 0;
length = 100;
};
};
3. ”块“的父类(Object.java)
由于”金块“和”石块“在处理的逻辑和其具有的属性基本都是相同的,所以使他们继承自相同的父类,简化代码。
import java.awt.*;
public class Object {
// 坐标
int x;
int y;
// 宽高
int width;
int height;
// 图片
Image img;
// 标记 是否能移动
boolean flag = false;
// 质量
int m;
// 积分
int count;
// 类型 1 金块 2 石块 3
int type;
void paintSelf(Graphics g) {
g.drawImage(img, x, y, null);
};
public int getWidth() {
return width;
};
// 获取矩形
public Rectangle getRec() {
return new Rectangle(x, y, width, height);
};
};
其中 getRec()
方法在主类中会用到,其作用是解决各种块的重叠问题
m
为质量,要区别不同的块的拉取速度,所以引入质量这个属性
4. 石块(Rock.java)
import java.awt.*;
public class Rock extends Object {
Rock() {
this.x = (int) (Math.random() * 700);
this.y = (int) (Math.random() * 550 + 300);
this.width = 71;
this.height = 71;
this.flag = false;
this.m = 100;
this.count = 15;
this.type = 2;
this.img = Toolkit.getDefaultToolkit().getImage("imgs/rock1.png");
};
};
5. 金块(Gold.java)
import java.awt.*;
public class Gold extends Object {
Gold() {
this.x = (int) (Math.random() * 700);
this.y = (int) (Math.random() * 550 + 300);
this.width = 52;
this.height = 52;
this.flag = false;
this.m = 30;
this.count = 250;
this.type = 1;
this.img = Toolkit.getDefaultToolkit().getImage("imgs/gold1.gif");
};
};
class GoldMini extends Gold {
GoldMini() {
this.width = 36;
this.height = 36;
this.m = 30;
this.count = 150;
this.img = Toolkit.getDefaultToolkit().getImage("imgs/gold0.gif");
};
};
class GoldPlus extends Gold {
GoldPlus() {
this.x = (int) (Math.random() * 650);
this.width = 105;
this.height = 105;
this.m = 100;
this.count = 500;
this.img = Toolkit.getDefaultToolkit().getImage("imgs/gold2.gif");
};
};
6. 主类(GameWin.java)
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
public class GameWin extends JFrame {
// 0未开始,1运行中,2商店,3失败,4胜利
static int state = 0;
// 存储金块、石块
List<Object> objectList = new ArrayList<>();
Bg bg = new Bg();
Line line = new Line(this);
{
// 是否可以放置
boolean isPlace = true;
for (int i = 0; i < 11; i++) {
double random = Math.random();
Gold gold; // 存放当前生成的金块
if (random < 0.3) {
gold = new GoldMini();
}; else if (random < 0.7) {
gold = new Gold();
}; else {
gold = new GoldPlus();
};
for (Object obj : objectList) {
if (gold.getRec().intersects(obj.getRec())) {
// 不可放置
isPlace = false;
};
};
if (isPlace) {
objectList.add(gold);
}; else {
isPlace = true;
i--;
};
};
for (int i = 0; i < 5; i++) {
Rock rock = new Rock();
for (Object obj : objectList) {
if (rock.getRec().intersects(obj.getRec())) {
isPlace = false;
};
};
if (isPlace) {
objectList.add(rock);
}; else {
isPlace = true;
i--;
};
};
};
Image offScreenImage;
void launch() {
this.setVisible(true);
this.setSize(768, 1000);
this.setLocationRelativeTo(null);
this.setTitle("黄金矿工");
setDefaultCloseOperation(EXIT_ON_CLOSE);
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
switch (state) {
case 0:
if (e.getButton() == 3) {
state = 1;
bg.startTime = System.currentTimeMillis();
};
break;
case 1:
if (e.getButton() == 1 && line.state == 0) {
line.state = 1;
};
if (e.getButton() == 3 && line.state == 3 && Bg.waterNum > 0) {
Bg.waterFlag = true;
Bg.waterNum--;
};
break;
case 2:
if (e.getButton() == 1) {
bg.shop = true;
};
if (e.getButton() == 3) {
state = 1;
bg.startTime = System.currentTimeMillis();
};
break;
case 3:
case 4:
if (e.getButton() == 1) {
state = 0;
bg.reGame();
line.reGame();
};
break;
default:
};
};
};);
// 红线摇摆
while (true) {
repaint();
nextLevel();
try {
Thread.sleep(50);
}; catch (InterruptedException e) {
e.printStackTrace();
};
};
};
// 下一关
public void nextLevel() {
if (bg.gameTime() && state == 1) {
if (Bg.count >= bg.goal) {
if (Bg.level == 10) {
state = 4;
}; else {
state = 2;
Bg.level++;
};
}; else {
state = 3;
};
dispose();
GameWin gameWin1 = new GameWin();
gameWin1.launch();
};
};
@Override
public void paint(Graphics g) {
offScreenImage = this.createImage(768, 1000);
Graphics gImage = offScreenImage.getGraphics();
bg.paintSelf(gImage);
if (state == 1) {
// 绘制金块、石块
for (Object obj : objectList) {
obj.paintSelf(gImage);
};
// 绘制红线
line.paintSelf(gImage);
};
g.drawImage(offScreenImage, 0, 0, null);
};
public static void main(String[] args) {
GameWin game = new GameWin();
game.launch();
};
};
四、总结
源码:GoldMiner
经过我简单测试,存在一个Bug,运行时有几率出现”红线和抓钩“消失的情况,需要刷新窗口才可正常运行,由于这段时间要准备期末考试,所以也就没有时间再去深究这个。但是我猜测可能的原因是因为:在 Line.java
类中,state
属性没有初始化的原因,但是 state
是int型,创建对象时会默认初始化为0,不知道是不是这个原因。
最后,再次重申一遍,这个小项目没有什么技术难点,用到的方法也基本都是一些基础的东西,适合像我一样的小白练手用,大家也不要太过纠结方法实现的”好坏“,毕竟,我也还是一个小白。
当然,我还是欢迎一切积极、善意的讨论与指正 ^_^