I'm new to swing and I am trying to make the square move but only when the key is released (ex W) but when i hold down the key the square just moves
KeyListener Class
I want to make sure that a key was pressed and if it is still pressed it should return false but true if it was pressed then released
package javaGD.GameAssistant.Input;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class KeyManager implements KeyListener {
private boolean[] keys;
public KeyManager() {
keys = new boolean[256];
}
#Override
public void keyPressed(KeyEvent e) {
keys[e.getKeyCode()] = true;
}
#Override
public void keyReleased(KeyEvent e) {
keys[e.getKeyCode()] = false;
}
#Override
public void keyTyped(KeyEvent e) {
}
public boolean KeyPressed(int keycode) {
return keys[keycode];
}
public boolean KeyReleased(int keycode) {
return keys[keycode];
}
}
Class where the square should move.
KeyListener is inherited from GameAssistant(JFrame is created with the KeyListener)
package TestCode;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import javaGD.GameAssistant.GameAssistant;
public class Game extends GameAssistant {
public int x, xSpeed, y, ySpeed;
public Game(String title, int width, int height, boolean makeResizable) {
super(title, width, height, makeResizable);
}
#Override
public void setup() {
x = 0;
y = 0;
xSpeed = 5;
ySpeed = 5;
}
#Override
public void update() {
if (this.Keyboard().KeyReleased(KeyEvent.VK_D)) {
x += xSpeed;
}
if (this.Keyboard().KeyReleased(KeyEvent.VK_A)) {
x -= xSpeed;
}
if (this.Keyboard().KeyReleased(KeyEvent.VK_W)) {
y -= ySpeed;
}
if (this.Keyboard().KeyReleased(KeyEvent.VK_S)) {
y += ySpeed;
}
}
#Override
public void draw(Graphics g) {
g.setColor(Color.BLACK);
g.fillRect(x, y, 20, 20);
}
}
KeyReleased should be returning !keys[keycode] Otherwise it will return false when released and true when pressed
public boolean KeyReleased(int keycode) {
return !keys[keycode];
}
I would also recommend using the Key bindings API over KeyListener as it's more reliable and re-usable.
If you are only planning on a limited number of input operations, I would use Set and a enum, this way you can decouple the "how" from the "what".
You update method doesn't care "how" the inputs are managed, only the "what is the state"
Conceptually, maybe something like...
public enum GameInput {
UP, DOWN, LEFT, RIGHT;
}
public class KeyManager implements KeyListener {
private Set<GameInput> inputs = new HashSet<>();
public KeyManager() {
}
#Override
public void keyPressed(KeyEvent e) {
// Check the key code, verify if it's one of the configured
// actions keys
// The key code could come from a configuration file which might
// be customisable by the user...
if (e.getKeyCode() == KeyEvent.VK_W) {
inputs.add(GameInput.UP);
} else if (e.getKeyCode() == KeyEvent.VK_S) {
// etc...
} // etc...
}
#Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_W) {
inputs.remove(GameInput.UP);
} else if (e.getKeyCode() == KeyEvent.VK_S) {
// etc...
} // etc...
}
#Override
public void keyTyped(KeyEvent e) {
}
public boolean isKeyPressed(GameInput input) {
return inputs.contains(input);
}
public boolean isKeyReleased(GameInput input) {
return !isKeyPressed(input);
}
}
And your update method might look like...
#Override
public void update() {
if (this.Keyboard().isKeyReleased(GameInput.RIGHT)) {
x += xSpeed;
}
if (this.Keyboard().isKeyReleased(GameInput.LEFT)) {
x -= xSpeed;
}
if (this.Keyboard().isKeyReleased(GameInput.UP)) {
y -= ySpeed;
}
if (this.Keyboard().isKeyReleased(GameInput.DOWN)) {
y += ySpeed;
}
}
Now your update method doesn't care "how" the inputs are generated, only what to do when they are (or aren't) set.
Personally, I'd use a InputManager class and further decouple it, so inputs could be generate via other means, like buttons, mouse inputs, game pads, etc...
Runnable example...
Conceptually, this should work. I say "conceptually", because while I was testing I came across a number of "weird" issues that I can't contribute to either Java (1.8) or MacOS - or because they are a combination of both...more details below...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Rectangle;
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 class TestPane extends JPanel {
private Box box = new Box();
public TestPane() {
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
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");
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");
am.put("Up.pressed", new KeyAction(InputAction.UP, true));
am.put("Up.released", new KeyAction(InputAction.UP, false));
am.put("Down.pressed", new KeyAction(InputAction.DOWN, true));
am.put("Down.released", new KeyAction(InputAction.DOWN, false));
am.put("Left.pressed", new KeyAction(InputAction.LEFT, true));
am.put("Left.released", new KeyAction(InputAction.LEFT, false));
am.put("Right.pressed", new KeyAction(InputAction.RIGHT, true));
am.put("Right.released", new KeyAction(InputAction.RIGHT, false));
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
box.update(getBounds());
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
box.draw(g);
}
}
public class Box {
public int x, xSpeed, y, ySpeed, width, height;
public Box() {
x = 0;
y = 0;
xSpeed = 1;
ySpeed = 1;
width = 10;
height = 10;
}
public void update(Rectangle bounds) {
if (!InputManager.INSTANCE.isSet(InputAction.LEFT)) {
x -= xSpeed;
}
if (!InputManager.INSTANCE.isSet(InputAction.RIGHT)) {
x += xSpeed;
}
if (InputManager.INSTANCE.isSet(InputAction.UP) && InputManager.INSTANCE.isSet(InputAction.DOWN)) {
//
} else if (!InputManager.INSTANCE.isSet(InputAction.UP)) {
y -= ySpeed;
} else if (!InputManager.INSTANCE.isSet(InputAction.DOWN)) {
y += ySpeed;
}
if (x < bounds.x) {
x = 0;
} else if (x + width > (bounds.x + bounds.width)) {
x = bounds.x + (bounds.width - width);
}
if (y < bounds.y) {
y = 0;
} else if (y + height > (bounds.y + bounds.height)) {
y = bounds.y + (bounds.height - height);
}
}
public void draw(Graphics g) {
g.setColor(Color.BLACK);
g.fillRect(x, y, width, height);
}
}
public enum InputAction {
UP, DOWN, LEFT, RIGHT;
}
public enum InputManager {
INSTANCE;
private Set<InputAction> actions = new HashSet<>();
public void set(InputAction action) {
actions.add(action);
}
public void remove(InputAction action) {
actions.remove(action);
}
public boolean isSet(InputAction action) {
return actions.contains(action);
}
}
public class KeyAction extends AbstractAction {
private InputAction action;
private boolean apply;
public KeyAction(InputAction action, boolean apply) {
this.action = action;
this.apply = apply;
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("!");
if (apply) {
System.out.println("Apply " + action);
InputManager.INSTANCE.set(action);
} else {
System.out.println("Remove " + action);
InputManager.INSTANCE.remove(action);
}
}
}
}
TL;DR
While testing the above example, I came across a bizarre number of issues, I've not seen before (not that I've been doing this kind of thing recently).
When using KeyListener, if I pressed (and held) two buttons, I could see the "pressed" action, but there was no repeating events, which is something I would normally expect (for any keys). When I released on key, I saw the "release" action, but when I pressed (and held it), no new "press" action was generated.
I tried the key bindings API (as demonstrated above) and still had no success (similar results).
I then attached a AWTEventListener directly to the event queue and monitored ALL the key strokes.
I noted that, some times (even is just tapping a key repeatedly) that "pressed" might not be generated.
I also noted that holding one or more keys down, releasing and pressing a key again, more often then not, did not generate a new press event (only release events)
I'm using macOS 10.13.6 and Java 1.8.0_144-b01 - it could be bug in either or both, but I don't have the means to test it otherwise
Update...
So, after updating from Java 1.8 to Java 1.10, the above mentioned issue seems to be resoled - however, this highlights another, hardware issue, where only a certain number of keys can be actively pressed at a time - See How do I remove the limit on PC keyboard button presses? for more details
Related
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
Hey so im trying to make a D&D game for a school project. When I draw my background image it works and draws it. But when I draw my background image and my player image only the player image shows up.
My Code
Well does your player have transparency? If the player doesn't have alpha then it will draw over the bg.
Also, I would recommend using this render method instead in conjunction with a proper game loop:
private void render() {
BufferStrategy bs = getBufferStrategy();
if (bs == null) {
createBufferStrategy(3);
return;
}
Graphics2D g2d = (Graphics2D) bs.getDrawGraphics();
g2d.setColor(Color.black);
g2d.fillRect(0, 0, WIDTH, HEIGHT); // background (can also be an image)
player.render(g2d); // player
g2d.dispose();
bs.show();
}
So, your basic problem can be summed up here...
public class UIFrame extends JFrame {
//...
public UIFrame() {
setSize(size);
add(background);
add(userScreen);
Two thins are wrong with this. First, JFrame uses a BorderLayout to layout it's components, meaning that only the last component added will actually be laid out.
See Laying Out Components Within a Container for more details
Second, components are painted in LIFO order, so in your case, that would be userScreen then background - but since background is never laid out (it's size is 0x0) you won't see it.
The reason I mention it is because, you shouldn't be trying to design you program this way.
Next issue ...
public static Image[] scheme_player = new Image[1];
public static boolean imagesLoaded = false;
public static boolean leftPress = false;
public static boolean rightPress = false;
public static boolean upPress = false;
public static boolean downPress = false;
public static boolean mouseClick = false;
This is a pretty good sign of a bad design. static is not your friend and can lead to some very interesting (and difficult to debug) behaviour
Instead, we want to think about your design from in a more agnostic manner.
Disclaimer
This is an "example", intended to provide a "base line" of ideas from which you can grow your own API/concept from. There are some things in the example which I might do differently if I were designing my own solution, but have been done for brevity and to reduce the overall complexity
Basic concepts
You have stuff that paints, you have stuff that moves, you probably have stuff that collides and other stuff to. You need some way to manage and coordinate all this "stuff"
A basic principle of OOP (and programming in general) is the concept of "model-view-controller". The idea is to seperate your concepts into smaller, decoupled units of work, which can then be put back together, in what ever way you need, to form a large picture (or body of work)
So, let's start with the basic building blocks, the "entity". A "entity" is something in your program which carries information and may perform different tasks and roles. Because you might have any number of types of entities, I would start with a series of basic interfaces which describe basic behaviour of different types of entities...
public interface Entity {
// Other common properties
}
public interface MovableEntity extends Entity {
public void move(GameModel model);
// Other "movable" entities might also have
// speed adjustments, but I might consider those as
// seperate entities in of themselves, but that's me
}
public interface PaintableEntity extends Entity {
public void paint(Graphics2D g2d, GameModel model);
}
Notice, I didn't make movable or paintable into a single entity (you could create a fourth interface which does), this is because you might have a movable entity which isn't painted or a painted entity which doesn't move, like the background!
Next, we need some kind container to hold the important information which the rest of the game might need in order to do their jobs, some kind of, model!
public interface GameModel {
public enum Input {
UP, DOWN, LEFT, RIGHT;
}
public boolean hasInput(Input input);
public Dimension getViewableArea();
// Other properties which might be needed by the
// entities
}
This is pretty basic, but this is the concept around which information is shared. Here I've focused only on those elements which the entities really needed (no need to expose functionality that they don't really need).
I've also prepared the state for "input", which is agnostic from how that input actually occurs. The rest of the API doesn't care, they only want to know if the input is available or not.
From this, I can start building some basic entities...
public class BackgroundEntity implements PaintableEntity {
#Override
public void paint(Graphics2D g2d, GameModel model) {
g2d.setColor(Color.BLUE);
Dimension bounds = model.getViewableArea();
g2d.fillRect(0, 0, bounds.width, bounds.height);
}
}
public class PlayerEntity implements PaintableEntity, MovableEntity {
private int speed = 2;
private int x, y;
public PlayerEntity() {
// load the player image
}
public int getSpeed() {
return speed;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
#Override
public void paint(Graphics2D g2d, GameModel model) {
g2d.setColor(Color.RED);
g2d.drawRect(getX(), getY(), 10, 10);
}
#Override
public void move(GameModel model) {
int speed = getSpeed();
int x = getX();
int y = getY();
if (model.hasInput(GameModel.Input.UP)) {
y -= speed;
} else if (model.hasInput(GameModel.Input.DOWN)) {
y += speed;
}
if (model.hasInput(GameModel.Input.LEFT)) {
x -= speed;
} else if (model.hasInput(GameModel.Input.RIGHT)) {
x += speed;
}
Dimension bounds = model.getViewableArea();
if (y < 0) {
y = 0;
} else if (y + 10 > bounds.height) {
y = bounds.height - 10;
}
if (x < 0) {
x = 0;
} else if (x + 10 > bounds.width) {
x = bounds.width - 10;
}
setX(x);
setY(y);
}
}
Okay, that's all fine and good, but we need some way to actually update the state, in this case, we devise a concept of mutable model. The intent been to "hide" functionality, so we don't expose it to parts of the API which don't need (the player doesn't need to be able to add/remove new entities)
public interface MutableGameModel extends GameModel {
public void setInput(Input input, boolean enabled);
public void add(Entity entity);
public void remove(Entity entity);
public void update();
// Decision, who needs access to these lists
public List<PaintableEntity> paintableEntities();
public List<MovableEntity> moveableEntities();
}
And because we actually need some way to work...
public class DefaultGameModel implements MutableGameModel {
private Set<Input> inputs = new HashSet<>();
private List<Entity> entities = new ArrayList<>(25);
#Override
public boolean hasInput(Input input) {
return inputs.contains(input);
}
#Override
public void setInput(Input input, boolean enabled) {
if (enabled) {
inputs.add(input);
} else {
inputs.remove(input);
}
}
#Override
public Dimension getViewableArea() {
return new Dimension(400, 400);
}
public void update() {
for (MovableEntity entity : moveableEntities()) {
entity.move(this);
}
}
// This is not the most efficent approach. You might consider
// caching each entity type into seperate lists when they are added
// instead
public List<PaintableEntity> paintableEntities() {
return entities.stream()
.filter(e -> e instanceof PaintableEntity)
.map(e -> (PaintableEntity) e)
.collect(Collectors.toList());
}
public List<MovableEntity> moveableEntities() {
return entities.stream()
.filter(e -> e instanceof MovableEntity)
.map(e -> (MovableEntity) e)
.collect(Collectors.toList());
}
#Override
public void add(Entity entity) {
entities.add(entity);
}
#Override
public void remove(Entity entity) {
entities.remove(entity);
}
}
This is pretty basic concept, but it puts into place the foundations of the rest of work - please not this is a "EXAMPLE", I've cut some corners for brevity and so I could get it up and running quickly, but the basic concept should hold up
And finally, we get to the "view" (and controller). This is the part where we monitor for input states, update the model accordingly, run the main loop to update the model state, based on the current inputs, schedule and performing painting
public class GamePane extends JPanel {
private MutableGameModel model;
public GamePane(MutableGameModel model) {
this.model = model;
setupBindings();
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
model.update();
repaint();
}
});
timer.start();
}
public void setupBindings() {
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Up.pressed");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Up.released");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Down.pressed");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Down.released");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Left.pressed");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Left.released");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Right.pressed");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Right.released");
actionMap.put("Up.pressed", new InputAction(model, GameModel.Input.UP, true));
actionMap.put("Up.released", new InputAction(model, GameModel.Input.UP, false));
actionMap.put("Down.pressed", new InputAction(model, GameModel.Input.DOWN, true));
actionMap.put("Down.released", new InputAction(model, GameModel.Input.DOWN, false));
actionMap.put("Left.pressed", new InputAction(model, GameModel.Input.LEFT, true));
actionMap.put("Left.released", new InputAction(model, GameModel.Input.LEFT, false));
actionMap.put("Right.pressed", new InputAction(model, GameModel.Input.RIGHT, true));
actionMap.put("Right.released", new InputAction(model, GameModel.Input.RIGHT, false));
}
#Override
public Dimension getPreferredSize() {
return model.getViewableArea().getSize();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (PaintableEntity entity : model.paintableEntities()) {
Graphics2D g2d = (Graphics2D) g.create();
entity.paint(g2d, model);
g2d.dispose();
}
}
}
public class InputAction extends AbstractAction {
private MutableGameModel model;
private GameModel.Input input;
private boolean pressed;
public InputAction(MutableGameModel model, GameModel.Input input, boolean pressed) {
this.model = model;
this.input = input;
this.pressed = pressed;
}
#Override
public void actionPerformed(ActionEvent e) {
model.setInput(input, pressed);
}
}
Pretty simple :P
The solution makes use of the Key Bindings which is generally less troublesome them KeyListener and should be used in 99% of cases you want to monitor a subset of input.
I've also used a Swing Timer as my "main-loop". This is a safer option (in Swing) as it does not violate the single threaded nature of the API
Example...
Because I know how hard it can be to try and put together "snippets" of code...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
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.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
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 Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
DefaultGameModel model = new DefaultGameModel();
model.add(new BackgroundEntity());
model.add(new PlayerEntity());
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new GamePane(model));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class GamePane extends JPanel {
private MutableGameModel model;
public GamePane(MutableGameModel model) {
this.model = model;
setupBindings();
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
model.update();
repaint();
}
});
timer.start();
}
public void setupBindings() {
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Up.pressed");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Up.released");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Down.pressed");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Down.released");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Left.pressed");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Left.released");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Right.pressed");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Right.released");
actionMap.put("Up.pressed", new InputAction(model, GameModel.Input.UP, true));
actionMap.put("Up.released", new InputAction(model, GameModel.Input.UP, false));
actionMap.put("Down.pressed", new InputAction(model, GameModel.Input.DOWN, true));
actionMap.put("Down.released", new InputAction(model, GameModel.Input.DOWN, false));
actionMap.put("Left.pressed", new InputAction(model, GameModel.Input.LEFT, true));
actionMap.put("Left.released", new InputAction(model, GameModel.Input.LEFT, false));
actionMap.put("Right.pressed", new InputAction(model, GameModel.Input.RIGHT, true));
actionMap.put("Right.released", new InputAction(model, GameModel.Input.RIGHT, false));
}
#Override
public Dimension getPreferredSize() {
return model.getViewableArea().getSize();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (PaintableEntity entity : model.paintableEntities()) {
Graphics2D g2d = (Graphics2D) g.create();
entity.paint(g2d, model);
g2d.dispose();
}
}
}
public class InputAction extends AbstractAction {
private MutableGameModel model;
private GameModel.Input input;
private boolean pressed;
public InputAction(MutableGameModel model, GameModel.Input input, boolean pressed) {
this.model = model;
this.input = input;
this.pressed = pressed;
}
#Override
public void actionPerformed(ActionEvent e) {
model.setInput(input, pressed);
}
}
public class DefaultGameModel implements MutableGameModel {
private Set<Input> inputs = new HashSet<>();
private List<Entity> entities = new ArrayList<>(25);
#Override
public boolean hasInput(Input input) {
return inputs.contains(input);
}
#Override
public void setInput(Input input, boolean enabled) {
if (enabled) {
inputs.add(input);
} else {
inputs.remove(input);
}
}
#Override
public Dimension getViewableArea() {
return new Dimension(400, 400);
}
public void update() {
for (MovableEntity entity : moveableEntities()) {
entity.move(this);
}
}
// This is not the most efficent approach. You might consider
// caching each entity type into seperate lists when they are added
// instead
public List<PaintableEntity> paintableEntities() {
return entities.stream()
.filter(e -> e instanceof PaintableEntity)
.map(e -> (PaintableEntity) e)
.collect(Collectors.toList());
}
public List<MovableEntity> moveableEntities() {
return entities.stream()
.filter(e -> e instanceof MovableEntity)
.map(e -> (MovableEntity) e)
.collect(Collectors.toList());
}
#Override
public void add(Entity entity) {
entities.add(entity);
}
#Override
public void remove(Entity entity) {
entities.remove(entity);
}
}
public interface GameModel {
public enum Input {
UP, DOWN, LEFT, RIGHT;
}
public boolean hasInput(Input input);
public Dimension getViewableArea();
// Other properties which might be needed by the
// entities
}
public interface MutableGameModel extends GameModel {
public void setInput(Input input, boolean enabled);
public void add(Entity entity);
public void remove(Entity entity);
public void update();
// Decision, who needs access to these lists
public List<PaintableEntity> paintableEntities();
public List<MovableEntity> moveableEntities();
}
public interface Entity {
// Other common properties
}
public interface MovableEntity extends Entity {
public void move(GameModel model);
// Other "movable" entities might also have
// speed adjustments, but I might consider those as
// seperate entities in of themselves, but that's me
}
public interface PaintableEntity extends Entity {
public void paint(Graphics2D g2d, GameModel model);
}
public class BackgroundEntity implements PaintableEntity {
#Override
public void paint(Graphics2D g2d, GameModel model) {
g2d.setColor(Color.BLUE);
Dimension bounds = model.getViewableArea();
g2d.fillRect(0, 0, bounds.width, bounds.height);
}
}
public class PlayerEntity implements PaintableEntity, MovableEntity {
private int speed = 2;
private int x, y;
public PlayerEntity() {
// load the player image
}
public int getSpeed() {
return speed;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
#Override
public void paint(Graphics2D g2d, GameModel model) {
g2d.setColor(Color.RED);
g2d.drawRect(getX(), getY(), 10, 10);
}
#Override
public void move(GameModel model) {
int speed = getSpeed();
int x = getX();
int y = getY();
if (model.hasInput(GameModel.Input.UP)) {
y -= speed;
} else if (model.hasInput(GameModel.Input.DOWN)) {
y += speed;
}
if (model.hasInput(GameModel.Input.LEFT)) {
x -= speed;
} else if (model.hasInput(GameModel.Input.RIGHT)) {
x += speed;
}
Dimension bounds = model.getViewableArea();
if (y < 0) {
y = 0;
} else if (y + 10 > bounds.height) {
y = bounds.height - 10;
}
if (x < 0) {
x = 0;
} else if (x + 10 > bounds.width) {
x = bounds.width - 10;
}
setX(x);
setY(y);
}
}
}
nb - For every one whose going to tell more how horribly inefficient it is to use List#stream this way, yes, you are correct, I've mentioned that several times - the point for using is brevity.
If I was doing this, I'd either constraint the requirements at the MutableModelLevel (using things like add(PaintableEntity) or have the add method determine which series of Lists the entity needs to be added to) or make a series of generic "models" which constraint the functionality, so that when I devise my implementation, I can pick and choose which functionality I want to use - but that's just me
package Objects;
import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
public class PowerBar extends JPanel implements ActionListener, KeyListener {
private double x;
private final double y = 420;
private double xv = 0;
private final int width, height;
private boolean left = false, right = false;
private Timer t = new Timer(5, this);
public PowerBar(JPanel j) {
width = j.getWidth();
height = j.getHeight();
x = 180;
t.start();
this.addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
}
public void move() {
}
public void powerbarPosition() {
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Rectangle2D rect = new Rectangle2D.Double(x, y, 100, 15);
g2.setColor(Color.DARK_GRAY);
g2.fill(rect);
}
#Override
public void actionPerformed(ActionEvent ae) {
x += xv;
repaint();
}
#Override
public void keyPressed(KeyEvent ev) {
if (ev.getKeyCode() == KeyEvent.VK_LEFT) {
left = !left;
if (left == true) {
xv = -2;
} else if (ev.getKeyCode() == KeyEvent.VK_RIGHT) {
right = !right;
if (right == true) {
xv = 2;
}
}
}
}
#Override
public void keyReleased(KeyEvent ev) {
}
#Override
public void keyTyped(KeyEvent ke) {
}
}
Im kinda new to programming and this is my very first game to program. Im trying to get the power bar to move with the left and right key but there is no response from these keys when pressed. The power bar is drawn in a separate class called DrawMain which uses the paintComponent method.
A JPanel doesn't listen to Action, it is a container rather than a controller. Hence, it doesn't have a addActionListener(actionListener) function.
For listening to Key event- presse, release etc, the target component must have focus. However, you can invoke requestFocusInWindow() on target component to gain focus if you wish to.
It is preferable not to implement a listener to class which doesn't listen to such listener, in your context it is ActionListener. Make use of inline anonymous class or declare another class implementing the ActionListener
As #AndrewThompson and other swing gigs of stackoverflow will suggest, It is preferable using Key binding using key input map and action input map to a component which is rather higher level implementation. Try avoiding make use of lower level AWT implementation KeyListener as much as possible.
Check the official tutorial page:
How to use Key Bingings
How to Write a Key Listener
How to Write an Action Listener
1) Your ActionListener doesn't attached to JPanel and components on it, because of that it doesn't work.
2) Don't use KeyListener instead of use Key Bindings:
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,0), "doSomething1");
getActionMap().put("doSomething1", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent arg0) {
left = !left;
if (left == true) {
xv = -2;
}
}
});
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,0), "doSomething2");
getActionMap().put("doSomething2", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent arg0) {
right = !right;
if (right == true) {
xv = 2;
}
}
});
I've written this simple program which displays key presses, draws the phrase "Hello World," where one's cursor moves (with a trail mode when clicked) and also cycles through the Colors in which "Hello World," is picked out when the mouse wheel is scrolled. However there is a problem with this: When the mouse wheel scrolls the entire window goes blank (showing the default grey color from when you first make a component visible) and will then be redrawn with the color change (a very small change only to the "Hello World," which doesn't seem to require that the whole frame be redrawn.
The time for which the blankness occurs seems to correlate with the force with which the mouse wheel is scrolled, if I scroll very lightly there is only a very tiny moment where everything is not displayed, however scrolling very hard can make the window go blank for 2-3 seconds.
I've tried double buffering - thinking that this might be some sort of screen flicker - but it has made no change and I'm at a loss as to what could be causing this weird effect. It's as if the frame image is loading while the Wheel motion event is happening. (Is there perhaps a way to exit from the wheel event immediately so as to reduce loading time? (This is just my guesses as to possible solutions)).
The code is below. Any ideas would be greatly appreciated.
package keymouse;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferStrategy;
import java.util.LinkedList;
import javax.swing.JFrame;
public class KeyMouse implements KeyListener,
MouseMotionListener, MouseListener, MouseWheelListener, Runnable {
boolean trailMode = false;
boolean exists = false;
display window;
LinkedList wordList;
LinkedList trailList;
LinkedList colorList;
Point mousePoint;
int TRAIL_SIZE = 10;
boolean init = true;
int FONT_SIZE = 32;
int mouseY;
int mouseX;
int y;
int colorCount = 0;
public static void main(String[] args) {
KeyMouse k = new KeyMouse();
k.run();
}
public KeyMouse() {
window = new display();
window.addKeyListener(this);
window.addMouseMotionListener(this);
window.addMouseListener(this);
window.addMouseWheelListener(this);
window.setBackground(Color.WHITE);
window.setForeground(Color.BLACK);
wordList = new LinkedList();
trailList = new LinkedList();
colorList = new LinkedList();
colorList.add(Color.BLACK);
colorList.add(Color.BLUE);
colorList.add(Color.YELLOW);
colorList.add(Color.GREEN);
colorList.add(Color.PINK);
}
#Override
public void keyTyped(KeyEvent e) {
// do nothing
}
#Override
public void keyPressed(KeyEvent e) {
int keyCode;
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
stop();
}
keyCode = e.getKeyCode();
addMessage("Pressed:" + e.getKeyText(keyCode));
}
#Override
public void keyReleased(KeyEvent e) {
//do nothing
}
#Override
public void mouseDragged(MouseEvent e) {
Point p = new Point(e.getX(), e.getY());
addLocation(p);
}
#Override
public void mouseMoved(MouseEvent e) {
Point p = new Point(e.getX(), e.getY());
addLocation(p);
}
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
trailMode = true;
}
#Override
public void mouseReleased(MouseEvent e) {
trailMode = false;
}
#Override
public void mouseEntered(MouseEvent e) {
//do nothing
}
#Override
public void mouseExited(MouseEvent e) {
//do nothing
}
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
System.out.println(e.getWheelRotation());
colorCount++;
if (colorCount > 4) {
colorCount = 0;
}
window.setForeground((Color) colorList.get(colorCount));
}
#Override
public void run() {
window.createBufferStrategy(2);
BufferStrategy strategy = window.getBufferStrategy();
while (true) {
draw(strategy.getDrawGraphics());
strategy.show();
try {
Thread.sleep(20);
} catch (Exception ex) {
}
}
}
public void draw(Graphics g) {
//draw background
g.setColor(window.getBackground());
g.fillRect(0, 0, window.getWidth(), window.getHeight());
//draw Text
g.setColor(window.getForeground());
g.setFont(new Font("sansserif", Font.BOLD, 32));
int count = trailList.size();
if (trailList.size() > 1 && trailMode == false) {
count = 1;
}
if (exists == true) {
for (int i = 0; i < count; i++) {
Point p = (Point) trailList.get(i);
g.drawString("Hello World", p.x, p.y);
}
}
g.setColor(Color.BLACK);
y = 56;
for (int i = 0; i < wordList.size(); i++) {
String word = (String) wordList.get(i);
g.drawString((String) wordList.get(i), 100, y);
y += 32;
}
}
public void addMessage(String message) {
if (y >= window.getHeight()) {
wordList.remove(0);
}
wordList.add(message);
}
public void addLocation(Point h) {
exists = true;
trailList.addFirst(h);
if (trailList.size() > TRAIL_SIZE) {
trailList.removeLast();
}
}
public void printMessages() {
for (int i = 0; i < wordList.size(); i++) {
System.out.println(wordList.get(i));
}
}
private void stop() {
System.exit(0);
}
Absent a complete example, I can't reproduce the effect you describe. You might compare your code to this example, which exhibits no apparent blanking.
In general,
JPanel is double buffered by default; it's unusual to need a different buffer strategy.
AnimationTest illustrates a Swing Timer and how to display your average paint period.
MouseAdapter is convenient for overriding a small number of methods.
When possible, use generic parameters for type safety.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseWheelEvent;
import java.util.LinkedList;
import java.util.Queue;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
/**
* #see https://stackoverflow.com/a/10970892/230513
*/
public class ColorWheel extends JPanel {
private static final int N = 32;
private final Queue<Color> clut = new LinkedList<Color>();
private final JLabel label = new JLabel();
public ColorWheel() {
for (int i = 0; i < N; i++) {
clut.add(Color.getHSBColor((float) i / N, 1, 1));
}
this.setBackground(clut.peek());
label.setText(getBackground().toString());
this.add(label);
this.addMouseWheelListener(new MouseAdapter() {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
setBackground(clut.peek());
label.setText(getBackground().toString());
clut.add(clut.remove());
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(320, 240);
}
private void display() {
JFrame f = new JFrame("ColorWheel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new ColorWheel().display();
}
});
}
}
I have literally no idea why my program isn't recognizing keyboard input. I have places print statements throughout the program to determine the issue, and I have determined that the keyPressed method never activates. This is for a game which I am making for a class project, and yes I am a relatively beginner programmer. Thanks in advance! (Code below)
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.ImageIcon;
import javax.swing.JApplet;
public class Dodger extends JApplet implements Runnable, KeyListener {
Thread myThread;
public Image bg;
public Image pic;
public boolean loaded;
public int cx, cy, speed, x, y;
public void init(){
setSize(800,800);
loaded = false;
x = 2;
y = 400;
cx = 0;
cy = 0;
speed = 3;
myThread = new Thread(this);
myThread.start();
addKeyListener(this);
}
public void run(){
loadpic();
repaint();
while (myThread!=null)
{
try{
myThread.sleep(18);
}catch(InterruptedException e){
e.printStackTrace();
}
repaint();
}
}
public void upMotion(){
cy = cy + speed;
}
public void downMotion(){
cy = cy - speed;
}
public void leftMotion(){
cx = cx - speed;
}
public void rightMotion(){
cx = cx + speed;
}
#Override
public void keyPressed(KeyEvent k) {
if (k.getKeyCode() == KeyEvent.VK_LEFT) {
System.out.println("work");
leftMotion();
}
if (k.getKeyCode() == KeyEvent.VK_RIGHT) {
rightMotion();
}
if (k.getKeyCode() == KeyEvent.VK_UP) {
upMotion();
}
if (k.getKeyCode() == KeyEvent.VK_DOWN) {
downMotion();
}
}
#Override
public void keyReleased(KeyEvent e) {
}
#Override
public void keyTyped(KeyEvent e) {
}
public void loadpic(){
bg = new ImageIcon(getClass().getResource("back.png")).getImage();
pic = new ImageIcon(getClass().getResource("smile.png")).getImage();
loaded = true;
repaint();
}
public void paint(Graphics g){
g.drawImage(bg, 0, 0, this);
g.drawImage(pic, cx, cy, this);
}
}
The key events are detected just fine when the applet is focusable & has focus. Managing focus has always been a problem with applets. The problem is mostly that Sun never bothered to specify the focus behavior that should ideally apply to a mixed page of focusable HTML elements and applet(s).
As per Tom's advice, add to the end of init()
setFocusable(true);
To be safe, also override:
public void start() {
this.requestFocusInWindow();
}
As an aside, generally it is better to use key bindings in Swing. They also require the applet to have input focus.
First off, you probably want to separate out your class from your KeyListener, as it makes it a bit harder to read.
Next, you want to get rid of the bare Thread and wrap it in a timer.
import javax.swing.Timer;
class Dodger extends JApplet {
Timer imageUpdater; //replaces Thread
/*...*/
public void init() {
/*etc*/
loadpic();
int repaintInterval = 100;
imageUpdater = new Timer(repaintInterval,
new ActionListener() {
public void actionPerformed(ActionEvent e) {
repaint();
}
}
);
imageUpdater.start();
addKeyListener(new KeyHandler());
setFocusable(true);
}
/*...*/
private class KeyHandler extends KeyAdapter {
/* Note that with this implementation, you do not have to override
* unnecessary methods, as KeyAdapter is an abstract class that
* implements all of the methods of KeyListener.
*/
#Override
public void keyPressed(KeyEvent e) {
/*...*/
}
}
/*...*/
}
Most of this is just code cleanup - the actual problem may be fixed (according to Tom, see comments) with the setFocusable(true).
I'm not really sure if it applies to Applets, but your Thread may not allowing the event dispatching to occur.
Try to run your "work" with SwingWorker.