java多线程游戏设计-弹球游戏(包含全部代码)

目录

  1. 需求分析 3
    1.1目的 3
    1.2背景 3
  2. 系统设计 3
    2.1 项目结构 3
    2.2游戏初始化界面设计 4
    2.3游戏开始界面设计 5
    2.4游戏结束界面设计 5
  3. 系统实现 6
    3.1 游戏初始化(UI类) 6
    3.1.1 框架结构 6
    3.1.2 构造函数 6
    3.1.3 窗体初始化 7
    3.1.4 数据初始化 7
    3.1.5 Paint方法 8
    3.1.6 监听 8
    3.2 球线程(Thread Ball类) 9
    3.2.1 框架结构 9
    3.2.2 run方法 10
    3.3 球板线程(Thread Paddle类) 11
    3.3.1框架结构 11
    3.3.2 run方法 11
    3.4 绘图线程(Thread Controle类) 12
    3.4.1框架结构 12
    3.4.2 run方法 12
    3.5主函数(Main 类) 13
  4. 系统测试 13
    4.1游戏状态切换测试 13
    4.1.1未开始-开始 13
    4.1.2开始-结束 14
    4.1.3结束-重新开始 14
    4.2游戏运行bug改进 15
    4.2.1 小球和球板碰撞bug 15
    4.2.2 游戏界面闪烁 15
  5. 小结 16

1.需求分析

1.1目的
利用多线程的思想,编写一个游戏。
1.2背景
1).游戏名称:弹球游戏。
2).开发环境:IDEA。
3).游戏规则:小球实现从不同角度,不同坐标落下,与挡板接触后反弹并加分,落在地面上则游戏结束失败。

2.系统设计

2.1 项目结构
在这里插入图片描述

在这里插入图片描述

由五个类构成:
在这里插入图片描述
1).三个线程类分别控制球的移动,球板的移动和画面的重绘。
2).UI类控制界面的初始化和绘制。
3).Main类用来创建线程和启动线程。

2.2游戏初始化界面设计
在这里插入图片描述
1).游戏界面显示:点击开始游戏。
2).点击以后,游戏状态改变,显示开始游戏界面。
2.3游戏开始界面设计
在这里插入图片描述
1).画出两个对象:球和球板。
2).显示得分。

2.4游戏结束界面设计
在这里插入图片描述
1).给出提示信息:游戏结束以及得分情况。
2).给出提示信息:按下空格可以重新开始。

3.系统实现

3.1 游戏初始化(UI类)

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;

public class Ui extends JFrame implements KeyListener ,MouseListener{
    
    
    //判断球还活着
    static boolean blIsOver;
    //分数
    static int intSore;
    //定义游戏状态 0:游戏未开始/1:游戏开始/2:暂停/3:结束
    static int state=0;
    //双缓存解决界面闪烁
    Image offScreenImage=null;
    //构造函数
    public Ui() {
    
    
        initFrame();
        initData();
        addKeyListener(this);
        addMouseListener(this);
        setVisible(true);
    }
    public void initFrame(){
    
    
        setTitle("弹球游戏");
        setBackground(Color.WHITE);
        setSize(900,600);
        //setLocationRelativeTo(null);
        setLocation(300,50);
        setResizable(false);
        setFocusable(true);                               //获取键盘焦点,将键盘聚集在游戏界面上
        //setVisible(true);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }//初始化窗体
    public void initData(){
    
    
        //球的初始化坐标
        ThreadBall.PositionX=(int)(Math.random()*100)+300;//300-400
        ThreadBall.PositionY=(int)(Math.random()*100)+100;//100-200
        //初始化角度
        ThreadBall.intDu= (int)(Math.random()*3)+1;//1-4
        //初始化球板
        ThreadPaddles.PositionA=0;
        ThreadPaddles.PositionB=450;
        //初始化
        intSore=0;
        System.out.println("state="+state);
    }//初始化数据
    public void paint(Graphics gImage) {
    
    
        if(offScreenImage==null){
    
    
            offScreenImage=createImage(900,600);
        }
        //获取画笔对象
        Graphics g=offScreenImage.getGraphics();//画笔对象
        g.fillRect(0,0,900,600); //填充一个宽900,高600的区域
        if(state==0){
    
    //游戏未开始
            g.clearRect(0, 0, 900, 600);//0,0:是相对于容器的坐标
            //g.drawImage(GameUtils.bgImg,0,0,null);
            g.setFont(new Font("仿宋",Font.BOLD,40));
            g.drawString("点击开始游戏",300,300);
            repaint();
        }
        if(state==1){
    
    
            g.clearRect(0, 0, 900, 600);//0,0:是相对于容器的坐标
            //球板:fillRect用笔刷g(红色)填充一个矩形(从左上边界开始填充)
            g.setColor(Color.red);
            g.fillRect(ThreadPaddles.PositionA,ThreadPaddles.PositionB,
                    ThreadPaddles.RecWidth,ThreadPaddles.RecHeight);
            //球:设置画笔颜色为绿色,fillOval用笔刷g(绿色)填充一个圆(从左上边界开始填充)
            g.setColor(Color.green);
            g.fillOval(ThreadBall.PositionX,ThreadBall.PositionY, ThreadBall.BallWidth,
                    ThreadBall.BallHeight);
            g.setFont(new Font("宋体", ALLBITS, 50));
            g.setColor(Color.BLUE);
            g.drawString(new String("分数:" + String.valueOf(intSore)), 550, 150);
            repaint();

        }


        if(state==3){
    
    //游戏结束
            //if(blIsOver) {//绘制游戏结束的界面
                    g.clearRect(0, 0, 900, 600);//0,0:是相对于容器的坐标
                    g.setFont(new Font("宋体", ALLBITS, 50));
                    g.setColor(Color.RED);
                    g.drawString(new String("游戏结束!你的得分:" +
                            String.valueOf(intSore)) , 249, 250);
                    g.setColor(Color.BLUE);
                    g.drawString(new String("按下空格重新开始"), 250, 350);
                    repaint();

            //}
        }

        //将绘制好的图片一次性呈现出来
        gImage.drawImage(offScreenImage,0,0,null);

    }//画笔
    //监听
        @Override//鼠标监听
        public void mouseClicked (MouseEvent e){
    
    
        if (e.getButton() == 1 && state == 0) {
    
    //按下鼠标或者状态为没开始
            state = 1;//更改游戏状态并且重绘
            repaint();
            System.out.println("state=" +state);
        }
    }
        @Override//键盘监听
        public void keyPressed (KeyEvent e){
    
    
        char b = e.getKeyChar();
        int a = e.getKeyCode();
        if (b == 'd' || b == 'D' || a == 39) {
    
    
            ThreadPaddles.PositionA+=10;
        } else if (b == 'a'||b == 'A' || a == 37) {
    
    
            ThreadPaddles.PositionA-=10;
        } else if(a==32&&state==3){
    
    
                state=0;
                initData();
                repaint();
        }
                /*if(a==32){//暂停
                    switch (state){
                        case 1:
                            state=2;
                            System.out.println("s"+state);
                            break;
                        case 2:
                            state=1;
                            System.out.println("s"+state);
                            break;
                        default:
                    }
                }*/
    }
        @Override
        public void mousePressed (MouseEvent e){
    
    
    }
        @Override
        public void mouseReleased (MouseEvent e){
    
    
    }
        @Override
        public void mouseEntered (MouseEvent e){
    
    
    }
        @Override
        public void mouseExited (MouseEvent e){
    
    
    }
        @Override
        public void keyTyped (KeyEvent e){
    
    

    }
        @Override
        public void keyReleased (KeyEvent e){
    
    
    }


    }

3.1.1 框架结构
1).属性:
(1)游戏状态=0: 0:游戏未开始/1:游戏开始/2:暂停/3:结束。
(2)分数=0。
(3)缓存画布:解决页面闪烁。
2).方法:
(1)无参构造。
(1)初始化窗体。
(2)初始化数据。
(3)重写鼠标监听和键盘监听。
(4)画笔。

3.1.2 构造函数
构造方法中调用了两个初始化方法和添加了两个监听。

3.1.3 窗体初始化
1).Init Frame用于初始化窗体。
2).标题:弹球游戏;
背景色:白色;
窗体大小:900*600;
窗体位置:(300,50);
窗体大小不可变;
键盘焦点汇聚在界面上;
设置退出方式:按下右上角×就可以结束进程。

3.1.4 数据初始化
1).initData用于初始化数据。
2).初始化球的坐标:用random函数获得一个随机数:x(300-400),y(100-200)。
3).初始化球的角度:用random函数获得一个随机数:intDu(1-4)。
4).初始化球板的坐标:(0,450)。
5).初始化分数:0。

3.1.5 Paint方法
1).state=0:定义了游戏未开始的界面。
2).在界面上画一个字符串“点击开始游戏”。
3).添加一个鼠标监听,当按下鼠标后,游戏状态=“1”。

1).state=1:定义了游戏开始的界面。
2).在界面上画一个绿色球和一个红色的球板。
3).在界面上画一个字符串“分数:”。

1).tate=3:定义了游戏结束的界面。
2).在界面上画两个字符串“游戏结束1 分数:”和提示信息“按下空格后重新开始”。
3).添加一个键盘监听,当按下空格,state=0,游戏重新开始。

3.1.6 监听
1).鼠标监听:当游戏状态state=0游戏未开始时,检测到鼠标按下,则将游戏状态state改为1,并且重绘。
2).键盘监听:
(1)监听键盘A/D/a/d/←/→按键,用来控制球板的坐标。
(2)当游戏状态state=3游戏结束时,监听空格键,检测到空格键按下时,将状态state改为0,初始化数据,并且重绘界面。

3.2 球线程(Thread Ball类)


public class ThreadBall extends Thread{
    
    
    //球的坐标及大小
    static int BallWidth=25,BallHeight=25,PositionX,PositionY;
    static int intDu;
    static boolean blUpOrDown;//游戏状态开始?
    public ThreadBall(){
    
    }
    public void run(){
    
    
        while(true){
    
    
            if(Ui.state==1) {
    
    
                if (PositionY >= 600) {
    
    
                    blUpOrDown=true;
                    Ui.state = 3;//游戏状态为结束
                    Ui.blIsOver = true;//游戏结束
                    System.out.println("state="+Ui.state);
                } else if (PositionY < 600) {
    
    
                    Ui.state = 1;//游戏状态为开始
                    blUpOrDown = false;
                }
                //向上碰撞 上墙面的情况
                if (PositionY <= 0) {
    
    
                    if (intDu == 3) {
    
     intDu = 1; }
                    else if (intDu == 4) {
    
     intDu = 2;}
                }
                //左侧墙面碰撞
                if (PositionX <= 0) {
    
    
                    if (intDu == 2) {
    
     intDu = 1; }
                    else if (intDu == 4) {
    
     intDu = 3; }
                }
                //右侧墙面碰撞
                if (PositionX >= 900) {
    
    
                    if (intDu == 1) {
    
     intDu = 2; }
                    else if (intDu == 3) {
    
     intDu = 2; }
                }
            }
            if(!blUpOrDown){
    
    
                switch (intDu){
    
    
                    //1为右下方行进,2为左下方,3 为右上方,4为左上方
                    case 1:
                        PositionY+=6;
                        PositionX+=6;
                        break;
                    case 2:
                        PositionY+=6;
                       PositionX-=6;
                        break;
                    case 3:
                        PositionY-=6;
                        PositionX+=6;
                        break;
                    case 4:
                        PositionY-=6;
                        PositionX-=6;
                        break;
                }
            }

            try {
    
    
                sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    public int getBallWidth() {
    
    
        return BallWidth;
    }

    public void setBallWidth(int ballWidth) {
    
    
        BallWidth = ballWidth;
    }

    public int getBallHeight() {
    
    
        return BallHeight;
    }

    public void setBallHeight(int ballHeight) {
    
    
        BallHeight = ballHeight;
    }

    public int getPositionX() {
    
    
        return PositionX;
    }

    public void setPositionX(int positionX) {
    
    
        PositionX = positionX;
    }

    public int getPositionY() {
    
    
        return PositionY;
    }

    public void setPositionY(int positionY) {
    
    
        PositionY = positionY;
    }

    public int getIntDu() {
    
    
        return intDu;
    }

    public void setIntDu(int intDu) {
    
    
        this.intDu = intDu;
    }
}

3.2.1 框架结构
1).属性:球的坐标,宽高和角度。
2).方法:
(1)重写Run方法。
(2)set和get方法。

3.2.2 run方法
1).首先判断游戏是否结束:
当状态为1时,并且小球坐标<600,继续,否则,将游戏状态改为3结束。
2).当小球存活时:判断与界面碰撞的情况,由入射角度将角度设置为相对应的初始角度。
3).用一个Switch语句控制小球运动的坐标。
4).sleep中的休眠时间控制小球移动速度,数值越小,小球移动速度越快。

3.3 球板线程(Thread Paddle类)


import javax.swing.*;

public class ThreadPaddles  extends Thread{
    
    
    //球板
    static int PositionA=0,PositionB=450,RecWidth=200,RecHeight=20,Width=900;
    public ThreadPaddles(){
    
    }

    public void run(){
    
    

        while(true){
    
    
            if(Ui.state==1&&ThreadBall.PositionX>PositionA&&ThreadBall.PositionX+25<=PositionA+RecWidth){
    
    
                if(ThreadBall.PositionY>=PositionB&&ThreadBall.PositionY<=PositionB+RecHeight){
    
    
                    //ThreadBall.blUpOrDown=true;
                    Ui.intSore+=10;
                    System.out.println(Ui.intSore);

                    switch (ThreadBall.intDu){
    
    
                        case 1:
                            ThreadBall.intDu=3;
                            break;
                        case 2:
                            ThreadBall.intDu=4;
                            break;
                    }
                    JOptionPane.showMessageDialog(null,ThreadBall.PositionX,"2", JOptionPane.INFORMATION_MESSAGE);
                }
            }

            try {
    
    
                sleep(100);//参数改小时,分数会错误,因为执行的次数多。
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    public int getPositionA() {
    
    
        return PositionA;
    }
    public void setPositionA(int positionA) {
    
    
        PositionA = positionA;
    }
    public int getPositionB() {
    
    
        return PositionB;
    }
    public void setPositionB(int positionB) {
    
    
        PositionB = positionB;
    }
    public int getRecWidth() {
    
    
        return RecWidth;
    }
    public int getRecHeight() {
    
    
        return RecHeight;
    }
}

3.3.1框架结构
1).属性:球板的坐标,宽高。
2).方法:
(1)重写Run方法。
(2)set和get方法。

3.3.2 run方法
1).当游戏状态为1开始状态,并且小球横坐标>球板的横坐标,并且小球的横坐标+宽度<球板的横坐标+板子宽度。并且小球的纵坐标<球板纵坐标,并且小球纵坐标>球板纵坐标+球板高度时,小球与球板碰撞。分数+10。
2).碰撞后重新设置角度。
3).sleep中的休眠时间控制球板移动速度,数值越小,控制球板移动速度越快。

3.4 绘图线程(Thread Controle类)


public class ThreadControle extends Thread {
    
    
    Ui ui=new Ui();
    public void run(){
    
    
            if (Ui.state==1) {
    
    //还活着,就重画
                while (true) {
    
    
                    ui.repaint();
                }
            }
        try {
    
    
            Thread.sleep(100);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

3.4.1框架结构
1).实例化UI类
2).重写run方法。

3.4.2 run方法
1).当游戏状态为开始时,不断重绘界面。

3.5主函数(Main 类)

public class Main {
    
    
    public static void main(String[] args) {
    
    
        //创建三个线程
        ThreadControle threadControle = new ThreadControle();
        ThreadBall threadBall = new ThreadBall();
        ThreadPaddles threadPaddles = new ThreadPaddles();

        //启动他们
        threadPaddles.start();
        threadControle.start();
        threadBall.start();

        try {
    
    
            threadPaddles.join();//防止线程堵塞,相当于用了wait()
            threadControle.join();
            threadBall.join();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

1).创建三个线程类对象
2).启动三个线程对象。
3).在try/catch中调用join,防止线程堵塞。

4.系统测试

4.1游戏状态切换测试
4.1.1未开始-开始
state=0——state=1
在这里插入图片描述
在这里插入图片描述

4.1.2开始-结束
state=1——state=3
在这里插入图片描述
在这里插入图片描述

4.1.3结束-重新开始
state=3——state=0
在这里插入图片描述

4.2游戏运行bug改进
4.2.1 小球和球板碰撞bug
1.Bug描述:
休眠时间太长会造成:明明小球和球板已经已经碰上了,小球却穿过了球板并没有反弹。
休眠时间太短会造成:线程堵塞,分数出现错误。
在这里插入图片描述

2.解决方案:
1.选取合适的休眠值,并且将小球和球板重绘的休眠时间改为一样。
2.经过测试,选取100ms是比较合适的。
在这里插入图片描述

4.2.2 游戏界面闪烁
1.问题描述:
小球和球板实现视觉上的移动,原理就是在间隔很小的时间,将这些对象绘制在窗体上。由于是不断绘制的,会造成视觉上的闪烁。
2.解决方案:
定义一个缓冲画布,将这些对象一次性绘制在缓冲画布上,再一次性绘制出来,就能解决频闪问题。

5.小结

1)建立了三个线程类,小球线程,球板线程,重绘线程,理解了多线程思想和怎么创建多线程。
(a)建立线程类有两种方法,一种是继承Thread,另一种是继承Implement接口,我采用的是第一种方法:继承Thread类。
(b)继承Thread后要重写run方法。我在run方法中写入了小球的坐标改变,球板的坐标改变,重绘。
(c)在Main函数中实例化三个线程类,线程实例化后的对象调用run方法启动线程。
(d)线程停止,采用用标志位的方法。
(e)线程休眠:运用在了控制球板和球的移动,重绘依次休眠一会,休眠时间越短,重绘速度越快,移动速度越快。
2)UI类实现了窗体的构建,利用构造函数初始化窗体,将初始化的语句封装成函数写入构造函数。 UI类继承了监听接口,所以要重写监听函数。继承了JFrame类,重写paint方法,获取画笔,将需要的图形对象画在缓冲区,再一次性呈现出来。
3)监听:实现监听接口,重写监听函数。
4)可以优化的地方:
(a)界面不够美观。
(b)游戏规则比较简单,可以添加一个时间线程,可以随着时间的增加,掉下来的小球数目增加,或者障碍物增加。
可以添加背景音乐线程,随着游戏的开

猜你喜欢

转载自blog.csdn.net/qq_46026355/article/details/125808717
今日推荐