我正在尝试做一个游戏,在游戏中有一个背景,有许多从右到左慢慢移动的小星星。在全屏的情况下,我可能每帧绘制 170 颗星星。渲染每一帧的大部分 CPU 时间都花在绘制星星上。有没有办法更有效地利用资源?
我已经考虑了这个答案中的要点:https://stackoverflow.com/a/200493/21618530
我使用的一种方法是使用设备兼容的 VolatileImages,使用 3 种变体中的一种来避免使用 BITMASK 透明度进行缩放。以这种方式渲染每一帧平均需要 22 毫秒。
对星星使用
fillOval()
而不是绘制图像将运行时间减少到每帧约 20 毫秒,使用此分析器输出:
这是在配备 Intel UHD 620 和 3840x2160 显示器的笔记本电脑上。我打算让这个游戏简单易玩,并且可以在各种图形上玩,所以如果它能在这样的设备上运行良好就很好了。
我对其他策略持开放态度以获得相同的结果,但我的目标是更好地理解 Swing 2d 性能,以便我可以整体改进游戏。
MRE 展示了我是如何做到这一点的,在我的机器上,不稳定图像的使用平均比每次绘制椭圆要慢。
package space;
import java.awt.AWTException;
import java.awt.BufferCapabilities;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.ImageCapabilities;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.VolatileImage;
import java.util.Random;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.RepaintManager;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class MRE {
private static final int OVAL_SIZE = 4;
private static final int NUM_STARS = 150;
private static final boolean useImages = false;
private static VolatileImage volatileImage;
public static void main(String[] args) {
System.setProperty("sun.java2d.opengl", "true");
System.setProperty("sun.java2d.accthreshold", "0");
JFrame frame = new JFrame();
frame.setSize(new Dimension(1000, 800));
frame.setIgnoreRepaint(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
RepaintManager manager = new RepaintManager() {
@Override
public synchronized void addDirtyRegion(JComponent c, int x, int y, int w, int h) {
// Ignored
}
@Override
public Rectangle getDirtyRegion(JComponent aComponent) {
return aComponent.getBounds();
}
@Override
public void markCompletelyDirty(JComponent aComponent) {
// Ignored
}
@Override
public void markCompletelyClean(JComponent aComponent) {
// Ignored
}
};
createVolatileImage(frame);
RepaintManager.setCurrentManager(manager);
Timer st = new Timer(250, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
long startTime;
startTime = System.nanoTime();
JComponent contentPanel = ((JComponent) frame.getContentPane());
contentPanel.paintImmediately(contentPanel.getBounds());
long elapsed = System.nanoTime() - startTime;
System.out.println("Rendering time: " + elapsed / 1000000.0 + "ms.");
}
});
frame.setContentPane(createContentPane());
setWindowVisible(frame, true);
st.start();
}
private static void createVolatileImage(JFrame frame) {
GraphicsConfiguration gc = frame.getGraphicsConfiguration();
volatileImage = gc.createCompatibleVolatileImage(4, 4, Transparency.BITMASK);
Graphics2D g2 = volatileImage.createGraphics();
g2.setColor(Color.WHITE);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.fillOval(0, 0, OVAL_SIZE, OVAL_SIZE);
g2.dispose();
}
private static JPanel createContentPane() {
JPanel contentPane = new JPanel() {
private static final long serialVersionUID = 2500224655024127049L;
@Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
Random rand = new Random();
g2.setColor(Color.BLACK);
g2.fillRect(0, 0, getWidth(), getHeight());
for(int i = 0; i < NUM_STARS; i++) {
double x = rand.nextDouble() * getWidth();
double y = rand.nextDouble() * getHeight();
g2.translate(x, y);
if(useImages) {
g2.drawImage(volatileImage, 0, 0, null);
} else {
g2.setColor(Color.WHITE);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.fillOval(0, 0, OVAL_SIZE, OVAL_SIZE);
}
g2.translate(-x, -y);
}
}
};
return contentPane;
}
private static void setWindowVisible(JFrame frame, final boolean visible) {
SwingUtilities.invokeLater(() -> {
frame.setVisible(visible);
try {
final BufferCapabilities caps = new BufferCapabilities(new ImageCapabilities(true),
new ImageCapabilities(true), BufferCapabilities.FlipContents.UNDEFINED);
frame.createBufferStrategy(2, caps);
} catch (final AWTException e) {
frame.createBufferStrategy(2);
System.err.println("Could not set double buffered FLIP strategy, using default double buffer: "
+ e.getLocalizedMessage());
}
});
}
}
我使用了
BufferedImage
而不是 VolatileImage,时间从 40 下降到 4:
import java.awt.*;
import java.awt.BufferCapabilities;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.ImageCapabilities;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.*;
import java.util.Random;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.RepaintManager;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class MRE1 {
private static final int OVAL_SIZE = 4;
private static final int NUM_STARS = 150;
private static final boolean useImages = true;
private static VolatileImage volatileImage;
private static BufferedImage bufferedImage;
public static void main(String[] args) {
System.setProperty("sun.java2d.opengl", "true");
System.setProperty("sun.java2d.accthreshold", "0");
JFrame frame = new JFrame();
frame.setSize(new Dimension(1000, 800));
frame.setIgnoreRepaint(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
createVolatileImage(frame);
Timer st = new Timer(250, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
long startTime;
startTime = System.nanoTime();
JComponent contentPanel = ((JComponent) frame.getContentPane());
contentPanel.paintImmediately(contentPanel.getBounds());
long elapsed = System.nanoTime() - startTime;
System.out.println("Rendering time: " + elapsed / 1000000.0 + "ms.");
}
});
frame.setContentPane(createContentPane());
setWindowVisible(frame, true);
st.start();
}
private static void createVolatileImage(JFrame frame) {
GraphicsConfiguration gc = frame.getGraphicsConfiguration();
volatileImage = gc.createCompatibleVolatileImage(4, 4, Transparency.BITMASK);
Graphics2D g2 = volatileImage.createGraphics();
g2.setColor(Color.WHITE);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.fillOval(0, 0, OVAL_SIZE, OVAL_SIZE);
g2.dispose();
bufferedImage = new BufferedImage(OVAL_SIZE, OVAL_SIZE, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bufferedImage.createGraphics();
g2d.setColor( Color.WHITE );
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.fillOval(0, 0, OVAL_SIZE, OVAL_SIZE);
g2d.dispose();
}
private static JPanel createContentPane() {
JPanel contentPane = new JPanel() {
private static final long serialVersionUID = 2500224655024127049L;
@Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.setColor(Color.BLACK);
g2.fillRect(0, 0, getWidth(), getHeight());
Random rand = new Random();
for(int i = 0; i < NUM_STARS; i++) {
double x = rand.nextDouble() * getWidth();
double y = rand.nextDouble() * getHeight();
g2.translate(x, y);
if(useImages) {
// g2.drawImage(volatileImage, 0, 0, null);
g2.drawImage(bufferedImage, 0, 0, null);
} else {
g2.setColor(Color.WHITE);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.fillOval(0, 0, OVAL_SIZE, OVAL_SIZE);
}
g2.translate(-x, -y);
}
}
};
contentPane.setBackground( Color.BLACK );
return contentPane;
}
private static void setWindowVisible(JFrame frame, final boolean visible) {
SwingUtilities.invokeLater(() -> {
frame.setVisible(visible);
try {
final BufferCapabilities caps = new BufferCapabilities(new ImageCapabilities(true),
new ImageCapabilities(true), BufferCapabilities.FlipContents.UNDEFINED);
frame.createBufferStrategy(2, caps);
} catch (final AWTException e) {
frame.createBufferStrategy(2);
System.err.println("Could not set double buffered FLIP strategy, using default double buffer: "
+ e.getLocalizedMessage());
}
});
}
}
还摆脱了 RepaintManager,因为我认为不需要它。
我使用的是使用设备兼容的 VolatileImages,使用 3 种变体中的一种来避免使用 BITMASK 透明度进行缩放
如果这意味着您不希望图像在使用 150 的缩放因子时从 4 像素缩放到 6 像素,那么也许您可以使用
NoScalingIcon
。查看:https://stackoverflow.com/a/65742492/131872
您应该能够使用您的图像创建图标,然后将图标绘制到 BufferedImage,图像仍然是 4 像素。