【问题标题】:Repainting Graphics Issue重新绘制图形问题
【发布时间】:2014-06-11 21:48:40
【问题描述】:

我一直在尝试编写一个小型横向卷轴游戏,但遇到了 repaint() 无法重新绘制的问题。按键侦听器本身起作用,它在按下方向键时递增和递减 x 和 y 值。只是 repaint() 似乎没有重新绘制。

public class SideScroller extends JPanel implements KeyListener{
private Random random = new Random();
private int r,g,b;
private int x = 0, y = 0;

public void keyPressed(KeyEvent e) {
    if(e.getKeyCode() == e.VK_UP) {
        y -= 5;
    } else if(e.getKeyCode() == e.VK_DOWN) {
        y += 5;
    } else if(e.getKeyCode() == e.VK_LEFT) {
        x -= 5;
    } else if(e.getKeyCode() == e.VK_RIGHT) {
        x += 5;
    }
    repaint();
}
public void keyReleased(KeyEvent e) {}
public void keyTyped(KeyEvent e) {}

public void paint(Graphics gg) {
    gg.setColor(new Color(r, g, b));
    gg.fillRect (x, y, 50, 50);
}

public static void main(String[] args) {
    SideScroller ss = new SideScroller();
    JFrame f = new JFrame();

    f.add(new SideScroller());
    f.setSize(500, 500);
    f.setLocationRelativeTo(null);
    f.setResizable(false);
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setVisible(true);
    f.addKeyListener(new SideScroller());
}

【问题讨论】:

    标签: java swing graphics keylistener


    【解决方案1】:
    • 您应该覆盖 paintComponent 并调用 super.paintComponent,而不是 paint

      protected void paintComponent(Graphics gg) {
          super.paintComponent(gg);
          gg.setColor(new Color(r, g, b));
          gg.fillRect (x, y, 50, 50);
      }
      
    • 另外我建议考虑使用Key Bindings 而不是KeyListener

    • 您还应该在 Event Dispacth 线程上运行 Swing 应用程序您可以通过将 main 中的代码包装在 SwingUtilities.invokeLater(...) 中来实现这一点

    这里是上面提到的所有修复

    import java.awt.Color;
    import java.awt.Graphics;
    import java.awt.event.ActionEvent;
    import java.util.Random;
    import javax.swing.AbstractAction;
    import javax.swing.ActionMap;
    import javax.swing.InputMap;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.KeyStroke;
    import javax.swing.SwingUtilities;
    
    public class SideScroller extends JPanel {
    
        private Random random = new Random();
        private int r, g, b;
        private int x = 0, y = 0;
    
        public SideScroller() {
            InputMap im = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
            ActionMap am = getActionMap();
            im.put(KeyStroke.getKeyStroke("UP"), "upaction");
            am.put("upaction", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    y -= 5;
    
                    repaint();
                }
            });
            im.put(KeyStroke.getKeyStroke("DOWN"), "downaction");
            am.put("downaction", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    y += 5;
                    repaint();
                }
            });
            im.put(KeyStroke.getKeyStroke("LEFT"), "leftaction");
            am.put("leftaction", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    x -= 5;
                    repaint();
                }
            });
            im.put(KeyStroke.getKeyStroke("RIGHT"), "rightaction");
            am.put("rightaction", new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    x += 5;
                    repaint();
                }
            });
        }
    
        protected void paintComponent(Graphics gg) {
            super.paintComponent(gg);
    
            gg.setColor(new Color(r, g, b));
            gg.fillRect(x, y, 50, 50);
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    JFrame f = new JFrame();
    
                    f.add(new SideScroller());
                    f.setSize(500, 500);
                    f.setLocationRelativeTo(null);
                    f.setResizable(false);
                    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    f.setVisible(true);
                }
            });
        }
    }
    

    【讨论】:

    • 调用invokeLater 是对的,但我不同意你的其他观点。对于游戏 JPanel,您可能想要控制外观的所有方面,因此禁止 L&F 可能尝试执行的任何默认用户界面绘图或图形对象配置是有意义的,因此调用 super.paintComponent 没有任何意义直接覆盖 paint 是错误的。此外,Swing 的 InputMap/ActionMap 废话非常冗长,我一直不明白为什么有人会推荐它而不是一个不错的 KeyListener 来用于具有/可以具有键盘焦点的组件。
    • @Boann super.paint[Component] 清除任何绘制工件的组件。如果你运行我的程序并注释掉super.paintComponent,你就会明白为什么要调用它。此外,如果您阅读KeyListener tutorial,它甚至会声明"To define special reactions to particular keys, use key bindings instead of a key listener"
    • ‍@peeskillet 大概 OP 会用一些东西填充背景。清除它两次,一次使用 JPanel 颜色,另一次使用游戏背景,只是对性能的影响。该教程页面没有解释为什么它推荐键绑定,所以这是一个非答案。在这种情况下,他们没有任何优势。 KeyListener 工作得很好。
    • @Boann:与非常低级别的 KeyListeners 相比,Key Bindings 被认为是“更高级别”的构造,这使它们产生更大的副作用风险。更重要的是,Key Bindings 更能容忍焦点问题,并且如果绑定的组件失去焦点,也不会失败,就像 KeyListeners 一样。请注意,Key Bindings 是 Swing 本身在其自己的组件中查找对操作的击键的方式。它们更容易为每个特定键打开和关闭...
    • @HovercraftFullOfEels 您是否不希望组件在失去焦点时停止响应关键事件? “它们更容易为每个特定键打开和关闭”。没错,但您也可以在 KeyListener 中使用自己的键码映射。我忘记的另一点:使用键绑定,动作以未知的速率触发。使用按键侦听器,尽管此问题中的简单代码也是如此,但可以单独跟踪按下和释放,使用计时器以固定速度移动游戏角色,而不管用户/操作系统如何配置按键重复率。
    【解决方案2】:

    对于原始海报和 Boann:Key Binding with a Swing Timer 示例:

    import java.awt.*;
    import javax.swing.*;
    import java.awt.event.*;
    import java.util.EnumMap;
    import java.util.HashMap;
    import java.util.Map;
    
    public class GamePanel extends JPanel {
       private static final int ANIMATION_DELAY = 15;
       private final int HEIGHT = 400;
       private final int WIDTH = 600;
       private Square square;
       private EnumMap<Direction, Boolean> dirMap = new EnumMap<>(Direction.class);
       private Map<Integer, Direction> keyToDir = new HashMap<>();
       private Timer animationTimer;
    
       public GamePanel() {
          for (Direction dir : Direction.values()) {
             dirMap.put(dir, Boolean.FALSE);
          }
          keyToDir.put(KeyEvent.VK_UP, Direction.UP);
          keyToDir.put(KeyEvent.VK_DOWN, Direction.DOWN);
          keyToDir.put(KeyEvent.VK_LEFT, Direction.LEFT);
          keyToDir.put(KeyEvent.VK_RIGHT, Direction.RIGHT);
          setKeyBindings();
          setBackground(Color.white);
          setPreferredSize(new Dimension(WIDTH, HEIGHT));
          setFocusable(true);
          square = new Square();
          animationTimer = new Timer(ANIMATION_DELAY, new AnimationListener());
          animationTimer.start();
       }
    
       private void setKeyBindings() {
          int condition = WHEN_IN_FOCUSED_WINDOW;
          final InputMap inputMap = getInputMap(condition);
          final ActionMap actionMap = getActionMap();
          boolean[] keyPressed = { true, false };
          for (Integer keyCode : keyToDir.keySet()) {
             Direction dir = keyToDir.get(keyCode);
             for (boolean onKeyPress : keyPressed) {
                boolean onKeyRelease = !onKeyPress;
                KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, 0,
                      onKeyRelease);
                Object key = keyStroke.toString();
                inputMap.put(keyStroke, key);
                actionMap.put(key, new KeyBindingsAction(dir, onKeyPress));
             }
          }
       }
    
       public void paintComponent(Graphics g) {
          super.paintComponent(g);
          square.display(g);
       }
    
       private class AnimationListener implements ActionListener {
          @Override
          public void actionPerformed(ActionEvent evt) {
             boolean repaint = false;
             for (Direction dir : Direction.values()) {
                if (dirMap.get(dir)) {
                   square.move(dir);
                   repaint = true;
                }
             }
             if (repaint) {
                repaint();
             }
          }
       }
    
       private class KeyBindingsAction extends AbstractAction {
          private Direction dir;
          boolean pressed;
    
          public KeyBindingsAction(Direction dir, boolean pressed) {
             this.dir = dir;
             this.pressed = pressed;
          }
    
          @Override
          public void actionPerformed(ActionEvent evt) {
             dirMap.put(dir, pressed);
          }
       }
    
       private static void createAndShowGUI() {
          GamePanel gamePanel = new GamePanel();
          JFrame frame = new JFrame("GamePanel");
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          frame.getContentPane().add(gamePanel);
          frame.pack();
          frame.setLocationRelativeTo(null);
          frame.setVisible(true);
          gamePanel.requestFocusInWindow();
       }
    
       public static void main(String[] args) {
          SwingUtilities.invokeLater(new Runnable() {
             public void run() {
                createAndShowGUI();
             }
          });
       }
    }
    
    enum Direction {
       UP(0, -1), DOWN(0, 1), LEFT(-1, 0), RIGHT(1, 0);
       private int incrX;
       private int incrY;
    
       private Direction(int incrX, int incrY) {
          this.incrX = incrX;
          this.incrY = incrY;
       }
    
       public int getIncrX() {
          return incrX;
       }
    
       public int getIncrY() {
          return incrY;
       }
    }
    
    class Square {
       private int x = 0;
       private int y = 0;
       private int w = 20;
       private int h = w;
       private int step = 1;
       private Color color = Color.red;
       private Color fillColor = new Color(255, 150, 150);
       private Stroke stroke = new BasicStroke(3f, BasicStroke.CAP_ROUND,
             BasicStroke.JOIN_ROUND);
    
       public void display(Graphics g) {
          Graphics2D g2d = (Graphics2D) g.create();
          g2d.setColor(fillColor);
          g2d.fillRect(x, y, w, h);
          g2d.setStroke(stroke);
          g2d.setColor(color);
          g2d.drawRect(x, y, w, h);
          g2d.dispose();
       }
    
       public void setStep(int step) {
          this.step = step;
       }
    
       public void move(Direction dir) {
          x += step * dir.getIncrX();
          y += step * dir.getIncrY();
       }
    }
    

    【讨论】:

    • 是的,你是对的,我现在明白你的意思了。我没有意识到键绑定可以单独跟踪新闻和发布事件(显示我避免它们的程度)。我一直认为只有 KeyListener 可以做到这一点。
    【解决方案3】:

    这里的问题有两个。您正在将关键侦听器添加到 new SideScroller - 与您正在渲染的不同,并且您没有覆盖正确的绘画方法。

    我建议你将addKeyListener(this);添加到SideScroller的构造函数中并删除f.addKeyListener(new SideScroller());,测试被调用然后测试应该是paintComponent(Graphics g)的paint方法。

    我希望您了解如何执行此操作以及为什么可以识别键但“不”呈现,我认为向初学者提供代码不是一个好主意。

    【讨论】:

      【解决方案4】:

      您正在创建三个不同的 SideScroller 对象。一个未使用,第二个添加到框架并显示,第三个仅用作 KeyListener(这是调用 repaint() 方法的那个)。您放入 ss 变量的 SideScroller 也是您应该添加到框架中并用作 KeyListener 的那个。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-02-06
        • 2021-11-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多