Mover JLabel com o click do mouse

Eae galera blz?

Então, estou desenvolvendo um joguinho e eu gostaria de saber o seguinte:
Tenho uma JLabel (por exemplo) que seria a imagem do meu personagem, e então o usuário clica em algum ponto na tela e a JLabel vai se movimentando em linha reta (ou diagonal) até o ponto onde cliquei.
Estou usando obviamente Java Desktop, JFrame.

Valeu!

Isso é pura trigonometria.

Primeiro você precisa saber:

  1. A coordenada de origem A(x, y);
  2. A coordenada de destino B(x, y);
  3. O ângulo formado pela reta AB;
  4. A distância que você quer deslocar sua figura.

Feito isso você precisa:

  1. Deslocar a coordenada A no eixo x a distância desejada;
  2. Calcular a nova coordenada de rotacionando A tantos graus quanto o ângulo de inclinação da reta AB.

Abaixo há dois métodos que lhe serão úteis.

/**
 * Obtém o ângulo formado pela reta entre os pontos <tt>a</tt> e <tt>b</tt>
 */
public static double angle(Point2D a, Point2D b) {
    double deltaX = b.getX() - a.getX();
    double deltaY = b.getY() - a.getY();
    return Math.atan2(deltaY, deltaX) * 180 / Math.PI;
}

/**
 * Move um ponto <tt>p</tt> a distância informada na direção específicada pelo ângulo
 */
public static Point2D move(Point2D p, double distance, double angle) {
    // coordenadas da origem
    double x0 = p.getX();
    double y0 = p.getY();
    int anguloArredondado = (int) (angle + 0.5);
    switch (anguloArredondado) {
        case 0: // ângulo reto para direita, fácil
            return new Point2D.Double(x0 + distance, y0);
        case 180: // ângulo reto para esquerda, fácil
            return new Point2D.Double(x0 - distance, y0);
        case 90: // ângulo reto para cima, fácil
            return new Point2D.Double(x0, y0 + distance);
        case 270: // ângulo reto para baixo, fácil
            return new Point2D.Double(x0, y0 - distance);
        default: // não é um ângulo reto, então tem que usar trigonometria
            // coordenadas da origem com translação no eixo x
            double x1 = x0 + distance;
            double y1 = y0;
            // coordenadas após a rotação
            double radians = Math.toRadians(angle);
            double cosA = Math.cos(radians);
            double sinA = Math.sin(radians);
            double x2 = x0 + ((x1 - x0) * cosA - (y1 - y0) * sinA);
            double y2 = y0 + ((x1 - x0) * sinA + (y1 - y0) * cosA);
            // devolver coordenadas rotacionadas
            return new Point2D.Double(x2, y2);
    }
}
3 curtidas

Hummm…
Eu entendi o que você fez, porém não sei se apliquei da forma certa.
Dê uma olhada como eu fiz:

package telas;

import java.awt.EventQueue;
import java.awt.geom.Point2D;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JLabel;
import java.awt.Color;

public class TelaTeste extends JFrame {

	private JPanel contentPane;
	private JLabel label;

	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					TelaTeste frame = new TelaTeste();
					frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}
	public double angle(Point2D a, Point2D b) {
	    double deltaX = b.getX() - a.getX();
	    double deltaY = b.getY() - a.getY();
	    return Math.atan2(deltaY, deltaX) * 180 / Math.PI;
	}
	public Point2D move(Point2D p, double distance, double angle) {
	    // coordenadas da origem
	    double x0 = p.getX();
	    double y0 = p.getY();
	    int anguloArredondado = (int) (angle + 0.5);
	    switch (anguloArredondado) {
	        case 0: // ângulo reto para direita, fácil
	            return new Point2D.Double(x0 + distance, y0);
	        case 180: // ângulo reto para esquerda, fácil
	            return new Point2D.Double(x0 - distance, y0);
	        case 90: // ângulo reto para cima, fácil
	            return new Point2D.Double(x0, y0 + distance);
	        case 270: // ângulo reto para baixo, fácil
	            return new Point2D.Double(x0, y0 - distance);
	        default: // não é um ângulo reto, então tem que usar trigonometria
	            // coordenadas da origem com translação no eixo x
	            double x1 = x0 + distance;
	            double y1 = y0;
	            // coordenadas após a rotação
	            double radians = Math.toRadians(angle);
	            double cosA = Math.cos(radians);
	            double sinA = Math.sin(radians);
	            double x2 = x0 + ((x1 - x0) * cosA - (y1 - y0) * sinA);
	            double y2 = y0 + ((x1 - x0) * sinA + (y1 - y0) * cosA);
	            // devolver coordenadas rotacionadas
	            return new Point2D.Double(x2, y2);
	    }
	}
	public TelaTeste() {
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 450, 300);
		contentPane = new JPanel();
		contentPane.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				Point2D pontoOrigem = label.getBounds().getLocation();
				Point2D a = move(pontoOrigem,  e.getX(),  angle(pontoOrigem, e.getPoint()));
				label.setBounds((int)a.getX(), (int)a.getY(), label.getWidth(), label.getHeight());
			}
		});
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(null);
		
		label = new JLabel("");
		label.setBackground(Color.RED);
		label.setOpaque(true);
		label.setBounds(0, 0, 20, 20);
		contentPane.add(label);
		
	}
}

Corrigi o que eu fiz de errado!

Estava nessa linha:

				Point2D pontoOrigem = label.getBounds().getLocation();
				Point2D a = move(pontoOrigem,  pontoOrigem.distance(e.getPoint()),  angle(pontoOrigem, e.getPoint()));
				label.setBounds((int)a.getX(), (int)a.getY(), label.getWidth(), label.getHeight());
1 curtida

O segundo parâmetro do move é a distância que deseja mover, por exemplo 5 pixels, mas você está informando o valor de x.

1 curtida

Porém eu preciso que a minha JLabel vá se movendo lentamente até o ponto, como se fosse um personagem andando, para isso eu iria utilizar Threads, tens alguma idéia de como eu posso fazer isso usando a maneira como você me explicou?

Valeu!

Sim, dispara uma thread que vai deslocando o label 1 ou 2 pixel até chegar na coordenada de destino.

1 curtida

Fiz desse jeito:

		contentPane.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				Thread animacaoAndando = new Thread() {
					public void run() {
						Point2D pontoOrigem = label.getBounds().getLocation();
						Point2D a = move(pontoOrigem,  2,  angle(pontoOrigem, e.getPoint()));
						while(label.getBounds().getLocation() != a) {
							a = move(label.getBounds().getLocation(),  2,  angle(label.getBounds().getLocation(), e.getPoint()));
							label.setBounds((int)a.getX(), (int)a.getY(), label.getWidth(), label.getHeight());
							try {
								sleep(10);
							} catch (InterruptedException e1) {}
						}
					}
				};
				animacaoAndando.start();
			}
		});

Porém quando a label fica bem próxima ao destino, ela se treme toda!

É por causa do seu while ele nunca será false pois você está comparando as coordenadas com !=, não pode, tem que usar o método equals

Outra coisa, olha só quantas vezes você chama o método getBounds().getLocation()
Use variáveis locais, o acesso à elas é mais rápido.

1 curtida

Pronto, fiz o que você recomendou, porém agora a label ela treme de vez em quando.

		contentPane.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				Thread animacaoAndando = new Thread() {
					public void run() {
						Point2D pontoLabel = label.getLocation();
						Point pontoDestino = e.getPoint();
						Point2D a = move(pontoLabel,  2,  angle(pontoLabel, pontoDestino));
						while(!pontoLabel.equals(pontoDestino)) {
							a = move(pontoLabel,  2,  angle(pontoLabel, pontoDestino));
							label.setLocation((int)a.getX(), (int)a.getY());
							try {
								sleep(10);
							} catch (InterruptedException e1) {}
							pontoLabel = label.getLocation();
						}
					}
				};
				animacaoAndando.start();
			}
		});
1 curtida

Havia 2 problemas no seu código:

  • O Point2D utiliza coordenadas do tipo double, então quando você compara pontoLabel.equals(pontoDestino) pode haver diferença devido à precisão dos valores, a solução para isso é comparar as coordenadas utilizando valores arredondados para x e y ou usar Point ao invés de Point2D;

  • Você sempre estava movendo seu ponto em 2 pixels então pode acontecer uma situação onde a distância entre o ponto de origem e o ponto de destino seja de somente 1 píxel, então se você mover 2 pixels, ele vai se afastar novamente um pixel do destino e na próxima iteração vai mover de volta 2 pixels ficando eternamente afastado 1 pixel de distância, causando uma “tremedeira” pois ele será movido de um lado pro outro sem nunca chegar no destino desejado. Neste caso tem que verificar se a distância que deseja deslocar a origem não é maior do que a distância restante para chegar ao destino.

Dei uma refatorada no seu código, veja:

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;

public class TelaTeste extends JFrame {

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    TelaTeste frame = new TelaTeste();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private JPanel contentPane;
    private JLabel label;
    private boolean movendo;

    public TelaTeste() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 450, 300);
        contentPane = new JPanel();
        contentPane.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                trataClique(e.getPoint());
            }
        });
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(null);

        label = new JLabel("");
        label.setBackground(Color.RED);
        label.setOpaque(true);
        label.setBounds(0, 0, 20, 20);
        contentPane.add(label);

    }

    private double angle(Point2D a, Point2D b) {
        double deltaX = b.getX() - a.getX();
        double deltaY = b.getY() - a.getY();
        return Math.atan2(deltaY, deltaX) * 180 / Math.PI;
    }

    private double distance(Point p1, Point p2) {
        double deltaX = p2.getX() - p1.getX();
        double deltaY = p2.getY() - p1.getY();
        return Math.sqrt((deltaX * deltaX) + (deltaY * deltaY));
    }

    private Point intPoint(double x, double y) {
        return new Point((int) (x + 0.5), (int) (y + 0.5));
    }
    
    private Point move(Point p, double distance, double angle) {
        // coordenadas da origem
        double x0 = p.getX();
        double y0 = p.getY();
        int anguloArredondado = (int) (angle + 0.5);
        switch (anguloArredondado) {
            case 0: // ângulo reto para direita, fácil
                return intPoint(x0 + distance, y0);
            case 180: // ângulo reto para esquerda, fácil
                return intPoint(x0 - distance, y0);
            case 90: // ângulo reto para cima, fácil
                return intPoint(x0, y0 + distance);
            case 270: // ângulo reto para baixo, fácil
                return intPoint(x0, y0 - distance);
            default: // não é um ângulo reto, então tem que usar trigonometria
                // coordenadas da origem com translação no eixo x
                double x1 = x0 + distance;
                double y1 = y0;
                // coordenadas após a rotação
                double radians = Math.toRadians(angle);
                double cosA = Math.cos(radians);
                double sinA = Math.sin(radians);
                double x2 = x0 + ((x1 - x0) * cosA - (y1 - y0) * sinA);
                double y2 = y0 + ((x1 - x0) * sinA + (y1 - y0) * cosA);
                // devolver coordenadas rotacionadas
                return intPoint(x2, y2);
        }
    }

    private void trataClique(Point destino) {
        if (movendo) {
            return; // se já está realizando a movimentação, ignora esse clique
        }
        movendo = true;
        Thread animacaoAndando = new Thread() {
            public void run() {
                try {
                    Point origem = label.getLocation();
                    int deslocamento = 2;
                    do {
                        double distancia = distance(origem, destino);
                        if (deslocamento > distancia) {
                            deslocamento = (int) distancia;
                        }
                        double direcao = angle(origem, destino);
                        origem = move(origem, deslocamento, direcao);
                        label.setLocation(origem);
                        Thread.sleep(10);
                    } while (!origem.equals(destino));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    movendo = false;
                }
            }
        };
        animacaoAndando.start();
    }
}