2017-10-06 36 views
0

나는 간단한 2D 게임을하고있다. 각 틱, 특정 효과 (페이드 전환, 오디오 페이드 인/아웃 등)에 대한 스레드를 시작하는 효과 대기열을 확인하고 싶습니다. 예를 들어, 메뉴 화면에서 "재생"을 누르면이 큐에 "FadeOut"메시지가 추가되고 처리되고 스레드가 시작되어 GamePanel에서 알파 값이 증가하는 검은 색 사각형이 그려집니다.PaintComponent() 외부의 JPanel에 그려진 BufferedImage를 getGraphics()로 덮어 쓰는 법

paintComponent()를 재정의하고 Graphics 객체를 현재 상태 인 draw()에 전달하는 GameStateManager에 내 Graphics 객체를 보내는 중입니다. 현재 paintComponent() 그래픽 객체를 라우트하는 효과 상태 (어쩌면해야 할 것 같은)가 없지만 getGraphics()를 사용하여 내 게임 스레드를 내 효과 스레드에 전달합니다. gameloop은 여전히 ​​게임을 렌더링하므로 GamePanel에 직사각형을 직접 그리면 깜박임이 발생합니다.

나는 BufferedImage에 알파를 증가시키면서 검은 사각형을 그릴 수 있고, AlphaComposite.Src (새로운 그리기가 이전을 대체하게 함)로 컴포지트를 설정 한 다음 게임 패널에 BufferedImage를 그립니다. 문제는 게임 패널에 그린 BufferedImages가 각 그리기를 오버라이드하지 않기 때문에 다양한 알파의 검은 BufferedImages가 서로 겹치기 때문에 페이드 아웃이 실제로 빠르게 발생한다는 것입니다.

이 짧은 프로그램을 작성하여 컴포지트 설정을 테스트하고 무엇이 오버라이드되는지 확인하십시오. 모든 드로잉은 draw()에서 수행됩니다.이 드로잉은 effect 스레드에서 내 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'의 복합을 설정하면 이전에 있던 이미지 위에 그려지는 완전히 불투명 한 검정색 이미지의 결과로, 멀리 이동합니다 BufferedImage의 어떤 알파 원인입니다. 검은 색이 아닌 다른 색으로 설정하는 것조차도 효과가 없습니다.

oImageGraphics.setComposite(AlphaComposite.SrcIn); 

을 표시 할 아무 것도 발생하지 않습니다 : 흥미 무엇

는에 BufferedImage에 대한 복합을 설정합니다. Java2D에서 그래픽을 합성하는 Oracle 설명서는 'SrcIn'에 대해 다음과 같이 설명합니다.

"소스와 대상의 픽셀이 겹치면 겹치는 영역의 소스 픽셀 만 렌더링됩니다."

그래서, 나는 이것이 AlphaComposite.Src에서 얻은 것과 같은 행동을 할 것으로 기대합니다.

어쩌면 누군가가 이러한 합성물의 진행 상황에 대해 밝힐 수 있으며 원하는 효과를 얻는 방법을 알 수 있습니다.

+0

이 draw() 메서드 대신 패널에서 onDraw (Graphics g2d)를 재정의 할 수 있습니까? –

+0

@MarcosVasconcelos 네, 그게 제가 현재하고있는 일입니다. 그러나 내 게임 (전환, 화면 및 오디오 페이드 인 및 페이드 아웃)에서 효과를 처리하는 방법은 동작을 수행하는 것이 었습니다 (메뉴에서 "재생"을 클릭하거나 특정 타일을 걷는 문자)하면 대기열에 메시지가 추가됩니다 각 게임 틱. 이러한 효과는 스레드에서 실행되어 처리되고 게임 루프는 계속 진행될 수 있습니다. 현재 paintComponent()에서 Graphics 객체를 내 Effects 클래스로 라우팅하지 않습니다. 이것이 효과 스레드에서 getGraphics를 사용하는 이유입니다. – WhitJackman

+0

코드에 따라, 당신은 'paintComponent'를 전혀 무시하지 않고, getGraphics를 사용하고 있습니다. 권장하지는 않습니다. UI 외부에서 UI를 업데이트하려고 시도하고 있습니다. Swing이 정의한 페인트 사이클의 컨텍스트 또한 생성하지 않은'Graphics' 문맥을 처리해서는 안됩니다. 나는 또한 BufferedImage의 요점을 보지 못했다. 또한 애니메이션이 Swing에서 작동하는 방법에 대한 개념이없는 것처럼 보입니다. – MadProgrammer

답변

1

는 "보이는"무엇과 다수의 문제가 있습니다

  • 이 구성 요소에 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; 
     } 

    } 

} 

Fade to black

, 모든 효과를 기억 또는 변경은 동일한 "메인 루프"내에서 적용되어야한다, 당신을 가능한 경우 추가 스레드가 없어야합니다. 이 예에서는 ActionLister actionPerformed 메서드가 EDT의 컨텍스트 내에서 호출되므로 UI를 업데이트해도 안전하므로 Swing Timer을 "주 루프"로 사용합니다. 또한 actionPerformed 메서드를 호출하는 동안 UI를 그릴 수 없으므로 간단한 동기화 방법을 제공합니다.

+0

응답 해 주셔서 감사합니다. 그렇다면 효과 목록을 확인하는 타이머가 항상 실행되고 있습니까? 그리고 나서 현재 배경을 BufferedImage에 그리고 나서 그 다음에 효과를 그리고 메인 패널로 돌아가서 화면에 다시 그려줍니다. 내가 이미 가지고있는 게랑 루프와 함께 타이머를 사용할 수 있을까요? 하나 또는 다른 것이 사용될 수있는 것처럼 보입니다. – WhitJackman

+0

기본적으로 - 기본 이미지는 여기에서 동적으로 만들 수 있습니다. 그러나 아이디어는 각 페인트 패스가 처음부터 시작됩니다. – MadProgrammer

+0

그래서 항상 paintComponent()에서 시작하여 해당 그래픽 객체를 적절한 방법으로 라우팅해야합니다. – WhitJackman