So I've been working on building my own 2D game ("StarGame") from scratch for nearly 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 move to proper graphics and a friend of mine was willing to create some retro-styled images for me.
So far, so good.
The thing is: since I switched to proper graphics, my game does not recognize keyboard input anymore.
I was using KeyListener for the input at that time and when searching for a solution, the only thing that I thought made sense was to move from KeyListener to KeyBindings.
So I did that, at no avail.
My debugging has only gotten me as far as this:
Key input works in the main menu.
Key input works on the credits screen.
Key input does not work when anything is drawn onto the screen at the game screen.
My game works as follows:
public static void main(String args[]) {
Game game = new Game();
game.mainMenu();
}
The constructor for Game initializes lots of variables, the important part:
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 its 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 method runGame() stops menu music playback and fires 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 window and adds the in-game items and music:
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 gameState variable that keeps track of what "state" the game currently is in, e. g. MAIN_MENU or GAME_RUNNING. The main game logic is a while(true) loop with a switch that determines what to do based on the game state:
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 time/frame, that loop calls the repaint() method which in turn calls the three methods clearScreen(), draw() and drawToScreen() 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 more debugging I found out that even without any of the three drawing methods being called in the loop, keyboard input doesn't work.
So here's the question:
How do I make my keyboard input work again and why is it not working?
I would seriously appreciate your help. Thanks in advance!
I tried to apply KeyBindings in a simplified test harness. The general pattern, attaching the binding at the panel level and switching panels was the same as you demonstrate in your code. I encountered several cases where the binding would not work, all based on which factory method was used to create the KeyStroke.
What did surprisingly not work was
getKeyStroke("s")
, it had to begetKeyStroke("S")
.You might want to adapt the switchPanel() approach, it might be that at some point you have multiple panels attached to your window (which you probably would not see, since your actively rendering) with conflicting key bindings. By simply removing whatever is attached to the frame, it makes sure that there is always only the one active panel.
Other than that, from all your code, I don't see any reason why it would not work.
Testing Sample: