【问题标题】:Java Keyboard input not working using KeyBindings - selfmade rendering engine at fault?Java 键盘输入无法使用 KeyBindings - 自制渲染引擎出现故障?
【发布时间】:2015-01-07 00:58:54
【问题描述】:

因此,近一年来,我一直致力于从头开始构建自己的 2D 游戏(“StarGame”)。
起初,这些图形只不过是一些具有不同颜色的 AWT 矩形/多边形,但最近,我决定转向适当的图形,我的一个朋友愿意为我创建一些复古风格的图像。 到目前为止,一切顺利。

问题是:由于我切换到正确的图形,我的游戏不再识别键盘输入。 我当时使用 KeyListener 作为输入,在寻找解决方案时,我认为唯一有意义的事情是从 KeyListener 移动到 KeyBindings
所以我就这么做了,无济于事

我的调试只能做到这一点:

按键输入在主菜单中起作用。
关键输入可在学分屏幕上使用。
当游戏画面上的任何内容被绘制到屏幕上时,按键输入不起作用

我的游戏运行如下:

public static void main(String args[]) {
    Game game = new Game();
    game.mainMenu();
}

Game的构造函数初始化了很多变量,重要的部分:

public Game() {
    // ...
    window = new JFrame("StarGame Beta "+version);
    menuPanel = new JPanel();
    gamePanel = new JPanel();
    mainMenu = new JLabel(new ImageIcon("gifs/mainMenuBG.gif"));
    credits = new JLabel(new ImageIcon("gifs/credits.gif"));

    window.addWindowListener(windowAdapter);
    window.setBounds(new Rectangle(WIDTH, HEIGHT));
    window.setResizable(false);
    window.setFocusable(true);
    window.setLocationRelativeTo(null);

    menuPanel.setFocusable(true);  // NEW, didn't fix it!
    gamePanel.setFocusable(true);  // NEW, didn't fix it!

    setTheKeyBindings();

    setNewGameState(MAIN_MENU);

    gameRenderer = new GameRenderer(gamePanel);
}

setTheKeyBindings() 顾名思义,摘录:

    InputMap menuInputMap = menuPanel.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
    menuInputMap.put(KeyStroke.getKeyStroke("S"), "runGame");
    ActionMap menuActionMap = menuPanel.getActionMap();
    menuActionMap.put("runGame", new AbstractAction() {
        @Override
        public void actionPerformed(ActionEvent e)
        {
            runGame();
        }
    });

runGame() 方法停止菜单音乐播放并触发 startGame() 方法:

private void runGame()
    {
        menuOggClip.stop();
        System.out.println("Running game");

        startGame();
        stopGame();

        menuOggClip.loop();
        showMainMenu();
    }

startGame() 将 JPanel 游戏面板添加到 JFrame 窗口并添加游戏内物品和音乐:

public void startGame()
{
    window.remove(menuPanel);
    if(gameRenderer == null) {
        gameRenderer = new GameRenderer(gamePanel);
    }
    window.add(gamePanel);
    // See showMainMenu() for explanation
    SwingUtilities.updateComponentTreeUI(window);

    gameRenderer.add(ship);
    for (int i = 0; i < 21; i++) {
        rocks[i] = new Rock();
        gameRenderer.add(rocks[i]);
    }
    for (int i = 0; i < 4; i++) {
        aliens[i] = new Alien();
        gameRenderer.add(aliens[i]);
    }
    //gameRenderer.setFirstRun(false);

    shotSound = new ShotSound();
    shotSoundThread = new Thread(shotSound);

    bgMusic = new BackgroundMusic();
    bgMusicThread = new Thread(bgMusic);

    setNewGameState(INTRO);
    System.out.println("Game initialized!");

    gamePanel.requestFocus();

    game();
}    

有一个 gameState 变量可以跟踪游戏当前所处的“状态”,例如。 G。 MAIN_MENU 或 GAME_RUNNING。主要的游戏逻辑是一个带有开关的 while(true) 循环,该开关根据游戏状态确定要做什么:

       case GAME_RUNNING:
            gamePanel.requestFocus(); // this is just a failsafe to make sure the game stays in focus

            gameRenderer.remove(laser);

            if (!didICrash(ship.getShipRect())) { // if the player didn't crash, move the rocks/asteroids a bit to the left -> it seems like the ship is moving right.
                for (int i = 0; i < 21; i++) {
                    rocks[i].tick();
                }
                for (int i = 0; i < 4; i++) {
                    if (aliens[i].tick()) { // if the alien got to the left of the screen and respawned, add it again
                        gameRenderer.add(aliens[i]);
                    }
                    if (!aliens[i].isVisible()) { // if the alien is invisible, remove it from drawing queue
                        gameRenderer.remove(aliens[i]);
                    }
                }
                addToDistance(1);
            } else {
                setNewGameState(CRASHED);
            }

            // Animation
            repaint();

            timeDiff = System.currentTimeMillis() - beforeTime;
            sleep = sleepMax - timeDiff;
            if (sleep < 0) {
                sleep = 0;
            }

            try {
                Thread.sleep(sleep);
            }
            catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }
            break;

每个时间/帧,该循环调用 repaint() 方法,该方法又调用三个方法 clearScreen()draw()drawToScreen() 在这个类中:

package Game;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JPanel;


public class GameRenderer {

    public JPanel gamePanel;
    public List<IDrawObject> listDOs;

    private BufferedImage completeImage;
    private Graphics2D g;

    private boolean firstRun = true;


    public GameRenderer(JPanel gamePanel) {
        this.gamePanel = gamePanel;
        listDOs=new ArrayList<>();
        completeImage = new BufferedImage(Game.WIDTH,Game.HEIGHT,BufferedImage.TYPE_INT_RGB);
        g = completeImage.createGraphics();
    }


    public void clearScreen() {
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, Game.WIDTH, Game.HEIGHT);
    }


    public void draw() {
        for(int i = 0; i < listDOs.size(); i ++) {
            listDOs.get(i).draw(g);
        }
    }

    /*
     * Essential method, called last in repaint(): Draws the entire image to the screen.
     */
    public void drawToScreen() {
        Graphics g2 = gamePanel.getGraphics();
        g2.drawImage(completeImage, 0, 0, Game.WIDTH, Game.HEIGHT, null);
    }


    public void add(IDrawObject ido) {
        listDOs.add(ido);
    }
    public void remove(IDrawObject ido) {
        listDOs.remove(ido);
    }
    public JPanel getGamePanel() {
        return gamePanel;
    }
    public void setFirstRun(boolean firstRun) {
        this.firstRun = firstRun;
    }
}

经过更多调试后,我发现即使没有在循环中调用三种绘图方法中的任何一种,键盘输入也不起作用。

那么问题来了:

如何让我的键盘输入再次起作用?为什么它不起作用?

非常感谢您的帮助。提前致谢!

【问题讨论】:

  • 您显示的代码片段没有显示您 GamePanel 上安装的任何 KeyBinding,我猜这将是游戏运行时窗口中唯一的面板?此外,默认情况下,JPanel 可能不会响应 KeyListener(可能还有 KeyBinding),因为它不可聚焦(请参阅此处stackoverflow.com/questions/11487369)。尝试使用 setFocusable(true) 使面板具有焦点,并确保将 KeyBinding 添加到最顶部的相关容器中。
  • @Durandal 感谢您的提示。我已将 gamePanel.setFocusable(true) 添加到 Game() 构造函数中,但这并没有帮助。键盘仍然只能在主菜单中使用。在gamePanel上安装KeyBinding是什么意思,最顶层的容器是什么?我的 JFrame(我叫我的窗口)?
  • 希望每个新用户都能提出这样的问题。
  • 我不确定您是如何使用面板的,我错过了您实际将面板添加到窗口的地方;而且我不确定您是否有一个或多个键绑定(每个面板一个?) - “最顶层”是指将绑定添加到最外面的封闭容器,这 可能 是窗口(或它的 ContentPane),具体取决于您是在所有“状态”中使用一个绑定还是每个状态一个绑定。您能否在实际将面板添加到窗口的位置添加代码?
  • @Durandal 我添加了方法 runGame() 和 startGame() 以进一步澄清。 runGame() 只是暂停菜单音乐并调用 startGame(); startGame() 很有趣,因为它将 JPanel gamePanel 添加到 JFrame 窗口中,请参阅上面我更新的帖子。我有多个 KeyBindings 用于不同的键,如 S、C、ESCAPE 和 UP/DOWN。我通过将它们添加到相关的 JPanel 来区分每个状态的绑定;例如在 GAME_RUNNING 状态下,menuPanel 会从 JFrame 窗口中移除。

标签: java keyboard key-bindings 2d-games


【解决方案1】:

我尝试在简化的测试工具中应用 KeyBindings。一般模式,在面板级别附加绑定和切换面板与您在代码中演示的相同。我遇到了几种绑定不起作用的情况,都是基于使用哪个工厂方法来创建 KeyStroke

令人惊讶的是不起作用getKeyStroke("s"),它必须是getKeyStroke("S")

您可能想要调整 switchPanel() 方法,可能是在某些时候,您有多个面板附加到您的窗口(您可能不会看到,因为您正在积极渲染)与冲突的键绑定。通过简单地移除附着在框架上的任何东西,它可以确保始终只有一个活动面板。

除此之外,从您的所有代码中,我看不出它有任何不起作用的原因。

测试样本:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;

public class KeyBindingTest {

    JFrame window = new JFrame("Demo");

    static class DemoPanel extends JPanel {
        private String text;

        public DemoPanel(String text) {
            this.text = text;
            setBounds(0, 0, 200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(Color.BLACK);
            g.drawString(text, 50, 50);
        }
    }

    JPanel menuPanel = new DemoPanel("S to Start");
    JPanel gamePanel = new DemoPanel("S to Stop");

    public static void main(String[] argv) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                KeyBindingTest game = new KeyBindingTest();
                game.start();
            }
        });
    }

    public void start() {
        setup();
        window.setVisible(true);
    }

    private void setup() {
        window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        window.setBounds(50, 50, 200, 200);
        window.setLayout(null);
        window.add(menuPanel);
        bind(menuPanel, KeyStroke.getKeyStroke("S"), "start",
                new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                switchPanel(window, gamePanel);
                System.out.println("Start!");
            }
        });
        bind(gamePanel, KeyStroke.getKeyStroke("S"), "stop",
                new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                switchPanel(window, menuPanel);
                System.out.println("Stop!");
            }
        });
    }

    private void switchPanel(JFrame window, JComponent panel) {
        window.getContentPane().removeAll();
        window.add(panel);
        panel.revalidate();
        panel.repaint();
    }

    private static void bind(JComponent component, KeyStroke key, String string, Action action) {
        component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(key, string);
        component.getActionMap().put(string, action);
    }

}

【讨论】:

  • 感谢您的详细解答。我已经实现了 switchPanel() 方法并修改了我的代码以使其正常工作。但是,这并没有解决我的问题。我认为值得注意的是,即使是我用来在用户关闭窗口时调用 exit() 方法的 WindowAdapter 在游戏开始时也停止工作,而当用户返回主菜单时一切都恢复正常(我必须模拟显然使用代码)。
猜你喜欢
  • 1970-01-01
  • 2011-02-26
  • 1970-01-01
  • 1970-01-01
  • 2013-06-08
  • 2014-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多