Non-responsive event listener

Started by
6 comments, last by boogyman19946 10 years, 6 months ago

I've been having problems getting any kind of input from the window (or anything really) after I start the game. Here's my problem:

I start off the game at the menu and add a key listener and a window listener to the window. So long as I stay in the menu, it works, but as soon as I switch to the panel that draws the game, the listeners all become unresponsive.

I thought the window removes the listeners when I remove the menu panel to replace it with the game panel, however, after I made sure to add the listeners again after the switch, it's still no go. On the other hand, if I switch to the game panel, and then make the game switch back immediately to the menu panel, the listeners all work again. I'm out of ideas at this point. I tried implementing key bindings, but that didn't help, the results were still the same.

Here are the four main classes that are involved:

Game class - drives core mechanics


package Core.Game;
 
import Core.Menu.MainMenu;
import Core.Menu.PauseMenu;
import Entity.EntityFactory;
import Entity.Systems.AnimationSystem;
import Entity.Systems.ControlSystem;
import Entity.Systems.DrawingSystem;
import Entity.Systems.MotionSystem;
import Graphics.GameCanvas;
import Graphics.GameWindow;
import Input.KeyManager;
import Level.Level;
import Level.LevelLoadingScript;
import Level.World;
import Menu.MenuStack;
import Util.GameTimer;
import java.awt.Color;
import java.awt.event.KeyEvent;
 
public class Game {
   private GameWindow mWindow;
 
   private MenuStack mMenuStack;
 
   private boolean mPaused;
   private boolean mQuit;
 
   private EntityFactory mEntityFactory;
   private Level mLevel;
   private World mWorld;
 
   private GameTimer mFrameTimer;
 
   public Game() {
      mWindow = new GameWindow("Digestion");
      mEntityFactory = new EntityFactory();
      mFrameTimer = new GameTimer();
      mFrameTimer.setTimeInterval( (1/30)*1000 /* 33 millisecond */ );
      mLevel = new Level();
      mWorld = new World();
      mPaused = true;
      setupMenuStack();
   }
 
   private void setupMenuStack() {
      mMenuStack = new MenuStack();
      mWindow.switchTo(mMenuStack);
      MainMenu mainMenu = new MainMenu(this, mMenuStack);
      mainMenu.setBackground(Color.BLACK);
      mMenuStack.pushScreen(mainMenu);
   }
 
   public void startLevel(LevelLoadingScript loadingScript) {
      loadingScript.loadLevel(mLevel);
      loadingScript.createEntities(mEntityFactory, mWorld);
 
      mQuit = false;
 
      GameCanvas canvas = new GameCanvas();
      mWindow.switchTo(canvas);
      execute(canvas);
   }
 
   public void pause() { 
      mPaused = true;
      PauseMenu pauseMenu = new PauseMenu(this, mMenuStack);
      pauseMenu.display();
   }
 
   public void resume() {
      mPaused = false;
   }
 
   public void unpause() { 
      if(mLevel == null)
         return;
 
      mPaused = false;
      mFrameTimer.reset();
   }
 
   private void execute(GameCanvas canvas) {
      unpause();
 
      boolean escProcessed = false;
      while(!mQuit) {
         if(mPaused) {
            if(KeyManager.isKeyPressed(KeyEvent.VK_ESCAPE) && !escProcessed) {
               escProcessed = true;
               resume();
            }
         } else {
            ControlSystem.manipulate(mWorld);
            MotionSystem.move(mWorld);
            AnimationSystem.animate(mWorld);
 
            if(mFrameTimer.hasTimeIntervalPassed()) {
               DrawingSystem.draw(mWorld, canvas);
               canvas.showCanvas();
               mWindow.update();
               mFrameTimer.reset();
            }
 
            if(KeyManager.isKeyPressed(KeyEvent.VK_ESCAPE) && !escProcessed) {
               escProcessed = true;
               pause();
            }
         }
 
         if(!KeyManager.isKeyPressed(KeyEvent.VK_ESCAPE) && escProcessed)
            escProcessed = false;
      }
 
      mPaused = true;
 
      mWindow.switchTo(mMenuStack);
   }
}

GameWindow


package Graphics;
 
import Core.Game.GameWindowListener;
import Input.KeyManager;
import Util.ErrorLog;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;
 
public class GameWindow {
   private JFrame mWindow;
 
   private GameWindowListener mWindowListener;
   private KeyManager mKeyDispatcher;
 
   private GraphicsDevice mFullscreenDevice;
   private BufferStrategy mBackBuffer;
 
   private Component mDisplayedItem;
 
   private boolean mFullscreen;
   private int mWidth;
   private int mHeight;
 
   public GameWindow(String title) {
      setupWindow(title);
      loadSettings();
      mWindowListener = new GameWindowListener();
      mKeyDispatcher = new KeyManager();
   }
 
   private void setupWindow(String title) {
      mWindow = new JFrame(title);
      mWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      mWindow.setVisible(true);
      setupBackBuffer();
   }
 
   private void setupBackBuffer() {
      mWindow.createBufferStrategy(2);
      mBackBuffer = mWindow.getBufferStrategy();
   }
 
   private void loadSettings() {
      mFullscreenDevice = null;
      mFullscreen = false;
      setSize(800, 600);
   }
 
   public void switchFullscreen() {
      mFullscreen = !mFullscreen;
 
      if(mFullscreen) {
         switchToFullscreen();
      } else {
         switchToWindow();
      }
   }
 
   private void switchToFullscreen() {
      GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
 
      mFullscreenDevice = env.getDefaultScreenDevice();
      if(mFullscreenDevice.isFullScreenSupported()) {
         mFullscreenDevice.setFullScreenWindow(mWindow);
         mFullscreen = true;
      }
 
      if(!mFullscreen) {
         ErrorLog errorLog = ErrorLog.getInstance();
         errorLog.displayMessageDialog("It seems there are no screens that support fullscreen mode.\nRemaining in Windowed mode");
      }
   }
 
   private void switchToWindow() {
      mFullscreenDevice.setFullScreenWindow(null);
      mFullscreenDevice = null;
 
      mFullscreen = false;
   }
 
   public boolean isFullscreen() {
      return mFullscreen;
   }
 
   public int getWidth() {
      return mWidth;
   }
 
   public int getHeight() {
      return mHeight;
   }
 
   public void setSize(int width, int height) {
      if(mFullscreen) {
         setFullscreenSize(width, height);
      } else {
         setWindowedSize(width, height);
      }
   }
 
   private void setFullscreenSize(int width, int height) {
      mWidth = width;
      mHeight = height;
      mWindow.setSize(width, height);
   }
 
   private void setWindowedSize(int width, int height) {
      Insets insets = mWindow.getInsets();
 
      mWidth = width + insets.left + insets.right;
      mHeight = height + insets.top + insets.bottom;
      mWindow.setSize(width, height);
   }
 
   public void switchTo(Component component) {
      if(mDisplayedItem != null)
         mWindow.remove(mDisplayedItem);
      mWindow.add(component);
      mDisplayedItem = component;
 
      updateListeners();
 
      update();
   }
 
   private void updateListeners() {
      mWindow.addWindowListener(mWindowListener);
 
      KeyboardFocusManager
      .getCurrentKeyboardFocusManager()
      .addKeyEventDispatcher(mKeyDispatcher);
   }
 
   public void update() {
      Graphics g = mBackBuffer.getDrawGraphics();
      mWindow.paintAll(g);
      g.dispose();
      mBackBuffer.show();
   }
}
 

GameCanvas


package Graphics;
 
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import javax.swing.JPanel;
 
public class GameCanvas extends JPanel implements CanvasInterface { 
   private GameViewport mViewport;
   private boolean mViewportEnabled;
 
   private ImageQueue mImageQueue;
 
   public GameCanvas() {
      mViewport = new GameViewport();
      mViewportEnabled = false;
 
      mImageQueue = new ImageQueue();
   }
 
 
   public void setViewport(GameViewport viewport) { 
      mViewport = viewport;
      mViewportEnabled = true;
   }
 
   @Override
   public ColorMode setGraphicsMode(ColorMode newMode) {
      return mImageQueue.setMode(new ColorMode(newMode));
   }
 
   @Override
   public void drawImage(ImageItem imageItem) {
      mImageQueue.addImage(imageItem);
   }
 
   @Override
   public void drawImage(Image image, int x, int y, int z, int width, int height) {
      ImageItem imageItem = new ImageItem();
      imageItem.image = image;
      imageItem.x = x;
      imageItem.y = y;
      imageItem.z = z;
      imageItem.width = width;
      imageItem.height = height;
 
      mImageQueue.addImage(imageItem);
   }
 
   @Override
   public void showCanvas() { 
      repaint();
   }
 
   private void drawItems(Graphics2D g) {
      if(!mViewportEnabled) {
         ImageItem imageItem;
         while(mImageQueue.hasImages()) {
            imageItem = mImageQueue.nextImage(g);
            g.drawImage(imageItem.image, 
                     imageItem.x, 
                     imageItem.y, 
                     imageItem.width, 
                     imageItem.height, 
                     null);
         }
      } else {
         mViewport.update();
 
         Rectangle objectRect = new Rectangle();
         ImageItem imageItem;
         while(mImageQueue.hasImages()) {
            imageItem = mImageQueue.nextImage(g);
            objectRect.x = imageItem.x;
            objectRect.y = imageItem.y;
            objectRect.width = imageItem.width;
            objectRect.height = imageItem.height;
 
            if(!mViewport.contains(objectRect))
               continue;
 
            mViewport.translate(imageItem.x, imageItem.y, imageItem.width, imageItem.height, objectRect);
            g.drawImage(imageItem.image,
                     objectRect.x,
                     objectRect.y,
                     objectRect.width,
                     objectRect.height,
                     null);
         }
      }
   }
 
   @Override
   public void paint(Graphics g) {
      mImageQueue.sort();
 
      drawItems((Graphics2D) g);
   }
}
 

MenuStack - Switches between menu screens (this is the menu panel)


package Menu;
 
import java.awt.BorderLayout;
import java.awt.Color;
import java.util.LinkedList;
import javax.swing.JPanel;
 
public class MenuStack extends JPanel {
   private LinkedList<MenuScreen> mScreenStack;
 
   public MenuStack() {
      mScreenStack = new LinkedList<>();
      setLayout(new BorderLayout());
   }
 
   public void pushScreen(MenuScreen screen) {
      if(!mScreenStack.isEmpty()) 
         remove(mScreenStack.getLast());
      mScreenStack.add(screen);
 
      add(mScreenStack.getLast(), BorderLayout.CENTER);
      revalidate();
      repaint();
   }
 
   public void popScreen() {
      if(mScreenStack.isEmpty())
         return;
 
      remove(mScreenStack.getLast());
      mScreenStack.removeLast();
 
      add(mScreenStack.getLast(), BorderLayout.CENTER);
      repaint();
   }
 
   public MenuScreen currentScreen() {
      return (MenuScreen)getComponent(getComponentCount()-1);
   }
}
 

Sorry to post so much code. Thanks for any help.

Edit: fixed all the code formatting. That was weird.

Yo dawg, don't even trip.

Advertisement

Alright guys, I solved it, I figured out what's going on. It turns out that I was starting the game by running it in the event thread which means the thread was busy drawing the game to the screen instead of dispatching events. Wow, I can't believe I actually realized that this was happening >.> lol Getting better every day I guess ^.^

Yo dawg, don't even trip.

That's the kind of debugging that means you're "really" learning you to program. I looked over the code yesterday, but was at work and couldn't run it. For my own curiosity, what did you change to fix it?

I think, therefore I am. I think? - "George Carlin"
My Website: Indie Game Programming

My Twitter: https://twitter.com/indieprogram

My Book: http://amzn.com/1305076532

The bug was caused by this piece of code in the SinglePlayerMenu class


public class SinglePlayerMenu extends MenuScreen implements ActionListener {

   /* Other Code */

   @Override
   public void actionPerformed(ActionEvent e) {
      String action = e.getActionCommand();
      
      if(action.compareTo(ACTION_START_GAME) == 0) {
         // Return stack to the Main Menu
         mStack.popScreen();
         
         // This function enters the game loop and never returns.
         mGame.startLevel(mLevelMenu.getSelectedLevel());
      } else if(action.compareTo(ACTION_CHOOSE_LEVEL) == 0) {
         mStack.pushScreen(mLevelMenu);
      } else if(action.compareTo(ACTION_CHOOSE_CHARACTER) == 0) {
         mStack.pushScreen(mCharacterMenu);
      } else if(action.compareTo(ACTION_BACK) == 0) {
         mStack.popScreen();
      }
   }
   
}

to fix it, I ran the startLevel in a separate thread:


public class SinglePlayerMenu extends MenuScreen implements ActionListener {

   /* Other Code */

   @Override
   public void actionPerformed(ActionEvent e) {
      String action = e.getActionCommand();
      
      if(action.compareTo(ACTION_START_GAME) == 0) {
         // Return stack to the Main Menu
         mStack.popScreen();
         
         Thread thread = 
         new Thread(
            new Runnable() {
               @Override
               public void run() {
                  mGame.startLevel(mLevelMenu.getSelectedLevel());
               }
            });
         thread.start();
      } else if(action.compareTo(ACTION_CHOOSE_LEVEL) == 0) {
         mStack.pushScreen(mLevelMenu);
      } else if(action.compareTo(ACTION_CHOOSE_CHARACTER) == 0) {
         mStack.pushScreen(mCharacterMenu);
      } else if(action.compareTo(ACTION_BACK) == 0) {
         mStack.popScreen();
      }
   }
   
}

I'm going to have to figure out what the best approach to stopping the thread is going to be, although I think the code will already cause it to die on its own when the level is quit in some way.

Yo dawg, don't even trip.

I have to say what you're doing strikes me as "works but is fundamentally wrong." Basically you're starting a thread for a completely wrong reason and the fact it works at all is basically because you're glueing together two completely unrelated sections of code.

You realize that by calling a function to start the game loop from within game logic you're basically going to "freeze" anything located outside of it, right? If anything your game should initialize and then enter the game loop and all manipulation to the state of the game should happen inside that loop.

I'm not sure why moving a menu to the game loop makes sense or why separating the two is fundamentally wrong. I feel like if I move the menu to the game loop it will clutter it by forcing me to add unnecessary state keeping code, whereas swing is perfectly capable of doing it with callback functions. I'm also not sure what you mean by my game loop freezing anything outside of it.

Yo dawg, don't even trip.

I'm not sure why moving a menu to the game loop makes sense or why separating the two is fundamentally wrong.

Because the game loop is a loop over all content, from menus to gameplay to any other screen. How most games do it is when you call the update and draw part of the loop you grab the active screen or whatever you like to call it, and update or draw that. It doesn't make sense to have more than one loop or to have the loop buried in a function somewhere because there's no fundamental reason for it to need to exit it except when the program ends.

I feel like if I move the menu to the game loop it will clutter it by forcing me to add unnecessary state keeping code, whereas swing is perfectly capable of doing it with callback functions. I'm also not sure what you mean by my game loop freezing anything outside of it.

Your listeners stop responding because from the viewpoint of the code as soon as you enter

mGame.startLevel(mLevelMenu.getSelectedLevel());
It essentially "stalls" the thread by making it go and infinitely loop over the game loop and never backing out of the function, when you start it in a seperate thread it can draw and update and all that while the "event thread" continues running happily. it "works" but I want to say the whole design of the thing is just going to continue to cause you trouble as you go along.

I find it weird you chose that sort of design, traditionally all the input is handled inside the game loop so that you can control when it gets executed, otherwise you'd have to deal with multithreading and silly behavior like asynchronous callbacks changing data partially through an update loop.

Either way, as far as I can tell, I can't get keyboard or mouse input any other way but with callback functions, so rather I not spawn a separate thread for my game or not, I still need an event thread running in the background which will cause the problems you described. I understand the design you describe. I've used it before I started with Java.

Yo dawg, don't even trip.

This topic is closed to new replies.

Advertisement