0

I'm having a bit of an issue with getting my game characters to jump. I've set my jump function as the space key, but the jump animation doesn't happen all at once. Instead, pressing space causes the character to move a little up every time until it reaches the maximum jump height limit. Then, once it reaches that height, pressing space causes the player to move a little down every time until it reaches the ground limit. It looks normal when the space button is being held down, but once it is released, the character is stuck in the air where the space bar was released. I want the sequence to happen all at once with one click of the space bar. I've looked at my code for quite some time and tried changing where I call the "act" method I have for my Player class from the JPanel. Here is my Player class where the jump feature is meant to be assigned its values:

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;


public class Player {
    ImageIcon movements[];
    ImageIcon leftMove[];
    ImageIcon move[];
    ImageIcon attack;
    boolean ableAttack, canJump;
    int count, x, y, width, height;
    int fallingSpeed = 0;
    int gravity = 1;
    int jumpPower = -20;

    public Player(int count, int x, int y) {
        this.x = x;
        this.y = y;
        this.count = count;
        movements = new ImageIcon[8];
        leftMove = new ImageIcon[8];
        attack = new ImageIcon("attackRight.gif");
        this.width = 80;
        this.height = 130;
        for (int i = 0; i < 8; i++) {
            movements[i] = new ImageIcon("right_"+(i + 1) + ".png");
            leftMove[i] = new ImageIcon("go_"+(i+1)+".png");
        }
        move = movements.clone();
    }

    public void act() {
        if (isOnGround()) {
            canJump = true;
            jump();

        } else {
            canJump = false;
            fall();
        }
    }
    public void jump() {
        fallingSpeed = jumpPower;
        fall();
    }
    public void fall() {
        y += fallingSpeed;
        fallingSpeed = fallingSpeed + gravity;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

    public boolean isOnGround() {
        if (y > 410) 
            return true;
        return false;
    }

    public void setAttack(boolean attack) {
        ableAttack = attack;
    }

    public void setImageArrLeft() {
        move = leftMove.clone();
        attack = new ImageIcon("attack.gif");
    }
    public void setImageArrRight() {
        move = movements.clone();
        attack = new ImageIcon("attackRight.gif");
    }

    public void increaseCount() {
        count++;
    }

    public void myDraw(Graphics g) {
        if (ableAttack) {
            g.drawImage(attack.getImage(), x, y, width + 30, height + 10, null);
        } 
        else {
            g.drawImage(move[count % 8].getImage(), x, y, width, height, null);
        }   
    }
}

Then, in my JPanel class, I call the act() method in the Player class under my keyPressed method, checking for VK_ENTER, from the KeyListener interface:

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.*;

public class MyPanel extends JPanel implements ActionListener, MouseListener, MouseMotionListener, KeyListener{
    int mouseX, mouseY, x = 100, y = 420;
    Timer timer;
    ImageIcon background = null;
    Player p;
    
    public MyPanel() {
        timer = new Timer(60, this);
        p = new Player((int)(Math.random() * 8), x, y);
        background = new ImageIcon("battlefield1.png");
        addKeyListener(this);
        setFocusable(true);
    }
    
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(background.getImage(), 0, 0, 1375, 750, null);
        p.myDraw(g);
        repaint();
    }
    public void actionPerformed(ActionEvent e) {
        if(e.getSource()==timer) {
        }
    }

    public void mouseClicked(MouseEvent me) {}
    public void mouseEntered(MouseEvent me) {}
    public void mouseExited(MouseEvent me) {}
    public void mousePressed(MouseEvent me) {}
    public void mouseReleased(MouseEvent me) {}
    public void mouseDragged(MouseEvent me) {}
    public void mouseMoved(MouseEvent e) {}

    @Override
    public void keyTyped(KeyEvent e) {}

    @Override
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
            p.increaseCount();
            if (x < 1300)
                x += 10;
            p.setX(x);
            p.setImageArrRight();
        }
        if (e.getKeyCode() == KeyEvent.VK_LEFT) {
            p.increaseCount();
            if (x > 0) 
                x -= 5;
            p.setX(x);
            p.setImageArrLeft();
        }
        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
            p.setAttack(true);
        }
        if (e.getKeyCode() == KeyEvent.VK_SPACE) {
            p.act();
        } 
    }
    @Override
    public void keyReleased(KeyEvent e) {
        // TODO Auto-generated method stub
        p.setAttack(false);

    }
}

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • 1
    Jumping is an "animation" state - where by you need to determine if a "jump" is in progress and what the current "jump" value is, so you can then make modifications to the value accordingly (keep going up, start coming down) – MadProgrammer Mar 20 '21 at 01:15
  • For [example](https://stackoverflow.com/questions/13318574/why-does-the-height-of-the-rectangles-jump-vary/13319161#13319161), [example](https://stackoverflow.com/questions/21081972/strange-behavior-with-platform-collision-and-jumping/21082030#21082030) and [example](https://stackoverflow.com/questions/16493809/how-to-make-sprite-jump-in-java/16494178#16494178) – MadProgrammer Mar 20 '21 at 01:20
  • @MadProgrammer My program seems to understand when to keep pushing the character up and when it should start coming down. The problem is that it's not one swift animation after the space bar is pressed and each press is like a fragment of the sequence. Holding the space bar down makes the character's jump look like normal, though. I'm just not sure why the jump won't execute all at once. – SeekNDestroy Mar 20 '21 at 01:52
  • You haven't included any code that demonstrates how you're handling the animation... It seems that you keep restarting the jump action on every keyevent, but really, you should be checking to see whether a jump is in progress. If not, start iterating through the frames of the jump animation. If you're already performing a jump, then do nothing and let the existing jump sequence play out. – MarsAtomic Mar 20 '21 at 02:29
  • @MarsAtomic that's precisely my issue. I'm wondering how I could iterate through all the frames of this jump animation instead of it being fragmented for every click of the space. Do you have any suggestions on how to do that? – SeekNDestroy Mar 20 '21 at 03:07
  • @SeekNDestroy Based on my observations - there is no "delta" to tell a animation loop that the state is changing or by what, so curiosity is, how does this all work - an out of context snippet doesn't make it easy to provide you with an answer. So the only time things "seem" to get updated is when you action then key – MadProgrammer Mar 20 '21 at 04:41
  • @MadProgrammer Sorry, I'm new to Java and I didn't realize my code seemed out of context. If it helps, I've provided the two classes involved in the handling of this jump sequence, the MyPanel class that draws and the Player class that assigns the values to the different jump variables. I hope this makes it somewhat clearer to understand what my issue is and how to potentially go about it. – SeekNDestroy Mar 20 '21 at 04:53
  • We need a [mre] that we can copy into our IDE and run. Most of us can't just look at code and see what's wrong, unless it's obvious. @MadProgrammer gave you three examples of animation. Why don't those examples help you solve your problem? – Gilbert Le Blanc Mar 20 '21 at 05:12
  • Call `repaint` within the `actionPerformed` method. Swing uses a passive rendering model, so it will only update when it thinks it needs do – MadProgrammer Mar 20 '21 at 07:17
  • @MadProgrammer I've tried that and it didn't work. I'll keep searching though. Thanks for your help. – SeekNDestroy Mar 20 '21 at 18:43

1 Answers1

3

You need a better understand of animation (generally) and games loops in particular.

Swing uses a passive rendering approach, so you need to set up a "game loop" which updates the state of the game, taking into account any inputs the user has given, and then schedule a repaint.

This means, you can not update the state of the player in the KeyListener, instead, you need to setup a state which allows the "game loop" to update the overall state.

This is very basic example...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new MyPanel());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class MyPanel extends JPanel implements ActionListener, MouseListener, MouseMotionListener, KeyListener {

        int mouseX, mouseY, x = 100, y = 420;
        Timer timer;
        Player p;

        public MyPanel() {
            timer = new Timer(60, this);
            p = new Player((int) (Math.random() * 8), x, y);
            setBackground(Color.BLUE);
            addKeyListener(this);
            setFocusable(true);
        }

        @Override
        public void addNotify() {
            super.addNotify();
            timer.start();
        }

        @Override
        public void removeNotify() {
            super.removeNotify();
            timer.stop();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(800, 800);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            p.myDraw(g);
        }

        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == timer) {
                p.update();
                repaint();
            }
        }

        public void mouseClicked(MouseEvent me) {
        }

        public void mouseEntered(MouseEvent me) {
        }

        public void mouseExited(MouseEvent me) {
        }

        public void mousePressed(MouseEvent me) {
        }

        public void mouseReleased(MouseEvent me) {
        }

        public void mouseDragged(MouseEvent me) {
        }

        public void mouseMoved(MouseEvent e) {
        }

        @Override
        public void keyTyped(KeyEvent e) {
        }

        @Override
        public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
                p.increaseCount();
                if (x < 1300) {
                    x += 10;
                }
                p.setX(x);
                p.setImageArrRight();
            }
            if (e.getKeyCode() == KeyEvent.VK_LEFT) {
                p.increaseCount();
                if (x > 0) {
                    x -= 5;
                }
                p.setX(x);
                p.setImageArrLeft();
            }
            if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                p.setAttack(true);
            }
            if (e.getKeyCode() == KeyEvent.VK_SPACE) {
                p.act();
            }
        }

        @Override
        public void keyReleased(KeyEvent e) {
            // TODO Auto-generated method stub
            p.setAttack(false);

        }
    }

    public class Player {

        boolean ableAttack, canJump;
        int count, x, y, width, height;
        int fallingSpeed = 0;
        int gravity = 1;
        int jumpPower = -20;

        private boolean isJumping = false;

        public Player(int count, int x, int y) {
            this.x = x;
            this.y = y;
            this.count = count;
            width = 10;
            height = 10;
        }

        public void act() {
            if (isOnGround()) {
                jump();
//                canJump = true;
//                jump();
//
//            } else {
//                canJump = false;
//                fall();
            }
        }

        public void jump() {
            isJumping = true;
            fallingSpeed = jumpPower;
            fall();
        }

        public void fall() {
            y += fallingSpeed;
            fallingSpeed = fallingSpeed + gravity;
        }

        public void setX(int x) {
            this.x = x;
        }

        public void setY(int y) {
            this.y = y;
        }

        public boolean isOnGround() {
            if (y > 410) {
                return true;
            }
            return false;
        }

        public void setAttack(boolean attack) {
            ableAttack = attack;
        }

        public void setImageArrLeft() {
        }

        public void setImageArrRight() {
        }

        public void increaseCount() {
            count++;
        }

        public void update() {
            if (isJumping) {
                if (!isOnGround()) {
                    fall();
                } else {
                    isJumping = false;
                }
            }
        }

        public void myDraw(Graphics g) {
            g.setColor(Color.RED);
            g.fillRect(x, y, width, height);
        }
    }
}

Personally, I would make use Key Bindings over KeyListener, as it will solve the immediate issue of keyboard focus and provide a more configurable approach.

This would mean that the "input state" would be independent of the player and the game loop would become responsible for taking control over the player state change based on the game state

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thanks for sticking with me while I figured out the code. Fortunately, I found better logic for implementing the jump function - using in part your advice on updating graphics through action listener and not key listener. – SeekNDestroy Mar 23 '21 at 02:09