Java Keyboard input not working using KeyBindings - is the homemade rendering engine faulty?

So, I've been working on building my own 2D game ("StarGame") from scratch for almost a year now. At first, the graphics were nothing more than some AWT rectangles / polygons with different colors, but recently I decided to make the transition to proper graphics and my friend was ready to create some retro styled images for me. So far so good.

The thing is, since I switched to the correct graphics, my game no longer recognizes keyboard input. I was using a KeyListener for input at the time and while looking for a solution , the only thing that made sense in my opinion was to go from KeyListener to KeyBindings.
So I did it to no avail.

My debugging just got to me:

Key input works in the main menu.
The enter key works on the credits screen.
Key entry does not work when the screen displays anything on screen.

My game works like this:

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

      

The constructor for the game initializes a lot of variables, the important part is:

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 () does, as the name suggests, an excerpt:

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

      

The runGame () method stops the music in the menu and starts the startGame () method:

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

        startGame();
        stopGame();

        menuOggClip.loop();
        showMainMenu();
    }

      

startGame () adds the JPanel gamePanel to the JFrame and adds elements and music to the game:

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

      

There is a variable gameState that keeps track of what "state" the game is currently in, for example. MAIN_MENU or GAME_RUNNING. The main game logic is a while (true) loop with a switch that determines what to do depending on the state of the game:

       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;

      

Every moment / frame, this loop calls the repaint () method, which in turn calls the three clearScreen (), draw () and drawToScreen () methods in this class:

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

      

After some additional debugging, I found out that even without any of the three drawing methods being called in the loop, keyboard input does not work.

So here's the question:

How to make the keyboard work again and why is it not working?

I would really appreciate your help. Thanks in advance!

+3


source to share


1 answer


I tried to apply KeyBindings in a simplified test harness. The general drawing attaching panel and toggle bar level anchoring was the same as in your code. I ran into several cases where binding would not work, all based on which a factory method was used to create the KeyStroke.

What surprisingly didn't work getKeyStroke("s")

, it should have been getKeyStroke("s")

.

You might want to adapt the switchPanel () approach, perhaps at some point you have multiple panels attached to your window (which you probably won't see from your active rendering) with conflicting key bindings. By simply removing whatever is attached to the frame, it ensures that there is always only one active panel.



Other than that, from all of your code, I see no reason why it won't work.

Testing example:

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

}

      

+1


source







All Articles