JAVA经典小游戏再现————黄金矿工

这次的黄金矿工项目实现并不难,只要明白游戏运行的逻辑,就可以上手写出这个经典的小游戏。

代码后有注释。

首先创建一个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);//把画布绘制在窗口中
    }

这样一个简易的黄金矿工小游戏就开发完成了。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值