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.
Deslocar a coordenada A no eixo x a distância desejada;
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);
}
}
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?
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();
}
}