Java multi-threaded game design - pinball game (including all codes)

Table of contents

  1. Requirements Analysis 3
    1.1 Purpose 3
    1.2 Background 3
  2. System Design 3
    2.1 Project Structure 3
    2.2 Game Initialization Interface Design 4
    2.3 Game Start Interface Design 5
    2.4 Game End Interface Design 5
  3. System implementation 6
    3.1 Game initialization (UI class) 6
    3.1.1 Framework structure 6
    3.1.2 Constructor 6
    3.1.3 Window initialization 7
    3.1.4 Data initialization 7
    3.1.5 Paint method 8
    3.1.6 Listening 8
    3.2 Ball thread (Thread Ball class) 9
    3.2.1 Framework structure 9
    3.2.2 run method 10
    3.3 Ball board thread (Thread Paddle class) 11
    3.3.1 Framework structure 11
    3.3.2 run method 11
    3.4 Drawing thread (Thread Controle class) 12
    3.4 .1 Frame structure 12
    3.4.2 run method 12
    3.5 main function (Main class) 13
  4. System Test 13
    4.1 Game State Switching Test 13
    4.1.1 Not Started - Started 13
    4.1.2 Started - Finished 14
    4.1.3 Ended - Restarted 14
    4.2 Game Running Bug Improvement 15
    4.2.1 Ball and Board Collision Bug 15
    4.2 .2 The game interface flickers 15
  5. Summary 16

1. Demand Analysis

1.1 Purpose
Use the idea of ​​multi-threading to write a game.
1.2 Background
1). Game name: pinball game.
2). Development environment: IDEA.
3). Rules of the game: The ball falls from different angles and coordinates. After touching the baffle, it bounces and gains points. If it falls on the ground, the game ends and fails.

2. System design

2.1 Project structure
insert image description here

insert image description here

It consists of five classes:
insert image description here
1). Three thread classes respectively control the movement of the ball, the movement of the ball board and the redrawing of the screen.
2). The UI class controls the initialization and drawing of the interface.
3). The Main class is used to create threads and start threads.

2.2 Game initialization interface design
insert image description here
1). Game interface display: click to start the game.
2). After clicking, the game status changes and the game start interface is displayed.
2.3 Game start interface design
insert image description here
1). Draw two objects: the ball and the paddle.
2). Display score.

2.4 Game end interface design
insert image description here
1). Give prompt information: game end and score.
2). Give a prompt message: press the space to start again.

3. System implementation

3.1 Game initialization (UI class)

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 Frame structure
1). Attributes:
(1) Game state = 0: 0: The game has not started / 1: The game has started / 2: Paused / 3: Ended.
(2) Score = 0.
(3) Cache Canvas: Solve page flickering.
2). Method:
(1) Construct without parameters.
(1) Initialize the form.
(2) Initialize data.
(3) Rewrite mouse monitoring and keyboard monitoring.
(4) Brushes.

3.1.2 Constructor
In the constructor, two initialization methods are called and two monitors are added.

3.1.3 Form initialization
1).Init Frame is used to initialize the form.
2).Title: pinball game;
background color: white;
window size: 900*600;
window position: (300,50);
window size is not changeable;
keyboard focus converges on the interface;
set exit method: press Press the X in the upper right corner to end the process.

3.1.4 Data initialization
1) .initData is used to initialize data.
2). Initialize the coordinates of the ball: use the random function to obtain a random number: x (300-400), y (100-200).
3). Initialize the angle of the ball: use the random function to obtain a random number: intDu(1-4).
4). Initialize the coordinates of the bat: (0,450).
5). Initialize score: 0.

3.1.5 Paint method
1).state=0: defines the interface where the game has not started.
2). Draw a character string "click to start the game" on the interface.
3). Add a mouse monitor, when the mouse is pressed, the game state = "1".

1).state=1: defines the interface where the game starts.
2). Draw a green ball and a red ball board on the interface.
3). Draw a character string "Score:" on the interface.

1).tate=3: Defines the game ending interface.
2). Draw two character strings on the interface: "Game over 1 score:" and a prompt message "Restart after pressing the space".
3). Add a keyboard monitor, when the space is pressed, state=0, the game restarts.

3.1.6 Monitoring
1). Mouse monitoring: When the game state is state=0 and the game is not started, it detects that the mouse is pressed, then changes the game state to 1, and redraws.
2). Keyboard monitoring:
(1) Monitor the keyboard A/D/a/d/←/→ keys to control the coordinates of the ball.
(2) When the game state is state=3 and the game is over, monitor the space bar, and when it detects that the space bar is pressed, change the state state to 0, initialize the data, and redraw the interface.

3.2 Ball thread (Thread Ball class)


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 Frame structure
1). Attributes: coordinates, width, height and angle of the ball.
2). Method:
(1) Rewrite the Run method.
(2) set and get methods.

3.2.2 Run method
1). First judge whether the game is over:
when the state is 1, and the ball coordinates <600, continue, otherwise, change the game state to 3 and end.
2). When the ball survives: judge the collision with the interface, and set the angle to the corresponding initial angle according to the incident angle.
3). Use a Switch statement to control the coordinates of the ball movement.
4). The sleep time in sleep controls the moving speed of the ball. The smaller the value, the faster the moving speed of the ball.

3.3 Paddle thread (Thread Paddle class)


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 Frame structure
1). Attributes: coordinates, width and height of the ball.
2). Method:
(1) Rewrite the Run method.
(2) set and get methods.

3.3.2 Run method
1). When the game state is 1 start state, and the abscissa of the ball > the abscissa of the ball board, and the abscissa of the ball + width < the abscissa of the ball board + the width of the board. And when the ordinate of the ball < the ordinate of the ball plate, and the ordinate of the ball > the ordinate of the ball plate + the height of the ball plate, the ball collides with the plate. Score +10.
2). Reset the angle after collision.
3) The sleep time in .sleep controls the moving speed of the paddle, the smaller the value, the faster the paddle moves.

3.4 Drawing thread (Thread Controle class)


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 Framework structure
1). Instantiate the UI class
2). Rewrite the run method.

3.4.2 run method
1). When the game state is started, the interface is constantly redrawn.

3.5 Main function (Main class)

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). Create three thread class objects
2). Start three thread objects.
3). Call join in try/catch to prevent thread blockage.

4. System test

4.1 Game state switching test
4.1.1 Not started - start
state=0 - state=1
insert image description here
insert image description here

4.1.2 Start-end
state=1——state=3
insert image description here
insert image description here

4.1.3 end-restart
state=3——state=0
insert image description here

4.2 Game running bug improvement
4.2.1 Ball and board collision bug
1. Bug description:
If the sleep time is too long, it will cause: the ball and the board have already collided, but the ball passes through the board and does not rebound .
Too short sleep time will cause: thread blockage, score error.
insert image description here

2. Solutions:
1. Select an appropriate sleep value, and change the sleep time of redrawing the ball and the board to be the same.
2. After testing, it is more appropriate to choose 100ms.
insert image description here

4.2.2 The game interface flickers
1. Description of the problem:
The ball and the paddle can move visually, and the principle is to draw these objects on the window at a small interval of time. Because it is constantly drawn, it will cause visual flickering.
2. Solution:
Define a buffer canvas, draw these objects on the buffer canvas at one time, and then draw them at one time to solve the stroboscopic problem.

5. Summary

1) Established three thread classes, small ball thread, ball board thread, and redrawing thread, and understood the idea of ​​multithreading and how to create multithreading.
(a) There are two ways to create a thread class, one is to inherit Thread, and the other is to inherit the Implement interface. I use the first method: inherit the Thread class.
(b) After inheriting Thread, the run method must be rewritten. I wrote in the run method that the coordinates of the ball change, the coordinates of the ball board change, and redraw.
(c) Three thread classes are instantiated in the Main function, and the instantiated object calls the run method to start the thread.
(d) The thread stops, using the method of using the flag bit.
(e) Thread sleep: It is used to control the movement of the bat and the ball, and the redrawing will sleep for a while in turn. The shorter the sleep time, the faster the redrawing speed and the faster the moving speed.
2) The UI class realizes the construction of the form, uses the constructor to initialize the form, and encapsulates the initialized statement into a function and writes it into the constructor. The UI class inherits the listening interface, so the listening function needs to be rewritten. Inherit the JFrame class, rewrite the paint method, get the brush, draw the required graphics object in the buffer, and then present it at once.
3) Monitor: implement the monitor interface and rewrite the monitor function.
4) Things that can be optimized:
(a) The interface is not beautiful enough.
(b) The rules of the game are relatively simple, and a time thread can be added. As the time increases, the number of falling balls increases, or the number of obstacles increases.
A background music thread can be added, as the game progresses

Guess you like

Origin blog.csdn.net/qq_46026355/article/details/125808717