I have a dynamic number of JComponents moving in a JPanel. I want to make them stop when another Jcomponent is in it's way. I've tried to make a search about 2d raycasting, unfortunately i don't know how to implement it in my code. Stackoferflow have some answers for this but there they're building colliders, I need something more similar to raycasting.
Heres the JPanel code Map.java:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Map extends JPanel implements ActionListener {
final int windowWidth = 1300;
final int windowHeight = 750;
Map() {
this.setPreferredSize(new Dimension(windowWidth, windowHeight));
Timer timer = new Timer(3000, this);
timer.start();
}
@Override
public void paint(Graphics g) {
super.paint(g);
// painting map
}
// Update method of the frame
@Override
public void actionPerformed(ActionEvent e) {
Car car = new Car();
this.add(car);
// For loop responds for data cleaning
for(int i=0;i<this.getComponents().length;i ) {
Car iCar = (Car) this.getComponents()[i];
if(iCar.x >= 1400 || iCar.x <= -300 || iCar.y <= -300 || iCar.y >= 950) {
this.remove(i);
}
}
System.out.println(this.getComponentCount());
this.updateUI();
}
}
Heres JComponent code Car.java:
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;
public class Car extends JComponent implements ActionListener {
private BufferedImage car;
private final GeoPoint startPoint = GeoSides.pickGeoPoint();
public int x = 0;
public int y = 0;
private int xVelocity;
private int yVelocity;
private final int imageX = 160;
private final int imageY = 120;
private double turnAngle = 0;
Timer timer;
// Initializing Car() object
Car() {
this.setPreferredSize(new Dimension(170, 130));
this.timer = new Timer(1, this); // Setting up the timer for update method
this.timer.start();
try {
Random randomize = new Random();
car = ImageIO.read(new File("src/assets/" (randomize.nextInt(10) 1) ".png")); // Getting a random image of car
} catch (IOException e) {
e.printStackTrace();
}
// Setting start position
}
// This method should be called when another car is in the way
public void stopCar() {
xVelocity = 0;
yVelocity = 0;
}
// Method scales the image and rotate it if needed
private void drawCar(Graphics2D render2D, double rotationAngle) {
//Image scaling and rotating
}
// Inherited method from JComponent
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
this.setLocation(new Point(x, y));
Graphics2D render2D = (Graphics2D) g;
drawCar(render2D, turnAngle);
}
// Frame update of car's x, y speed and turn angle
@Override
public void actionPerformed(ActionEvent e) {
switch (startPoint){
// Setting destination points
}
x = xVelocity;
y = yVelocity;
this.repaint();
}
}
CodePudding user response:
- Don't call
updateUI
, it's not doing what you think it's doing. To schedule a new paint pass, you should be callingrepaint
on the component which has changed. - I'd, personally, not use components this way.
- Your "main loop" should be ensuring that any changes to a "entity" won't cause a collision, if they do, then you need to take appropriate action
- Calling
this.setLocation(new Point(x, y));
inside yourpaintComponent
is going to cause you no end up issues, as theGraphics
context is actually translated (so that0x0
is the location of the component within it's parent) beforepaintComponent
is called - Swing
Timer
does not scale well. That is, having moreTimer
s could actually have a departmental effect on performance. Better to have a singleTimer
onto which you could attach multiple listeners, or better yet, a singleTimer
and single listener acting as the "main loop"
What you mean by "main loop"?
"Main loop" is a common term (especially in gaming, but Swing will call it the "Event Dispatching Thread" or "main event loop"). In context to you, this would be the Timer
's ActionListener
in your Map
class.
This should be called on a regular bases, it is responsible for updating the entities, based on their needs, performing collision detection, updating the state in any other meaningful way and scheduling repaints.
Example
The following example simplifies the idea. It uses a single "main loop" which is responsible for updating the "entities" position, performing bounds and collision detection and scheduling the repaints.
Please, beware that this is an overtly simplified example. I would, personally, make Entity
an interface
and then make a dedicated Car
class which implemented, but this is just to demonstrate the core concepts
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new MainPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MainPane extends JPanel {
private Timer ticker;
private List<Entity> entities;
public MainPane() {
entities = new ArrayList<>(32);
// For demonstration purposes
int y = (200 - 20) / 2;
entities.add(new Entity(0, y, 1, 0, Color.RED));
entities.add(new Entity(400 - 20, y, -1, 0, Color.BLUE));
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 200);
}
@Override
public void addNotify() {
super.addNotify();
if (ticker != null) {
ticker.stop();
ticker = null;
}
ticker = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
tickDidOccur();
}
});
ticker.start();
}
@Override
public void removeNotify() {
super.removeNotify();
if (ticker != null) {
ticker.stop();
ticker = null;
}
}
// It's more efficent then creating a local variable each time
private List<Entity> entitiesToBeRemoved = new ArrayList<>(32);
protected void tickDidOccur() {
entitiesToBeRemoved.clear();
// So some choices. You can either update ALL the enties and then
// do the collision detection, or, you can do the collision detection
// after each enity is been updated.
Rectangle viewBounds = getBounds();
for (Entity entity : entities) {
Rectangle bounds = entity.peekNextPosition();
if (!viewBounds.intersects(bounds)) {
entitiesToBeRemoved.add(entity);
} else {
boolean conflict = false;
for (Entity other : entities) {
if (other == entity) {
continue;
}
// Please note, depending on the amount of change
// it's possible that the enities may be positioned
// further apart. You will need to access this based
// on your needs.
// Bascially what this is going to do, is check to see
// if the next update can be perform or not, if there
// is a conflict, the entities are stopped and the
// current entities update is discarded
if (bounds.intersects(other.bounds)) {
other.stop();
entity.stop();
conflict = true;
}
}
if (!conflict) {
// Commit the next position
entity.update();
}
}
// This is more of a "long winded" bounds check, but as you
// can see, we can simply make use of the functionality
// Rectangle provides to do the same thing
//if (bounds.x bounds.width < 0 || bounds.y bounds.height < 0) {
// entitiesToBeRemoved.add(entity);
//} else if (bounds.x > getWidth() || bounds.y > getHeight()) {
// entitiesToBeRemoved.add(entity);
//}
}
entities.removeAll(entitiesToBeRemoved);
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Entity entity : entities) {
// Because I trust no one
Graphics2D g2d = (Graphics2D) g.create();
entity.paint(g2d);
g2d.dispose();
}
}
}
public class Entity {
private Rectangle bounds;
private Point velocity;
private Color color;
public Entity(int x, int y, int xDelta, int yDelta, Color color) {
bounds = new Rectangle(x, y, 20, 20);
velocity = new Point(xDelta, yDelta);
this.color = color;
}
public Rectangle getBounds() {
return bounds;
}
public Color getColor() {
return color;
}
public void stop() {
velocity = new Point(0, 0);
}
public void start(int xDelta, int yDelta) {
velocity = new Point(0, 0);
}
public Rectangle peekNextPosition() {
Rectangle next = new Rectangle(bounds);
int x = next.x velocity.x;
int y = next.y velocity.y;
next.setLocation(x, y);
return next;
}
public Rectangle update() {
int x = bounds.x velocity.x;
int y = bounds.y velocity.y;
bounds.setLocation(x, y);
return bounds;
}
public void paint(Graphics2D g) {
Graphics2D g2d = (Graphics2D) g.create();
// Now what ever we do wont' have a cascading effect on what
// ever comes next. This is really helpful for when you need
// to apply AffineTransformation
g2d.setColor(getColor());
g2d.fill(bounds);
g2d.dispose();
}
}
}
Why wouldn't use "component" based entities?
The main reasons are, they are "heavy". They have a lot of core functionality wrapped around them which is designed to do other work, they just aren't well designed for this kind of work.
Components already have a lot of location/size operations associated with them, which is generally managed by the layout management API, so you could (all too easily) end up fighting that.
Components are, generally, not well suited to this kind of work.