java - 主动渲染时按键监听器不工作

标签 java swing graphics

我正在尝试用 Java 编写一个非常简单的平台游戏。我有一部分使用被动渲染(使用 Timer 对象调用 repaint()revalidate() 等),但我已经一直在尝试实现主动渲染。它有点有效 - 因为它可以渲染,动画也可以工作,但由于某种我不太明白的原因,它似乎阻止了关键的监听器(之前工作得很好)。

我已在下面尽可能少地重现了该问题。当你按下一个键时,应该有终端输出,但是却没有。如果有人能告诉我为什么 keyPressed 等方法没有触发,我将不胜感激。

编辑 - 根据请求将演示代码修改为一份复制/粘贴

Edit2 - 按照安德鲁·汤普森的建议,我已经删除了所有全屏代码,并且按键监听器仍然无法工作

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;

class FullScreenRenderWithListener implements KeyListener {

   private JFrame frame;
   private World  world;

   public static void main(String[] args)
   {
      FullScreenRenderWithListener main = new FullScreenRenderWithListener();
      SwingUtilities.invokeLater(main::run);
   }

   private void run()
   {
      initWindow();
      setupWorld();
      frame.setIgnoreRepaint(true);
      frame.pack();
      frame.createBufferStrategy(2);
      frame.setVisible(true);
      world.startActive(frame.getBufferStrategy());
   }

   private void initWindow()
   {
      frame = new JFrame();
      frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
      frame.setLocationByPlatform(true);
   }

   private void setupWorld()
   {
      world = new World();
      frame.addKeyListener(this);
      frame.add(world);
      world.addKeyListener(this);
   }

   @Override
   public void keyPressed(KeyEvent event)
   {
      System.out.println("Pressed");
   }

   @Override
   public void keyReleased(KeyEvent event)
   {
      System.out.println("Released");
   }

   @Override
   public void keyTyped(KeyEvent event)
   {
      System.out.println("Typed");
   }
}

class World extends JPanel {

   private static final int FRAMES_PER_SEC = 60;
   private static final int MILL_IN_SEC    = 1000;
   private static final int TICK_LENGTH    =
      MILL_IN_SEC / FRAMES_PER_SEC;
   private BufferStrategy strategy;

   private void sleepUntilEndOfFrame()
   {
      try {
         long used = System.currentTimeMillis() % TICK_LENGTH;
         long left = TICK_LENGTH - used;
         Thread.sleep(left);
      } catch(InterruptedException e) {
         // ... Handle this error
      }
   }

   public void startActive(BufferStrategy strategy)
   {
      this.strategy = strategy;
      setIgnoreRepaint(true);
      while(true) {
         doFrame();
      }
   }

   private void doFrame()
   {
      updateGameState();
      activeRenderFrame();
   }

   private void updateGameState()
   {
      // ..
   }

   private void activeRenderFrame()
   {
      Graphics2D graphicsContext = (Graphics2D)strategy
         .getDrawGraphics();
      paintComponent(graphicsContext);
      strategy.show();
      Toolkit.getDefaultToolkit().sync();
      graphicsContext.dispose();
      sleepUntilEndOfFrame();
   }

   @Override
   public Dimension getPreferredSize()
   {
      return new Dimension(500, 500);
   }


   // Have overridden this method because the class
   // also implements passive rendering if active is
   // not supported
   @Override
   public void paintComponent(Graphics g)
   {
      super.paintComponent(g);
      // .. drawing code
   }
}

最佳答案

第一个问题是不要考虑使用KeyBinding策略来代替KeyListener,特别是当您使用JPanel时。您可以通过注释无限 while 循环(禁用 Activity 渲染)来检查这一点。

当您使用KeyListener时,无论是否使用主动渲染进程,都不会触发KeyEvent(或者至少我们可以说我们的监听器没有被调用)。

使用keyBinding将解决这个问题。

但是当您取消注释无限 while 循环时,问题会再次出现。那么什么可以解决这个问题呢?用于更新框架的新线程是关键!

检查您的程序,该程序由 KeyBinding 和新的 Thread 增强,用于使用主动渲染策略更新帧:

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferStrategy;
import java.util.HashMap;
import java.util.Map;

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 FullScreenRenderWithListener implements Runnable
{

    private JFrame frame;
    private World world;

    public static void main ( String[] args )
    {
        FullScreenRenderWithListener main = new FullScreenRenderWithListener ();
        SwingUtilities.invokeLater ( main );
    }

    public void run ()
    {
        initWindow ();
        setupWorld ();
        frame.setIgnoreRepaint ( true );
        frame.pack ();
        frame.createBufferStrategy ( 2 );
        frame.setVisible ( true );
        world.startActive ( frame.getBufferStrategy () );
    }

    private void initWindow ()
    {
        frame = new JFrame ();
        frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
        frame.setLocationByPlatform ( true );
    }

    private void setupWorld ()
    {
        world = new World ();
        frame.add ( world );
        frame.setFocusable ( true );
        world.setFocusable ( true );
    }
}

class World extends JPanel
{

    private static final int FRAMES_PER_SEC = 10;
    private static final int MILL_IN_SEC = 1000;
    private static final int TICK_LENGTH = MILL_IN_SEC / FRAMES_PER_SEC;
    private BufferStrategy strategy;
    //
    private static final String PRESSED = "Pressed";
    private static final String RELEASED = "Released";
    private Map < Direction , Boolean > directionMap = new HashMap < Direction , Boolean > ();

    private void sleepUntilEndOfFrame ()
    {
        try
        {
            long used = System.currentTimeMillis () % TICK_LENGTH;
            long left = TICK_LENGTH - used;
            Thread.sleep ( left );
        }
        catch ( InterruptedException e )
        {
            // ... Handle this error
            e.printStackTrace ();
        }
    }

    private void setBindings() {
          int context = JComponent.WHEN_IN_FOCUSED_WINDOW;
          InputMap inputMap = getInputMap(context);
          ActionMap actionMap = getActionMap();

          for (Direction direction : Direction.values()) {
             inputMap.put(KeyStroke.getKeyStroke(direction.getKeyCode(), 0, false), direction.getName() + PRESSED);
             inputMap.put(KeyStroke.getKeyStroke(direction.getKeyCode(), 0, true), direction.getName() + RELEASED);

             // set corresponding actions for the  key presses and releases above
             actionMap.put(direction.getName() + PRESSED, new ArrowKeyAction(true, direction));
             actionMap.put(direction.getName() + RELEASED, new ArrowKeyAction(false, direction));
          }
    }

    public void startActive ( BufferStrategy strategy )
    {
        for ( Direction direction : Direction.values () )
        {
            directionMap.put ( direction , Boolean.FALSE );
        }
        setBindings ();
        //
        this.strategy = strategy;
        setIgnoreRepaint ( true );
        Thread t = new Thread (){
            @Override
            public void run ()
            {
                while ( true )
                {
                    doFrame ();
                }
            }
        };
        t.start ();
    }

    private void doFrame ()
    {
        updateGameState ();
        activeRenderFrame ();
    }

    private void updateGameState ()
    {
        // ..
    }

    private void activeRenderFrame ()
    {
        Graphics2D graphicsContext = (Graphics2D) strategy.getDrawGraphics ();
        paintComponent ( graphicsContext );
        strategy.show ();
        Toolkit.getDefaultToolkit ().sync ();
        graphicsContext.dispose ();
        sleepUntilEndOfFrame ();
    }

    @Override
    public Dimension getPreferredSize ()
    {
        return new Dimension ( 500 , 500 );
    }

    // Have overridden this method because the class
    // also implements passive rendering if active is
    // not supported
    @Override
    public void paintComponent ( Graphics g )
    {
        super.paintComponent ( g );
        // .. drawing code
    }

    private class ArrowKeyAction extends AbstractAction
    {
        private Boolean pressed;
        private Direction direction;

        public ArrowKeyAction ( boolean pressed , Direction direction )
        {
            this.pressed = Boolean.valueOf ( pressed );
            this.direction = direction;
        }

        @Override
        public void actionPerformed ( ActionEvent arg0 )
        {
            directionMap.put ( direction , pressed );
            System.out.println ("Direction: "+ direction + ", State: " + pressed);
        }
    }
}

enum Direction {
   UP("Up", KeyEvent.VK_UP, new Point(0, -1)),
   DOWN("Down", KeyEvent.VK_DOWN, new Point(0, 1)),
   LEFT("Left", KeyEvent.VK_LEFT, new Point(-1, 0)),
   Right("Right", KeyEvent.VK_RIGHT, new Point(1, 0));

   private String name;
   private int keyCode;
   private Point vector;
   private Direction(String name, int keyCode, Point vector) {
      this.name = name;
      this.keyCode = keyCode;
      this.vector = vector;
   }
   public String getName() {
      return name;
   }
   public int getKeyCode() {
      return keyCode;
   }
   public Point getVector() {
      return vector;
   }
   @Override
   public String toString() {
      return name;
   }
}

此示例仅为箭头键绑定(bind)一些KeyBinding。您还可以通过更改 setBindings 方法来检查其他键。此外,您可能想为其他键定义更多的 Event 和另一个 enum

希望这有帮助。

关于java - 主动渲染时按键监听器不工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37511071/

相关文章:

java - 如何捕获 "Unable to sendViaPost to url"?

java - JMenu Action 监听器

使用drawImage和imageSmoothing禁用Javascript Canvas不完美像素

java - Android:如何将位图的百分比设为黑白,其余部分保留颜色?

java - 为什么我应该避免使用 Java 标签语句?

java - iText 7 - 将变量值放入pdf文本

java - JTable 已填充但未显示。使用默认表模型

java - 我不知道如何将 Jbutton 返回到调用它的前一个方法

c++ - QGraphicsItem::paint():如何检查 QGraphicsScene 是否被打印

java - 如何在 Java 中旋转屏幕组件?