Search code examples
javaswinggraphicspaintcomponentkey-bindings

Program not Listening to KeyBindings or paintComponent()


I have a program that shoots a bullet every time the space bar is clicked. There are two problems with the program though: it won't call the paintComponent() or listen to my KeyBindings. Here is my code:

import java.awt.Graphics;
import java.awt.event.KeyEvent;

import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;

public class Shoot extends JPanel {

static JFrame frame;

static Ship s1;
static Shoot shoot;

public Shoot() {

    addKeyBinding(KeyEvent.VK_LEFT, "left.pressed", new MoveAction(true, s1, Direction.LEFT), true);
    addKeyBinding(KeyEvent.VK_LEFT, "left.released", new MoveAction(false, s1, Direction.LEFT), false);

    addKeyBinding(KeyEvent.VK_RIGHT, "right.pressed", new MoveAction(true, s1, Direction.RIGHT), true);
    addKeyBinding(KeyEvent.VK_RIGHT, "right.released", new MoveAction(false, s1, Direction.RIGHT), false);

    addKeyBinding(KeyEvent.VK_SPACE, "space.pressed", new MoveAction(true, s1, Direction.SPACE), true);
    addKeyBinding(KeyEvent.VK_SPACE, "space.released", new MoveAction(false, s1, Direction.SPACE), false);

    setDoubleBuffered(true);
}

@Override
public void paintComponent(Graphics g) {
    // Draw the ship
    super.paintComponent(g);
    s1.draw(g);
}

protected void addKeyBinding(int keyCode, String name, Action action, boolean keyPressed) {
    if (keyPressed) {
        addKeyBinding(KeyStroke.getKeyStroke(keyCode, 0, false), name, action);
    } else {
        addKeyBinding(KeyStroke.getKeyStroke(keyCode, 0, true), name, action);
    }
}

protected void addKeyBinding(KeyStroke keyStroke, String name, Action action) {
    InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
    ActionMap actionMap = getActionMap();
    inputMap.put(keyStroke, name);
    actionMap.put(name, action);
}

public static void main(String[] args) {
    // Set the frame up
    frame = new JFrame();
    frame.setSize(400, 300);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setResizable(false);
    frame.setVisible(true);

    // Get some more necessary objects
    s1 = new Ship();
    shoot = new Shoot();

    // Threads
    Thread ship = new Thread(s1);
    ship.start();
}
}


import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;

public class Ship implements Runnable {
int x, y, xDirection, bx, by;
boolean readyToFire, shooting = false;
Rectangle bullet;

public Ship() {
    x = 175;
    y = 275;
    bullet = new Rectangle(0, 0, 3, 5);
}

public void draw(Graphics g) {
    g.setColor(Color.BLUE);
    g.fillRect(x, y, 40, 10);
    g.fillRect(x + 18, y - 7, 4, 7);
    if (shooting) {
        g.setColor(Color.RED);
        g.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
    }
}

public void move() {
    x += xDirection;
    if (x <= 0)
        x = 0;
    if (x >= 360)
        x = 360;
}

public void shoot() {
    if (shooting) {
        bullet.y--;
    }
}

public void setXDirection(int xdir) {
    xDirection = xdir;
}

@Override
public void run() {
    try {
        while (true) {
            shoot();
            move();
            Thread.sleep(5);
        }
    } catch (Exception e) {
        System.err.println(e.getMessage());
    }

}
}


import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.util.HashSet;

import javax.swing.AbstractAction;

public class MoveAction extends AbstractAction {

boolean pressed;
Ship s1;
Direction dir;
private HashSet<Direction> movement;

public MoveAction(boolean pressed, Ship s1, Direction dir) {
    this.pressed = pressed;
    this.s1 = s1;
    this.dir = dir;
}

@Override
public void actionPerformed(ActionEvent e) {
    if (movement.contains(Direction.LEFT)) {
        if (pressed) {
            s1.setXDirection(-1);
        } else {
            s1.setXDirection(0);
        }
    } else if (movement.contains(Direction.RIGHT)) {
        if (pressed) {
            s1.setXDirection(1);
        } else {
            s1.setXDirection(0);
        }
    } else if (movement.contains(Direction.SPACE)) {
        if (pressed) {
            if (s1.bullet == null)
                s1.readyToFire = true;
            if (s1.readyToFire) {
                s1.bullet.x = s1.x + 18;
                s1.bullet.y = s1.y - 7;
                s1.shooting = true;
            }
        } else {
            s1.readyToFire = false;
            if (s1.bullet.y <= -7) {
                s1.bullet = null;
                s1.shooting = false;
                s1.bullet = null;
                s1.bullet = new Rectangle(0, 0, 0, 0);
                s1.readyToFire = true;
            }
        }
    }
}

}

Solution

  • Let's look at your main method:

    public static void main(String[] args) {
        // Set the frame up
        frame = new JFrame();
        frame.setSize(400, 300);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
        frame.setVisible(true);
    
        // Get some more necessary objects
        s1 = new Ship();
        shoot = new Shoot();
    
        // Threads
        Thread ship = new Thread(s1);
        ship.start();
    }
    

    You create a new Shoot object -- but where do you put it? Not in any displayed JFrame. For key bindings to work and for paintComponent to work, the bound and drawing component must be part of the displayed GUI -- and so add it to your GUI.

    For example, consider:

    public static void main(String[] args) {
        // Set the frame up
        frame = new JFrame();
        frame.setSize(400, 300);  // ** you really don't want to do this
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
    
        // Get some more necessary objects
        s1 = new Ship();
        shoot = new Shoot();
    
        frame.add(shoot);  // ** added to GUI
        frame.setVisible(true);  // ** display GUI **after** addition
    

    Also, as per camickr's comment -- don't forget to call repaint() on the drawing component any time its state is changed in a way that should alter its drawing.