【Java】图形及事件驱动程序设计——弹球游戏

一、问题提出

利用Javafx界面设计一个弹球游戏,包括重玩和下一关的按键,并且显示游戏时间和得分。

二、解决思路

创建一个多线程的小球类,随机生成小球的位置和速度,并实现多个小球下落和碰撞效果;创建一个游戏窗体,显示各个按键和标签,并设置按键驱动,实现不同关卡的切换。设计的游戏规则为:计时10秒后判断界面中剩余小球即得分,按键下一关、重玩、挡板变短都会重新开始游戏,重新计时,按键键盘‘A’可以增加小球数量,最后显示出各次得分,以及最佳得分。

三、代码实现

1.球类定义

初始化

class BallThread extends Thread{
    BallPane bp;        //线程没有窗体类不能调用this
    int n=0;
    Random r=new Random();
    double x;
    double y;
    double rad;
    double vy;
    double vx;
    double a;      //重力加速度
    double d;                         //触碰边界摩擦力
    double d2;                      //地面摩擦力
    Circle c=new Circle(0,0,rad);;
    boolean dragging=false;
    public void init(){   //初始化
        r=new Random();
        x=300+r.nextDouble()*600-300; //0-600
        y=250+r.nextDouble()*400-200; //50-450
        c.setCenterX(x);
        c.setCenterY(y);
        rad=30+r.nextDouble()*30-25;//5-35
        c.setRadius(rad);
        vy=1.5+r.nextDouble()*4-1;//0.5-4.5
        vx=1.5+r.nextDouble()*4-1;//0.5-4.5
        a=0.08+r.nextDouble()*.06-.02;//0.06-0.12
        d=.8;
        d2=.99;
        dragging=false;
        c.setFill(Color.rgb(r.nextInt(255),r.nextInt(255),r.nextInt(255),r.nextDouble()));
    }
    public BallThread(BallPane bp){
        this.bp=bp;
    }

小球交叠调整

    public void adjust(){
        for(int i=0;i<10;i++)
            for(BallThread b:bp.bt){
                if(this==b||b==null)
                    continue;
                double dist=distCen(b);
                while (dist<=rad+b.rad){
                    x=300+r.nextDouble()*600-300;
                    y=250+r.nextDouble()*400-200;
                    c.setCenterX(x);
                    c.setCenterY(y);
                    rad=30+r.nextDouble()*30-25;
                    c.setRadius(rad);
                    dist=distCen(b);
                }
            }
    }

小球初始距离

    public double distCen(BallThread b){    //初始距离
        double x=c.getCenterX();
        double y=c.getCenterY();
        double x2=b.c.getCenterX();
        double y2=b.c.getCenterY();
        return Math.sqrt((x-x2)*(x-x2)+(y-y2)*(y-y2));
    }

小球运动后的距离

    public double distCenNext(BallThread b){   //运动后的距离
        double x=c.getCenterX()+vx;
        double y=c.getCenterY()+vy;
        double x2=b.c.getCenterX()+b.vx;
        double y2=b.c.getCenterY()+b.vy;
        return Math.sqrt((x-x2)*(x-x2)+(y-y2)*(y-y2));
    }

小球运动函数

    public void run() {
        init();         //初始化
        adjust();
        c.setStroke(Color.BLACK);
        c.setStrokeWidth(2);
        bp.getChildren().add(c);
        c.setOnMousePressed(e->{
            vx=0;
            vy=0;
            dragging=true;
        });
        c.setOnMouseDragged(e->{             //鼠标拖动位置
            c.setCenterX(e.getX());
            c.setCenterY(e.getY());
        });
        c.setOnMouseReleased(e->{            //松开鼠标
            dragging=false;
        });

        Timeline t=new Timeline(new KeyFrame(Duration.millis(20),e->{
            if(dragging)
                return;
            vy+=a;
            c.setCenterX(c.getCenterX()+vx);
            if(c.getCenterX()+vx-rad>0 &&c.getCenterX()+vx+rad<600)        //左右运动范围
                c.setCenterX(c.getCenterX()+vx);
            else if(c.getCenterX()+vx+rad>=600){       //右边界
                c.setCenterX(600 - rad);
                vx*=-d;                                //反弹
            }
            else{                                      //左边界
                c.setCenterX(rad);
                vx*=-d;
            }
            //碰撞中交叠处理
            for(BallThread b:bp.bt){
                if(this==b || b==null)                  //同一个小球
                    continue;
                if(distCen(b)<=rad+b.rad) {
                    double s=rad+b.rad-distCen(b);
                    double x=c.getCenterX();
                    double y=c.getCenterY();
                    double x2=b.c.getCenterX();
                    double y2=b.c.getCenterY();
                    double k=(y-y2)/(x-x2);
                    double theta=Math.atan(k);
                    c.setCenterX(x+s*Math.cos(theta));
                    c.setCenterY(y+s*Math.sin(theta));
                }
            }

            //小球碰撞
            for(BallThread b:bp.bt){
                if(this==b || b==null)                  //同一个小球
                    continue;
                if(distCenNext(b)<=rad+b.rad) {
                    vx *= -d2;
                    vy *= -d;
                }
            }
            //挡板向下移
            if(c.getCenterY()+rad>bp.pad.getY()-bp.pad.getHeight()/2   //小球下边缘在挡板上
                    && c.getCenterY()-rad<=bp.pad.getY()+bp.pad.getHeight()/2       //小球上边缘在挡板下
                    && c.getCenterX()>=bp.pad.getX()
                    && c.getCenterX()<=bp.pad.getX()+bp.pad.getWidth()){
                c.setCenterY(
                        bp.pad.getY() - bp.pad.getHeight()/2 - c.getRadius());
                vx*=d2;
                vy*=-d;
            }

            if(c.getCenterY()+rad<bp.pad.getY()*1.1&&               //碰到挡板反弹
                    c.getCenterY()+vy+rad>=bp.pad.getY()&&
                    c.getCenterX()>=bp.pad.getX()&&
                    c.getCenterX()<=bp.pad.getX()+bp.pad.getWidth()){
                c.setCenterY(bp.pad.getY() - c.getRadius());
                vx*=d2;
                vy*=-d;
            }
            else
                c.setCenterY(c.getCenterY()+vy);

            //先碰撞再下落,否则会出现小球进入另一个小球内继续下落

        }));
        t.setCycleCount(Timeline.INDEFINITE);
        t.play();
    }
}

2.主界面定义

class BallPane extends Pane{
    BallThread[] bt;
    Rectangle pad;
    MyTask myTask = new MyTask();
    public int judge(){ //判断有几个球在界面内
        int n=0;
        for(int i=0;i< bt.length;i++)
            if(bt[i].c.getCenterX()+bt[i].c.getRadius()<=600
                    &&bt[i].c.getCenterX()-bt[i].c.getRadius()>=0
                    &&bt[i].c.getCenterY()+bt[i].c.getRadius()<=600
                    &&bt[i].c.getCenterY()-bt[i].c.getRadius()>=0)
                n++;
        return n;
    }
    public void newcre()   //重新初始化
    {
        for(int i=0;i<bt.length;i++) {
            bt[i].init();
            bt[i].adjust();
        }
        myTask.restart();
        myTask.setStartNumber(-1);
    }

设置界面初始布局,包括标签,挡板,小球等

    public BallPane() throws InterruptedException {
        Label count=new Label();
        Label label=new Label("time:");
        label.setFont(new Font("Times New Roman",20));
        count.setFont(new Font("Times New Roman",20));
        label.setLayoutX(450);
        label.setLayoutY(20);
        count.setLayoutX(500);
        count.setLayoutY(20);
        TextField tf=new TextField("score:0");
        tf.setFont(new Font("Times New Roman",20));
        tf.setLayoutX(100);
        tf.setLayoutY(20);
        tf.setPrefWidth(100);
        TextField tf2=new TextField("bset score:0");
        tf2.setFont(new Font("Times New Roman",20));
        tf2.setLayoutX(200);
        tf2.setLayoutY(20);
        tf2.setPrefWidth(150);
        pad=new Rectangle(400,500,250,10);   //挡板
        pad.setFill(Color.INDIANRED);
        pad.setStroke(Color.BLACK);
        pad.setStrokeWidth(2);
        this.getChildren().addAll(pad,count,label);
        this.setOnMouseMoved(e->{
            pad.setX(e.getX()-pad.getWidth()/2);
        });

        bt=new BallThread[10];
        for(int i=0;i<bt.length;i++)
        {
            bt[i]=new BallThread(this);
            bt[i].run();

        }
        myTask.restart();
        count.textProperty().bind(myTask.messageProperty());

关卡设置

    System.out.print("第一关:");
        Timer timer=new Timer();
        TimerTask task=new TimerTask(){
            public void run(){
                if(Integer.parseInt(tf2.getText(11,tf2.getLength()))<judge())
                    tf2.setText("\tbest score:"+judge());
                tf.setText("score:"+judge());
                System.out.println(judge());
                timer.cancel();
            }
        };
     timer.schedule(task,10000); //第一关时间

按键盘A,可增加小球

    this.setOnKeyPressed(e->{
        if(e.getCode()== KeyCode.A)  //按键A 增加小球
            {
                System.out.print("增加了10个小球:");
                for(int i=0;i<bt.length;i++) {
                bt[i]=new BallThread(this);
                bt[i].run();
            }}});

功能按钮设置

    Button b=new Button("reset");  //重新开始
        b.setFont(new Font(30));
        b.setLayoutY(520);
        b.setOnAction(e->{
            tf.setText("score:0");
            System.out.print("本关重新开始:");
            newcre();
            Timer timer1=new Timer();//第二关时间
            TimerTask task1=new TimerTask(){
                public void run(){
                    if(Integer.parseInt(tf2.getText(11,tf2.getLength()))<judge())
                        tf2.setText("\tbest score:"+judge());
                    tf.setText("score:"+judge());
                    System.out.println(judge());
                    timer1.cancel();
                }
            };
            timer1.schedule(task1,10000);
        });

        Button b2=new Button("next");  //下一关 可垂直移动
        b2.setFont(new Font(30));
        b2.setLayoutY(520);
        b2.setLayoutX(450);
        b2.setOnAction(e->{
            tf.setText("score:0");
            System.out.print("第二关:");
            newcre();
            Timer timer2=new Timer();
            TimerTask task2=new TimerTask(){
                public void run(){
                    if(Integer.parseInt(tf2.getText(11,tf2.getLength()))<judge())
                        tf2.setText("\tbest score:"+judge());
                    tf.setText("score:"+judge());
                    System.out.println(judge());
                    timer2.cancel();
                }
            };
            this.setOnMouseMoved(e2->{
                pad.setX(e2.getX()-pad.getWidth()/2);
                pad.setY(e2.getY()-pad.getHeight()/2);
            });
            timer2.schedule(task2,10000);
        });

        Button b3=new Button("short");
        b3.setFont(new Font(30));
        b3.setLayoutY(520);
        b3.setLayoutX(200);
        b3.setOnAction(e->{
            tf.setText("score:0");
            System.out.print("挡板变短后:");
            pad.setWidth(pad.getWidth()-50);
            newcre();
            Timer timer3=new Timer();
            TimerTask task3=new TimerTask(){
                public void run(){
                    if(Integer.parseInt(tf2.getText(11,tf2.getLength()))<judge())
                        tf2.setText("\tbest score:"+judge());
                    tf.setText("score:"+judge());
                    System.out.println(judge());
                    timer3.cancel();
                }
            };
            timer3.schedule(task3,10000);
        });
        this.getChildren().addAll(b,b2,b3,tf,tf2);
    }
}

3.任务类定义

class MyTask extends Service<Void> {
    private int startNumber = -1;
    public void setStartNumber(int startNumber) {
        this.startNumber = startNumber;
    }
    @Override
    protected Task<Void> createTask() {
        return new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                if (startNumber == -1)startNumber = 0;
                for (int i = startNumber; i <=10000 ; i++) {
                    updateMessage(Integer.toString(i));
                    Thread.sleep(1000);
                }
                return null;
            }
        };
    }
}

4.主函数定义

public class BallFall extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        BallPane();
        Scene s=new Scene(p,600,600);
        primaryStage.setScene(s);
        primaryStage.show();
    }
}

四、结果分析

初始界面:                          

  第一关10秒后:

第一关重试:reset      

第二关:next

挡板变短后:short  

控制台打印出每次得分:

未到10秒显示当前得分为0:

超过10秒仍可以继续游戏,但不会再计分:

五、实验总结

        为实现多个小球共同运动,需要将它们设置为多线程任务,互不干扰,实验中设置重力加速度和摩擦力模拟小球运动,每隔一定时间更新小球坐标即可实现运动的动画效果,若不同小球间发生碰撞则令它们速度反向,若出现小球重叠的现象需要重新设置它们的坐标。小球碰到挡板会发生反弹,但会出现挡板移动过快时从上往下穿过小球并接住,需要重新修改小球坐标。

        对于窗体界面,设计三个按钮以及一个键盘事件实现不同的事件驱动,难点在于计时器的实现,我设计了一个mytask类来实现,每过一秒更新显示。另外,当时间到达10秒时打印出当前界面中小球,我利用的是TimerTask来实现延迟事件,但是存在一定问题,进行下一个TimerTask时必须先取消上一个,即必须完成了第一关才能点击下一个按键,点击按键后会重置计时器以及新建TimerTask。

        对于游戏规则,可以计时(记录达到指定小球数的时间),也可以计分(记录在指定时间内达到的小球数),我选择的是计分,但是我只能将每次得分打印出来,不能显示在图形界面中,我试过将得分设置为标签文字,但是报错“Not on FX application thread;”,于是我改用textfild可以实现,显示当前得分以及最佳得分,还将每一次的得分都打印出来,程序结束时也能得知本次游戏过程。另外,为了避免代码冗余,可以将一些重复的代码改为方法,需要时调用即可。

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,我可以为你介绍一下如何使用 Java 和 Eclipse 开发弹球游戏开始界面。首先,你需要创建一个新的 Java 项目,并创建一个新的 JFrame 窗口。 下面是一个简单的示例代码,可以创建一个包含开始按钮和退出按钮的 JFrame 窗口: ```java import javax.swing.*; import java.awt.event.*; public class StartMenu extends JFrame { private JButton startButton; private JButton exitButton; public StartMenu() { setTitle("弹球游戏"); setSize(400, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); startButton = new JButton("开始游戏"); startButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // 在这里添加开始游戏的代码 } }); exitButton = new JButton("退出游戏"); exitButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // 在这里添加退出游戏的代码 } }); JPanel panel = new JPanel(); panel.add(startButton); panel.add(exitButton); add(panel); setVisible(true); } public static void main(String[] args) { StartMenu menu = new StartMenu(); } } ``` 这里我们使用了 Java 的 GUI 框架 Swing 来创建界面。在这个窗口中,我们创建了两个按钮(开始游戏和退出游戏),并在按钮上添加了 ActionListener 监听器,当用户点击按钮时会触发相应的事件。 你可以在开始按钮的 ActionListener 中添加打开游戏界面的代码,退出按钮的 ActionListener 中添加关闭游戏的代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值