0
\$\begingroup\$

I am writing a simple ascii game in java. My main loop has a tickrate set to once every 10 seconds, which calls a KeyListener attacked to the JTextArea that the game is displayed in. The KeyListener decides how the game gets updated based on what the user input was.

The delta time for the tick has to be set really high, otherwise instead of moving one space at a time, my character will move 2 or 3 or up to the entire x or y distance in a single click. Also, as the game runs longer, the character begins skipping places anyways, as if the tick rate has been decreased.

Here is my main game loop logic.

    long startLoopTime = Calendar.getInstance().getTimeInMillis();
    long updateLoopTime = Calendar.getInstance().getTimeInMillis();
    boolean firstLoop = true;

    while (true) { // keep running
        startLoopTime = Calendar.getInstance().getTimeInMillis();
        long tickCheck = startLoopTime - updateLoopTime;
        if(firstLoop || tickCheck > 10000)
        {
            firstLoop = false;
            listener(board, player, map);
            updateLoopTime = Calendar.getInstance().getTimeInMillis();
        }     
    }

Going through debug mode, I can see that the tick check is always > 10,000 when entering the conditional, and takes about 10 seconds to get there. However, even when the game is not in the tick check, the KeyListener is still active.

Here is my KeyListener code:

    public static void listener (JTextArea board, Player player, Map map)
{

    board.addKeyListener(new KeyListener(){
        @Override
        public void keyPressed(KeyEvent e){
            Map reMap = new Map();
            switch (e.getKeyCode()){
            case KeyEvent.VK_W:
                if(player.getInInventory())
                {
                    //do some stuff
                }
                else{
                reMap = moveMapUp(map, player);
                board.setText(map.getMapString());
                }
                return;
            case KeyEvent.VK_S:
                if(player.getInInventory())
                {
                    //do some stuff
                }
                else{
                reMap = moveMapDown(map, player);
                board.setText(map.getMapString());
                }
                return;
            case KeyEvent.VK_A:
                if(player.getInInventory())
                {
                    //do some stuff
                }
                else{
                reMap = moveMapToLeft(map, player);
                board.setText(map.getMapString());
                }
                return;
            case KeyEvent.VK_D:
                if(player.getInInventory())
                {
                    //do some stuff
                }
                else{
                reMap = moveMapToRight(map, player);
                board.setText(map.getMapString());
                }
                return;
            case KeyEvent.VK_I:         
                if(!player.getInInventory()){
                    board.setText(inventoryString(player));
                    player.setInInventory(true);
                }
                else{
                    board.setText(map.getMapString());
                    player.setInInventory(false);
                }
                return;
            case KeyEvent.VK_SPACE:
                if(player.getInInventory())
                {
                    //do some stuff
                }
                else
                {
                    interact(map, player);
                }
            }
        }
        @Override
        public void keyTyped(KeyEvent e) {
        }
        @Override
        public void keyReleased(KeyEvent e) {
        }
    });
}

I dont really understand why the game is able to update more frequently than the tickrate, while still being tied to it. I also dont understand why as the game goes on, the apparent tick rate seems to drop, and my character begins speeding across the map on a single key press, instead of just moving one space like it had before. Any help or explanations would be appreciated.

\$\endgroup\$
3
\$\begingroup\$

Your code has more than one issue.

Issue: Do not call listener() inside the "while" loop. Because this adds a new key listener each time it is called, resulting in a new listener each time. This produces multiple updates of the map, more updates each 10 seconds because listener() inserts a new listener each time.

Solution: Move the call to listener() function out of the "while" loop.

long startLoopTime = Calendar.getInstance().getTimeInMillis();
long updateLoopTime = Calendar.getInstance().getTimeInMillis();
boolean firstLoop = true;
listener(board, player, map);

while (true) { // keep running
    startLoopTime = Calendar.getInstance().getTimeInMillis();
    long tickCheck = startLoopTime - updateLoopTime;
    if(firstLoop || tickCheck > 10000)
    {
        firstLoop = false;
        updateLoopTime = Calendar.getInstance().getTimeInMillis();
    }     
}

Issue: Even doing the fix proposed above, you're missing that event listeners callback functions are fired in the event dispatch thread that is not the main Thread. Your game has now at least two threads, I know this because with that code, if the main loop were in the same thread than the UI, then the application would hang, trapped in the main loop without never reaching the line where the key press event is handled.

As the event is being handled, I assume that your main loop is in the main thread (that is not the UI thread if you are using awt/swing, the presence of a JTextArea suggests you are). Because of this is that you get the event processed as soon as you press a key and not once each 10 seconds as you want.

You can confirm my theory by inserting calls to System.out.println("Thread ID = " + Thread.currentThread().getId()). Insert one right before entering the "while" loop and other in the keyPressed() function of the key listener. You will see then that the key listener callback functions are called in a different thread than your main loop.

Solution: A classic game programming approach is to move the key handling to the main loop. To do this and at the same time benefit from all UI API goodies as are the listeners system, I suggest you to set some key state variable inside the key listener callback function and then check that state variable in the main loop. Problem solved.

Example, modify to adapt to your needs:

Change listener code to this (I didn't tried to compile):

// We need a new private variable, either in the main class or the JFrame derived
private boolean [] keysDown = new boolean [KeyEvent.KEY_LAST];

And when we create the anonymous KeyListener:

board.addKeyListener(new KeyListener(){
    @Override
    public void keyPressed(KeyEvent e){
        keysDown[e.getKeyCode()] = true;
    }
});

Note that I only including the keyPressed callback. You must add keyTyped and keyReleased.

And then, process the key in the main loop and set that key as not pressed, if the player maintains it pressed, or release and press it again, it will get marked as pressed again by the key listener code.

long startLoopTime = Calendar.getInstance().getTimeInMillis();
long updateLoopTime = Calendar.getInstance().getTimeInMillis();
boolean firstLoop = true;
listener(board, player, map);

while (true) { // keep running
    startLoopTime = Calendar.getInstance().getTimeInMillis();
    long tickCheck = startLoopTime - updateLoopTime;
    if(firstLoop || tickCheck > 10000)
    {
        firstLoop = false;
        updateLoopTime = Calendar.getInstance().getTimeInMillis();
    }

    if (keysDown[KeyEvent.VK_W] == true)
    {
        // Move to here the code previously in the key listener.            

        keysDown[KeyEvent.VK_W] = false;
    } else
    if (keysDown[KeyEvent.VK_S] == true)
    {
        // Move to here the code previously in the key listener.

        keysDown[KeyEvent.VK_S] = false;
    }
    // And so on...
}

You don't have to use a boolean array, it's only an example. You can use a List, a Map, or what works best for you. You don't have to use booleans, you can collect as many information as you need about the key events. For example you can save the whole KeyEvent object (I think that may be a bit extreme, but you can).

There are other ways of doing it, like let the boolean stay as true and set it to false only when a keyReleased for that key code is detected.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.