这次的黄金矿工项目实现并不难,只要明白游戏运行的逻辑,就可以上手写出这个经典的小游戏。
代码后有注释。
首先创建一个GameUI,实现窗口的绘制。
public class GameUI extends JFrame {
public void launch(){
setSize(768,1000);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setTitle("黄金矿工v2");
setLocationRelativeTo(null);
setVisible(true);}}
窗口绘制完之后,就是添加游戏的背景图片, 所以我们创建一个Background类,来绘制背景图片。
上面是一些背景图片,保存在imgs里。
import java.awt.*;
public class Background {
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");
public void paint(Graphics g){
g.drawImage(bg,0,200,null);
g.drawImage(bg1,0,0,null);
g.drawImage(peo,315,65,null);
g.drawImage(water,500,100,null);}}
用Toolkit函数获取图片内容,用Graphics把背景画到窗口上。这样就把背景的图片画到了窗口上,接下去就是抓钩的红线绘制。
首先明白在游戏刚开始时,红线的末尾是在以半径一定的圆弧上的运动,那么这个逻辑实现就是写一个红线末端的运动函数,然后不断把红线重新绘制在窗口上。
然后就是红线的伸长与收缩,当红线触碰到了金块的矩形范围内,Line中的choose变量就会赋值为3(此函数为istouch函数),判断是否触碰物体。
当鼠标左键点击了之后,Line中的choose变量会被赋值为1(此函数方法在gameUI中)。
import java.awt.*;
public class Line {
int x = 390;
int y = 180;
int endx;
int endy;
int length = 113;//长度
int dir = 1;//方向
double angle = 0.07;//角度
int choose = 0;
boolean extend = true;//判断红线是否在伸长,如果在伸长就为false,在伸长的过程中就为ture
GameUI frame;
Image hook = Toolkit.getDefaultToolkit().getImage("imgs/hook.png");
int total;
Background background=new Background();
Line(GameUI frame) {
this.frame = frame;
}
public void istouch() {//下面这个语句是增强for语句,把list中的依次物体取出来,命名为obj,再调用obj的参数与endx和endy进行比较。
for (Body obj : this.frame.list) {
if (endx >= obj.x && endx <= obj.x + obj.width && endy >= obj.y && endy <= obj.y + obj.high) {
obj.touch = true;//表示此物体被触碰,并且可以移动。
choose = 3;
}
}
}//判断红线的末端是否碰到了物体的矩形范围。
public void paint(Graphics g) {
endx = (int) (x + length * Math.cos(angle * Math.PI));
endy = (int) (y + length * Math.sin(angle * Math.PI));
g.setColor(Color.red);
g.drawLine(x, y, endx, endy);
g.drawLine(x + 1, y, endx + 1, endy);
g.drawLine(x + 1, y, endx + 1, endy);
g.drawImage(hook, endx - 36, endy - 5, null);
}//封装了画红线这个方法
public void PaintLine(Graphics g, int ac) {
istouch();//首先调用istouch函数,先判断红线末端是否触碰
switch (choose) {
case 0:
extend = true;
if (angle < 0.9) {
angle = angle + 0.005 * dir;
} else {
dir = -1;
}
if (angle > 0.1) {
angle = angle + 0.005 * dir;
} else {
dir = 1;
}
paint(g);
break;//第零种情况,鼠标未进行点击,不断变化角度,当角度变化到阈值范围外后,角度变换方向改变,并且每改变一次就画一次红线。
case 1:
if (length < 750) {
length = length + 11;
paint(g);
extend = false;
} else {
choose = 2;
}
istouch();
break;//第一种情况,鼠标左键点击,红线伸长
case 2:
if (length > 100) {
length = length - 10;
paint(g);
extend = false;
} else {
choose = 0;
}
break;//第二种情况,,未抓取到物体,红线达到最大值,停止伸长并且收缩
case 3:
for (Body obj : this.frame.list) {
if (obj.touch) {
length = length - obj.weight - ac;
paint(g);
obj.x = endx - obj.getwidth() / 2;
obj.y = endy + 15;
if (length <= 100) {
obj.x = -1000;
obj.y = -1000;
background.count+= obj.score;
obj.touch = false;//此物体不让它移动
choose = 0;
System.out.println(background.count);
}
}
}
break;//第三种情况,遍历列表,找出被抓取的物体,并不断更新它的坐标,达到跟着红线走的效果。当红线缩短到100时,把物体的坐标改到窗口外,达到物体消失的效果。
case 4:
for (Body obj : this.frame.list) {
if (obj.touch) {
if(obj.name.equals("rock")){
obj.x = -1000;
obj.y = -1000;
obj.touch=false;
}
choose=2;
}
}
}
}}
红线绘制好了之后,就需要金块和石块在背景中等待着被抓取。首先思考他们都有什么特征,他们都应该具有初始坐标、长度、宽度、质量、名称、分数、是否可以被抓取最后还有它的背景图片。所以我们可以创建一个父类,即Body类,金块类和石头类就可以继承Body类,这样可以使代码更加简洁方便。
import java.awt.*;
public class Body {
int width;//宽度
int high;//高度
int x;//画在面板上x轴的坐标
int y;//画在面板上y轴的坐标
boolean touch;//是否被触碰
int weight;//重量
Image image;//物体的贴图
String name;//名称
int score;//分数
public void paint(Graphics g,Image image, int x, int y){
g.drawImage(image,x,y,null);
}//paint方法,画出物体的图片
public int getwidth(){
return width;
}//获取物体的宽度
}
父类绘制好了之后就是金块类和石块类,这两个类都继承了Body类,代码内容基本一致。
下面是金块类
import java.awt.*;
//继承Body类
public class Gold extends Body{
Image gold0=Toolkit.getDefaultToolkit().getImage("imgs/gold0.gif");//获取gold0的图片
Gold(){
this.image=gold0;
this.x= (int) (Math.random()*600);//每一个金块的坐标都随机生成。
this.y= (int) ((Math.random()*550)+300);
this.width=36;
this.high=36;
this.weight=7;
this.touch=false;
this.name="gold";
this.score=10;
}//定义从父类里继承来的变量
}
class Gold1 extends Gold{
Image gold1=Toolkit.getDefaultToolkit().getImage("imgs/gold1.gif");
Gold1(){
this.image=gold1;
this.x= (int) (Math.random()*700);
this.y= (int) ((Math.random()*550)+300);
this.width=52;
this.high=52;
this.weight=5;
this.touch=false;
this.name="gold1";
this.score=30;
}
}
class Gold2 extends Gold{
Image gold2=Toolkit.getDefaultToolkit().getImage("imgs/gold2.gif");
Gold2(){
this.image=gold2;
this.x= (int) (Math.random()*700);
this.y= (int) ((Math.random()*550)+300);
this.width=115;
this.high=115;
this.weight=2;
this.touch=false;
this.name="gold2";
this.score=50;
}
}
下面是石块类,跟金块类一摸一样。
import java.awt.*;
public class Rock extends Body{
Image rock=Toolkit.getDefaultToolkit().getImage("imgs/rock1.png");
Rock(){
this.width=71;
this.high=71;
this.x= (int) (Math.random()*600);
this.y= (int) ((Math.random()*550)+300);
this.image=rock;
this.weight=3;
this.name="rock";
this.score=-20;
}
}
那最后就是需要一个主函数类即gameUI类来把一切都整合起来。
以下是gameUI的代码。
首先我们需要创建一个列表,把生成的金块和石块都存入列表里。
然后调用其他类中的变量。
下面代码比较长,所以我把它分块来讲解。
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
public class GameUI extends JFrame {
java.util.List<Body> list=new ArrayList<>();
Background background=new Background();
Line line=new Line(this);
Image offScreenImage;
Rectangle rect1;
Rectangle rect2;
boolean ispress;
int accelerate;
{
for (int i = 0; i < 11; i++) {
Gold gold;
double a = Math.random();
if (a < 0.3) {
if(isStacking(gold = new Gold())) {list.add(gold);}
else {i--;}
} else if (a < 0.8) {
if(isStacking(gold = new Gold1())) {list.add(gold);}
else {i--;}
} else if (a < 1) {
if(isStacking(gold = new Gold2())) {list.add(gold);}
else {i--;}
}
}
for (int i = 0; i < 5; i++) {
Rock rock;
double a = Math.random();
if (a < 0.6) {
if(isStacking(rock=new Rock())) list.add(rock);
else {i--;}
}
}
}
public boolean isStacking(Body body) {
rect2 = new Rectangle(body.x,body.y,body.width,body.high);
for (Body obj : list) {
rect1 = new Rectangle(obj.x, obj.y, obj.width, obj.high);
if (rect1.intersects(rect2)) {
return false;
}
}
return true;
}
public void launch(){
setSize(768,1000);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setTitle("黄金矿工v2");
setLocationRelativeTo(null);
setVisible(true);
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
if(e.getButton()==1&&line.extend==true){
line.choose=1;
System.out.println("1");
}
}
//右键按下使线收缩加速,用鼠标监听器对右键进行监听,如果鼠标一直按下,则把绳子伸缩的速度加快
//读取链表中的值,如果当前值为rock,侧把他的位置移到窗口外
@Override
public void mousePressed(MouseEvent e) {
super.mousePressed(e);
ispress=true;
if (SwingUtilities.isRightMouseButton(e)) {
for (Body obj : list) {
if (obj.touch) {
if(background.watercount>0) {
if (obj.name.equals("rock")) {
line.choose = 4;
background.watercount--;
break;}
background.watercount--;
accelerate = 7;}
}
}
}
}
@Override
public void mouseReleased(MouseEvent e) {
super.mouseReleased(e);
ispress=false;
accelerate=0;
}
});
while (true){
repaint();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public void paint(Graphics g){
offScreenImage = this.createImage(768, 1000);
Graphics gImage = offScreenImage.getGraphics();//??
background.paint(gImage);
line.PaintLine(gImage,accelerate);
for(Body obj:list){
obj.paint(gImage,obj.image,obj.x,obj.y);
}
g.drawImage(offScreenImage,0,0,null);
}
public static void main(String[] args) {
GameUI gameUI=new GameUI();
gameUI.launch();
}
}
首先是开头的代码块(代码块又叫初始化块,属于类中的成员,即使类的一部分 类似于方法,将逻辑语句封装在方法提中,通过{ }包围起来。但和方法不同,没有方法名,没有返回值,没有参数,只有方法体,而且不用通过对象或类显示调用,而是在加载类的时候或者创建对象的时候隐式调用。),这里用了一个for循环来进行金块和石块的生成。并用生成的随机数a来控制生成金块的概率。
{
for (int i = 0; i < 11; i++) {
Gold gold;
double a = Math.random();
if (a < 0.3) {
if(isStacking(gold = new Gold())) {list.add(gold);}
else {i--;}
} else if (a < 0.8) {
if(isStacking(gold = new Gold1())) {list.add(gold);}
else {i--;}
} else if (a < 1) {
if(isStacking(gold = new Gold2())) {list.add(gold);}
else {i--;}
}
}
for (int i = 0; i < 5; i++) {
Rock rock;
double a = Math.random();
if (a < 0.6) {
if(isStacking(rock=new Rock())) list.add(rock);
else {i--;}
}
}
}
上面的代码块中有isStacking方法,这个方块是用来判断物体是否重叠。
public boolean isStacking(Body body) {
rect2 = new Rectangle(body.x,body.y,body.width,body.high);//创建一个矩形,把新生成的物体的值赋值给它
for (Body obj : list) {
rect1 = new Rectangle(obj.x, obj.y, obj.width, obj.high);//再创建一个矩形,依次赋值为列表内的物体的值
if (rect1.intersects(rect2)) {
return false;//判断物体是否重叠,如果重叠则返回false
}
}
return true;
}
下面是gameUI中的启动函数,我们先创建一个窗口,再添加鼠标监听器。
鼠标监听器的作用是得到鼠标按下键的信息传给line中的choose值,然后执行Line中相应的程序。
public void launch(){
setSize(768,1000);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setTitle("黄金矿工v2");
setResizable(false);
setLocationRelativeTo(null);
setVisible(true);
//鼠标左键按下红线伸长
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
if(e.getButton()==1&&line.extend==true){
line.choose=1;
System.out.println("1");
}
}
//右键按下使线收缩加速,用鼠标监听器对右键进行监听,如果鼠标一直按下,则把绳子伸缩的速度加快
//读取链表中的值,如果当前值为rock,侧把他的位置移到窗口外
@Override
public void mousePressed(MouseEvent e) {
super.mousePressed(e);
ispress=true;
if (SwingUtilities.isRightMouseButton(e)) {
for (Body obj : list) {
if (obj.touch) {
if(background.watercount>0) {
if (obj.name.equals("rock")) {
line.choose = 4;
background.watercount--;
break;}
background.watercount--;
accelerate = 7;}
}
}
}
}
@Override
public void mouseReleased(MouseEvent e) {
super.mouseReleased(e);
ispress=false;
accelerate=0;
}
});
//循环程序,让金块石块和红线的坐标一直改变
while (true){
repaint();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
最后是画笔的程序,如果直接把每一个背景画到窗口上,那么程序每循环执行一次就会把原来的图片背景覆盖,这样则会造成背景闪烁的问题。
那我们可以先把背景,金块,红线都先绘制在一张图片上,再用画笔把这一张图片绘制在窗口上,这样就可以解决原来的背景闪烁的问题。
public void paint(Graphics g){
offScreenImage = this.createImage(768, 1000);//类似创建一个新的画布
Graphics gImage = offScreenImage.getGraphics();//创建一个在画布上画的画笔
background.paint(gImage);//把背景这些都画在画布上
line.PaintLine(gImage,accelerate);
for(Body obj:list){
obj.paint(gImage,obj.image,obj.x,obj.y);
}
g.drawImage(offScreenImage,0,0,null);//把画布绘制在窗口中
}
这样一个简易的黄金矿工小游戏就开发完成了。