I got it this way:
package trens;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class Trens {
private static final double QUADROS_POR_SEGUNDO = 20.0;
public static void preparar() {
JFrame t = new JFrame();
t.setLayout(null);
t.setSize(1200, 900);
t.setTitle("Semáforo");
t.setLocationRelativeTo(null);
t.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Trilho t1 = new Trilho(300, 100, 300, 170);
Trilho t2 = new Trilho(600, 100, 300, 170);
Trilho t3 = new Trilho(450, 270, 300, 170);
Trem a = new Trem(t1, Color.BLUE, 350, 100, -170.0);
Trem b = new Trem(t2, Color.GREEN, 650, 100, 0.5);
Trem c = new Trem(t3, Color.RED, 450, 335, 44.0);
t.add(a);
t.add(b);
t.add(c);
t.add(t1);
t.add(t2);
t.add(t3);
Runnable moverTudo = () -> {
EventQueue.invokeLater(() -> {
a.mover(1 / QUADROS_POR_SEGUNDO);
b.mover(1 / QUADROS_POR_SEGUNDO);
c.mover(1 / QUADROS_POR_SEGUNDO);
});
};
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(moverTudo, 0, (int) (1000 / QUADROS_POR_SEGUNDO), TimeUnit.MILLISECONDS);
t.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(Trens::preparar);
}
public static class Trilho extends JComponent {
public Trilho(int x, int y, int width, int height) {
this.setBounds(x, y, width, height);
this.setBorder(BorderFactory.createLineBorder(Color.BLACK));
}
}
public static class Trem extends JComponent {
private Color cor;
private Trilho trilho;
private int x;
private int y;
private double velocidade; // pixels por segundo
private double restante; // Frações de pixels que faltou andar.
public Trem(Trilho trilho, Color cor, int x, int y, double velocidade) {
this.trilho = trilho;
this.cor = cor;
this.x = x;
this.y = y;
this.velocidade = velocidade;
this.setBounds(x - 5, y - 5, 10, 10);
}
@Override
public void paintComponent(Graphics g) {
g.setColor(cor);
g.fillRect(0, 0, getWidth(), getHeight());
}
public void mover(double deltaT) {
if (velocidade == 0) return;
boolean sentidoHorario = velocidade > 0;
double distancia = Math.abs(restante + velocidade * deltaT);
int tLeft = trilho.getX();
int tTop = trilho.getY();
int tRight = tLeft + trilho.getWidth();
int tBottom = tTop + trilho.getHeight();
for (int i = 0; i < (int) distancia; i++) {
// Se deve ir à esquerda:
if (x > tLeft && y == (sentidoHorario ? tBottom : tTop)) {
x--;
// Se deve ir à direita:
} else if (x < tRight && y == (sentidoHorario ? tTop : tBottom)) {
x++;
// Se deve ir para cima:
} else if (y > tTop && x == (sentidoHorario ? tLeft : tRight)) {
y--;
// Se deve ir para baixo:
} else if (y < tBottom && x == (sentidoHorario ? tRight : tLeft)) {
y++;
// Se não for nenhum dos anteriores, o trem está descarrilhado. Coloca de novo no trilho.
} else {
x = tLeft;
y = tTop;
}
}
restante = distancia % 1;
setLocation(x - 5, y - 5);
}
}
}
The animation speed (not that of the trains) is defined by that constant QUADROS_POR_SEGUNDO
. As defined in 20, it will (re)calculate the position of objects 20 times per second. I use the Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(...)
to call a Runnable
every 1/20 seconds, rounded into an integer number of milliseconds (i.e., if you want 30 frames per second, it would give 33 milliseconds per frame, not 331/3 milliseconds per frame).
The blue train is at a speed of -170.0 pixels per second. That is, it goes pretty fast counterclockwise.
The red train is at a speed of 44.0 pixels per second clockwise.
The green train is at a speed of 0.5 pixels per second. That is, it moves very slowly clockwise. I put this small value to prove that it works correctly even if the speed is less than one pixel each step and that it does not end up neither staying still nor forcing a movement in each frame of animation.
Note that I have a class for the Trem
and one for the Trilho
. Both inherit from JComponent
, and therefore are Swing components and designed by Swing. Where it is needed, I write the method paintComponent
, and not the paint
.
I use the calls to EventQueue.invokeLater
for do not manipulate Swing objects outside the EDT. There are three threads here: the EDT, the main thread and the Executors
. Therefore, so that the components are manipulated only in the EDT, the other two threads perform all their work only through the EventQueue.invokeLater
.
Already the method mover
class Trem
find out where part of the train is and move it in the right direction. The approach I used is not the most efficient, but it should work in your case. This approach consists of calculating the position iteratively, pixel by pixel according to the speed, so that the curves of the rails are obeyed. The ideal approach would calculate the final position without using a for
for that reason.
Question follow-up: https://answall.com/q/226640/132
– Victor Stafusa