Moving circle on screen with thread and jFrame's paint method

0

Studying the drawing method of JFrame in the case paint and wanted to make a circle move on the screen using threads , but the circle does not move right. Maybe I would need a movimenta method to define where I want to put a particular object on the screen.

public class PackMan extends JFrame implements KeyListener, Runnable{
    JLabel label = null;
    int contador = 0;
    int autura = -1;
    int direita = -2;
    int esquerda = -3;
    int baixo = -4;


    public PackMan(){
        super("PackMan");
        label = new JLabel();
        setLayout(new BorderLayout());
        add(label, BorderLayout.CENTER);
        //setLocation(100,100);
        setSize(600, 600);


        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
        addKeyListener(this);
    }

    public void paint(Graphics g){
        if (g!=null) {
            g.setColor(Color.RED);
            g.setColor(Color.YELLOW);
            g.fillArc(contador, contador, 70, 70, 135, -270);
            g.setPaintMode();

            //repaint();
        }

    }
    public void movimenta(int parametro){

    }
    @Override
    public void keyTyped(KeyEvent e) {
        // TODO Auto-generated method stub

    }

    @Override
    public void keyPressed(KeyEvent evento) {
        Thread t = new Thread(this);
        if(evento.getKeyCode() == KeyEvent.VK_UP){
            repaint();
            movimenta(-1);
            t.start();

        }
        if(evento.getKeyCode() == KeyEvent.VK_LEFT){
            t.start();
            repaint();
        }
        if(evento.getKeyCode() == KeyEvent.VK_RIGHT){
            t.start();
            repaint();
        }
        if(evento.getKeyCode() == KeyEvent.VK_DOWN){
            t.start();
            repaint();
        }

    }

    @Override
    public void keyReleased(KeyEvent e) {
        // TODO Auto-generated method stub

    }

    public static void main(String[] args) {
        new PackMan();
    }

    @Override
    public void run() {
        while(true)
        {
            while (contador < 600) {
                contador += 1;
                pause();
            }
            contador = 600;
            while (contador > 0) {
                contador -= 1;
                pause();
            }
        }

    }
    public void pause() {
        try {
            Thread.sleep(30);
        } 
        catch (Exception e) {}
    }

}
    
asked by anonymous 28.04.2014 / 05:07

1 answer

1

As I already mentioned in the comment, this other answer will be very useful to you, mainly because there I describe a number of needs (including mainly the issue of painting with double buffering ).

To make the move you want, the key capture must change the direction of the character's movement. In your current code, your main game loop ( game loop ) completely ignores any code you can build, as it continually increments and then decrements the contador variable, which you use as the your character at the time of painting.

A very simple way to solve the problem is to have two variables, one to store the value of X and another to save the value of Y, and to use them at the time of painting. You will need a third variable to store the direction of movement, so that within your loop you must continuously increment or decrement just one of the variables X and Y according to that direction . Something like:

while(true) {
    if(direcao == 0) // para cima
        Y = Y - 1
    else if(direcao == 1) // para baixo
        Y = Y + 1
    else if(direcao == 2) // para direita
        X = X + 1
    else if(direcao == 3) // para esquerda
        X = X - 1
    Thread.sleep(100);
}

In the case of PackMan (as you called it, although the character's name in the original game is PacMan) this approach may be sufficient, but as I mention in the other response quoted there are more details needed (mainly because threads are not generally used separated for different moving objects - and I imagine that eventually you will include the ghosts in the game, right?).

I changed my original response code to make an example with your PackMan. This code uses vectors to indicate character movement, and the game scene does not use a JFrame, but a JComponent (because of buffering already implemented) if you try to move the character by forcing calls from repaint in a JFrame will see that the screen is not correctly painted). Although using vectors, the movement is quite square (up, down, left and right, as in the original PacMan game). But using vectors allows you to make smoother transitions in motion (as in the example of the original response ball). If your character does not just move in cross, it is easy to change the code to instead of simply changing the speed, adding the new speed to the current speed to make it move on diagonals, for example.

Here's the example:

Game Class

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.KeyStroke;

/**
 * Classe principal, de implementação do jogo.
 * Exemplo para ilustração no SOPT.
 * @author Luiz C. Vieira
 */
@SuppressWarnings("serial")
public class Game extends JFrame implements Runnable {

    /** Thread de execução da applet. */
    private Thread m_oMainThread;

    /** Indicador de que o jogo está em execução. */
    private boolean m_bRunning;

    /** Taxa de quadros por segundo (framerate) ideal do jogo. */
    private static float FPS = 1000f / 60f; 

    private boolean m_bUpPressed = false;
    private boolean m_bDownPressed = false;
    private boolean m_bLeftPressed = false;
    private boolean m_bRightPressed = false;

    /**
     * Construtor do jogo.
     */
    public Game() {
        // Define tamanho da janela de jogo
        setSize(800, 600);

        // Adiciona a cena ao jogo
        setLayout(new BorderLayout());
        add(GameScene.instance);

        // O jogo não se inicia automaticamente
        m_bRunning = false;

        // Captura do pressionamento e liberação de teclas
        getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "onUpPressed");
        getRootPane().getActionMap().put("onUpPressed", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                m_bUpPressed = true;
            }
        });        

        getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "onUpReleased");
        getRootPane().getActionMap().put("onUpReleased", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                m_bUpPressed = false;
            }
        });

        getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "onDownPressed");
        getRootPane().getActionMap().put("onDownPressed", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                m_bDownPressed = true;
            }
        });        

        getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "onDownReleased");
        getRootPane().getActionMap().put("onDownReleased", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                m_bDownPressed = false;
            }
        });        

        getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "onLeftPressed");
        getRootPane().getActionMap().put("onLeftPressed", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                m_bLeftPressed = true;
            }
        });        

        getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "onLeftReleased");
        getRootPane().getActionMap().put("onLeftReleased", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                m_bLeftPressed = false;
            }
        });

        getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "onRightPressed");
        getRootPane().getActionMap().put("onRightPressed", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                m_bRightPressed = true;
            }
        });        

        getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "onRightReleased");
        getRootPane().getActionMap().put("onRightReleased", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                m_bRightPressed = false;
            }
        });

        // Captura o fechamento da janela para encerrar o jogo
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                stop();
            }
        });        
    }

    /**
     * Método de início do jogo.
     */
    public void start() {
        m_bRunning = true;
        m_oMainThread = new Thread(this);
        m_oMainThread.start();
        setVisible(true);
    }

    /**
     * Método de interrupção do jogo.
     */
    public void stop() {
        m_bRunning = false;
    }

    /**
     * Método getter do atributo de execução do jogo.
     * @return Valor lógico indicando se o jogo está (true) ou não (false) em execução.
     */
    public boolean isRunning() {
        return m_bRunning;
    }

    /**
     * Método de execução da thread da applet do jogo.
     * Basicamente implementa o game loop, atualizando na cena
     * todos os componentes a cada quadro (cada iteração).
     */
    @Override
    public void run() {

        long lPrevious = System.nanoTime() / 1000000;
        long lLag = 0;

        // Gameloop do jogo
        while(m_bRunning) {

            // Contabilização de tempo
            long lCurrent = System.nanoTime() / 1000000;
            long lElapsed = lCurrent - lPrevious;
            lPrevious = lCurrent;
            lLag += lElapsed;

            // Processamento de entrada (teclado, mouse, etc)!
            // Muda a direção da velocidade conforme a tecla pressionada
            Vector2D vVelocity = GameScene.instance.getPackMan().getVelocity();
            if(m_bUpPressed)
                vVelocity = new Vector2D(0, -8);
            else if(m_bDownPressed)
                vVelocity = new Vector2D(0, 8);
            else if(m_bLeftPressed)
                vVelocity = new Vector2D(-8, 0);
            else if(m_bRightPressed)
                vVelocity = new Vector2D(8, 0);
            GameScene.instance.getPackMan().setVelocity(vVelocity);             

            // Atualização do mundo!
            // Faz chamadas de update enquanto o número desejado de quadros
            // por segundo não tiver sido atingido
            while(lLag >= Game.FPS)
            {
                GameScene.instance.update();
                lLag -= Game.FPS;
            }

            // Renderização do jogo!
            GameScene.instance.repaint();
        }

        System.exit(0);
    }

    public static void main(String[] args) {
        Game oGame = new Game();
        oGame.start();
        while(oGame.isRunning())
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                oGame.stop();
                e.printStackTrace();
            }
    }
}

GameScene Class

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;

import javax.swing.JComponent;

/**
 * Classe de implementação de cenas de jogo.
 * @author Luiz C. Vieira
 */
@SuppressWarnings("serial")
public class GameScene extends JComponent {

    /** Referência estática dos limites do mundo. */
    public static Rectangle BOUNDS = new Rectangle(0, 0, 800, 600);

    /** Referência ao objeto PackMan. */
    private PackMan m_oPackMan;

    /** Instância singleton da cena. */
    public static GameScene instance = new GameScene();

    /**
     * Construtor protegido.
     */
    protected GameScene() {
        // Cria o personagem do jogo
        m_oPackMan = new PackMan();
    }

    /**
     * Getter do packman.
     * @return Objeto PackMan na cena.
     */
    public PackMan getPackMan() {
        return m_oPackMan;
    }

    /**
     * Método de atualização da cena. É chamado a cada quadro do jogo.
     */
    public void update() {
        // Atualiza todos os objetos em cena
        m_oPackMan.update();
    }

    /**
     * Método de pintura da cena.
     * @param g Intância com o objeto Graphics da applet para pintura. 
     */
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        // Pinta o fundo da cena (simplesmente todo preto nesse exemplo)
        g.setColor(Color.black);
        g.fillRect(0, 0, GameScene.BOUNDS.width, GameScene.BOUNDS.height);

        // Faz a repintura de cada um dos objetos em cena
        m_oPackMan.paint(g);
    }
}

PackMan Class

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;

/**
 * Classe do PackMan (não seria PacMan? :) ).
 * Ela não implementa thread! Esse controle fica por parte das classes
 * Game e GameScene que executam o método update.
 * @author Luiz C. Vieira
 */
public class PackMan {

    /** Vetor com a posição atual do personagem. */
    private Vector2D m_vPosition;

    /** Vetor velocidade (direção de movimentação + velocidade do movimento) do personagem. */
    private Vector2D m_vVelocity;

    /** Indicação de modo de depuração, para desenho do vetor velocidade. */
    private static boolean DEBUG = true;

    /**
     * Construtor padrão. Inicializa o personagem.
     */
    public PackMan() {
        m_vPosition = new Vector2D(0, 0);
        m_vVelocity = new Vector2D(8, 0);
    }

    /**
     * Getter da posição atual.
     * @return Vetor com a posição atual.
     */
    public Vector2D getPosition() {
        return m_vPosition;
    }

    /**
     * Setter da posição atual.
     * @param vPosition Vetor com a nova posição.
     */
    public void setPosition(Vector2D vPosition) {
        m_vPosition = vPosition;
    }

    /**
     * Getter da velocidade.
     * @return Vetor com a velocidade atual.
     */
    public Vector2D getVelocity() {
        return m_vVelocity;
    }

    /**
     * Setter da velocidade.
     * @param vVelocity Vetor com a nova velocidade.
     */
    public void setVelocity(Vector2D vVelocity) {
        m_vVelocity = vVelocity;
    }

    /**
     * Método de desenho do personagem. É chamado pela cena sempre que for necessário
     * repintar.
     * @param g Instância do objeto Graphics para pintura do personagem.
     */
    public void paint(Graphics g) {
        // Simplesmente desenha no Graphics o PackMan.

        g.setColor(Color.RED);
        g.setColor(Color.YELLOW);
        g.fillArc((int) m_vPosition.x, (int) m_vPosition.y, 70, 70, 135, -270);
        g.setPaintMode();

        // Se a depuração está ligada, desenha o vetor velocidade
        if(PackMan.DEBUG) {
            g.setColor(Color.red);
            Vector2D vVel = m_vVelocity.scalarMult(10); // Escala x10 para facilitar a visualização
            Vector2D vAux = m_vPosition.plus(vVel);
            g.drawLine((int) m_vPosition.x, (int) m_vPosition.y, (int) vAux.x, (int) vAux.y);
        }
    }

    /**
     * Método de atualização do objeto. É chamado a cada quadro do jogo.
     */
    public void update() {
        // Atualiza a posição de acordo com a velocidade (direção e valor)
        m_vPosition = m_vPosition.plus(m_vVelocity);

        // Trata colisões com o mundo
        handleCollisions();
    }

    /**
     * Método de tratamento das colisões. "Teletransporta" o personagem nos eixos x e y quando
     * ele "some" em um dos cantos da tela.
     */
    protected void handleCollisions() {
        Rectangle oBounds = GameScene.BOUNDS;
        int iWidth = 70; // A mesma largura que você usa no desenho
        int iHeight = 70; // Idem para a altura

        if(m_vPosition.x <= oBounds.x - iWidth)
            m_vPosition.x = oBounds.width + iWidth;
        else if(m_vPosition.x >= oBounds.width + iWidth)
            m_vPosition.x = -iWidth;

        if(m_vPosition.y <= oBounds.y - iHeight)
            m_vPosition.y = oBounds.height + iHeight;
        else if(m_vPosition.y >= oBounds.height + iHeight)
            m_vPosition.y = -iHeight;
   }
}

The class Vector2D is the same as referenced in my original response. Below a screen of the working example (use the arrow keys to change the direction of constant movement):

    
29.04.2014 / 15:14