Java Swing KeyBindings stop working only on Mac - java

I'm working on a game using Swing and I'm using KeyBindings for input from the keyboard.
I'm having issues where the KeyBindings stop responding. It happens every time I run the application, but as far as I can tell, it's not when a certain chain of events occurs. The KeyBindings just stop receiving input from the keyboard. I also use mouse input, which continues to work, so I know it's not related to input in general.
Some things I've tried:
made sure my object was not garbage collected
looked for a certain reason the issue occurs (ex: after a certain amount of time, a certain key combination pressed), to which I could find none
tried to use a KeyListener instead
None of these worked.
I then copied the project (without changing any code) to a windows machine, and after testing, I could not get the problem to occur once. I didn't paste code here because I'm 99% positive this is not related to my code, but related to the operating system it's running on.
Is this a problem with the macOS, or the JDK for Mac, or something else I don't know about?
I'm using JDK version 8 update 112, the computer is running macOS Sierra version 10.12.2.
It seems someone else had the same problem I now do, but they never got an answer.
EDIT
Here's a code example where the problem occurs:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
//an example of the problem where the keyboard stops receiving input randomly
public class ProblemExample extends JPanel {
private static final long serialVersionUID = 1L;
private int xPos = 200, yPos = 200;
private boolean wKey, aKey, sKey, dKey;
private BufferedImage image; //sprite
public ProblemExample() {
JFrame frame = new JFrame();
frame.add(this);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
//makes the sprite a red square
image = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
int[] redPixels = new int[50 * 50];
for (int i = 0; i < redPixels.length; i++) {
redPixels[i] = 0xffff0000;
}
image.setRGB(0, 0, 50, 50, redPixels, 0, 50);
initializeKeys();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 600);
}
//sets up Key Bindings
private void initializeKeys() {
final String W = "W",
A = "A",
S = "S",
D = "D",
PRESSED = "PRESSED",
RELEASED = "RELEASED";
InputMap inputMap = this.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = this.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), W + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), A + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), S + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), D + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), W + RELEASED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), A + RELEASED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), S + RELEASED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), D + RELEASED);
Action wActionPressed = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
wKey = true;
}
};
Action aActionPressed = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
aKey = true;
}
};
Action sActionPressed = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
sKey = true;
}
};
Action dActionPressed = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
dKey = true;
}
};
Action wActionReleased = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
wKey = false;
}
};
Action aActionReleased = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
aKey = false;
}
};
Action sActionReleased = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
sKey = false;
}
};
Action dActionReleased = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
dKey = false;
}
};
actionMap.put(W + PRESSED, wActionPressed);
actionMap.put(A + PRESSED, aActionPressed);
actionMap.put(S + PRESSED, sActionPressed);
actionMap.put(D + PRESSED, dActionPressed);
actionMap.put(W + RELEASED, wActionReleased);
actionMap.put(A + RELEASED, aActionReleased);
actionMap.put(S + RELEASED, sActionReleased);
actionMap.put(D + RELEASED, dActionReleased);
}
public void loop() {
if (wKey) yPos -= 5;
if (aKey) xPos -= 5;
if (sKey) yPos += 5;
if (dKey) xPos += 5;
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, xPos, yPos, null);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
ProblemExample example = new ProblemExample();
Timer timer = new Timer(60, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
example.loop();
}
});
timer.start();
}
});
}
}

You might think this is a bug in MAC, while it is not Because MAC has the feature of secondary keys. As you are the user of MAC, I hope you are aware of this feature. This feature might be the the cause of your problem.
The Secondary key feature of MAC : is used where holding down a letter key will display variations of that letter, like holding down “u” to get “ü.” This comes in handy when spelling non-English words.
There’s a simple way to take control and change the behavior of long key presses to accommodate your needs.
Open the Terminal app and write:
defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool false
Then restart any open app in which you want this setting to activate.
REVERTING BACK: Just,simply add true in place of false to the previous command like this:
defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool true
UPDATE:
You can speed up the rate of key repeats or decrease the delay before a held key starts repeating, by going to the System Preferences, and make changes under the keyboard header.

Besides what Tahir Hussain Mir suggest about changing the Mac key settings, I find the problem lies with the looping, it'd be more efficient to implement it in a event-driven way, I took the liberty to change your code a bit. Together with Hussain Mir suggest 's solution, it should solve your problem.
You could also handle key-repeating yourself, by e.g., starting a timer on key press and stoping it on key release, but then the key pressing behavior will differ between Windows and Mac, which isn't really the way you want to go.
package swing;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
//an example of the problem where the keyboard stops receiving input randomly
public class SOMacKeyBindings extends JPanel
{
private BufferedImage image; //sprite
private Point point = new Point(200, 200);
private int steps = 5;
private class KeyAction extends AbstractAction
{
private Runnable runnable;
public KeyAction(Runnable runnable)
{
this.runnable = runnable;
}
#Override
public void actionPerformed(ActionEvent e)
{
runnable.run();
}
}
public SOMacKeyBindings()
{
JFrame frame = new JFrame();
frame.add(this);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
//makes the sprite a red square
image = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
int[] redPixels = new int[50 * 50];
for (int i = 0; i < redPixels.length; i++)
{
redPixels[i] = 0xffff0000;
}
image.setRGB(0, 0, 50, 50, redPixels, 0, 50);
initializeKeys();
}
#Override
public Dimension getPreferredSize()
{
return new Dimension(800, 600);
}
//sets up Key Bindings
private void initializeKeys()
{
final String W = "W",
A = "A",
S = "S",
D = "D",
PRESSED = "PRESSED";
InputMap inputMap = this.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = this.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), W + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), A + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), S + PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), D + PRESSED);
actionMap.put(W + PRESSED, new KeyAction(() -> { point.y -= steps; repaint(); }));
actionMap.put(A + PRESSED, new KeyAction(() -> { point.x -= steps; repaint(); }));
actionMap.put(S + PRESSED, new KeyAction(() -> { point.y += steps; repaint(); }));
actionMap.put(D + PRESSED, new KeyAction(() -> { point.x += steps; repaint(); }));
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(image, point.x, point.y, null);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> new SOMacKeyBindings());
}
}

Related

KeyBinding multiple keys pressed not working

So I have a painted rectangle that I want to move with the arrow keys that includes diagonal movement, or rather allowance of multiple keys being pressed at the same time (In other words, movement that is similar to player movement in a 2D game). I have attempted to do this with a KeyListener, which was not working. So I decided to move to KeyBindings and I found an example from this website: https://coderanch.com/t/606742/java/key-bindings (Rob Camick's post)
I directly copied the code and it works as intended, except it is moving an icon and not a painted rectangle like I want to do. I have attempted to modify the code so that it would move a painted rectangle, but was only failing. Here is my latest attempt, which is also a minimal reproducible:
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;
public class Test extends JPanel implements ActionListener
{
public JPanel component = this;
public int x = 100;
public int y = 100;
private Timer timer;
private int keysPressed;
private InputMap inputMap;
public void addAction(String name, int keyDeltaX, int keyDeltaY, int keyCode)
{
new NavigationAction(name, keyDeltaX, keyDeltaY, keyCode);
}
// Move the component to its new location
private void handleKeyEvent(boolean pressed, int deltaX, int deltaY)
{
keysPressed += pressed ? 1 : -1;
x += deltaX;
y += deltaY;
// Start the Timer when the first key is pressed
if (keysPressed == 1)
timer.start();
// Stop the Timer when all keys have been released
if (keysPressed == 0)
timer.stop();
}
// Invoked when the Timer fires
public void actionPerformed(ActionEvent e)
{
repaint();
}
class NavigationAction extends AbstractAction implements ActionListener
{
private int keyDeltaX;
private int keyDeltaY;
private KeyStroke pressedKeyStroke;
private boolean listeningForKeyPressed;
public NavigationAction(String name, int keyDeltaX, int keyDeltaY, int keyCode)
{
super(name);
this.keyDeltaX = keyDeltaX;
this.keyDeltaY = keyDeltaY;
pressedKeyStroke = KeyStroke.getKeyStroke(keyCode, 0, false);
KeyStroke releasedKeyStroke = KeyStroke.getKeyStroke(keyCode, 0, true);
inputMap.put(pressedKeyStroke, getValue(Action.NAME));
inputMap.put(releasedKeyStroke, getValue(Action.NAME));
component.getActionMap().put(getValue(Action.NAME), this);
listeningForKeyPressed = true;
}
public void actionPerformed(ActionEvent e)
{
// While the key is held down multiple keyPress events are generated,
// we only care about the first event generated
if (listeningForKeyPressed)
{
handleKeyEvent(true, keyDeltaX, keyDeltaY);
inputMap.remove(pressedKeyStroke);
listeningForKeyPressed = false;
}
else // listening for key released
{
handleKeyEvent(false, -keyDeltaX, -keyDeltaY);
inputMap.put(pressedKeyStroke, getValue(Action.NAME));
listeningForKeyPressed = true;
}
}
}
public void paintComponent(Graphics tool) {
super.paintComponent(tool);
System.out.println(x + ", " + y);
tool.drawRect(x, y, 50, 50);
}
public Test() {}
public static void main(String[] args)
{
new Test().create();
}
public void create() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.setSize(600, 600);
frame.setLocationRelativeTo( null );
frame.getContentPane().add(component);
inputMap = component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
timer = new Timer(24, this);
timer.setInitialDelay( 0 );
addAction("Left", -3, 0, KeyEvent.VK_LEFT);
addAction("Right", 3, 0, KeyEvent.VK_RIGHT);
addAction("Up", 0, -3, KeyEvent.VK_UP);
addAction("Down", 0, 3, KeyEvent.VK_DOWN);
frame.setVisible(true);
}
}
Okay, so one of the issues I have with the code you've presented is the fact that you're leak responsibility. It's not the responsibility of the NavigationAction to register key bindings or modify the state the of UI. It should only be responsible for generating a notification that the state has changed back to a responsible handler.
This could be achieved through the use of some kind of model, which maintains the some kind of state, which is updated by the NavigationAction and read by the UI or, as I've chosen to do, via a delegation callback.
Another issue is, what happens when the user releases the key? Another issue is, how do you deal with the delay in repeated key press (ie when you hold the key down, there is a small delay between the first key event and the repeating key events)?
We can deal with these things through a simple change in mind set. Instead of using the NavigationAction to determine "what" should happen, it should be used to tell our UI "when" something has happened. In this a directional key has been pressed or released.
Instead of reacting to key stroke directly, we either "add" or "remove" the direction from a state model and allow the Swing Timer to update the state of the UI accordingly, this will generally be faster and smoother.
It also has the side effect of decoupling logic, as you no longer need to care about "how" the state was changed, only that it was and you can then decide how you want to react to it.
This approach is generally commonly used (in some form or another) in gaming engines.
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.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
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 TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface KeyActionHandler {
public void keyActionPerformed(Direction direction, boolean pressed);
}
public enum Direction {
LEFT, RIGHT, DOWN, UP;
}
class NavigationAction extends AbstractAction implements ActionListener {
private KeyActionHandler keyActionHandler;
private Direction direction;
private Boolean pressed;
public NavigationAction(Direction direction, boolean pressed, KeyActionHandler keyActionHandler) {
this.direction = direction;
this.pressed = pressed;
this.keyActionHandler = keyActionHandler;
}
public void actionPerformed(ActionEvent e) {
keyActionHandler.keyActionPerformed(direction, pressed);
}
}
public class TestPane extends JPanel implements ActionListener, KeyActionHandler {
private int xDelta = 0;
private int yDelta = 0;
public int x = 100;
public int y = 100;
private Timer timer;
public TestPane() {
timer = new Timer(24, this);
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "Pressed-Left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "Pressed-Right");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "Pressed-Up");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "Pressed-Down");
actionMap.put("Pressed-Left", new NavigationAction(Direction.LEFT, true, this));
actionMap.put("Pressed-Right", new NavigationAction(Direction.RIGHT, true, this));
actionMap.put("Pressed-Up", new NavigationAction(Direction.UP, true, this));
actionMap.put("Pressed-Down", new NavigationAction(Direction.DOWN, true, this));
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "Released-Left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "Released-Right");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "Released-Up");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "Released-Down");
actionMap.put("Released-Left", new NavigationAction(Direction.LEFT, false, this));
actionMap.put("Released-Right", new NavigationAction(Direction.RIGHT, false, this));
actionMap.put("Released-Up", new NavigationAction(Direction.UP, false, this));
actionMap.put("Released-Down", new NavigationAction(Direction.DOWN, false, this));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
public void addNotify() {
super.addNotify();
timer.start();
}
#Override
public void removeNotify() {
super.removeNotify();
timer.stop();
}
private Set<Direction> directions = new HashSet<>();
// Invoked when the Timer fires
#Override
public void actionPerformed(ActionEvent e) {
if (directions.contains(Direction.LEFT)) {
x -= 3;
} else if (directions.contains(Direction.RIGHT)) {
x += 3;
}
if (directions.contains(Direction.UP)) {
y -= 3;
} else if (directions.contains(Direction.DOWN)) {
y += 3;
}
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawRect(x, y, 50, 50);
}
#Override
public void keyActionPerformed(Direction direction, boolean pressed) {
if (pressed) {
directions.add(direction);
} else {
directions.remove(direction);
}
}
}
}
This is just one more approach to solve the same problem ;)
However, I am encountering a problem: it seems that removeNotify() is never called. I wanted to verify this and copied this post and pasted it into a new java file. I then added print statements in the add and remove notify methods, and only the print statement in the addNotify() method was printed. Also, if you add a print statement in the actionPerformed method, it prints repeatedly never ending
removeNotify is only called when the component, or a parent container, is removed.
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
JButton btn = new JButton("Remove");
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
Container parent = getParent();
parent.remove(TestPane.this);
parent.revalidate();
parent.repaint();
}
});
add(btn);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void addNotify() {
super.addNotify();
System.out.println("Add");
}
#Override
public void removeNotify() {
super.removeNotify();
System.out.println("Remove");
}
}
}
I verified that removeNotify will be called through the hierarchy when a parent container is removed.

KeyListener method keyPressed not working

I am trying to make a little game using java swing and have gotten pretty far but now my KeyListener is not using the method keyPressed.
Here is some of my code
public class Screen extends JPanel implements Runnable{
private static final int WIDTH = 300, HEIGHT = 300, RIGHT = 0, LEFT = 1, UP = 2, DOWN = 3, STILL = 4;
private Thread thread;
private boolean running = false;
private int direction = DOWN;
public Screen() {
setFocusable(true);
addKeyListener(new Key());
setPreferredSize(new Dimension(WIDTH, HEIGHT));
start();
}
public void tick(){
System.out.println(direction)
}
public void start() {
running = true;
thread = new Thread(this, "Game Loop");
thread.start();
}
public void run() {
while (running) {
tick();
repaint();
}
}
private class Key implements KeyListener{
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_D) {
direction = RIGHT;
}
if (key == KeyEvent.VK_A) {
direction = LEFT;
}
if (key == KeyEvent.VK_W) {
direction = UP;
}
if (key == KeyEvent.VK_S) {
direction = DOWN;
}
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
}
}
I am looking at the console and expecting the output to change from 3 (which is DOWN) whatever button I press but it never does. After some time I realized that something was wrong with the keyPressed method, I just don't know what.
You don't need a separate "Game Loop" thread. Swing is event driven. Your key listener code will be called when the user presses a key on the computer keyboard.
As MadProgrammer mentioned in his comment to your question
modifying he state of the UI from outside the context of the EDT may cause painting issues
In other words, Swing is single threaded and that thread is referred to as the Event Dispatch Thread (EDT) and all your code that changes the GUI should be executed on the EDT only.
The below code is a stand-alone, Swing application that displays a focussable JPanel. When you press one of the relevant keyboard "direction" keys, i.e. A or D or S or W, the console displays the direction. Pressing any other key causes the direction STILL to be written to the console. Note that I used a enum for the various directions, rather than integer constants.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
public class Screen01 extends JPanel implements Runnable {
public enum Directions {RIGHT, LEFT, UP, DOWN, STILL};
private static final int WIDTH = 300, HEIGHT = 300;
private Directions direction;
public Screen01() {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setFocusable(true);
addKeyListener(new Key());
}
public void run() {
createAndDisplayGui();
}
private void createAndDisplayGui() {
JFrame frame = new JFrame("Screen");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(this, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private class Key implements KeyListener {
#Override
public void keyTyped(KeyEvent e) {
// Do nothing.
}
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
switch (key) {
case KeyEvent.VK_D:
direction = Directions.RIGHT;
break;
case KeyEvent.VK_A:
direction = Directions.LEFT;
break;
case KeyEvent.VK_W:
direction = Directions.UP;
break;
case KeyEvent.VK_S:
direction = Directions.DOWN;
break;
default:
direction = Directions.STILL;
}
System.out.println("Direction = " + direction);
}
#Override
public void keyReleased(KeyEvent e) {
// Do nothing.
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Screen01());
}
}
Make use of the key bindings API, it will solve this, and all other, KeyListener related issues without a lot of hacking and head scratching over why it "sometimes" works
Swing is single threaded. This means you should be careful about updating the state of the UI (directly or indirectly) from outside of the context of the Event Dispatching Thread, this also includes any state the UI might be dependent on.
Have a look at Concurrency in Swing for more details.
Without more details, one of the simplest solutions would be to use a Swing Timer to act as the primary "tick" action. Each time it "ticks" you'd inspect the state of the input and update the state of the model and trigger a repaint to update the UI
For example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class TestPane extends JPanel {
enum Input {
UP, DOWN, LEFT, RIGHT
}
private Set<Input> input = new HashSet<>();
public TestPane() {
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Left.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Left.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Right.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Right.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Up.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Up.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Down.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Down.released");
am.put("Left.pressed", new InputAction(Input.LEFT, input, false));
am.put("Left.released", new InputAction(Input.LEFT, input, true));
am.put("Right.pressed", new InputAction(Input.RIGHT, input, false));
am.put("Right.released", new InputAction(Input.RIGHT, input, true));
am.put("Down.pressed", new InputAction(Input.DOWN, input, false));
am.put("Down.released", new InputAction(Input.DOWN, input, true));
am.put("Up.pressed", new InputAction(Input.UP, input, false));
am.put("Up.released", new InputAction(Input.UP, input, true));
input.add(Input.UP);
input.add(Input.DOWN);
input.add(Input.LEFT);
input.add(Input.RIGHT);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Check what's currently been pressed
// and update the state accordingly...
if (input.contains(Input.UP)) {
//..
}
if (input.contains(Input.DOWN)) {
//..
}
if (input.contains(Input.LEFT)) {
//..
}
if (input.contains(Input.RIGHT)) {
//..
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int midX = getWidth() / 2;
int midY = getHeight() / 2;
FontMetrics fm = g2d.getFontMetrics();
int spaceWidth = fm.stringWidth("M");
int spaceHeight = fm.getHeight();
if (input.contains(Input.UP)) {
String text = "UP";
g2d.drawString(text,
midX - (fm.stringWidth(text)) / 2,
(midY - (spaceHeight * 2) + fm.getAscent()));
}
if (input.contains(Input.DOWN)) {
String text = "DOWN";
g2d.drawString(text,
midX - (fm.stringWidth(text)) / 2,
(midY + spaceHeight + fm.getAscent()));
}
if (input.contains(Input.LEFT)) {
String text = "LEFT";
g2d.drawString(text,
(midX - spaceWidth - fm.stringWidth(text)),
(midY + (fm.getAscent() / 2)));
}
if (input.contains(Input.RIGHT)) {
String text = "RIGHT";
g2d.drawString(text,
(midX + spaceWidth),
(midY + (fm.getAscent() / 2)));
}
g2d.dispose();
}
public class InputAction extends AbstractAction {
private Input input;
private Set<Input> inputSet;
private boolean onRelease;
public InputAction(Input input, Set<Input> inputSet, boolean onRelease) {
this.input = input;
this.inputSet = inputSet;
this.onRelease = onRelease;
}
#Override
public void actionPerformed(ActionEvent e) {
if (onRelease) {
inputSet.remove(input);
} else {
inputSet.add(input);
}
}
}
}
}
This is a basic example and prints the state of the key inputs directly within the paintComponent method. A more realistic implementation would use the Timer's ActionListener to inspect the input state and make changes to the desired model accordingly, which is hinted at in the example.
If you require more direct control, then you need to avoid the Swing painting system and take control yourself. For this, you'd need to use a BufferStrategy. This is a more complicated solution, but it is also more powerful, as you gain complete control over when the UI is updated

Canvas double buffered graphics not working

I tried to make double buffered graphics for my canvas but it always disappears right after rendering, and sometimes it doesn't even render, Here is the code:
package initilizer;
import java.awt.AWTException;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import input.Keyboard;
public class Main extends Canvas{
static int width = 800;
static int height = 600;
int cx = width/2;
int cy = height/2;
boolean initilized = false;
double FOV = 0.5 * Math.PI;
Camera cam = new Camera(1.0, 5.0, 3.0);
Camera cam1 = new Camera(10.0, 50.0, 30.0);
long lastFpsCheck = System.currentTimeMillis();
public static JFrame frame = new JFrame("3D Engine");
Robot robot;
static Keyboard keyboard = new Keyboard();
Image img;
public static void main(String[] args) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Canvas canvas = new Main();
canvas.setSize(width, height);
canvas.addKeyListener(keyboard);
canvas.setFocusable(true);
canvas.setBackground(Color.black);
frame.add(canvas);
frame.pack();
frame.setVisible(true);
BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
// Create a new blank cursor.
Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
cursorImg, new Point(0, 0), "blank cursor");
// Set the blank cursor to the JFrame.
canvas.setCursor(blankCursor);
}
void init() {
try {
robot = new Robot();
} catch (AWTException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
double[] rotate2D(double[] pos,double[] rot) {
double x = pos[0];
double y = pos[1];
double s = rot[0];
double c = rot[1];
double[] result = {(x * c) - (y * s), (y * c) + (x * s)};
return result;
}
public void paint(Graphics MainGraphics) {
Point startMousePos = MouseInfo.getPointerInfo().getLocation();
double startMouseX = startMousePos.getX();
double startMouseY = startMousePos.getY();
if(img == null)
{
img = createImage(width, height);
}
Graphics g = img.getGraphics();;
// First run initialization
if (initilized == false) {
initilized = true;
init();
}
// Storing start time for FPS Counting
long startTime = System.currentTimeMillis();
// Clearing Last Frame
//g.clearRect(0, 0, width, height);
// Drawing Crosshair
g.setColor(Color.white);
g.fillRect(cx - 8, cy - 1, 16, 2);
g.fillRect(cx - 1, cy - 8, 2, 16);
// Drawing Debugger Menu
g.drawString("HI WASSUp", 0, 16);
g.dispose();
if (frame.isFocused() == true) {
robot.mouseMove(cx, cy);
Point endMousePos = MouseInfo.getPointerInfo().getLocation();
double endMouseX = endMousePos.getX();
double endMouseY = endMousePos.getY();
double[] rel = {startMouseX - endMouseX, startMouseY - endMouseY};
cam.mouseMotion(rel);
}
// Limiting FPS
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Calculating FPS
long endTime = System.currentTimeMillis();
double delta_time = (endTime - startTime);
if ((lastFpsCheck + 1000) < endTime) {
lastFpsCheck = endTime;
frame.setTitle("3D Engine - FPS: " + (int) (1000/delta_time));
}
// Controlling camera movement
if (keyboard.getW() == true) {
cam.update(delta_time, "W");
}
if (keyboard.getA() == true) {
cam.update(delta_time, "A");
}
if (keyboard.getS() == true) {
cam.update(delta_time, "S");
}
if (keyboard.getD() == true) {
cam.update(delta_time, "D");
}
if (keyboard.getE() == true) {
cam.update(delta_time, "E");
}
if (keyboard.getQ() == true) {
cam.update(delta_time, "Q");
}
// Draw rendered frame
MainGraphics.drawImage(img, 0,0, null);
// Draw next frame
repaint();
}
}
I posted a question recently about this code, You could check keyboard java from that last post if you wanted to, But please help me with this I'm new to java programming (I still have some programming experience tho), Thank you
The answer to your question is complicated.
Java Swing JPanel (or JComponent) are double buffered by default
Swing already has a painting mechanism, which you don't control, so you need to work within it's functionality.
The only real reason you would use a java.awt.Canvas is if you want to take complete control over the painting process
The first thing I would suggest you do is take a look at Performing Custom Painting and Painting in AWT and Swing to get a better idea of how painting works in Swing/AWT. This will provide you a better understanding of the API and whether you want to work with it or define your own.
Some other areas of concern:
Don't do Thread.sleep(1000); in the paint method, nothing will be rendered until AFTER the paint method returns. You want to seperate the "update pass" from the "paint pass". Painting does nothing else by paints. All you decision making should be done as part of your "update pass" from your "main loop", which should be executed (in this case) off the Event Dispatching Thread, so as to prevent possible issues, but which then raises a bunch of other issues
MouseInfo.getPointerInfo().getLocation() is NOT how you should be tracking the position of the mouse. Swing already provides a number of mechanisms for tracking the mouse events. See How to write a Mouse Listener and How to Write a Mouse-Motion Listener for more details.
Based on every thing, I'd also be concerned about how you're tracking keyboard input and would highly recommend that you take a look at How to Use Key Bindings for the most commonly recommended method for managing key board input from the user.
The following example simply uses a JPanel as it's primary rendering surface. It takes advantage of the pre-existing painting process and uses a Swing Timer as the "main loop" mechanism, which is responsible for processing the user input and update the state before scheduling a new paint pass.
Remember, Swing is NOT thread safe and you should not update the UI or anything the UI might depend on, from outside the context of the Event Dispatching Thread.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.time.Duration;
import java.time.Instant;
import java.util.HashSet;
import java.util.Set;
import java.util.StringJoiner;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
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();
Main main = new Main();
frame.add(main);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
main.start();
}
});
}
// Decouple the input from the implementation
enum Input {
UP, DOWN, LEFT, RIGHT
}
public class Main extends JPanel {
boolean initilized = false;
double FOV = 0.5 * Math.PI;
private Instant lastFpsCheck = Instant.now();
private Point mousePosition;
private Timer timer;
private Set<Input> input = new HashSet<>();
public Main() {
MouseAdapter mouseHandler = new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
// This is within the components coordinate space
mousePosition = e.getPoint();
}
#Override
public void mouseEntered(MouseEvent e) {
mousePosition = e.getPoint();
}
#Override
public void mouseExited(MouseEvent e) {
mousePosition = null;
}
};
addMouseMotionListener(mouseHandler);
addMouseListener(mouseHandler);
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Pressed.up");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Released.up");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Pressed.down");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Released.down");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Pressed.left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Released.left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Pressed.right");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Released.right");
actionMap.put("Pressed.up", new InputAction(input, Input.UP, true));
actionMap.put("Released.up", new InputAction(input, Input.UP, false));
actionMap.put("Pressed.down", new InputAction(input, Input.DOWN, true));
actionMap.put("Released.down", new InputAction(input, Input.DOWN, false));
actionMap.put("Pressed.left", new InputAction(input, Input.LEFT, true));
actionMap.put("Released.left", new InputAction(input, Input.LEFT, false));
actionMap.put("Pressed.right", new InputAction(input, Input.RIGHT, true));
actionMap.put("Released.right", new InputAction(input, Input.RIGHT, false));
timer = new Timer(15, new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
update();
}
});
}
public void start() {
startTime = Instant.now();
timer.start();
}
public void stop() {
timer.stop();
}
// The start time of a given cycle
private Instant startTime;
// The estimated number of frames per second
private double fps = 0;
// The number of acutal updates performed
// within a given cycle
private int updates = 0;
protected void update() {
if (startTime == null) {
startTime = Instant.now();
}
if (input.contains(Input.UP)) {
//cam.update(delta_time, "W");
}
if (input.contains(Input.LEFT)) {
//cam.update(delta_time, "A");
}
if (input.contains(Input.DOWN)) {
//cam.update(delta_time, "S");
}
if (input.contains(Input.RIGHT)) {
//cam.update(delta_time, "D");
}
// Don't know what these do, so you will need to devices
// your own action
//if (input.contains(Input.UP)) {
//cam.update(delta_time, "E");
//}
//if (input.contains(Input.UP)) {
//cam.update(delta_time, "Q");
//}
Instant endTime = Instant.now();
Duration deltaTime = Duration.between(startTime, endTime);
if (lastFpsCheck.plusSeconds(1).isBefore(endTime)) {
System.out.println(deltaTime.toMillis());
lastFpsCheck = endTime;
fps = updates;
updates = 0;
startTime = Instant.now();
}
updates++;
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 600);
}
double[] rotate2D(double[] pos, double[] rot) {
double x = pos[0];
double y = pos[1];
double s = rot[0];
double c = rot[1];
double[] result = {(x * c) - (y * s), (y * c) + (x * s)};
return result;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//Point startMousePos = MouseInfo.getPointerInfo().getLocation();
//double startMouseX = startMousePos.getX();
//double startMouseY = startMousePos.getY();
Graphics2D g2d = (Graphics2D) g.create();
// Drawing Debugger Menu
g2d.drawString("HI WASSUp", 0, 20);
if (mousePosition != null) {
g2d.drawString(mousePosition.x + "x" + mousePosition.y, 0, 40);
// Your old code is broken, because MouseInfo.getPointerInfo
// doesn't give you the position of the mouse from within
// the components coordinate space, but in the screen space
// instead
//robot.mouseMove(cx, cy);
//Point endMousePos = MouseInfo.getPointerInfo().getLocation();
//double endMouseX = endMousePos.getX();
//double endMouseY = endMousePos.getY();
//double[] rel = {startMouseX - endMouseX, startMouseY - endMouseY};
//cam.mouseMotion(rel);
}
g2d.drawString(Double.toString(fps), 0, 60);
StringJoiner sj = new StringJoiner(", ");
for (Input item : input) {
switch (item) {
case DOWN:
sj.add("down");
break;
case UP:
sj.add("up");
break;
case LEFT:
sj.add("left");
break;
case RIGHT:
sj.add("right");
break;
}
}
g2d.drawString(sj.toString(), 0, 80);
g2d.dispose();
}
public class InputAction extends AbstractAction {
private final Set<Input> input;
private final Input direction;
private final boolean add;
public InputAction(Set<Input> input, Input direction, boolean add) {
this.input = input;
this.direction = direction;
this.add = add;
}
#Override
public void actionPerformed(ActionEvent evt) {
if (add) {
input.add(direction);
} else {
input.remove(direction);
}
}
}
}
}
Now, because of the way Swing's paint process works, the FPS is at best a "guesstimate" and I'd personally no rely to heavy on it. I might consider setting the Timer to use a 5 millisecond delay instead and just go as fast as you can.
Now, if you absolutely, positively must have, without question, complete control over the painting process, then you will need to start with a java.awt.Canvas and make use of the BufferStrategy API.
This will give you complete control over the painting process. It's more complicated and will require you to take into consideration more edge cases, but will provide you with complete control over scheduling when a paint pass occurs and thus, better control over the FPS.
I would recommend having a look at the JavaDocs as the example is better.
I used the Thread.sleep(1000); to limit FPS only, It was 1000/60 but I changed it to this because I thought the problem may be in the speed of rendering
This is, to be frank, is a naive approach and demonstrates a lack of understanding with how the painting process works - no offensive, you've got to start somewhere. But a better place to start would be by reading the available documentation, which I've provided above so you can gain a better understanding of how the API actually works and make better decisions about whether you want to use it (ie JPanel) or roll your own (ie Canvas)

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;
}
}
}
}
}
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.

How to eliminate delay in keyPress?

So I have seen a few threads about this already and I need some help about how to fix it specifically. When you hold down a key, Java will read the first key press, then there will be a small delay, and then it will continuously read the key press until you release the key.
public void keyPressed(KeyEvent key) {
int code = key.getKeyCode();
if (code == KeyEvent.VK_DOWN) {
//Do stuff
}
if (code == KeyEvent.VK_LEFT) {
//Do stuff
}
if (code == KeyEvent.VK_RIGHT) {
//Do stuff
}
if (code == KeyEvent.VK_UP) {
//Do stuff
}
}
That is my current code. I heard that to fix this you can create a timer which rapidly checks for key presses, but I'm not really sure how to do that. Would appreciate some help here or if there is a better solution.
The basic answer to your question is, you can't, the delay is OS specific.
The longer answer is, you should be ignoring the individual events themselves and monitor a change in state (between press and release) through the use of appropriate flags.
This means, that when a key is pressed, you set some flag which you program can use to change the state of the program and when released, you reset it.
This disassociates the event from state change and provides you with much more flexibility, as your program doesn't care what caused the state change, only that the state has changed and it should react to it.
This will require you to have some kind of "loop" whose responsibility it is, is to monitor this change and react to it accordingly. In gaming, this is commonly known as a "game-loop", but can also been known as "main-loop".
It's this "loops" responsibility to update the state of the program and get it painted.
Below is a very simple example which uses the key bindings API and a javax.swing.Timer to demonstrate the basic concepts
import com.sun.glass.events.KeyEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class MoveMe {
public static void main(String[] args) {
new MoveMe();
}
public MoveMe() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MovementState {
public int xDirection;
public int yDirection;
}
public class TestPane extends JPanel {
private MovementState movementState;
private Rectangle box;
public TestPane() {
movementState = new MovementState();
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "down-pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "down-released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "up-pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "up-released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "left-pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "left-released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "right-pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "right-released");
am.put("down-pressed", new YDirectionAction(movementState, 2));
am.put("down-released", new YDirectionAction(movementState, 0));
am.put("up-pressed", new YDirectionAction(movementState, -2));
am.put("up-released", new YDirectionAction(movementState, 0));
am.put("left-pressed", new XDirectionAction(movementState, -2));
am.put("left-released", new XDirectionAction(movementState, 0));
am.put("right-pressed", new XDirectionAction(movementState, 2));
am.put("right-released", new XDirectionAction(movementState, 0));
box = new Rectangle(90, 90, 20, 20);
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
box.x += movementState.xDirection;
box.y += movementState.yDirection;
if (box.x < 0) {
box.x = 0;
} else if (box.x + box.width > getWidth()) {
box.x = getWidth() - box.width;
}
if (box.y < 0) {
box.y = 0;
} else if (box.y + box.height > getHeight()) {
box.y = getHeight() - box.height;
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.fill(box);
g2d.dispose();
}
}
public abstract class AbstractDirectionAction extends AbstractAction {
private final MovementState movementState;
private final int value;
public AbstractDirectionAction(MovementState movementState, int value) {
this.movementState = movementState;
this.value = value;
}
public MovementState getMovementState() {
return movementState;
}
public int getValue() {
return value;
}
}
public class YDirectionAction extends AbstractDirectionAction {
public YDirectionAction(MovementState movementState, int value) {
super(movementState, value);
}
#Override
public void actionPerformed(ActionEvent e) {
getMovementState().yDirection = getValue();
}
}
public class XDirectionAction extends AbstractDirectionAction {
public XDirectionAction(MovementState movementState, int value) {
super(movementState, value);
}
#Override
public void actionPerformed(ActionEvent e) {
getMovementState().xDirection = getValue();
}
}
}

Categories