Рисование на JPanel из нескольких мест

#java #swing #jpanel #2d

Вопрос:

В настоящее время я работаю над 2D-игрой на Java для школы. Мы должны использовать абстрактный шаблон заводского дизайна. Для 2D-реализации я использую фабрику следующим образом:

 public class Java2DFact extends AbstractFactory {
    public Display display;
    private Graphics g;
    public Java2DFact() {
        display = new Display(2000, 1200);
    }


    @Override
    public PlayerShip getPlayership()
    {
        return new Java2DPlayership(display.panel);
    }
 

В своем классе отображения я создаю JFrame и Jpanel

 public class Display {
    public JFrame frame;
    public JPanel panel;
    public int width, height;

    public Display(int width, int height) {
        this.width = width;
        this.height = height;

        frame = new JFrame();
        frame.setTitle("SpaceInvaders");
        frame.setSize(1200,800);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        
        panel = new JPanel(){
           @Override
            protected void paintComponent(Graphics g){
               super.paintComponent(g);
            }
        };
        panel.setFocusable(true);
        frame.add(panel);
    }
}
 

Теперь из моего основного gameloop я вызываю метод визуализации внутри класса Java2DPLayership, чтобы визуализировать свою игровую аудиторию

 public class Java2DPlayership extends PlayerShip  {
    private JPanel panel;
    private Graphics2D g2d;
    private Image image;
    private BufferStrategy bs;


    public Java2DPlayership(JPanel panel) {
        super();
        this.panel = panel;
    }

    public void visualize()  {
        try {
            image = ImageIO.read(new File("src/Bee.gif"));
            Graphics2D g = (Graphics2D) bs.getDrawGraphics();
            //g.setColor(new Color(0, 0, 0));
            //g.fillRect(10, 10, 12, 8);
            g.drawImage(image, (int) super.getMovementComponent().x, (int) super.getMovementComponent().y, null);
            Toolkit.getDefaultToolkit().sync();
            g.dispose();
            panel.repaint();
        } catch(Exception e){
            System.out.println(e.toString());
        }
    }
}
 

Моя цель состоит в том, чтобы передать JPanel каждому объекту и позволить ему отобразить его содержимое на панели, прежде чем показывать его. Однако я, кажется, не могу понять, как это сделать. При использовании этого подхода, изменяя графику панели, я получаю много мерцания.

Комментарии:

1. Вы должны рисовать только из контекста paintComponent , который вы переопределяете в JPanel . Больше нигде. Это означает, что, войдя, paintComponent вы можете вызвать другие методы и передать графический контекст в качестве аргумента. И сохраняйте свою логику рисования как можно короче (с точки зрения времени) каждый раз, когда вызывается перекраска, чтобы избежать задержки потока отправки событий.

Ответ №1:

Вот полностью функциональный, хотя и простой пример, который я написал некоторое время назад. У него просто куча balls отскакивающих от боковых сторон панели. Обратите внимание, что render метод Ball class принимает графический контекст из paintComponent . Если бы у меня было больше классов, которые нужно было визуализировать, я мог бы создать Renderable интерфейс и заставить каждый класс реализовать его. Тогда у меня мог бы быть список Renderable объектов, я мог бы просто просмотреть их и вызвать метод. Но, как я также сказал, это должно было бы произойти быстро, чтобы избежать привязки EDT.

 import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Bounce extends JPanel {
   private static final int    COLOR_BOUND  = 256;
   private final static double INC          = 1;
   private final static int    DIAMETER     = 40;
   private final static int    NBALLS       = 20;
   private final static int    DELAY        = 5;
   private final static int    PANEL_WIDTH  = 800;
   private final static int    PANEL_HEIGHT = 600;
   private final static int    LEFT_EDGE    = 0;
   private final static int    TOP_EDGE     = 0;
   private JFrame              frame;
   private double              rightEdge;
   private double              bottomEdge;
   private List<Ball>          balls        = new ArrayList<>();
   private Random              rand         = new Random();
   private List<Long>          times        = new ArrayList<>();
   private int width;
   private int height;
   
   public Bounce(int width, int height) {
      this.width = width;
      this.height = height;
      frame = new JFrame("Bounce");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.add(this);
      addComponentListener(new MyComponentListener());
      frame.pack();
      frame.setLocationRelativeTo(null);
      rightEdge = width - DIAMETER;
      bottomEdge = height - DIAMETER;
      for (int j = 0; j < NBALLS; j  ) {
         int r = rand.nextInt(COLOR_BOUND);
         int g = rand.nextInt(COLOR_BOUND);
         int b = rand.nextInt(COLOR_BOUND);
         Ball bb = new Ball(new Color(r, g, b), DIAMETER);
         balls.add(bb);

      }
      frame.setVisible(true);
   }

   public Dimension getPreferredSize() {
       return new Dimension(width, height);
   }
   
   public static void main(String[] args) {
      new Bounce(PANEL_WIDTH, PANEL_HEIGHT).start();
   }

   public void start() {
      /**
       * Note: Using sleep gives a better response time than
       * either the Swing timer or the utility timer. For a DELAY
       * of 5 msecs between updates, the sleep "wakes up" every 5
       * to 6 msecs while the other two options are about every
       * 15 to 16 msecs. Not certain why this is happening though
       * since the other timers are run on threads.
       * 
       */
     Timer timer = new Timer(0,(ae)-> {repaint();
         for (Ball b : balls) {
            b.updateDirection(); 
         }} );
      timer.setDelay(5); // 5 ms.
      timer.start();
   }
   
  
   public void paintComponent(Graphics g) {
      super.paintComponent(g);
      Graphics2D g2d = (Graphics2D) g;
      g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
      
      for (Ball ball : balls) {
         ball.render(g2d);
      }
   }

   class MyComponentListener extends ComponentAdapter {
      public void componentResized(ComponentEvent ce) {
         Component comp = ce.getComponent();
         rightEdge = comp.getWidth() - DIAMETER;
         bottomEdge = comp.getHeight() - DIAMETER;
         for (Ball b : balls) {
            b.init();
         }
      }
   }
   class Ball {
      private Color  color;
      public double  x;
      private double y;
      private double yy;
      private int    ydir = 1;
      private int    xdir = 1;
      private double slope;
      private int    diameter;

      public Ball(Color color, int diameter) {
         this.color = color;
         this.diameter = diameter;
         init();
      }

      public void init() {
         // Local constants not uses outside of method
         // Provides default slope and direction for ball
         slope = Math.random() * .25   .50;
         x = (int) (rightEdge * Math.random());
         yy = (int) (bottomEdge * Math.random())   diameter;
         xdir = Math.random() > .5 ? -1
                                   : 1;
         ydir = Math.random() > .5 ? -1
                                   : 1;
         y = yy;
      }

      public void render(Graphics2D g2d) {
         g2d.setColor(color);
         g2d.fillOval((int) x, (int) y, diameter, diameter);
      }

      public void updateDirection() {
         x  = (xdir * INC);
         yy  = (ydir * INC);
         y = yy * slope;
         if (x < LEFT_EDGE || x > rightEdge) {
            xdir = -xdir;
         }
         if (y < TOP_EDGE || y > bottomEdge) {
            ydir = -ydir;
         }
      }
   }
}
 

Комментарии:

1. Ты когда-нибудь пробовал это? Помогло ли это решить вашу проблему?

2. @camickr Исправлено. Спасибо!