如何使用getGraphics()覆盖在PaintComponent()外部绘制到JPanel的BufferedImage()
我正在开发一个简单的2D游戏。每个勾号,我想检查一个效果队列,它将启动一个特定效果的线程(淡入淡出转换,音频淡入淡出等)。例如,按下菜单屏幕上的“播放”将向该队列添加一个“FadeOut”消息,该消息将被处理,并启动一个线程来绘制一个黑色矩形,并在我的GamePanel上增加一个alpha值。我将重写paintComponent()并将我的Graphics对象发送给我的GameStateManager,GameStateManager将Graphics对象传递给当前状态的draw()。我目前没有一个效果状态(也许我应该)将paintComponent()图形对象路由到,但是我将我的gamepanel传递给了我的效果线程,我可以在其中使用getGraphics()来绘制它。由于游戏循环仍然在渲染游戏,所以直接绘制矩形到GamePanel只会导致闪烁。如何使用getGraphics()覆盖在PaintComponent()外部绘制到JPanel的BufferedImage()
我发现我可以绘制一个带有增加alpha的BufferedImage的黑色矩形,将复合设置为AlphaComposite.Src(这会导致新绘图替换旧绘图),然后通过游戏面板绘制BufferedImage。问题是绘制到游戏面板的BufferedImages不会被每个绘制覆盖,所以淡出很快就会发生,因为这些不同alpha的黑色BufferedImages彼此堆叠在一起。
我写了这个简短的程序来测试复合设置,看看什么是重写。所有绘图都在draw()中完成,这将是我在效果线程中的run()。
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ScratchPad extends JPanel implements Runnable
{
private JFrame oFrame = null;
private Thread oGameThread = null;
private Graphics2D oPanelGraphics = null;
private Graphics2D oImageGraphics = null;
private BufferedImage oImage = null;
public static void main(String args[]) throws Exception
{
new ScratchPad();
}
public ScratchPad()
{
createFrame();
initPanel();
addAndShowComponents();
oGameThread = new Thread(this, "Game_Loop");
oGameThread.start();
}
private void addAndShowComponents()
{
oFrame.add(this);
oFrame.setVisible(true);
}
private void initPanel()
{
this.setOpaque(true);
this.setBackground(Color.cyan);
}
private void createFrame()
{
oFrame = new JFrame("Fade");
oFrame.setSize(700, 300);
oFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
oFrame.setLocationRelativeTo(null);
}
public void run()
{
oImage = new BufferedImage(200, 200, BufferedImage.TYPE_INT_ARGB);
while(true)
{
try
{
draw();
Thread.sleep(100);
}
catch(InterruptedException e)
{
}
}
}
private void draw()
{
oPanelGraphics = (Graphics2D)this.getGraphics();
oImageGraphics = oImage.createGraphics();
oImageGraphics.setComposite(AlphaComposite.Src);
oImageGraphics.setColor(new Color(0,0,0,90));
oImageGraphics.fillRect(0, 0, oImage.getWidth(), oImage.getHeight());
oPanelGraphics.drawImage(oImage, 10, 10, null);
oImageGraphics.setColor(new Color(0,0,0,60));
oImageGraphics.fillRect(0, 0, oImage.getWidth(), oImage.getHeight());
oPanelGraphics.drawImage(oImage, 220, 10, null);
oImageGraphics.setColor(new Color(0,0,0,30));
oImageGraphics.fillRect(0, 0, oImage.getWidth(), oImage.getHeight());
oPanelGraphics.drawImage(oImage, 430, 10, null);
// Drawing this image over location of first image, should overwrite first
// after setting composite to 'Src'
oPanelGraphics.setComposite(AlphaComposite.Src);
oImageGraphics.setColor(new Color(0,0,0,10));
oImageGraphics.fillRect(0, 0, oImage.getWidth(), oImage.getHeight());
oPanelGraphics.drawImage(oImage, 10, 10, null);
oImageGraphics.dispose();
oPanelGraphics.dispose();
}
} // end class
有趣的是设定在“oPanelGraphics”复合引起的任何Alpha使用BufferedImage走开,导致完全不透明的黑色图像上画出来,这是以前没有的图像。即使将颜色设置为黑色以外的其他颜色也没有效果。
什么也有趣的是,对于BufferedImage的设定合成到:
oImageGraphics.setComposite(AlphaComposite.SrcIn);
导致什么可显示。关于在Java2D中合成图形的Oracle文档声明了'SrcIn':
“如果源和目标中的像素重叠,则只会渲染重叠区域中的源像素。”
因此,我希望这与AlphaComposite.Src获得的行为相同。
也许有人在那里可以揭示这些复合材料正在发生什么,以及如何实现我想要的效果。
有你“似乎”有什么问题的数量要尝试做
- 不要叫
getGraphics
一个组件上。这可以返回null
,并且只返回在Swing绘制循环中上次绘制内容的快照。任何你画它会在下油漆周期 - 被删除你也永远不应该丢弃你没有创建,这样做可能会影响由摇摆
- 绘画是以复利画其他组件
Graphics
背景下,这意味着一次又一次地绘制到相同的Graphics
上下文(或BufferedImage
),将继续将这些更改应用于之前绘制的顶部 - 您似乎也没有关于动画应如何工作的概念。与其试图一次性绘制淡入淡出效果,而不能将结果应用于屏幕,您需要在每个循环上应用一个阶段,并允许在下一次运行之前将其更新到屏幕。
以下是我正在谈论的一个非常基本的例子。它需要一个“基础”图像(这可能是游戏的“基础”状态,但我已经使用了静态图像)以及顶部的涂料效果。
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Engine engine;
private Image frame;
public TestPane() {
engine = new Engine();
engine.setEngineListener(new EngineListener() {
@Override
public void updateDidOccur(Image img) {
frame = img;
repaint();
}
});
engine.start();
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
engine.addEffect(new FadeOutEffect(Color.BLACK));
}
});
}
@Override
public Dimension getPreferredSize() {
return engine.getSize();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (frame != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(frame, 0, 0, null);
g2d.dispose();
}
}
}
public interface EngineListener {
public void updateDidOccur(Image img);
}
public class Engine {
// This is the "base" image, without effects
private BufferedImage base;
private Timer timer;
private EngineListener listener;
private List<Effect> effects = new ArrayList<Effect>(25);
public Engine() {
try {
base = ImageIO.read(new File("/Volumes/Big Fat Extension/Dropbox/MegaTokyo/megatokyo_omnibus_1_3_cover_by_fredrin-d4oupef 50%.jpg"));
} catch (IOException ex) {
ex.printStackTrace();
}
timer = new Timer(10, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int width = base.getWidth();
int height = base.getHeight();
BufferedImage frame = new BufferedImage(width, height, base.getType());
Graphics2D g2d = frame.createGraphics();
g2d.drawImage(base, 0, 0, null);
Iterator<Effect> it = effects.iterator();
while (it.hasNext()) {
Effect effect = it.next();
if (!effect.applyEffect(g2d, width, height)) {
it.remove();
}
}
g2d.dispose();
if (listener != null) {
listener.updateDidOccur(frame);
}
}
});
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
public void addEffect(Effect effect) {
effects.add(effect);
}
public void setEngineListener(EngineListener listener) {
this.listener = listener;
}
public Dimension getSize() {
return base == null ? new Dimension(200, 200) : new Dimension(base.getWidth(), base.getHeight());
}
}
public interface Effect {
public boolean applyEffect(Graphics2D context, int width, int height);
}
public class FadeOutEffect implements Effect {
private int tick = 0;
private Color fadeToColor;
public FadeOutEffect(Color fadeToColor) {
this.fadeToColor = fadeToColor;
}
@Override
public boolean applyEffect(Graphics2D context, int width, int height) {
tick++;
float alpha = (float) tick/100.0f;
if (alpha > 1.0) {
return false;
}
Graphics2D g2d = (Graphics2D) context.create();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
g2d.setColor(fadeToColor);
g2d.fillRect(0, 0, width, height);
g2d.dispose();
return true;
}
}
}
记住,每效应或变化应相同的“主循环”中被应用,这意味着你不应该有多个线程,实际上,因为Swing不是线程安全的,你如果可能的话应该避免有任何额外的线程此示例使用Swing Timer
充当“主循环”,因为在EDT的上下文中调用方法,从而更新UI的安全性。它还提供了一个简单的同步方法,因为在调用actionPerformed
方法时无法绘制UI
感谢您的答复。所以,你有一个运行时间检查效果列表的计时器?然后将当前背景拖到BufferedImage上,然后在后面画出效果,然后传回重新绘制到屏幕的主面板?我可以使用这样的计时器吗?我已经使用了gameloop了吗?似乎可以使用任何一个或另一个。 – WhitJackman
基本上 - 这里的基础图像可以通过动态创建,但是想法是,每个paint pass从头开始 – MadProgrammer
因此,我总是必须从paintComponent()开始,并以适当的方式路由该图形对象。 – WhitJackman
无法在面板中重写onDraw(Graphics g2d)而不是执行此draw()方法吗? –
@MarcosVasconcelos是的,那就是我目前正在做的。然而,我在游戏中处理效果的想法(转换,屏幕和音频淡入和淡出)是有动作的(点击菜单上的“播放”或角色在某个图块上行走)将消息添加到被检查的队列每场比赛打勾。这些效果会触发一个线程并在那里进行处理,并且游戏循环可以继续。我目前没有将我的paintComponent()提供的Graphics对象路由到我的Effects类。这就是为什么我在效果线程中使用getGraphics – WhitJackman
根据你的代码,你根本不重写'paintComponent',而是使用'getGraphics',这是不推荐的,因为你试图更新UI之外的UI Swing定义的绘画循环的上下文。你也不应该处理你没有创建的'Graphics'上下文。我也没有看到'BufferedImage'的重点。你也似乎没有任何关于动画假设如何在Swing中工作的概念 – MadProgrammer