Java design de jogo multi-threaded - jogo de pinball (incluindo todos os códigos)

Índice

  1. Análise de Requisitos 3
    1.1 Objetivo 3
    1.2 Histórico 3
  2. Design do sistema 3
    2.1 Estrutura do projeto 3
    2.2 Inicialização do jogo Design da interface 4
    2.3 Design da interface de início do jogo 5
    2.4 Design da interface do final do jogo 5
  3. Implementação do sistema 6
    3.1 Inicialização do jogo (classe UI) 6
    3.1.1 Estrutura do framework 6
    3.1.2 Construtor 6
    3.1.3 Inicialização da janela 7
    3.1.4 Inicialização dos dados 7
    3.1.5 Método Paint 8
    3.1.6 Listening 8
    3.2 Ball thread (Thread Classe Ball) 9
    3.2.1 Estrutura do Framework 9
    3.2.2 Método run 10
    3.3 Thread Ball Board (classe Thread Paddle) 11
    3.3.1 Estrutura do Framework 11
    3.3.2 Método run 11
    3.4 Linha de desenho (classe Thread Controle) 12
    3.4 .1 Estrutura do quadro 12
    3.4.2 método run 12
    3.5 função principal (classe principal) 13
  4. Teste do sistema 13
    4.1 Teste de troca de estado do jogo 13
    4.1.1 Não iniciado - iniciado 13
    4.1.2 Iniciado - concluído 14
    4.1.3 Finalizado - reiniciado 14
    4.2 Melhoria do bug de execução do jogo 15
    4.2.1 Bug de colisão de bola e placa 15
    4.2 .2 O interface do jogo pisca 15
  5. Resumo 16

1. Análise de demanda

1.1 Objetivo
Usar a ideia de multi-threading para escrever um jogo.
1.2 Histórico
1) Nome do jogo: jogo de pinball.
2) Ambiente de desenvolvimento: IDEA.
3) Regras do jogo: A bola cai de diferentes ângulos e coordenadas. Após tocar no defletor, ela salta e ganha pontos. Se cair no chão, o jogo termina e falha.

2. Projeto do sistema

2.1 Estrutura do projeto
insira a descrição da imagem aqui

insira a descrição da imagem aqui

Consiste em cinco classes:
insira a descrição da imagem aqui
1) Três classes de thread controlam respectivamente o movimento da bola, o movimento do tabuleiro da bola e o redesenho da tela.
2) A classe UI controla a inicialização e o desenho da interface.
3) A classe Main é usada para criar threads e iniciar threads.

2.2 Design da interface de inicialização do jogo
insira a descrição da imagem aqui
1) Exibição da interface do jogo: clique para iniciar o jogo.
2) Após clicar, o status do jogo muda e a interface de início do jogo é exibida.
2.3 Design da interface de início do jogo
insira a descrição da imagem aqui
1) Desenhe dois objetos: a bola e a raquete.
2) Exibir pontuação.

2.4 Design da interface do final do jogo
insira a descrição da imagem aqui
1) Forneça informações imediatas: final do jogo e placar.
2) Dê uma mensagem de alerta: pressione o espaço para começar de novo.

3. Implementação do sistema

3.1 Inicialização do jogo (classe 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 Estrutura do quadro
1) Atributos:
(1) Estado do jogo = 0: 0: O jogo não começou / 1: O jogo começou / 2: Pausado / 3: Finalizado.
(2) Pontuação = 0.
(3) Cache Canvas: Resolva a oscilação da página.
2) Método:
(1) Construir sem parâmetros.
(1) Inicialize o formulário.
(2) Inicializar dados.
(3) Reescrever o monitoramento do mouse e o monitoramento do teclado.
(4) Escovas.

3.1.2 Construtor
No construtor, dois métodos de inicialização são chamados e dois monitores são adicionados.

3.1.3 Inicialização do formulário
1).Init Frame é usado para inicializar o formulário.
2). Título: jogo de pinball;
cor de fundo: branco;
tamanho da janela: 900*600;
posição da janela: (300,50);
tamanho da janela não pode ser alterado;
foco do teclado converge na interface;
definir método de saída: pressione Pressione o X no canto superior direito para finalizar o processo.

3.1.4 Inicialização de dados
1) .initData é usado para inicializar dados.
2) Inicialize as coordenadas da bola: use a função random para obter um número aleatório: x (300-400), y (100-200).
3) Inicialize o ângulo da bola: use a função random para obter um número aleatório: intDu(1-4).
4) Inicialize as coordenadas do bastão: (0,450).
5). Pontuação inicial: 0.

3.1.5 Método Paint
1).state=0: define a interface onde o jogo não foi iniciado.
2) Desenhe uma string de caracteres "clique para iniciar o jogo" na interface.
3) Adicione um monitor de mouse, quando o mouse é pressionado, o estado do jogo = "1".

1).state=1: define a interface onde o jogo começa.
2) Desenhe uma bola verde e um quadro vermelho na interface.
3) Desenhe uma string de caracteres "Pontuação:" na interface.

1).tate=3: Define a interface de finalização do jogo.
2) Desenhe duas cadeias de caracteres na interface: "Game over 1 score:" e uma mensagem de prompt "Reiniciar após pressionar o espaço".
3) Adicione um monitor de teclado, quando o espaço for pressionado, estado = 0, o jogo será reiniciado.

3.1.6 Monitoramento
1) Monitoramento do mouse: Quando o estado do jogo é o estado = 0 e o jogo não é iniciado, ele detecta que o mouse foi pressionado, então muda o estado do jogo para 1 e redesenha.
2) Monitoramento do teclado:
(1) Monitore as teclas A/D/a/d/←/→ do teclado para controlar as coordenadas da bola.
(2) Quando o estado do jogo for state=3 e o jogo terminar, monitore a barra de espaço e, quando detectar que a barra de espaço foi pressionada, altere o estado do estado para 0, inicialize os dados e redesenhe a interface.

3.2 Rosca esférica (classe 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 Estrutura do quadro
1) Atributos: coordenadas, largura, altura e ângulo da bola.
2) Método:
(1) Reescreva o método Run.
(2) definir e obter métodos.

3.2.2 Método de execução
1) Primeiro, julgue se o jogo acabou:
quando o estado for 1 e as coordenadas da bola <600, continue, caso contrário, mude o estado do jogo para 3 e termine.
2) Quando a bola sobreviver: julgue a colisão com a interface e defina o ângulo para o ângulo inicial correspondente de acordo com o ângulo incidente.
3) Use uma instrução Switch para controlar as coordenadas do movimento da bola.
4) O tempo de espera no sono controla a velocidade de movimento da bola. Quanto menor o valor, mais rápida a velocidade de movimento da bola.

3.3 Linha Paddle (Classe Paddle Thread)


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 Estrutura do quadro
1) Atributos: coordenadas, largura e altura da bola.
2) Método:
(1) Reescreva o método Run.
(2) definir e obter métodos.

3.3.2 Método de execução
1). Quando o estado do jogo é 1 estado inicial, e a abscissa da bola > a abscissa do tabuleiro da bola, e a abscissa da bola + largura < a abscissa do tabuleiro da bola + a largura do o quadro. E quando a ordenada da bola < a ordenada da placa esférica e a ordenada da bola > a ordenada da placa esférica + a altura da placa esférica, a bola colide com a placa. Pontuação +10.
2) Redefina o ângulo após a colisão.
3) O tempo de espera em .sleep controla a velocidade de movimento da raquete, quanto menor o valor, mais rápido a raquete se move.

3.4 Linha de desenho (classe 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 Estrutura do framework
1) Instancie a classe UI
2) Reescreva o método run.

3.4.2 método de execução
1) Quando o estado do jogo é iniciado, a interface é constantemente redesenhada.

3.5 Função principal (classe 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) Crie três objetos de classe de thread
2) Inicie três objetos de thread.
3) Chame join em try/catch para prevenir bloqueio de thread.

4. Teste do sistema

4.1 Teste de troca de estado do jogo
4.1.1 Não iniciado -
estado inicial = 0 - estado = 1
insira a descrição da imagem aqui
insira a descrição da imagem aqui

4.1.2 Estado inicial-final
=1——estado=3
insira a descrição da imagem aqui
insira a descrição da imagem aqui

4.1.3 estado de reinício final
=3——estado=0
insira a descrição da imagem aqui

4.2 Melhoria do bug de execução do jogo
4.2.1 Bug de colisão entre bola e tabuleiro
1. Descrição do bug:
Se o tempo de espera for muito longo, isso causará: a bola e o tabuleiro já colidiram, mas a bola passa pelo tabuleiro e não rebate .
Tempo de espera muito curto causará: bloqueio de encadeamento, erro de pontuação.
insira a descrição da imagem aqui

2. Soluções:
1. Selecione um valor de suspensão apropriado e altere o tempo de suspensão do redesenho da bola e do tabuleiro para que sejam iguais.
2. Após o teste, é mais apropriado escolher 100ms.
insira a descrição da imagem aqui

4.2.2 A interface do jogo pisca
1. Descrição do problema:
A bola e a raquete podem se mover visualmente, e o princípio é desenhar esses objetos na janela em um pequeno intervalo de tempo. Por ser constantemente desenhado, causará cintilação visual.
2. Solução:
Defina uma tela de buffer, desenhe esses objetos na tela de buffer de uma vez e, em seguida, desenhe-os de uma só vez para resolver o problema estroboscópico.

5. Resumo

1) Estabeleceu três classes de thread, thread de bola pequena, thread de placa de bola e thread de redesenho, e entendeu a ideia de multithreading e como criar multithreading.
(a) Existem duas maneiras de criar uma classe de thread, uma é herdar Thread e a outra é herdar a interface Implement.Eu uso o primeiro método: herdar a classe Thread.
(b) Após herdar Thread, o método run deve ser reescrito. Escrevi no método run que as coordenadas da bola mudam, as coordenadas do tabuleiro da bola mudam e redesenho.
(c) Três classes de thread são instanciadas na função Main, e o objeto instanciado chama o método run para iniciar o thread.
(d) O thread para, usando o método de usar o bit sinalizador.
(e) Thread sleep: É usado para controlar o movimento do bastão e da bola, e o redesenho irá dormir por um tempo por sua vez. Quanto menor o tempo de sono, mais rápida a velocidade de redesenho e mais rápida a velocidade de movimento.
2) A classe UI realiza a construção do formulário, usa o construtor para inicializar o formulário e encapsula a instrução inicializada em uma função e a grava no construtor. A classe UI herda a interface de escuta, então a função de escuta precisa ser reescrita. Herde a classe JFrame, reescreva o método paint, obtenha o pincel, desenhe o objeto gráfico necessário no buffer e apresente-o imediatamente.
3) Monitor: implemente a interface do monitor e reescreva a função do monitor.
4) Coisas que podem ser otimizadas:
(a) A interface não é bonita o suficiente.
(b) As regras do jogo são relativamente simples, podendo ser adicionado um fio de tempo: à medida que o tempo aumenta, aumenta o número de bolas que caem ou o número de obstáculos.
Um segmento de música de fundo pode ser adicionado, à medida que o jogo avança

Acho que você gosta

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