2017-10-06 7 views
0

Je travaille sur un jeu 2D simple. Chaque tick, je veux vérifier une file d'effets qui démarrera un thread pour un certain effet (transitions en fondu, fondu entrant et sortant audio, etc). Par exemple, en appuyant sur "Play" sur l'écran du menu ajoutera un message "FadeOut" à cette file d'attente, qui sera traitée et démarrera un thread pour dessiner un rectangle noir avec une valeur alpha croissante sur mon GamePanel.Comment remplacer une image BufferedImage dessinée à JPanel en dehors de PaintComponent() avec getGraphics()

Je substitue paintComponent() et envoie mon objet Graphics à mon GameStateManager, qui transmet l'objet Graphics aux états actuels draw(). Je n'ai pas actuellement d'état d'effets (ce que je devrais peut-être) pour router l'objet graphics paintComponent(), mais je passe mon gamepanel à mon thread d'effets, où je peux utiliser getGraphics() pour dessiner dessus. Dessiner un rectangle directement sur le GamePanel provoque simplement des scintillements, car le gameloop restaure le jeu.

J'ai trouvé que je peux dessiner un rectangle noir avec un alpha croissant à un BufferedImage, mettre le composite à AlphaComposite.Src (qui provoque le remplacement de l'ancien) puis dessiner le BufferedImage sur le panneau de jeu. Le problème est que les images BufferedImages dessinées sur le panneau de jeu ne sont pas remplacées à chaque tirage, de sorte que le fondu se produit très rapidement car ces images tamponnées noires de divers alphas se superposent.

J'ai écrit ce programme court pour tester les paramètres composites et voir ce qui est surchargé. Tout le dessin est fait dans le draw(), ce qui serait mon run() dans le thread d'effets.

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 

Ce qui est intéressant est la mise en composite sur « oPanelGraphics » provoque une alpha du BufferedImage pour aller, ce qui produit une image totalement opaque étant tiré sur l'image qui était auparavant là. Même le réglage de la couleur sur autre chose que le noir n'a aucun effet.

Ce qui est aussi intéressant est la mise en composite pour le BufferedImage à:

oImageGraphics.setComposite(AlphaComposite.SrcIn); 

provoque rien à afficher. La documentation d'Oracle sur les graphismes de composition dans Java2D indique ceci pour 'SrcIn':

"Si les pixels de la source et de la destination se chevauchent, seuls les pixels source dans la zone de chevauchement sont rendus." Donc, je m'attendrais à avoir le même comportement qu'avec AlphaComposite.Src. Peut-être que quelqu'un là-bas peut faire la lumière sur ce qui se passe avec ces composites, et comment je pourrais obtenir l'effet désiré.

+0

Vous pouvez remplacer l'onDraw (Graphics g2d) dans votre panneau au lieu de faire cette méthode draw()? –

+0

@MarcosVasconcelos Oui, c'est ce que je fais actuellement. Cependant, mon idée de manipuler les effets dans mon jeu (transitions, fondus d'écran et audio et fondus) consistait à avoir des actions (cliquer sur "Jouer" dans le menu, ou des caractères sur certaines tuiles) ajouter des messages à une file d'attente vérifiée chaque tick de jeu. Ces effets déclencheront un fil et seront traités là, et la boucle de jeu peut continuer. Je ne suis actuellement pas en train de router mon objet Graphics fourni paintComponent() à ma classe Effects. C'est pourquoi j'utilise getGraphics dans le thread d'effets – WhitJackman

+0

Basé sur votre code, vous ne surchargez pas du tout 'paintComponent', mais vous utilisez' getGraphics', ce qui n'est pas recommandé, car vous essayez de mettre à jour l'interface en dehors du contexte du cycle de peinture défini par Swing. Vous ne devriez jamais disposer d'un contexte 'Graphics' que vous n'avez pas créé. Je ne vois pas non plus le point de 'BufferedImage'. – MadProgrammer

Répondre

1

Il y a des problèmes de nombre avec ce que vous « semblez » pour essayer de faire

  • Ne pas appeler getGraphics sur un composant. Cela peut renvoyer null et renvoie uniquement un instantané de ce qui a été peint pour la dernière fois lors d'un cycle de peinture Swing.Tout ce que vous peignez à elle sera effacée sur le prochain cycle de peinture
  • Vous devriez également jamais disposer Graphics contexte vous n'avez pas créé, où cela pourrait affecter d'autres composants qui sont peints par Swing
  • La peinture est composition, cela signifie que peindre au même contexte (ou BufferedImage) encore et encore, continuera à appliquer ces changements au-dessus de ce qui a été précédemment peint
  • Vous aussi ne semblez pas avoir un concept de la façon dont l'animation devrait fonctionner. Au lieu d'essayer de peindre votre effet de fondu en un seul passage, où les résultats ne peuvent pas être appliqués à l'écran, vous devez appliquer une phase sur chaque cycle et permettre de mettre à jour l'écran avant la prochaine passe.

Voici un exemple vraiment fondamental de ce dont je parle. Il faut une image "de base" (cela pourrait être l'état "de base" du jeu, mais j'ai utilisé une image statique) et les effets de peinture sur le dessus.

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; 
     } 

    } 

} 

Rappelez-vous, tous les effets ou les changements doivent être appliqués dans le même « boucle principale », cela signifie que vous ne devriez pas avoir plusieurs threads, en fait, depuis Swing est pas thread-safe, vous devrait éviter d'avoir des discussions supplémentaires si possible. Cet exemple fait appel à un Swing Timer pour agir comme "boucle principale" car la méthode ActionLister s actionPerformed est appelée dans le contexte de l'EDT, ce qui permet de mettre à jour l'interface utilisateur en toute sécurité. Il fournit également une méthode de synchronisation simple, car l'interface utilisateur ne peut pas être peinte pendant que la méthode actionPerformed est appelée

+0

Merci pour la réponse. Donc, vous avez une minuterie tout le temps qui vérifie la liste des effets? Et puis vous dessinez l'arrière-plan en cours sur un BufferedImage, puis dessinez l'effet après, et revenez au panneau principal qui repeint à l'écran? Pourrais-je utiliser une minuterie comme celle-ci avec le gameloop que j'ai déjà en place? Il semble que l'un ou l'autre pourrait être utilisé. – WhitJackman

+0

Fondamentalement, l'image de base ici peut être créée dynamiquement, mais l'idée est que chaque passe de peinture commence à partir de zéro – MadProgrammer

+0

Donc, je dois toujours partir de paintComponent() et acheminer cet objet graphique d'une manière appropriée. – WhitJackman