如何向 JComponent 添加视觉
How to add vision to a JComponent
我有一个动态数量的 JComponents 在 JPanel 中移动。我想让它们在另一个 Jcomponent 挡道时停止。我试图搜索有关 2d 光线投射的信息,不幸的是我不知道如何在我的代码中实现它。 Stackoferflow 对此有一些答案,但他们正在构建对撞机,我需要更类似于光线投射的东西。
这是 JPanel 代码 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();
}
}
这里是 JComponent 代码 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();
}
}
- 不要调用
updateUI
,它并没有像您认为的那样工作。要安排新的绘制过程,您应该在已更改的组件上调用 repaint
。
- 我个人不会以这种方式使用组件。
- 您的“主循环”应该确保对“实体”的任何更改都不会导致冲突,如果发生冲突,那么您需要采取适当的措施
- 在您的
paintComponent
中调用 this.setLocation(new Point(x, y));
不会给您带来任何最终问题,因为 Graphics
上下文实际上已被翻译(因此 0x0
是位置其父组件中的组件)在调用 paintComponent
之前
- Swing
Timer
不能很好地扩展。也就是说,拥有更多 Timer
s 实际上会对部门绩效产生影响。最好有一个 Timer
可以附加多个侦听器,或者更好的是,一个 Timer
和一个侦听器充当“主循环”
What you mean by "main loop"?
“主循环”是一个常用术语(尤其是在游戏中,但 Swing 将其称为“事件调度线程”或“主事件循环”)。在您的上下文中,这将是 Map
class.
中的 Timer
的 ActionListener
这应该定期调用,它负责根据实体的需要更新实体、执行碰撞检测、以任何其他有意义的方式更新状态并安排重绘。
例子
下面的例子简化了这个想法。它使用一个“主循环”负责更新“实体”位置、执行边界和碰撞检测以及安排重绘。
请注意,这是一个明显简化的示例。我个人会将 Entity
设为 interface
,然后制作专用的 Car
class 来实现,但这只是为了演示核心概念
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?
主要原因是,它们“重”。他们有很多围绕他们设计的核心功能,这些功能旨在完成其他工作,但他们并不是为这类工作而设计的。
组件已经有很多 location/size 与之关联的操作,通常由布局管理 API 管理,因此您可能(很容易)结束它。
组件通常不太适合这种工作。
我有一个动态数量的 JComponents 在 JPanel 中移动。我想让它们在另一个 Jcomponent 挡道时停止。我试图搜索有关 2d 光线投射的信息,不幸的是我不知道如何在我的代码中实现它。 Stackoferflow 对此有一些答案,但他们正在构建对撞机,我需要更类似于光线投射的东西。
这是 JPanel 代码 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();
}
}
这里是 JComponent 代码 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();
}
}
- 不要调用
updateUI
,它并没有像您认为的那样工作。要安排新的绘制过程,您应该在已更改的组件上调用repaint
。 - 我个人不会以这种方式使用组件。
- 您的“主循环”应该确保对“实体”的任何更改都不会导致冲突,如果发生冲突,那么您需要采取适当的措施
- 在您的
paintComponent
中调用this.setLocation(new Point(x, y));
不会给您带来任何最终问题,因为Graphics
上下文实际上已被翻译(因此0x0
是位置其父组件中的组件)在调用paintComponent
之前 - Swing
Timer
不能很好地扩展。也就是说,拥有更多Timer
s 实际上会对部门绩效产生影响。最好有一个Timer
可以附加多个侦听器,或者更好的是,一个Timer
和一个侦听器充当“主循环”
What you mean by "main loop"?
“主循环”是一个常用术语(尤其是在游戏中,但 Swing 将其称为“事件调度线程”或“主事件循环”)。在您的上下文中,这将是 Map
class.
Timer
的 ActionListener
这应该定期调用,它负责根据实体的需要更新实体、执行碰撞检测、以任何其他有意义的方式更新状态并安排重绘。
例子
下面的例子简化了这个想法。它使用一个“主循环”负责更新“实体”位置、执行边界和碰撞检测以及安排重绘。
请注意,这是一个明显简化的示例。我个人会将 Entity
设为 interface
,然后制作专用的 Car
class 来实现,但这只是为了演示核心概念
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?
主要原因是,它们“重”。他们有很多围绕他们设计的核心功能,这些功能旨在完成其他工作,但他们并不是为这类工作而设计的。
组件已经有很多 location/size 与之关联的操作,通常由布局管理 API 管理,因此您可能(很容易)结束它。
组件通常不太适合这种工作。