2017-10-06 94 views
0

我正在開發一個簡單的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獲得的行爲相同。

也許有人在那裏可以揭示這些複合材料正在發生什麼,以及如何實現我想要的效果。

+0

無法在面板中重寫onDraw(Graphics g2d)而不是執行此draw()方法嗎? –

+0

@MarcosVasconcelos是的,那就是我目前正在做的。然而,我在遊戲中處理效果的想法(轉換,屏幕和音頻淡入和淡出)是有動作的(點擊菜單上的「播放」或角色在某個圖塊上行走)將消息添加到被檢查的隊列每場比賽打勾。這些效果會觸發一個線程並在那裏進行處理,並且遊戲循環可以繼續。我目前沒有將我的paintComponent()提供的Graphics對象路由到我的Effects類。這就是爲什麼我在效果線程中使用getGraphics – WhitJackman

+0

根據你的代碼,你根本不重寫'paintComponent',而是使用'getGraphics',這是不推薦的,因爲你試圖更新UI之外的UI Swing定義的繪畫循環的上下文。你也不應該處理你沒有創建的'Graphics'上下文。我也沒有看到'BufferedImage'的重點。你也似乎沒有任何關於動畫假設如何在Swing中工作的概念 – MadProgrammer

回答

1

有你「似乎」有什麼問題的數量要嘗試做

  • 不要叫getGraphics一個組件上。這可以返回null,並且只返回在Swing繪製循環中上次繪製內容的快照。任何你畫它會在下油漆週期
  • 被刪除你也永遠不應該丟棄你沒有創建,這樣做可能會影響由搖擺
  • 繪畫是以複利畫其他組件Graphics背景下,這意味着一次又一次地繪製到相同的Graphics上下文(或BufferedImage),將繼續將這些更改應用於之前繪製的頂部
  • 您似乎也沒有關於動畫應如何工作的概念。與其試圖一次性繪製淡入淡出效果,而不能將結果應用於屏幕,您需要在每個循環上應用一個階段,並允許在下一次運行之前將其更新到屏幕。

以下是我正在談論的一個非常基本的例子。它需要一個「基礎」圖像(這可能是遊戲的「基礎」狀態,但我已經使用了靜態圖像)以及頂部的塗料效果。

Fade to black

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

+0

感謝您的答覆。所以,你有一個運行時間檢查效果列表的計時器?然後將當前背景拖到BufferedImage上,然後在後面畫出效果,然後傳回重新繪製到屏幕的主面板?我可以使用這樣的計時器嗎?我已經使用了gameloop了嗎?似乎可以使用任何一個或另一個。 – WhitJackman

+0

基本上 - 這裏的基礎圖像可以通過動態創建,但是想法是,每個paint pass從頭開始 – MadProgrammer

+0

因此,我總是必須從paintComponent()開始,並以適當的方式路由該圖形對象。 – WhitJackman