Stone
Stone

Reputation: 1

ActionListener executes twice on JPanel when called from JFrame

I wish to animate my pac-man sprite however when my timer calls actionPerformed to update the image, it executes twice and skips over an animation.

Here is the code for the main class:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.concurrent.TimeUnit;
import javax.swing.Timer;

public class Pacman extends JFrame implements ActionListener, KeyListener{
    public static Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();  
    public static int FRAME_HEIGHT = (int)(dim.height-dim.height/5);
    public static int FRAME_WIDTH = (int)(dim.width-dim.width/5);
    public static Pacman frame = new Pacman();
    public static Game gameInterface = new Game();
    public static Timer timer;

    public Pacman() {
        super("Pac-man");
        timer = new Timer(175, this);
        timer.start();
        addKeyListener(this);
    }

    public static void main(String[] args) {
        ImageIcon frameIcon = new ImageIcon("Icon.png");

        frame.setSize(FRAME_WIDTH,FRAME_HEIGHT);
        frame.setIconImage(frameIcon.getImage());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
        frame.setResizable(false);

        frame.setContentPane(gameInterface);
        gameInterface.requestFocus();
        gameInterface.setFocusable(true);


        Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();    
        int JframeX = (dim.width-FRAME_WIDTH)/2;
        int JframeY = (dim.height-FRAME_HEIGHT)/2;
        frame.setLocation(JframeX, JframeY);
    }

    public void keyTyped(KeyEvent e) {
    }

    public void keyPressed(KeyEvent e) {
    }

    public void keyReleased(KeyEvent e) {
    }

    public void paint(Graphics g) {
        super.paint(g);     
    }

    public void actionPerformed(ActionEvent ev){
        repaint();
    }
}

Here is the code for the game class:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.concurrent.TimeUnit;
import javax.swing.Timer;

public class Game extends JPanel implements ActionListener, KeyListener{
    public static Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();  
    public static int FRAME_HEIGHT = (int)(dim.height-dim.height/5);
    public static int FRAME_WIDTH = (int)(dim.width-dim.width/5);
    public static Game gamePanel = new Game();
    public static Timer timer;
    public static boolean gameExit = false;
    public static int animCounter = 0, imgPacmanX1 = 840, imgPacmanY1 = 0, imgPacmanX2 = 890, imgPacmanY2 = 50;

    public Game() {
        timer = new Timer(500, this);
        timer.start();      
        addKeyListener(this);
    }

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

    public void keyTyped(KeyEvent e) {
    }

    public void keyPressed(KeyEvent e) {
    }

    public void keyReleased(KeyEvent e) {
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        ImageIcon animations = new ImageIcon("Pacman_Animations.png");

        g.setColor(Color.BLUE);
        g.fillRect(0, 0, FRAME_WIDTH, FRAME_HEIGHT);
        g.drawImage(animations.getImage(), 150, 200, 200, 250, imgPacmanX1, imgPacmanY1, imgPacmanX2, imgPacmanY2, null);
    }

    public void actionPerformed(ActionEvent ev){
        if(animCounter == 2){
            imgPacmanY1 = 0;
            imgPacmanY2 = 50;
            animCounter = 0;
        }
        else{
            imgPacmanY1 += 50; 
            imgPacmanY2 += 50;
            animCounter += 1;
        }
        repaint();
        System.out.print(imgPacmanY1 + "   " + imgPacmanY2 + "    ");
    }
}

If I run the game class separately, the action listener works fine and only executes once, however called from my main class the action listener executes twice and the animations starts skipping frames.

Upvotes: 0

Views: 78

Answers (1)

MadProgrammer
MadProgrammer

Reputation: 347194

You have two Timer's each one is trigger a repaint and ... welcome to the wonderful world of why your architecture is wrong.

  1. Don't override paint of top level containers like JFrame, in fact, extending from JFrame is generally discouraged as you're not actually adding a new functionality to the class and they are a jumble of components already.
  2. Avoid over use of static. If you find you're using static to allow you to reference other objects, then your design is wrong. You should be passing information to your class via the constructor or getters. This is where things like "model-view-controller" come in and are important
  3. Painting is painting only. You should never be performing logic which might change the state of the component. Paint should simply paint the current state, as a paint pass might be executed any time, many of the times without your doing anything to trigger it.
  4. Generally speaking, KeyListener is a poor choice, you should be using the Key bindings API instead.

The first thing you should focus on is decoupling the game core logic from the rendering work flow. This means you'd have a "main loop" (in this case, the Swing Timer) which would update the mode (or state) and then trigger a paint cycle. The rendering work flow would then use the current "state" for its rendering.

Upvotes: 2

Related Questions