1

Im trying to get my movement smoother. At the moment when I press the up key it will go up and then when I press the left key it stops and then turns left. I want to make it so when i press right it will do it straight away and have no delay. How could I with my code

                
        import java.awt.event.*;
        import javax.swing.*;
        import java.awt.*;
        import java.awt.Image;
        import javax.swing.*;
        
        
        class MyFrame extends JFrame implements KeyListener {
            JLabel label;
            
            MyFrame(){ 
             this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
             addKeyListener(this);
             ImageIcon imageIcon = new ImageIcon("C:\\Users\\jacob\\Downloads\\player.png");
             
             Image image = imageIcon.getImage();
             Image newimg = image.getScaledInstance(150, 150,  java.awt.Image.SCALE_SMOOTH);
             imageIcon = new ImageIcon(newimg);
             label = new JLabel(imageIcon);
             this.add(label);
             this.setSize(500, 500);
             this.setVisible(true);
         }
            @Override
            public void keyTyped(KeyEvent e) {
                // TODO Auto-generated method stub
                
            }
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_UP)
                    label.setLocation(label.getX(), label.getY()-10);
                else if (e.getKeyCode() == KeyEvent.VK_DOWN)
                    label.setLocation(label.getX(), label.getY()+10);
                else if (e.getKeyCode() == KeyEvent.VK_LEFT) 
                    label.setLocation(label.getX()-10, label.getY());
                else if (e.getKeyCode() == KeyEvent.VK_RIGHT) 
                    label.setLocation(label.getX()+10, label.getY());
                
            }
            @Override
            public void keyReleased(KeyEvent e) {
                // TODO Auto-generated method stub
                
            }
        }
Jacob Ptak
  • 13
  • 2
  • 1
    Did you consider using Swing Timer class? It could be better solution for you. – DRINK Sep 28 '21 at 20:17
  • Try implementing it like this https://stackoverflow.com/questions/2623995/swings-keylistener-and-multiple-keys-pressed-at-the-same-time where you store the key presses in a hash set, so it doesn't work off of the event only.. – Dropout Sep 28 '21 at 20:52
  • Don't use a KeyListener. Instead you should be using `Key Bindings`. See: [Motion Using the Keyboard](https://tips4java.wordpress.com/2013/06/09/motion-using-the-keyboard/). The `KeyboardAnimation.java` will do what you want. – camickr Sep 28 '21 at 22:56

3 Answers3

2

The main problem here is that different operating systems have different times for key-repeat functionality (how often, and how much to accelerate, repeated key press deliveries).

The way to solve this is to maintain state for which keys are currently down or not, and in some timer thread, perform actions if the state is true or not.

For example, (in very rough Java, this won't compile without fixing I think) you want something like this

// mark that the key is down
public void keyPressed(KeyEvent e) {
    keyboardState[e.getKeyCode()] = true;
}

// mark that the key has been released
public void keyReleased(KeyEvent e) {
    keyboardState[e.getKeyCode()] = false;
}

// this function should be called at some repeatable interval, fast enough where it looks "smooth"
public void calledAtFixedInterval(int delta) {
    // we can query definitively if the key is currently down or up, rather than waiting on some event to be delivered at an arbitrary interval
    if (keyboardState[KeyEvent.VK_LEFT] == true) {
        // your logic here
    }
}

This architecture is roughly how most game engines work - they will handle user input using an event-driven architecture, but just maintain a giant map of which keys are currently down or not - that way it's easy and fast to query if a key is pressed deep within the render loop.

valkmit
  • 53
  • 4
1

A solution I commonly use these days is to separate the game animation thread from the input events (mouse and keyboard), employing loose coupling to communicate between the two.

The game loop can be driven by a java.util.Timer operating at 60 fps. I use this rate as it is pretty common for monitors to refresh at 60 fps, so there are diminishing returns for going at a faster rate.

Some prefer to use a javax.swing.Timer but IDK if that is such a good idea. The EDT (event dispatch thread) can become something of a bottle neck. (The classic Java game programming book: Killer Game Programming in Java compares the two timers and the util Timer comes out the winner.) Arguably, a ScheduledThreadPoolExecutor is more robust than either, though it requires a bit of a learning curve if you haven't used Executors for multi-threading yet. (I prefer using JavaFX, so I use its AnimationTimer.)

The class holding the game loop should have public methods where the keyboard or mouse events that are triggered can simply set a variable or toggle a boolean (or add a variable to the top of a queue). The public input method for the keyboard can have a bit of extra processing to compare the incoming key event with current key state (e.g., a maintained map of key states as described elsewhere on this thread) and set a field for the game-loop thread to read and act upon.

This sort of architecture for a game program adds a great deal of predictability and control, as well as clarifying the logic of the various layers. It also takes good advantage of the multi-threading capabilities of today's processors.

Phil Freihofner
  • 7,645
  • 1
  • 20
  • 41
  • Updates to Swing components or the state of the components should be done on the EDT. So you should be using `javax.swing.Timer` to make sure all updates are done on the EDT. – camickr Sep 28 '21 at 22:52
  • Goetz: Java Concurrency in Practice, pg 192. "For simple, short-running tasks, the entire action can stay in the event thread; for longer-running tasks, some of the processing should be offloaded to another thread." Most of the game loop work should be confined to the model and control levels, not directly engaged with Swing components. Do you really want extraneous processing slowing down the EDT when Swing components are not involved? Also, same page (near top), valid exception: "methods to enqueue a repaint" are callable from any thread. – Phil Freihofner Sep 29 '21 at 00:13
  • Setting the location of the component should be done on the EDT. You don't want the (remote) possibility of some Thread updating the location while Swing is trying to paint the component. This is how you get random results that are difficult to duplicate. – camickr Sep 29 '21 at 03:58
0

My guess would be to probably make a velocityX and velocityY. And also, if you want smooth movements. You need a tick method.

Basic tick method for Java Game Development:

public synchronized void start() {
    thread = new Thread(this);
    thread.start();
    running = true;
}

public synchronized void stop() {
    try {
        thread.join();
    }catch(Exception e){
        e.printStackTrace();
    }
    }

public void run() {
    long lastTime = System.nanoTime();
    double ns =1000000000 / 60.0;
    double delta = 0;
    long timer = System.currentTimeMillis();
    int ticks = 0;
    int frames = 0;
    while(running) {
        long now = System.nanoTime();
        delta += (now - lastTime) / ns;
        lastTime = now;
        while(delta >= 1) {
            tick();
            ticks++;
            delta--;
        }
        if(running) {
            render();
            frames++;
        }
        
        if(System.currentTimeMillis() - timer > 1000) {
            timer += 1000;
            System.out.println(ticks + " tps" + " | " + frames + " fps");
            frame.setTitle(name + " | " + ticks + " tps" + "  " + frames + " fps");
            frames = 0;
            ticks = 0;
            
            //meanings
            //ns = nanoseconds
            //amountOfTicks = ticks per second
            
        }
    }
    stop();
}

The start and stop methods are for the Thread. You can find easy tutorials on how to do the tick method and fully understand it.

When you have your ticks working properly, you can make your x and y += the velX and velY, just like this:

public void tick() {
        y += velY;
        x += velX;
}

Making the player update the x and y each tick

And the velX and velY need to react to your KeyInputs so they, y'know, have a value, something like this:

if(key == KeyEvent.VK_W) { tempObject.setVelY(-10); keyDown[0] = true; }
                if(key == KeyEvent.VK_S) { tempObject.setVelY(10); keyDown[1] = 
true; }
                if(key == KeyEvent.VK_D) { tempObject.setVelX(10); keyDown[2] = 
true; }
                if(key == KeyEvent.VK_A) { tempObject.setVelX(-10); keyDown[3] = 
true; }

This code, don't copy and paste it. It has different methods because I am basing this code off a game I made a while ago. So methods are different.

And so when you get that, you are basically done.

setVelY and setVelX look something like this:

public void setVelY(int velY){
    this.velY = velY;
}

public void setVelY(int velY){
    this.velX = velX;
}

So, conclusion. If you want smoother animations, you need a tick method, so the x and y update every second.

My code will basically serve nothing if you copy and paste it, but it's just the concept of how to make the movements smooth.

So x and y need to update every second. Making a Runnable tick method is your only way.

I really hope this helps you just a little bit.

Good luck! :D

Robatortas
  • 16
  • 6
  • 1
    Thanks. Where shall i put the tick method in my code? – Jacob Ptak Sep 28 '21 at 23:10
  • You can put the tick method in the main class. I like to keep my code organized, so the main method is called Game for all the projects I make, its just tu keep it cleaner, but the tick method can be inserted in any part of code, of course, not inside another method. And also, so you don't forget, The class that has the tick method needs to implement Runnable, just for the run method to actually run. And I have my code structured so the main tick method is located in the class Game, so I just basically call all the classes that have a tick method to the main tick method. – Robatortas Sep 29 '21 at 05:11
  • (Continuing last comment) Just like this (Remember this is the main tick method.) public void tick(){ example.tick(); example2.tick(); } So here as you can see, im calling the example and example2 tick methods. You can view my open source code of a game I made and just see how I did it to get better knowledge of what I am trying to explain. I am very bad at explaining, so the actual code will server you more than me. https://github.com/Robatortas/P-O-N-G/tree/main/PONG/src/main This is the link for the code. There you can view how I made it. Have a nice day! :D – Robatortas Sep 29 '21 at 05:15
  • One of the best things you can do is play with other people's code to understand it better! :D – Robatortas Sep 29 '21 at 05:24