Diseño de juegos de subprocesos múltiples de Java: juego de pinball (incluidos todos los códigos)

Tabla de contenido

  1. Análisis de requisitos 3
    1.1 Propósito 3
    1.2 Antecedentes 3
  2. Diseño del sistema 3
    2.1 Estructura del proyecto 3
    2.2 Diseño de la interfaz de inicialización del juego 4
    2.3 Diseño de la interfaz de inicio del juego 5
    2.4 Diseño de la interfaz del final del juego 5
  3. Implementación del sistema 6
    3.1 Inicialización del juego (clase UI) 6
    3.1.1 Estructura del marco 6
    3.1.2 Constructor 6
    3.1.3 Inicialización de la ventana 7
    3.1.4 Inicialización de datos 7
    3.1.5 Método de pintura 8
    3.1.6 Escuchar 8
    3.2 Ball thread (Thread Ball (clase Ball) 9
    3.2.1 Estructura del marco 9
    3.2.2 método de ejecución 10
    3.3 Hilo del tablero de bolas (clase Thread Paddle) 11
    3.3.1 Estructura del marco 11
    3.3.2 método de ejecución 11
    3.4 Hilo de dibujo (clase Thread Controle) 12
    3.4 .1 Estructura del marco 12
    3.4.2 método de ejecución 12
    3.5 función principal (clase principal) 13
  4. Prueba del sistema 13
    4.1 Prueba de cambio de estado del juego 13
    4.1.1 No iniciado - Iniciado 13
    4.1.2 Iniciado - Finalizado 14
    4.1.3 Finalizado - Reiniciado 14
    4.2 Mejora del error de ejecución del juego 15
    4.2.1 Error de colisión de bola y tablero
    15 la interfaz del juego parpadea 15
  5. Resumen 16

1. Análisis de la demanda

1.1 Propósito
Usar la idea de subprocesos múltiples para escribir un juego.
1.2 Antecedentes
1) Nombre del juego: juego de pinball.
2) Entorno de desarrollo: IDEA.
3) Reglas del juego: La bola cae desde diferentes ángulos y coordenadas, después de tocar el deflector rebota y gana puntos, si cae al suelo el juego termina y falla.

2. Diseño del sistema

2.1 Estructura del proyecto
inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

Consta de cinco clases:
inserte la descripción de la imagen aquí
1) Tres clases de subprocesos controlan respectivamente el movimiento de la bola, el movimiento del tablero de bolas y el redibujado de la pantalla.
2) La clase UI controla la inicialización y el dibujo de la interfaz.
3) La clase principal se usa para crear hilos e iniciar hilos.

2.2 Diseño de la interfaz de inicialización del juego
inserte la descripción de la imagen aquí
1) Visualización de la interfaz del juego: haga clic para iniciar el juego.
2) Después de hacer clic, el estado del juego cambia y se muestra la interfaz de inicio del juego.
2.3 Diseño de la interfaz de inicio del juego
inserte la descripción de la imagen aquí
1) Dibuja dos objetos: la pelota y la paleta.
2) Muestra la puntuación.

2.4 Diseño de la interfaz del final del juego
inserte la descripción de la imagen aquí
1) Proporcione información rápida: final del juego y puntuación.
2) Dé un mensaje rápido: presione el espacio para comenzar de nuevo.

3. Implementación del sistema

3.1 Inicialización del juego (clase 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 Estructura del marco
1) Atributos:
(1) Estado del juego = 0: 0: El juego no ha comenzado / 1: El juego ha comenzado / 2: En pausa / 3: Finalizado.
(2) Puntuación = 0.
(3) Lienzo de caché: Resuelva el parpadeo de la página.
2) Método:
(1) Construcción sin parámetros.
(1) Inicialice el formulario.
(2) Inicializar datos.
(3) Reescriba el monitoreo del mouse y el monitoreo del teclado.
(4) Cepillos.

3.1.2 Constructor
En el constructor, se llaman dos métodos de inicialización y se agregan dos monitores.

3.1.3 Inicialización del formulario
1).Init Frame se utiliza para inicializar el formulario.
2).Título: juego de pinball;
color de fondo: blanco;
tamaño de la ventana: 900*600;
posición de la ventana: (300,50);
el tamaño de la ventana no se puede cambiar;
el enfoque del teclado converge en la interfaz;
establezca el método de salida: presione Presione la X en la esquina superior derecha para finalizar el proceso.

3.1.4 Inicialización de datos
1) .initData se utiliza para inicializar datos.
2) Inicialice las coordenadas de la pelota: use la función aleatoria para obtener un número aleatorio: x (300-400), y (100-200).
3) Inicialice el ángulo de la bola: utilice la función aleatoria para obtener un número aleatorio: intDu(1-4).
4) Inicializar las coordenadas del murciélago: (0,450).
5) Puntuación de inicialización: 0.

3.1.5 Método Paint
1).state=0: define la interfaz donde el juego no ha comenzado.
2) Dibuje una cadena de caracteres "haga clic para iniciar el juego" en la interfaz.
3) Agregue un monitor de mouse, cuando se presiona el mouse, el estado del juego = "1".

1).state=1: define la interfaz donde comienza el juego.
2) Dibuja una bola verde y una bola roja en la interfaz.
3) Dibuje una cadena de caracteres "Puntuación:" en la interfaz.

1).tate=3: Define la interfaz de finalización del juego.
2) Dibuje dos cadenas de caracteres en la interfaz: "Juego sobre 1 puntaje:" y un mensaje de aviso "Reiniciar después de presionar el espacio".
3) Agregue un monitor de teclado, cuando se presiona el espacio, estado = 0, el juego se reinicia.

3.1.6 Monitoreo
1) Monitoreo del mouse: cuando el estado del juego es estado = 0 y el juego no se inicia, detecta que se presiona el mouse, luego cambia el estado del juego a 1 y vuelve a dibujar.
2) Monitoreo del teclado:
(1) Monitoree las teclas del teclado A/D/a/d/←/→ para controlar las coordenadas de la pelota.
(2) Cuando el estado del juego es estado = 3 y el juego ha terminado, controle la barra espaciadora y, cuando detecte que se presiona la barra espaciadora, cambie el estado a 0, inicialice los datos y vuelva a dibujar la interfaz.

3.2 Rosca de bola (clase Bola de rosca)


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 Estructura del marco
1) Atributos: coordenadas, ancho, alto y ángulo de la pelota.
2) Método:
(1) Vuelva a escribir el método Run.
(2) establecer y obtener métodos.

3.2.2 Método de ejecución
1) Primero juzgue si el juego ha terminado:
cuando el estado es 1 y las coordenadas de la pelota <600, continúe, de lo contrario, cambie el estado del juego a 3 y finalice.
2) Cuando la pelota sobrevive: juzgue la colisión con la interfaz y ajuste el ángulo al ángulo inicial correspondiente según el ángulo incidente.
3) Use una instrucción Switch para controlar las coordenadas del movimiento de la pelota.
4) El tiempo de sueño en el sueño controla la velocidad de movimiento de la pelota. Cuanto menor sea el valor, más rápida será la velocidad de movimiento de la pelota.

3.3 Hilo de paleta (clase de paleta de hilo)


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 Estructura del marco
1) Atributos: coordenadas, ancho y alto de la pelota.
2) Método:
(1) Vuelva a escribir el método Run.
(2) establecer y obtener métodos.

3.3.2 Método de ejecución
1). Cuando el estado del juego es 1 estado de inicio, y la abscisa de la pelota > la abscisa del tablero de la pelota, y la abscisa de la pelota + ancho < la abscisa del tablero de la pelota + el ancho de el tablero. Y cuando la ordenada de la bola < la ordenada de la placa de la bola, y la ordenada de la bola > la ordenada de la placa de la bola + la altura de la placa de la bola, la bola choca con la placa. Puntuación +10.
2) Restablecer el ángulo después de la colisión.
3) El tiempo de sueño en .sleep controla la velocidad de movimiento de la paleta, cuanto menor sea el valor, más rápido se mueve la paleta.

3.4 Dibujar hilo (clase 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 Estructura del marco
1) Instanciar la clase UI
2) Reescribir el método de ejecución.

3.4.2 método de ejecución
1) Cuando se inicia el estado del juego, la interfaz se vuelve a dibujar constantemente.

3.5 Función principal (clase principal)

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) Cree tres objetos de clase de subprocesos
2) Inicie tres objetos de subprocesos.
3) Llame a unirse en try/catch para evitar el bloqueo de hilos.

4. Prueba del sistema

4.1 Prueba de cambio de estado del juego
4.1.1 No iniciado -
estado de inicio = 0 - estado = 1
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

4.1.2 Estado inicial-final
=1—estado=3
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

4.1.3 fin-reinicio
estado=3——estado=0
inserte la descripción de la imagen aquí

4.2 Mejora del error de ejecución del juego
4.2.1 Error de colisión entre la bola y el tablero
1. Descripción del error:
si el tiempo de inactividad es demasiado largo, provocará: la pelota y el tablero ya chocaron, pero la pelota atraviesa el tablero y no rebota .
Un tiempo de suspensión demasiado corto causará: bloqueo de subprocesos, error de puntuación.
inserte la descripción de la imagen aquí

2. Soluciones:
1. Seleccione un valor de sueño apropiado y cambie el tiempo de sueño de volver a dibujar la bola y el tablero para que sea el mismo.
2. Después de la prueba, es más apropiado elegir 100 ms.
inserte la descripción de la imagen aquí

4.2.2 La interfaz del juego parpadea
1. Descripción del problema:
la pelota y la paleta pueden moverse visualmente, y el principio es dibujar estos objetos en la ventana en un pequeño intervalo de tiempo. Debido a que se dibuja constantemente, causará un parpadeo visual.
2. Solución:
defina un lienzo de búfer, dibuje estos objetos en el lienzo de búfer a la vez y luego dibújelos a la vez para resolver el problema estroboscópico.

5. Resumen

1) Establecí tres clases de subprocesos, subproceso de bola pequeña, subproceso de tablero de bolas e subproceso de redibujado, y comprendí la idea de subprocesos múltiples y cómo crear subprocesos múltiples.
(a) Hay dos formas de crear una clase de subproceso, una es heredar Thread y la otra es heredar la interfaz Implement. Yo uso el primer método: heredar la clase Thread.
(b) Después de heredar Thread, el método de ejecución debe reescribirse. Escribí en el método de ejecución que las coordenadas de la pelota cambian, las coordenadas del tablero de la pelota cambian y se vuelven a dibujar.
(c) Se crean instancias de tres clases de subprocesos en la función principal y el objeto instanciado llama al método de ejecución para iniciar el subproceso.
(d) El hilo se detiene, usando el método de usar el bit de bandera.
(e) Suspensión de hilo: se utiliza para controlar el movimiento del bate y la pelota, y el redibujado dormirá por un tiempo a su vez. Cuanto más corto sea el tiempo de sueño, más rápida será la velocidad de redibujado y más rápida la velocidad de movimiento.
2) La clase UI realiza la construcción del formulario, usa el constructor para inicializar el formulario y encapsula la declaración inicializada en una función y la escribe en el constructor. La clase UI hereda la interfaz de escucha, por lo que la función de escucha debe reescribirse. Herede la clase JFrame, reescriba el método de pintura, obtenga el pincel, dibuje el objeto gráfico requerido en el búfer y luego preséntelo de inmediato.
3) Monitor: implemente la interfaz del monitor y reescriba la función del monitor.
4) Cosas que se pueden optimizar:
(a) La interfaz no es lo suficientemente hermosa.
(b) Las reglas del juego son relativamente simples y se puede agregar un hilo de tiempo. A medida que aumenta el tiempo, aumenta el número de bolas que caen o aumenta el número de obstáculos.
Se puede agregar un hilo de música de fondo a medida que avanza el juego.

Supongo que te gusta

Origin blog.csdn.net/qq_46026355/article/details/125808717
Recomendado
Clasificación