I am testing out graphics movement with simple action and key listener commands, but am struggling to incorporate the Graphics2D class. I am looking to draw an ellipse, which is then controllable through the use of the up, down, left and right keys. I am struggling to put all this together as i am very knew to a lot of this and followed a very outdated guide, as I could not find anything recent. Any help would be appreciated. Here is the code below:
import java.awt.*;
import java.awt.Graphics2D;
import javax.swing.JPanel;
import java.awt.event.*;
import javax.swing.Timer;
public class Screen extends JPanel implements ActionListener, KeyListener {
Timer t = new Timer(5, this);
int x = 0, y = 0, velx = 0, vely = 0;
public Screen() {
t.start();
addKeyListener(this);
setFocusable(true);
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
GRAPHICS2D g2 = (GRAPHICS2D) g;
g2.fill(new Ellipse2D.Double(x, y, 40, 40));
}
public void actionPeformed(ActionEvent e) {
repaint();
x += velx;
y += vely;
}
public void up() {
vely = -1.5;
velx = 0;
}
public void down(); {
vely = 1.5;
velx = 0;
}
public void left(); {
vely = 0;
velx = -1.5;
}
public void right(); {
vely = 0;
velx = 1.5;
}
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
if (code == KeyEvent.VK_UP) {
up();
}
if (code == KeyEvent.VK_DOWN) {
down();
}
if (code == KeyEvent.VK_LEFT) {
left();
}
if (code == KeyEvent.VK_RIGHT) {
right();
}
}
public void keyTyped(KeyEvent e) {}
public void keyReleased(KeyEvent e) {}
}```
When ran, it gives an error saying it cannot find a symbol when drawing the Ellipse, as well as giving missing method body errors for all of the movement calls. Help would be appreciated!
You will want to become familiar with key bindings and actions it is going to seriously make your life simpler in the long run.
The following example is deliberately long winded, as it's intended to demonstrate the power of the Action API.
So, I started with a concept of "direction"
public enum Direction {
UP, DOWN,
LEFT, RIGHT;
}
This is what's going to be supported, in terms of movements.
I then setup a basic "direction action", which will add or remove a Direction to a Set. Through this workflow, the "main loop" will decide what to do based on what's in the Set
public class InputAction extends AbstractAction {
private Direction input;
private Set<Direction> manager;
private boolean release;
public InputAction(Direction input, Set<Direction> manager, boolean release) {
this.input = input;
this.manager = manager;
this.release = release;
}
#Override
public void actionPerformed(ActionEvent e) {
if (release) {
manager.remove(input);
} else {
manager.add(input);
}
}
}
Because it can become really confusing really fast, I created a "pressed" and "released" InputAction
public class InputPressedAction extends InputAction {
public InputPressedAction(Direction input, Set<Direction> manager) {
super(input, manager, false);
}
}
public class InputReleasedAction extends InputAction {
public InputReleasedAction(Direction input, Set<Direction> manager) {
super(input, manager, true);
}
}
Following from that, I created specific direction actions, again, to make it simpler to configure.
public class UpPressedAction extends InputPressedAction {
public UpPressedAction(Set<Direction> manager) {
super(Direction.UP, manager);
}
}
public class UpReleasedAction extends InputReleasedAction {
public UpReleasedAction(Set<Direction> manager) {
super(Direction.UP, manager);
}
}
public class DownPressedAction extends InputPressedAction {
public DownPressedAction(Set<Direction> manager) {
super(Direction.DOWN, manager);
}
}
public class DownReleasedAction extends InputReleasedAction {
public DownReleasedAction(Set<Direction> manager) {
super(Direction.DOWN, manager);
}
}
public class LeftPressedAction extends InputPressedAction {
public LeftPressedAction(Set<Direction> manager) {
super(Direction.LEFT, manager);
}
}
public class LeftReleasedAction extends InputReleasedAction {
public LeftReleasedAction(Set<Direction> manager) {
super(Direction.LEFT, manager);
}
}
public class RightPressedAction extends InputPressedAction {
public RightPressedAction(Set<Direction> manager) {
super(Direction.RIGHT, manager);
}
}
public class RightReleasedAction extends InputReleasedAction {
public RightReleasedAction(Set<Direction> manager) {
super(Direction.RIGHT, manager);
}
}
Next, I setup the key bindings...
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), ActionKey.UP_PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), ActionKey.UP_RELEASED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), ActionKey.DOWN_PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), ActionKey.DOWN_RELEASED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), ActionKey.LEFT_PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), ActionKey.LEFT_RELEASED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), ActionKey.RIGHT_PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), ActionKey.RIGHT_RELEASED);
actionMap.put(ActionKey.UP_PRESSED, new UpPressedAction(actions));
actionMap.put(ActionKey.UP_RELEASED, new UpReleasedAction(actions));
actionMap.put(ActionKey.DOWN_PRESSED, new DownPressedAction(actions));
actionMap.put(ActionKey.DOWN_RELEASED, new DownReleasedAction(actions));
actionMap.put(ActionKey.LEFT_PRESSED, new LeftPressedAction(actions));
actionMap.put(ActionKey.LEFT_RELEASED, new LeftReleasedAction(actions));
actionMap.put(ActionKey.RIGHT_PRESSED, new RightPressedAction(actions));
actionMap.put(ActionKey.RIGHT_RELEASED, new RightReleasedAction(actions));
And you can start to see why I created all those action classes.
Then the "main" loop just needs to inspect the state of the Set and make determinations about what it should do...
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (actions.contains(Direction.UP)) {
YPos -= 1;
}
if (actions.contains(Direction.DOWN)) {
YPos += 1;
}
if (actions.contains(Direction.LEFT)) {
xPos -= 1;
}
if (actions.contains(Direction.RIGHT)) {
xPos += 1;
}
if (YPos < 0) {
YPos = 0;
} else if (YPos > getHeight() - shape.getBounds().height) {
YPos = getHeight() - shape.getBounds().height;
}
if (xPos < 0) {
xPos = 0;
} else if (xPos > getWidth() - shape.getBounds().width) {
xPos = getWidth() - shape.getBounds().width;
}
repaint();
}
});
And painting becomes as simple as moving the shape...
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.translate(xPos, YPos);
g2d.fill(shape);
g2d.dispose();
}
Runnable Example
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.Ellipse2D;
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 enum Direction {
UP, DOWN,
LEFT, RIGHT;
}
public class InputAction extends AbstractAction {
private Direction input;
private Set<Direction> manager;
private boolean release;
public InputAction(Direction input, Set<Direction> manager, boolean release) {
this.input = input;
this.manager = manager;
this.release = release;
}
#Override
public void actionPerformed(ActionEvent e) {
if (release) {
manager.remove(input);
} else {
manager.add(input);
}
}
}
public class InputPressedAction extends InputAction {
public InputPressedAction(Direction input, Set<Direction> manager) {
super(input, manager, false);
}
}
public class InputReleasedAction extends InputAction {
public InputReleasedAction(Direction input, Set<Direction> manager) {
super(input, manager, true);
}
}
public class UpPressedAction extends InputPressedAction {
public UpPressedAction(Set<Direction> manager) {
super(Direction.UP, manager);
}
}
public class UpReleasedAction extends InputReleasedAction {
public UpReleasedAction(Set<Direction> manager) {
super(Direction.UP, manager);
}
}
public class DownPressedAction extends InputPressedAction {
public DownPressedAction(Set<Direction> manager) {
super(Direction.DOWN, manager);
}
}
public class DownReleasedAction extends InputReleasedAction {
public DownReleasedAction(Set<Direction> manager) {
super(Direction.DOWN, manager);
}
}
public class LeftPressedAction extends InputPressedAction {
public LeftPressedAction(Set<Direction> manager) {
super(Direction.LEFT, manager);
}
}
public class LeftReleasedAction extends InputReleasedAction {
public LeftReleasedAction(Set<Direction> manager) {
super(Direction.LEFT, manager);
}
}
public class RightPressedAction extends InputPressedAction {
public RightPressedAction(Set<Direction> manager) {
super(Direction.RIGHT, manager);
}
}
public class RightReleasedAction extends InputReleasedAction {
public RightReleasedAction(Set<Direction> manager) {
super(Direction.RIGHT, manager);
}
}
public class TestPane extends JPanel {
enum ActionKey {
UP_PRESSED,
UP_RELEASED,
DOWN_PRESSED,
DOWN_RELEASED,
LEFT_PRESSED,
LEFT_RELEASED,
RIGHT_PRESSED,
RIGHT_RELEASED;
}
private Set<Direction> actions = new HashSet<>();
private Timer timer;
private Shape shape = new Ellipse2D.Double(0, 0, 40, 40);
int YPos = 0;
int xPos = 100 - 20;
public TestPane() {
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), ActionKey.UP_PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), ActionKey.UP_RELEASED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), ActionKey.DOWN_PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), ActionKey.DOWN_RELEASED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), ActionKey.LEFT_PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), ActionKey.LEFT_RELEASED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), ActionKey.RIGHT_PRESSED);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), ActionKey.RIGHT_RELEASED);
actionMap.put(ActionKey.UP_PRESSED, new UpPressedAction(actions));
actionMap.put(ActionKey.UP_RELEASED, new UpReleasedAction(actions));
actionMap.put(ActionKey.DOWN_PRESSED, new DownPressedAction(actions));
actionMap.put(ActionKey.DOWN_RELEASED, new DownReleasedAction(actions));
actionMap.put(ActionKey.LEFT_PRESSED, new LeftPressedAction(actions));
actionMap.put(ActionKey.LEFT_RELEASED, new LeftReleasedAction(actions));
actionMap.put(ActionKey.RIGHT_PRESSED, new RightPressedAction(actions));
actionMap.put(ActionKey.RIGHT_RELEASED, new RightReleasedAction(actions));
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (actions.contains(Direction.UP)) {
YPos -= 1;
}
if (actions.contains(Direction.DOWN)) {
YPos += 1;
}
if (actions.contains(Direction.LEFT)) {
xPos -= 1;
}
if (actions.contains(Direction.RIGHT)) {
xPos += 1;
}
if (YPos < 0) {
YPos = 0;
} else if (YPos > getHeight() - shape.getBounds().height) {
YPos = getHeight() - shape.getBounds().height;
}
if (xPos < 0) {
xPos = 0;
} else if (xPos > getWidth() - shape.getBounds().width) {
xPos = getWidth() - shape.getBounds().width;
}
repaint();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void addNotify() {
super.addNotify();
timer.start();
}
#Override
public void removeNotify() {
super.removeNotify();
timer.stop();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.translate(xPos, YPos);
g2d.fill(shape);
g2d.dispose();
}
}
}
Why not use a KeyListener
🤨
If you spend anytime researching the problem, you will find that, often (too often), KeyListener is "recommend" to monitor key input. Also, probably more often, you come across a number of issues related to the use of KeyListener, especially in this role.
KeyListener needs the component it's registered to be focusable AND have current focus before it will react to the user input.
The above solution also overcomes a OS limitation, where, when a key is first pressed, there is a short delay between the first and repeating keys. Instead, the above solution simply raises a flag when the key is pressed and removes it when it's released - simple, but effective
Introduction
MadProgrammer focused on Key Listeners in his answer. I want to focus on the model / view / controller (MVC) pattern and code organization.
Here's the GUI I came up with.
I use Key Bindings, so the arrow keys and the WASD keys move the circle. MadProgrammer explained it in his answer, so I won't repeat his explanation. As his key bindings do, mine move the circle at a steady pace as long as you're holding one of the keys down. Release the key, and the circle stops moving.
Explanation
Oracle has a helpful tutorial, Creating a GUI With Swing, that will show you the basics of Swing. Skip the Netbeans section.
In the MVC pattern, you create the model first, then the view, then the controllers. There's usually not one controller to rule them all. Each ActionListener acts independently, controlling its portion of the view and the model.
Model
The first thing I did was create the application model. I created two classes, CircleMovementModel and Circle. Both are plain Java getter / setter classes. The Circle class holds the components to draw a circle. The CircleMovementModel holds a Circle instance, the width, and the height of the drawing JPanel.
View
I started the Swing application with a call to the SwingUtilities invokeLater method. This method ensures that the Swing components are created and executed on the Event Dispatch Thread.
I created the JFrame and the drawing JPanel in separate classes. The JFrame has a default BorderLayout, which I used. I separate the creation of the JFrame and all associated JPanels into separate methods and classes so it's easier for human readers to understand the code.
The JFrame methods must be called in a specific order. This is the order I use for my Swing applications.
The drawing JPanel draws the circle in the specified position. Period. Nothing else. The movement of the circle is handled in the Swing Timer.
Controller
The key bindings look pretty complicated. There's a set of key bindings when the key is pressed. There's another set of key bindings when the key is released. The one Action class, MovementAction, handles all of the movements by passing the appropriate deltaX and deltaY through the constructor.
Finally, the ActionListener of the Swing Timer class moves the Circle instance at a steady pace. I made this class an anonymous class because it was so short.
Code
Here's the complete runnable code. I made all the additional classes inner classes so I could post this code as one block.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.Ellipse2D;
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.SwingUtilities;
import javax.swing.Timer;
public class CircleMovementGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new CircleMovementGUI());
}
private final CircleMovementModel model;
private final DrawingPanel drawingPanel;
public CircleMovementGUI() {
this.model = new CircleMovementModel();
this.drawingPanel = new DrawingPanel(model);
}
#Override
public void run() {
JFrame frame = new JFrame("Circle Movement");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(drawingPanel, BorderLayout.CENTER);
setKeyBindings(drawingPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
Timer timer = new Timer(20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
model.getCircle().incrementX();
model.getCircle().incrementY();
drawingPanel.repaint();
}
});
timer.start();
}
private void setKeyBindings(DrawingPanel drawingPanel) {
InputMap inputMap = drawingPanel.getInputMap();
ActionMap actionMap = drawingPanel.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "up");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "down");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "right");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "up");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "down");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "right");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "stop");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "stop");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "stop");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "stop");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "stop");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "stop");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "stop");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "stop");
actionMap.put("up", new MovementAction(model, 0.0, -1.5));
actionMap.put("down", new MovementAction(model, 0.0, +1.5));
actionMap.put("left", new MovementAction(model, -1.5, 0.0));
actionMap.put("right", new MovementAction(model, +1.5, 0.0));
actionMap.put("stop", new MovementAction(model, 0.0, 0.0));
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private final CircleMovementModel model;
public DrawingPanel(CircleMovementModel model) {
this.model = model;
this.setBackground(Color.BLACK);
this.setPreferredSize(new Dimension(model.getDrawingPanelWidth(),
model.getDrawingPanelHeight()));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
Circle circle = model.getCircle();
int radius = circle.getRadius();
int diameter = radius + radius;
double x = Math.round(circle.getX()) - radius;
double y = Math.round(circle.getY()) - radius;
g2d.setColor(circle.getColor());
g2d.fill(new Ellipse2D.Double(x, y, diameter, diameter));
}
}
public class MovementAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private final double deltaX, deltaY;
private final CircleMovementModel model;
public MovementAction(CircleMovementModel model,
double deltaX, double deltaY) {
this.model = model;
this.deltaX = deltaX;
this.deltaY = deltaY;
}
#Override
public void actionPerformed(ActionEvent event) {
model.getCircle().setDeltaX(deltaX);
model.getCircle().setDeltaY(deltaY);
}
}
public class CircleMovementModel {
private final int drawingPanelWidth, drawingPanelHeight;
private final Circle circle;
public CircleMovementModel() {
this.drawingPanelWidth = 600;
this.drawingPanelHeight = drawingPanelWidth;
this.circle = new Circle(Color.YELLOW, 20, drawingPanelWidth / 2,
drawingPanelHeight / 2);
}
public int getDrawingPanelWidth() {
return drawingPanelWidth;
}
public int getDrawingPanelHeight() {
return drawingPanelHeight;
}
public Circle getCircle() {
return circle;
}
}
public class Circle {
private final int radius;
private double x, y, deltaX, deltaY;
private final Color color;
public Circle(Color color, int radius, int x, int y) {
this.color = color;
this.radius = radius;
this.x = x;
this.y = y;
this.deltaX = 0.0;
this.deltaY = 0.0;
}
public double getX() {
return x;
}
public void incrementX() {
this.x = x + deltaX;
}
public double getY() {
return y;
}
public void incrementY() {
this.y = y + deltaY;
}
public double getDeltaX() {
return deltaX;
}
public void setDeltaX(double deltaX) {
this.deltaX = deltaX;
}
public double getDeltaY() {
return deltaY;
}
public void setDeltaY(double deltaY) {
this.deltaY = deltaY;
}
public int getRadius() {
return radius;
}
public Color getColor() {
return color;
}
}
}
Related
So I'm trying to awnser this question for like 2-3 hours now, but I can't quite find an fix or resolution for my problem. Like there are no Video tutorials. And because I am new to programming, especially with Java, I just don't know, how to rewrite code, that it matches my code and perfectly works. Here is what I have right now:
Also my project is all based on 2D and you only see your player for right above. So I dont really need Animation for the Player and Entity models, just in case you wondered.
Class: GamePanel
package main;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
import entity.Player;
public class GamePanel extends JPanel implements Runnable{
// SCREEN SETTINGS
final int originalTitleSize = 16; // 16x16 title
final int scale = 3; //16x3(scale) = 48
public final int tileSize = originalTitleSize * scale; //48x48 title
final int maxScreenCol = 16;
final int maxScreenRow = 12;
final int screenWidth = tileSize * maxScreenCol; // 768 pixels
final int screenHeight = tileSize * maxScreenRow; // 576 pixels
//FPS
int FPS = 60;
KeyHandler keyH = new KeyHandler();
Thread gameThread;
Player player = new Player(this,keyH);
// Set player's default position
int playerX = 100;
int playerY = 100;
int playerSpeed = 4;
public GamePanel() {
this.setPreferredSize(new Dimension(screenWidth, screenHeight));
this.setBackground(Color.BLACK);
this.setDoubleBuffered(true);
this.addKeyListener(keyH);
this.setFocusable(true);
}
public void startGameThread() {
gameThread = new Thread(this);
gameThread.start();
}
#Override
// public void run() {
//
// double drawInterval = 1000000000/FPS; // 0.0166666... seconds
// double nextDrawTime = System.nanoTime() + drawInterval;
//
//
//
// while(gameThread != null) {
//
// // System.out.println("The game loop is running");
//
// // 1 UPDATE: update information such as character positions
// update();
//
//
//
// // 2 DRAW: draw the screen with the updated information
// repaint();
//
// try {
// double remainingTime = nextDrawTime - System.nanoTime();
// remainingTime = remainingTime/1000000;
//
// if(remainingTime < 0) {
// remainingTime = 0;
// }
//
// Thread.sleep((long) remainingTime);
//
// nextDrawTime += drawInterval;
//
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }
public void run() {
double drawInterval = 1000000000/FPS;
double delta = 0;
long lastTime = System.nanoTime();
long currentTime;
long timer = 0;
int drawCount = 0;
while(gameThread != null) {
currentTime =System.nanoTime();
delta += (currentTime - lastTime) / drawInterval;
timer += (currentTime -lastTime);
lastTime = currentTime;
if(delta >=1) {
update();
repaint();
delta--;
drawCount++;
}
if(timer >= 1000000000) {
System.out.println("FPS:" + drawCount);
drawCount = 0;
timer = 0;
}
}
}
public void update() {
player.update();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
player.draw(g2);
g2.dispose();
}
}
Class: KeyHandler
package main;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class KeyHandler implements KeyListener{
public boolean upPressed, downPressed, leftPressed, rightPressed;
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
if(code == KeyEvent.VK_W) {
upPressed = true;
}
if(code == KeyEvent.VK_S) {
downPressed = true;
}
if(code == KeyEvent.VK_A) {
leftPressed = true;
}
if(code == KeyEvent.VK_D) {
rightPressed = true;
}
}
#Override
public void keyReleased(KeyEvent e) {
int code = e.getKeyCode();
if(code == KeyEvent.VK_W) {
upPressed = false;
}
if(code == KeyEvent.VK_S) {
downPressed = false;
}
if(code == KeyEvent.VK_A) {
leftPressed = false;
}
if(code == KeyEvent.VK_D) {
rightPressed = false;
}
}
}
Class: Main
package main;
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
JFrame window = new JFrame();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setResizable(false);
window.setTitle("Zombio 0.0.0.01");
GamePanel gamePanel = new GamePanel();
window.add(gamePanel);
window.pack();
window.setLocationRelativeTo(null);
window.setVisible(true);
gamePanel.startGameThread();
}
}
I also have an Player:
package entity;
import java.awt.Color;
import java.awt.Graphics2D;
import main.GamePanel;
import main.KeyHandler;
public class Player extends Entity{
GamePanel gp;
KeyHandler keyH;
public Player(GamePanel gp, KeyHandler keyH) {
this.gp = gp;
this.keyH = keyH;
setDefaultValues();
}
public void setDefaultValues() {
x = 100;
y = 100;
speed = 4;
}
public void update() {
if(keyH.upPressed == true) {
y -= speed;
}
else if(keyH.downPressed == true) {
y += speed;
}
else if(keyH.leftPressed == true) {
x -= speed;
}
else if(keyH.rightPressed == true) {
x += speed;
}
}
public void draw(Graphics2D g2) {
g2.setColor(Color.yellow);
g2.fillRect(x, y, gp.tileSize, gp.tileSize); //(x, y, width, height)
}
}
And this Player is based on the normal-entity:
package entity;
public class Entity {
public int x, y;
public int speed;
}
I know, this is a lot you need to look through, but I really don't know, what exaclty you need. I would like to implement the rotation in the Player Class, if this is possible for you. Also please not only write Code and set it as awnser, I really have no expirience, so take my by the hand :)
Thanks for your help, appreciate it!
You will want to have a look at:
Key bindings - these will solve all the focus related issues with KeyListener
Concurrency in Swing and probably switch over to using a Swing Timer. Swing is NOT thread safe, it's not a good idea to use Thread as your "main loop".
So your basic problem is a "simple" trigonometry problem (I as say simple, but I'm an idiot). You have two points in space and need to calculate the angle between them, for example...
// Radians
-Math.atan2(startY - endY, startX - endX)
Runnable example...
import java.awt.Color;
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.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() {
GamePanel gamePanel = new GamePanel();
JFrame frame = new JFrame();
frame.add(gamePanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
gamePanel.startGameThread();
}
});
}
public class GamePanel extends JPanel { //implements Runnable {
// SCREEN SETTINGS
final int originalTitleSize = 16; // 16x16 title
final int scale = 3; //16x3(scale) = 48
public final int tileSize = originalTitleSize * scale; //48x48 title
final int maxScreenCol = 16;
final int maxScreenRow = 12;
final int screenWidth = tileSize * maxScreenCol; // 768 pixels
final int screenHeight = tileSize * maxScreenRow; // 576 pixels
//FPS
int FPS = 60;
Player player = new Player(this);
private Timer timer;
private Set<KeyAction.Direction> movementState = new HashSet<>();
private Point lastKnownMousePoint;
public GamePanel() {
this.setBackground(Color.BLACK);
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
lastKnownMousePoint = e.getPoint();
}
});
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Pressed.up");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Released.up");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Pressed.down");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Released.down");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Pressed.left");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Released.left");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Pressed.right");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Released.right");
ActionMap am = getActionMap();
am.put("Pressed.up", new KeyAction(KeyAction.Direction.UP, true, movementState));
am.put("Released.up", new KeyAction(KeyAction.Direction.UP, false, movementState));
am.put("Pressed.down", new KeyAction(KeyAction.Direction.DOWN, true, movementState));
am.put("Released.down", new KeyAction(KeyAction.Direction.DOWN, false, movementState));
am.put("Pressed.left", new KeyAction(KeyAction.Direction.LEFT, true, movementState));
am.put("Released.left", new KeyAction(KeyAction.Direction.LEFT, false, movementState));
am.put("Pressed.right", new KeyAction(KeyAction.Direction.RIGHT, true, movementState));
am.put("Released.right", new KeyAction(KeyAction.Direction.RIGHT, false, movementState));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(screenWidth, screenHeight);
}
public void startGameThread() {
if (timer == null) {
timer = new Timer((int) Math.floor(1000f / FPS), new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
update();
repaint();
}
});
}
timer.start();
}
public void update() {
if (movementState.contains(KeyAction.Direction.UP)) {
player.y -= player.speed;
} else if (movementState.contains(KeyAction.Direction.DOWN)) {
player.y += player.speed;
} else if (movementState.contains(KeyAction.Direction.LEFT)) {
player.x -= player.speed;
} else if (movementState.contains(KeyAction.Direction.RIGHT)) {
player.x += player.speed;
}
if (lastKnownMousePoint != null) {
// This assumes that character is facing "UP" by default
// That is, 0 has the character entity facing towards to the
// top of the sceen. If the character is facing in a different
// direction, then you will need to offset this calculation
// to compenstate, but that might be better done in the player
// entity
double angle = -Math.toDegrees(Math.atan2(player.x - lastKnownMousePoint.x, player.y - lastKnownMousePoint.y));
player.angleInDegrees = angle;
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
player.draw(g2);
g2.dispose();
}
}
public class KeyAction extends AbstractAction {
enum Direction {
UP, DOWN, LEFT, RIGHT
}
private Direction direction;
private boolean activate;
private Set<Direction> inputState;
public KeyAction(Direction direction, boolean activate, Set<Direction> inputState) {
this.direction = direction;
this.activate = activate;
this.inputState = inputState;
}
#Override
public void actionPerformed(ActionEvent e) {
if (activate) {
inputState.add(direction);
} else {
inputState.remove(direction);
}
}
}
public class Entity {
public int x;
public int y;
public int speed;
}
public class Player extends Entity {
GamePanel gp;
double angleInDegrees = 0;
public Player(GamePanel gp) {
this.gp = gp;
setDefaultValues();
}
public void setDefaultValues() {
x = 100;
y = 100;
speed = 4;
}
public void draw(Graphics2D g2) {
g2 = (Graphics2D) g2.create();
g2.translate(x, y);
g2.rotate(Math.toRadians(angleInDegrees), (gp.tileSize / 2), (gp.tileSize / 2));
g2.setColor(Color.yellow);
g2.fillRect(0, 0, gp.tileSize, gp.tileSize);
g2.setColor(Color.RED);
g2.drawLine((gp.tileSize / 2), (gp.tileSize / 2), (gp.tileSize / 2), 0);
g2.dispose();
}
}
}
Oh, and also the rotation point is not right in the middle of the player (rectangle). Some dont need accurancy, I really do
Just beware, you're probably never going to find the "exact" solutions to your problems and you're going to need to take the time to experiment ;)
Okay, admittedly, that seemed to work for me, until I started debugging it. The problem was, the original code was calculating the angle from the mouse point and the players current x/y point. It should be using the players "rotation" point, which, in this example, is the players mid point.
So, I added...
public Point midPoint() {
return new Point(x + (gp.tileSize / 2), y + (gp.tileSize / 2));
}
to Player, so we can easily get the player's mid point (and not have to retype that a lot)
I then updated the angle calculation to make use of it, for example..
Point playerMidPoint = player.midPoint();
double angle = Math.toDegrees(Math.atan2(lastKnownMousePoint.y - playerMidPoint.y, lastKnownMousePoint.x - playerMidPoint.x)) + 90d;
player.angleInDegrees = angle;
Runnable example...
import java.awt.Color;
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.awt.geom.AffineTransform;
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() {
GamePanel gamePanel = new GamePanel();
JFrame frame = new JFrame();
frame.add(gamePanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
gamePanel.startGameThread();
}
});
}
public class GamePanel extends JPanel { //implements Runnable {
// SCREEN SETTINGS
final int originalTitleSize = 16; // 16x16 title
final int scale = 3; //16x3(scale) = 48
public final int tileSize = originalTitleSize * scale; //48x48 title
final int maxScreenCol = 16;
final int maxScreenRow = 12;
final int screenWidth = tileSize * maxScreenCol; // 768 pixels
final int screenHeight = tileSize * maxScreenRow; // 576 pixels
//FPS
int FPS = 60;
Player player = new Player(this);
private Timer timer;
private Set<KeyAction.Direction> movementState = new HashSet<>();
private Point lastKnownMousePoint;
public GamePanel() {
this.setBackground(Color.BLACK);
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
lastKnownMousePoint = new Point(e.getPoint());
}
});
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Pressed.up");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Released.up");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Pressed.down");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Released.down");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Pressed.left");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Released.left");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Pressed.right");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Released.right");
ActionMap am = getActionMap();
am.put("Pressed.up", new KeyAction(KeyAction.Direction.UP, true, movementState));
am.put("Released.up", new KeyAction(KeyAction.Direction.UP, false, movementState));
am.put("Pressed.down", new KeyAction(KeyAction.Direction.DOWN, true, movementState));
am.put("Released.down", new KeyAction(KeyAction.Direction.DOWN, false, movementState));
am.put("Pressed.left", new KeyAction(KeyAction.Direction.LEFT, true, movementState));
am.put("Released.left", new KeyAction(KeyAction.Direction.LEFT, false, movementState));
am.put("Pressed.right", new KeyAction(KeyAction.Direction.RIGHT, true, movementState));
am.put("Released.right", new KeyAction(KeyAction.Direction.RIGHT, false, movementState));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(screenWidth, screenHeight);
}
public void startGameThread() {
if (timer == null) {
timer = new Timer((int) Math.floor(1000f / FPS), new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
update();
repaint();
}
});
}
timer.start();
}
public void update() {
if (movementState.contains(KeyAction.Direction.UP)) {
player.y -= player.speed;
} else if (movementState.contains(KeyAction.Direction.DOWN)) {
player.y += player.speed;
} else if (movementState.contains(KeyAction.Direction.LEFT)) {
player.x -= player.speed;
} else if (movementState.contains(KeyAction.Direction.RIGHT)) {
player.x += player.speed;
}
if (lastKnownMousePoint != null) {
// This assumes that character is facing "UP" by default
// That is, 0 has the character entity facing towards to the
// top of the sceen. If the character is facing in a different
// direction, then you will need to offset this calculation
// to compenstate, but that might be better done in the player
// entity
Point playerMidPoint = player.midPoint();
double angle = Math.toDegrees(Math.atan2(lastKnownMousePoint.y - playerMidPoint.y, lastKnownMousePoint.x - playerMidPoint.x)) + 90d;
player.angleInDegrees = angle;
repaint();
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
player.draw(g2);
g2.dispose();
if (lastKnownMousePoint != null) {
g2 = (Graphics2D) g;
g2.setColor(Color.GREEN);
int midX = player.x + (tileSize / 2);
int midY = player.y + (tileSize / 2);
g2.drawLine(midX, midY, lastKnownMousePoint.x, lastKnownMousePoint.y);
g2.dispose();
}
}
}
public class KeyAction extends AbstractAction {
enum Direction {
UP, DOWN, LEFT, RIGHT
}
private Direction direction;
private boolean activate;
private Set<Direction> inputState;
public KeyAction(Direction direction, boolean activate, Set<Direction> inputState) {
this.direction = direction;
this.activate = activate;
this.inputState = inputState;
}
#Override
public void actionPerformed(ActionEvent e) {
if (activate) {
inputState.add(direction);
} else {
inputState.remove(direction);
}
}
}
public class Entity {
public int x;
public int y;
public int speed;
}
public class Player extends Entity {
GamePanel gp;
double angleInDegrees = 0;
public Player(GamePanel gp) {
this.gp = gp;
setDefaultValues();
}
public void setDefaultValues() {
x = 100;
y = 100;
speed = 4;
}
public Point midPoint() {
return new Point(x + (gp.tileSize / 2), y + (gp.tileSize / 2));
}
public void draw(Graphics2D g2) {
g2 = (Graphics2D) g2.create();
AffineTransform at = AffineTransform.getTranslateInstance(x, y);
at.rotate(Math.toRadians(angleInDegrees), (gp.tileSize / 2d), (gp.tileSize / 2d));
g2.transform(at);
g2.setColor(Color.yellow);
g2.fillRect(0, 0, gp.tileSize, gp.tileSize);
g2.setColor(Color.RED);
g2.drawLine((gp.tileSize / 2), (gp.tileSize / 2), (gp.tileSize / 2), 0);
g2.dispose();
}
}
}
I'm new to java, and as a challenge I'm following some tutorials on how to create a small game. I've managed to make a nice keyboard input reader but sadly I don't know why my repaint(); isn't working... The point is to change the ints Py and Px, they are responsible in changing the position of the player!
This is the class f2, a JPanel that is added to a Jframe in another file.
import java.awt.*;
import java.awt.event.*;
public class f2 extends JPanel implements Runnable{
keys keyh = new keys();
Thread gameThreadMain;
//background color
Color bg2 = new Color(81,89,98);
Color txt1 = new Color(237,237,237);
//width control
final int w = 700, h = 700;
int wp = 64, hp = 64;
//spawn cords
//
int spawn1x = (w / 2) - (wp / 2);
int spawn1y = (h / 2) - (hp / 2);
//Player Speed
int Px = spawn1x;
int Py = spawn1y;
int Ps = 4;
//FPS
int fps = 60;
public f2(){
/* this.addKet*/
this.setBackground(bg2);
this.setPreferredSize(new Dimension(w,h));
this.setDoubleBuffered(true);
/*this.setLayout();*/
}
public void startGameThreadMain(){
gameThreadMain = new Thread(this);
gameThreadMain.start();
}
#Override
public void run(){
double drawInterval = 1000000000/fps;
double nextDrawTime = System.nanoTime() + drawInterval;
while(gameThreadMain != null){
update();
repaint();
/*System.out.print("check");*/
try{
double remainingTime = nextDrawTime - System.nanoTime();
remainingTime = remainingTime / 1000000;
if (remainingTime < 0) {
remainingTime = 0;
}
Thread.sleep((long) remainingTime);
nextDrawTime += drawInterval;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void update(){
//Keyboard
if(keyh.UpPressed == true) {
Py -= Ps;
}
else if(keyh.DownPressed == true) {
Py += Ps;
}
else if(keyh.LeftPressed == true) {
Px -= Ps;
}
else if(keyh.RightPressed == true) {
Px += Ps;
}else{
//
}
}
//player painter (this is the part where the 'repaint();' isn't working
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D player = (Graphics2D)g;
player.setColor(txt1);
player.fillRect(Px,Py,wp,hp);
System.out.println("repainted");
}
}```
//Thanks for reading!
startGameThreadMain is never called.
Swing is not thread AND is single threaded. You should never update the UI, or some state the UI depends on, from outside the context of the Event Dispatching Thread.
See Concurrency in Swing for more details and How to Use Swing Timers for one possible solution.
You should also be aware of:
How to Use Key Bindings
Painting in AWT and Swing
Performing Custom Painting
For example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.Rectangle2D;
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 class Player {
private int x;
private int y;
private Color backgroundColor = new Color(237, 237, 237);
private Shape shape;
public Player(int x, int y) {
this.x = x;
this.y = y;
this.shape = new Rectangle2D.Double(0, 0, getWidth(), getHeight());
}
public void setLocation(int x, int y) {
setX(x);
setY(y);
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getWidth() {
return 64;
}
public int getHeight() {
return 64;
}
public void paint(Graphics2D g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(backgroundColor);
g2d.translate(getX(), getY());
g2d.fill(shape);
g2d.dispose();
}
}
public enum Movement {
UP, DOWN, LEFT, RIGHT;
}
public interface MovementObserver {
public void movementDidActiviated(Object source, Movement movement);
public void movementWasDeactiviated(Object source, Movement movement);
}
public class TestPane extends JPanel {
private Timer timer;
private Player player;
private Set<Movement> movements = new HashSet<>();
public TestPane() {
player = new Player(0, 0);
int spawnY = (getPreferredSize().width - player.getWidth()) / 2;
int spawnX = (getPreferredSize().height - player.getHeight()) / 2;
player.setLocation(spawnX, spawnY);
setBackground(new Color(81, 89, 98));
timer = new Timer(15, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (movements.contains(Movement.UP)) {
player.setY(player.getY() - 1);
} else if (movements.contains(Movement.DOWN)) {
player.setY(player.getY() + 1);
}
if (movements.contains(Movement.LEFT)) {
player.setX(player.getX() - 1);
} else if (movements.contains(Movement.RIGHT)) {
player.setX(player.getX() + 1);
}
repaint();
}
});
MovementObserver observer = new MovementObserver() {
#Override
public void movementDidActiviated(Object source, Movement movement) {
movements.add(movement);
}
#Override
public void movementWasDeactiviated(Object source, Movement movement) {
movements.remove(movement);
}
};
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_S, 0, false), "Down.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Left.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Right.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Up.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Down.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Left.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Right.released");
am.put("Up.pressed", new MovementAction(Movement.UP, observer, true));
am.put("Down.pressed", new MovementAction(Movement.DOWN, observer, true));
am.put("Left.pressed", new MovementAction(Movement.LEFT, observer, true));
am.put("Right.pressed", new MovementAction(Movement.RIGHT, observer, true));
am.put("Up.released", new MovementAction(Movement.UP, observer, false));
am.put("Down.released", new MovementAction(Movement.DOWN, observer, false));
am.put("Left.released", new MovementAction(Movement.LEFT, observer, false));
am.put("Right.released", new MovementAction(Movement.RIGHT, observer, false));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(700, 700);
}
#Override
public void addNotify() {
super.addNotify();
if (timer == null) {
return;
}
timer.start();
}
#Override
public void removeNotify() {
super.removeNotify();
if (timer == null) {
return;
}
timer.stop();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (player == null) {
return;
}
Graphics2D g2d = (Graphics2D) g.create();
player.paint(g2d);
g2d.dispose();
}
}
public class MovementAction extends AbstractAction {
private Movement movement;
private MovementObserver observer;
private boolean activate;
public MovementAction(Movement movement, MovementObserver observer, boolean active) {
this.movement = movement;
this.observer = observer;
this.activate = active;
}
public Movement getMovement() {
return movement;
}
public MovementObserver getObserver() {
return observer;
}
public boolean doesActivate() {
return activate;
}
#Override
public void actionPerformed(ActionEvent e) {
if (doesActivate()) {
getObserver().movementDidActiviated(this, getMovement());
} else {
getObserver().movementWasDeactiviated(this, getMovement());
}
}
}
}
These are all of my classes, I'm trying to make a platformer game with an array list to hold my platforms, this is so I can add more platforms any time and anywhere. For some reason, it's not drawing the platforms.
Can someone please help me with this issue or give me an alternative?
NOTE: some of the variables and methods I either haven't used yet or forgot to delete when i was re-creating my code.
package Game;
import Game.Frame;
public class Main {
public static void main(String[] args) {
new Frame();
}
}
package Game;
import javax.swing.*;
import java.awt.*;
public class Frame extends JFrame {
GamePanel panel;
public Frame() {
panel = new GamePanel();
this.add(panel);
this.setTitle("Platformer Game");
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
}
}
package Game;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.Timer;
public class GamePanel extends JPanel implements ActionListener{
Player player1;
Map map1;
final int SCREEN_WIDTH = 1000;
final int SCREEN_HEIGHT = 600;
final int PLAYER_WIDTH = 50;
final int PLAYER_HEIGHT = 60;
final Dimension SCREEN_SIZE = new Dimension(SCREEN_WIDTH,SCREEN_HEIGHT);
boolean falling = false;
boolean playing = true;
Image backgroundImage;
Thread gameThread;
Image image;
Graphics graphics;
Timer gameTimer;
ArrayList<Map> platform = new ArrayList<>();
public GamePanel() {
java.net.URL imgIcon = Main.class.getResource(
"/Resources/spaceImage.jpg");
backgroundImage = new ImageIcon(imgIcon).getImage();
newPlayer();
newMap();
this.setFocusable(true);
this.setPreferredSize(SCREEN_SIZE);
this.setOpaque(true);
this.addKeyListener(new KeyListener(
) {
#Override
public void keyTyped(KeyEvent e) {}
#Override
public void keyPressed(KeyEvent e) {
KeyPressed(e);
}
#Override
public void keyReleased(KeyEvent e) {
KeyReleased(e);
}
});
gameTimer = new Timer();
gameTimer.schedule(new TimerTask(){
#Override
public void run() {
player1.move();
repaint();
}
}, 0 , 17);
}
public void paint(Graphics g) {
image = createImage(getWidth(),getHeight());
graphics = image.getGraphics();
draw(graphics);
g.drawImage(image, 0,0, null);
}
public void draw(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
g2D.drawImage(backgroundImage, 0,0, null);
player1.paint(g);
for(Map map1: platform) {
map1.paint(g2D);
}
}
public void KeyPressed(KeyEvent e) {
if(e.getKeyChar()=='a') {
player1.keyLeft = true;
}
if(e.getKeyChar()=='d') player1.keyRight = true;
if(e.getKeyChar()=='s') player1.keyDown = true;
if(e.getKeyChar()=='w') player1.keyUp = true;
}
public void KeyReleased(KeyEvent e) {
if(e.getKeyChar()=='a') player1.keyLeft = false;
if(e.getKeyChar()=='d') player1.keyRight = false;
if(e.getKeyChar()=='s') player1.keyDown = false;
if(e.getKeyChar()=='w') player1.keyUp = false;
}
public void newPlayer() {
player1 = new Player((SCREEN_WIDTH/2)-(PLAYER_WIDTH/2), (SCREEN_HEIGHT/2)-(PLAYER_WIDTH/2), PLAYER_WIDTH, PLAYER_HEIGHT, this);
}
public void newMap() {
for(int i=50;i<650;i+=50){
platform.add(new Map(i,600,50,50));
}
}
public void gameOver() {
}
#Override
public void actionPerformed(ActionEvent e) {
}
}
package Game;
import Game.GamePanel;
import java.awt.*;
import java.awt.event.KeyEvent;
public class Player extends Rectangle{
double velocityY = 0;
double velocityX = 0;
final int PLAYER_WIDTH = 50;
final int PLAYER_HEIGHT = 50;
static int speed = 2;
GamePanel panel;
boolean keyRight = false;
boolean keyLeft = false;
boolean keyUp = false;
boolean keyDown = false;
Rectangle hitbox;
public Player(int x, int y, int PLAYERWIDTH, int PLAYERHEIGHT, GamePanel panel) {
super(x,y,PLAYERWIDTH,PLAYERHEIGHT);
this.panel = panel;
hitbox = new Rectangle();
}
public void paint(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
g2D.setColor(Color.red);
g2D.fillRect(x, y, PLAYER_WIDTH, PLAYER_HEIGHT);
}
public void move() {
if(keyLeft && keyRight || !keyLeft && !keyRight) {
velocityX *= 0.8;
}
if(keyLeft && !keyRight) {
velocityX--;
}
if(keyRight && !keyLeft) {
velocityX++;
}
if(velocityX > 0 && velocityX < 0.75) velocityX = 0;
if(velocityX < 0 && velocityX > -0.75) velocityX = 0;
if(velocityX > 7) velocityX = 7;
if(velocityX < -7) velocityX = -7;
if(keyUp) {
velocityY = -6;
}
velocityY += 0.3;
y += velocityY;
x += velocityX;
hitbox.x = x;
hitbox.y = y;
}
}
package Game;
import java.awt.*;
public class Map {
int PLATFORM_WIDTH = 600;
int PLATFORM_HEIGHT = 150;
int x;
int y;
Rectangle hitbox;
public Map(int x, int y, int PLATFORM_WIDTH, int PLATFORM_HEIGHT) {
this.x = x;
this.y = y;
this.PLATFORM_WIDTH = PLATFORM_WIDTH;
this.PLATFORM_HEIGHT = PLATFORM_HEIGHT;
hitbox = new Rectangle(x,y,PLATFORM_WIDTH, PLATFORM_HEIGHT);
}
public void paint(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
g2D.setColor(Color.gray);
g2D.fillRect(x,y,PLATFORM_WIDTH,PLATFORM_HEIGHT);
}
}
So you set the screen height to 600, final int SCREEN_HEIGHT = 600; but then create your platforms y position to 600 as well, platform.add(new Map(i,600,50,50));.
Since they never move, this is going to paint them off screen, so, a quick solution is to change the y position to something which is within the visible range, maybe 550, that way you will see them (to start with).
Observations
There's a lot of, interesting, ideas going on and I'm not sure you entirely understand how the API works.
Start by having a look at:
Performing Custom Painting
Painting in AWT and Swing
This will give you a better understanding of how the paint system works in Swing and how you should work with it.
Having said that, Swing is double buffered by default, so you don't need your own backing buffer, just override paintComponent and paint to the Graphics context
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
draw(g2d);
g2d.dispose();
}
this will help eliminate one possible area of issues.
Swing is also not thread safe, so you should avoid making up dates to the UI (or state the UI relies on) from outside the context of the Event Dispatching Thread.
Instead of using java.util.Timer, you should be using javax.swing.Timer, which will generate it's callbacks within the context of the EDT.
See Concurrency in Swing and How to Use Swing Timers for more details
gameTimer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
player1.move();
repaint();
}
});
gameTimer.start();
KeyListener is well know for causing issues and there is a better system available which resolves these issues, see How to Use Key Bindings for more details.
I'm also not really sure what's going on with Player
public class Player extends Rectangle {
double velocityY = 0;
double velocityX = 0;
final int PLAYER_WIDTH = 50;
final int PLAYER_HEIGHT = 50;
static int speed = 2;
GamePanel panel;
boolean keyRight = false;
boolean keyLeft = false;
boolean keyUp = false;
boolean keyDown = false;
Rectangle hitbox;
public Player(int x, int y, int PLAYERWIDTH, int PLAYERHEIGHT, GamePanel panel) {
super(x, y, PLAYERWIDTH, PLAYERHEIGHT);
this.panel = panel;
hitbox = new Rectangle();
}
public void paint(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
g2D.setColor(Color.red);
g2D.fillRect(x, y, PLAYER_WIDTH, PLAYER_HEIGHT);
}
You extend it from Rectangle, but then you create another Rectangle within it and I have no idea what all the instance fields are doing at all (you basically ignore what ever's passed in, in favour of your properties)
You could just do something like and use Player as the hotbox itself
public class Player extends Rectangle {
enum Direction {
UP, DOWN, LEFT, RIGHT
}
private double velocityY = 0;
private double velocityX = 0;
private int speed = 2;
public Player(int x, int y, int width, int height) {
super(x, y, width, height);
}
public void paint(Graphics2D g2D) {
g2D.setColor(Color.RED);
g2D.fill(this);
}
Runnable example...
Key bindings can be fun to get your head around, so I've modified your code to support them (and the above mentioned changes) to give you a better idea.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.ImageIcon;
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 GamePanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class GamePanel extends JPanel implements ActionListener {
protected static final int SCREEN_WIDTH = 1000;
protected static final int SCREEN_HEIGHT = 600;
protected static final int PLAYER_WIDTH = 50;
protected static final int PLAYER_HEIGHT = 60;
protected static final Dimension SCREEN_SIZE = new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT);
boolean falling = false;
boolean playing = true;
Player player1;
Map map1;
Image backgroundImage;
Timer gameTimer;
ArrayList<Map> platform = new ArrayList<>();
public GamePanel() {
BufferedImage img = new BufferedImage(SCREEN_WIDTH, SCREEN_HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = img.createGraphics();
g2d.setColor(Color.BLUE);
g2d.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
g2d.dispose();
backgroundImage = new ImageIcon(img).getImage();
newPlayer();
newMap();
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Pressed.left");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Pressed.right");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Pressed.up");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Pressed.down");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Released.left");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Released.right");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Released.up");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Released.down");
am.put("Pressed.left", new MoveAction(player1, Player.Direction.LEFT, true));
am.put("Pressed.right", new MoveAction(player1, Player.Direction.RIGHT, true));
am.put("Pressed.up", new MoveAction(player1, Player.Direction.UP, true));
am.put("Pressed.down", new MoveAction(player1, Player.Direction.DOWN, true));
am.put("Released.left", new MoveAction(player1, Player.Direction.LEFT, false));
am.put("Released.right", new MoveAction(player1, Player.Direction.RIGHT, false));
am.put("Released.up", new MoveAction(player1, Player.Direction.UP, false));
am.put("Released.down", new MoveAction(player1, Player.Direction.DOWN, false));
gameTimer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
player1.move();
repaint();
}
});
gameTimer.start();
}
#Override
public Dimension getPreferredSize() {
return SCREEN_SIZE;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
draw(g2d);
g2d.dispose();
}
public void draw(Graphics2D g2D) {
g2D.drawImage(backgroundImage, 0, 0, null);
player1.paint(g2D);
for (Map map1 : platform) {
map1.paint(g2D);
}
}
public void newPlayer() {
player1 = new Player((SCREEN_WIDTH / 2) - (PLAYER_WIDTH / 2), (SCREEN_HEIGHT / 2) - (PLAYER_WIDTH / 2), PLAYER_WIDTH, PLAYER_HEIGHT);
}
public void newMap() {
for (int i = 50; i < 650; i += 50) {
platform.add(new Map(i, 550, 50, 50));
}
}
public void gameOver() {
}
#Override
public void actionPerformed(ActionEvent e) {
}
}
public class MoveAction extends AbstractAction {
private Player player;
private Player.Direction direction;
private boolean pressed;
public MoveAction(Player player, Player.Direction direction, boolean pressed) {
this.player = player;
this.direction = direction;
this.pressed = pressed;
}
#Override
public void actionPerformed(ActionEvent e) {
if (pressed) {
player.putDirection(direction);
} else {
player.removeDirection(direction);
}
}
}
public class Player extends Rectangle {
enum Direction {
UP, DOWN, LEFT, RIGHT
}
private double velocityY = 0;
private double velocityX = 0;
private int speed = 2;
private Set<Direction> directions = new TreeSet<>();
public Player(int x, int y, int width, int height) {
super(x, y, width, height);
}
public void putDirection(Direction direction) {
directions.add(direction);
}
public void removeDirection(Direction direction) {
directions.remove(direction);
}
public void paint(Graphics2D g2D) {
g2D.setColor(Color.RED);
g2D.fill(this);
}
protected boolean hasDirection(Direction direction) {
return directions.contains(direction);
}
public void move() {
System.out.println(hasDirection(Direction.UP));
if (hasDirection(Direction.LEFT) && hasDirection(Direction.RIGHT) || !hasDirection(Direction.LEFT) && !hasDirection(Direction.RIGHT)) {
velocityX *= 0.8;
}
if (hasDirection(Direction.LEFT) && !hasDirection(Direction.RIGHT)) {
velocityX--;
}
if (hasDirection(Direction.RIGHT) && !hasDirection(Direction.LEFT)) {
velocityX++;
}
if (velocityX > 0 && velocityX < 0.75) {
velocityX = 0;
}
if (velocityX < 0 && velocityX > -0.75) {
velocityX = 0;
}
if (velocityX > 7) {
velocityX = 7;
}
if (velocityX < -7) {
velocityX = -7;
}
if (hasDirection(Direction.UP)) {
velocityY = -6;
}
velocityY += 0.3;
y += velocityY;
x += velocityX;
}
}
public class Map {
int width;
int height;
int x;
int y;
Rectangle hitbox;
public Map(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
hitbox = new Rectangle(x, y, width, height);
}
public void paint(Graphics2D g2D) {
g2D.setColor(Color.GRAY);
g2D.fill(hitbox);
}
}
}
I've been constructing a short program that basically draws a spaceship on a JPanel and listens for keys that tell the program to shoot a bullet. The problem is that it's not even painting the spaceship or the bullets on the screen. I also suspect that the KeyBindings may not be working as that was a previous problem (that I may or may not have fixed), but the main issue at hand is still the fact that my screen isn't being painted. Here is my code:
public enum Direction {
LEFT, RIGHT, SPACE
}
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
JFrame frame;
Ship s1;
Shoot shoot;
// 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(s1);
frame.getContentPane().add(shoot);
s1.setShoot(shoot);
// Threads
Thread ship = new Thread(s1);
ship.start();
}
}
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
public class Shoot extends JPanel {
Ship s1;
public Shoot(Ship s1) {
this.s1 = s1;
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);
g.fill3DRect(40, 50, 10, 10, false);
}
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);
}
}
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;
Shoot shoot;
public Ship() {
x = 175;
y = 275;
bullet = new Rectangle(0, 0, 3, 5);
}
public void draw(Graphics g) {
// System.out.println("draw() called");
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);
}
shoot.repaint();
}
public void move() {
x += xDirection;
if (x <= 0)
x = 0;
if (x >= 360)
x = 360;
shoot.repaint();
}
public void shoot() {
if (shooting) {
bullet.y--;
shoot.repaint();
}
}
public void setXDirection(int xdir) {
xDirection = xdir;
}
public void setShoot(Shoot shoot) {
this.shoot = shoot;
}
#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 Set<Direction> movement;
public MoveAction(boolean pressed, Ship s1, Direction dir) {
System.out.println("moveaction class");
movement = new HashSet<Direction>();
this.pressed = pressed;
this.s1 = s1;
this.dir = dir;
}
#Override
public void actionPerformed(ActionEvent e) {
try {
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;
}
}
}
} catch (NullPointerException ex) {
System.out.println("NullPointerException");
}
}
So, there are a number of issues...
You should call setVisible on the JFrame last, this will ensure that components are laid out
Your keybindings issue seem to related to the fact that you're using the movement Set, but you never actually add anything to it. Instead you should be checking the dir value.
And probably a bunch of other things. Your code is tightly coupled and there isn't any centralised management of the state.
I'd start by having a better understand of the Model-View-Controller paradigm and separate the code into appropriate areas of responsibility.
The "data" for the game should separate from the "rendering" of the game, which should be separate from the decisions about how the game is to be updated.
What I'm about to present is an oversimplification intended to spark ideas, rather than been a concrete solution, as there are a number of ways you could achieve the physical implementation...
So, what we need is, a concept of something in the game, AKA an "entity", entities take many forms, but I'm going to focus on renderable/displayable entities. You need some kind of model that is responsible for modeling the current state of the game and which is responsible for implementing the rules. You need some kind of view, which is responsible for rendering the model and responding to user input. You need some kind of controller which controls how the model and the view bridged.
It's always a good idea to start with a series of interfaces which define the contractual expectations and outlines the expected means by which elements are expected to communicate with each other. Again, I've taken a simple, direct approach, but it's by no means the only...
public static enum Direction {
LEFT,
RIGHT,
SPACE
}
public interface Entity {
public void paint(Graphics2D g2d);
public Point getLocation();
public void setLocation(Point p);
public Dimension getSize();
}
public interface GameModel {
public Entity[] getEntities();
public void update(Rectangle bounds, Set<Direction> keys);
}
public interface GameController {
public Entity[] getEntities();
public void setDirection(Direction direction, boolean pressed);
public void start();
}
public interface GameView {
public void setController(GameController controller);
public GameController getController();
public Rectangle getViewBounds();
public void repaint();
}
Let's take a look at the implementation of the entities. This example has two entities, a Player and a Bullet...
public abstract class AbstractEntity implements Entity {
private final Point location = new Point(0, 0);
#Override
public Point getLocation() {
return new Point(location);
}
#Override
public void setLocation(Point p) {
location.setLocation(p);
}
}
public class Player extends AbstractEntity {
public Player(Rectangle bounds) {
int x = bounds.x + ((bounds.width - getSize().width) / 2);
int y = bounds.y + (bounds.height - getSize().height);
setLocation(new Point(x, y));
}
#Override
public Dimension getSize() {
return new Dimension(40, 17);
}
#Override
public void paint(Graphics2D g2d) {
Point p = getLocation();
Dimension size = getSize();
g2d.setColor(Color.BLUE);
g2d.fillRect(p.x, p.y + 7, size.width, 10);
g2d.fillRect(p.x + 18, p.y, 4, 7);
}
}
public class Bullet extends AbstractEntity {
#Override
public void paint(Graphics2D g2d) {
Rectangle bullet = new Rectangle(getLocation(), getSize());
g2d.setColor(Color.RED);
g2d.fill(bullet);
}
#Override
public Dimension getSize() {
return new Dimension(4, 8);
}
}
Nothing spectacular, but they each define their parameters and can render their states when asked.
Next, we have the model, controller and view. This example uses the controller as the primary game loop, responsible for updating the game (model) state and scheduling repaints. This is done with the use of a Swing Timer as this prevents possible race conditions between the update loop and the painting loop. A more complex implementation would need to take over the painting process through the use of a BufferStrategy and BufferStrategy and BufferCapabilities.
The model simply takes the current view boundaries and the current state of the keys and updates the state of the entities.
The view monitors user input, notifying the controller, and renders the current state of the game.
You'll note that the view and model never communicate directly with each other, this is the domain of the controller.
public class DefaultGameModel implements GameModel {
private final List<Entity> entities;
private Player player;
private Long lastShot;
public DefaultGameModel() {
entities = new ArrayList<>(25);
}
#Override
public Entity[] getEntities() {
return entities.toArray(new Entity[0]);
}
#Override
public void update(Rectangle bounds, Set<Direction> keys) {
if (player == null) {
player = new Player(bounds);
entities.add(player);
}
Point p = player.getLocation();
int xDelta = 0;
if (keys.contains(Direction.LEFT)) {
xDelta = -4;
} else if (keys.contains(Direction.RIGHT)) {
xDelta = 4;
}
p.x += xDelta;
if (p.x <= bounds.x) {
p.x = bounds.x;
} else if (p.x + player.getSize().width >= bounds.x + bounds.width) {
p.x = bounds.width - player.getSize().width;
}
player.setLocation(p);
Iterator<Entity> it = entities.iterator();
while (it.hasNext()) {
Entity entity = it.next();
if (entity instanceof Bullet) {
Point location = entity.getLocation();
Dimension size = entity.getSize();
location.y -= size.height;
if (location.y + size.height < bounds.y) {
it.remove();
} else {
entity.setLocation(location);
}
}
}
if (keys.contains(Direction.SPACE)) {
if (lastShot == null || System.currentTimeMillis() - lastShot > 100) {
lastShot = System.currentTimeMillis();
Bullet bullet = new Bullet();
int x = p.x + ((player.getSize().width - bullet.getSize().width) / 2);
int y = p.y - bullet.getSize().height;
bullet.setLocation(new Point(x, y));
entities.add(bullet);
}
}
}
}
public class DefaultGameController implements GameController {
private GameModel model;
private GameView view;
private Timer timer;
private Set<Direction> keys = new HashSet<>(25);
public DefaultGameController(GameModel gameModel, GameView gameView) {
gameView.setController(this);
view = gameView;
model = gameModel;
}
#Override
public Entity[] getEntities() {
return model.getEntities();
}
#Override
public void setDirection(Direction direction, boolean pressed) {
if (pressed) {
keys.add(direction);
} else {
keys.remove(direction);
}
}
#Override
public void start() {
if (timer != null && timer.isRunning()) {
timer.stop();
}
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
model.update(view.getViewBounds(), Collections.unmodifiableSet(keys));
view.repaint();
}
});
timer.start();
}
}
public class DefaultGameView extends JPanel implements GameView {
private GameController controller;
public DefaultGameView() {
addKeyBinding("left.pressed", KeyEvent.VK_LEFT, true, new DirectionAction(Direction.LEFT, true));
addKeyBinding("left.released", KeyEvent.VK_LEFT, false, new DirectionAction(Direction.LEFT, false));
addKeyBinding("right.pressed", KeyEvent.VK_RIGHT, true, new DirectionAction(Direction.RIGHT, true));
addKeyBinding("right.released", KeyEvent.VK_RIGHT, false, new DirectionAction(Direction.RIGHT, false));
addKeyBinding("space.pressed", KeyEvent.VK_SPACE, true, new DirectionAction(Direction.SPACE, true));
addKeyBinding("space.released", KeyEvent.VK_SPACE, false, new DirectionAction(Direction.SPACE, false));
}
protected void addKeyBinding(String name, int keyEvent, boolean pressed, DirectionAction action) {
addKeyBinding(name, KeyStroke.getKeyStroke(keyEvent, 0, !pressed), action);
}
protected void addKeyBinding(String name, KeyStroke keyStroke, DirectionAction action) {
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(keyStroke, name);
actionMap.put(name, action);
}
#Override
public void setController(GameController controller) {
this.controller = controller;
}
#Override
public GameController getController() {
return controller;
}
#Override
public Rectangle getViewBounds() {
return new Rectangle(new Point(0, 0), getSize());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
GameController controller = getController();
for (Entity entity : controller.getEntities()) {
// I don't trust you
Graphics2D g2d = (Graphics2D) g.create();
entity.paint(g2d);
g2d.dispose();
}
}
public class DirectionAction extends AbstractAction {
private Direction direction;
private boolean pressed;
public DirectionAction(Direction direction, boolean pressed) {
this.direction = direction;
this.pressed = pressed;
}
#Override
public void actionPerformed(ActionEvent e) {
getController().setDirection(direction, pressed);
}
}
}
Okay, but that's all fine and good, but how do you use it? Something like this for example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
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;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
GameModel model = new DefaultGameModel();
DefaultGameView view = new DefaultGameView();
GameController controller = new DefaultGameController(model, view);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(view);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
controller.start();
}
});
}
public static enum Direction {
LEFT,
RIGHT,
SPACE
}
public interface Entity {
public void paint(Graphics2D g2d);
public Point getLocation();
public void setLocation(Point p);
public Dimension getSize();
}
public interface GameModel {
public Entity[] getEntities();
public void update(Rectangle bounds, Set<Direction> keys);
}
public interface GameController {
public Entity[] getEntities();
public void setDirection(Direction direction, boolean pressed);
public void start();
}
public interface GameView {
public void setController(GameController controller);
public GameController getController();
public Rectangle getViewBounds();
public void repaint();
}
public class DefaultGameModel implements GameModel {
private final List<Entity> entities;
private Player player;
private Long lastShot;
public DefaultGameModel() {
entities = new ArrayList<>(25);
}
#Override
public Entity[] getEntities() {
return entities.toArray(new Entity[0]);
}
#Override
public void update(Rectangle bounds, Set<Direction> keys) {
if (player == null) {
player = new Player(bounds);
entities.add(player);
}
Point p = player.getLocation();
int xDelta = 0;
if (keys.contains(Direction.LEFT)) {
xDelta = -4;
} else if (keys.contains(Direction.RIGHT)) {
xDelta = 4;
}
p.x += xDelta;
if (p.x <= bounds.x) {
p.x = bounds.x;
} else if (p.x + player.getSize().width >= bounds.x + bounds.width) {
p.x = bounds.width - player.getSize().width;
}
player.setLocation(p);
Iterator<Entity> it = entities.iterator();
while (it.hasNext()) {
Entity entity = it.next();
if (entity instanceof Bullet) {
Point location = entity.getLocation();
Dimension size = entity.getSize();
location.y -= size.height;
if (location.y + size.height < bounds.y) {
it.remove();
} else {
entity.setLocation(location);
}
}
}
if (keys.contains(Direction.SPACE)) {
if (lastShot == null || System.currentTimeMillis() - lastShot > 100) {
lastShot = System.currentTimeMillis();
Bullet bullet = new Bullet();
int x = p.x + ((player.getSize().width - bullet.getSize().width) / 2);
int y = p.y - bullet.getSize().height;
bullet.setLocation(new Point(x, y));
entities.add(bullet);
}
}
}
}
public class DefaultGameController implements GameController {
private GameModel model;
private GameView view;
private Timer timer;
private Set<Direction> keys = new HashSet<>(25);
public DefaultGameController(GameModel gameModel, GameView gameView) {
gameView.setController(this);
view = gameView;
model = gameModel;
}
#Override
public Entity[] getEntities() {
return model.getEntities();
}
#Override
public void setDirection(Direction direction, boolean pressed) {
if (pressed) {
keys.add(direction);
} else {
keys.remove(direction);
}
}
#Override
public void start() {
if (timer != null && timer.isRunning()) {
timer.stop();
}
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
model.update(view.getViewBounds(), Collections.unmodifiableSet(keys));
view.repaint();
}
});
timer.start();
}
}
public abstract class AbstractEntity implements Entity {
private final Point location = new Point(0, 0);
#Override
public Point getLocation() {
return new Point(location);
}
#Override
public void setLocation(Point p) {
location.setLocation(p);
}
}
public class Player extends AbstractEntity {
public Player(Rectangle bounds) {
int x = bounds.x + ((bounds.width - getSize().width) / 2);
int y = bounds.y + (bounds.height - getSize().height);
setLocation(new Point(x, y));
}
#Override
public Dimension getSize() {
return new Dimension(40, 17);
}
#Override
public void paint(Graphics2D g2d) {
Point p = getLocation();
Dimension size = getSize();
g2d.setColor(Color.BLUE);
g2d.fillRect(p.x, p.y + 7, size.width, 10);
g2d.fillRect(p.x + 18, p.y, 4, 7);
}
}
public class Bullet extends AbstractEntity {
#Override
public void paint(Graphics2D g2d) {
Rectangle bullet = new Rectangle(getLocation(), getSize());
g2d.setColor(Color.RED);
g2d.fill(bullet);
}
#Override
public Dimension getSize() {
return new Dimension(4, 8);
}
}
public class DefaultGameView extends JPanel implements GameView {
private GameController controller;
public DefaultGameView() {
addKeyBinding("left.pressed", KeyEvent.VK_LEFT, true, new DirectionAction(Direction.LEFT, true));
addKeyBinding("left.released", KeyEvent.VK_LEFT, false, new DirectionAction(Direction.LEFT, false));
addKeyBinding("right.pressed", KeyEvent.VK_RIGHT, true, new DirectionAction(Direction.RIGHT, true));
addKeyBinding("right.released", KeyEvent.VK_RIGHT, false, new DirectionAction(Direction.RIGHT, false));
addKeyBinding("space.pressed", KeyEvent.VK_SPACE, true, new DirectionAction(Direction.SPACE, true));
addKeyBinding("space.released", KeyEvent.VK_SPACE, false, new DirectionAction(Direction.SPACE, false));
}
protected void addKeyBinding(String name, int keyEvent, boolean pressed, DirectionAction action) {
addKeyBinding(name, KeyStroke.getKeyStroke(keyEvent, 0, !pressed), action);
}
protected void addKeyBinding(String name, KeyStroke keyStroke, DirectionAction action) {
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(keyStroke, name);
actionMap.put(name, action);
}
#Override
public void setController(GameController controller) {
this.controller = controller;
}
#Override
public GameController getController() {
return controller;
}
#Override
public Rectangle getViewBounds() {
return new Rectangle(new Point(0, 0), getSize());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
GameController controller = getController();
for (Entity entity : controller.getEntities()) {
// I don't trust you
Graphics2D g2d = (Graphics2D) g.create();
entity.paint(g2d);
g2d.dispose();
}
}
public class DirectionAction extends AbstractAction {
private Direction direction;
private boolean pressed;
public DirectionAction(Direction direction, boolean pressed) {
this.direction = direction;
this.pressed = pressed;
}
#Override
public void actionPerformed(ActionEvent e) {
getController().setDirection(direction, pressed);
}
}
}
}
Again, you're going to need to go away and do some more research, but this is the general idea
Your drawing depends on the boolean variable shooting; there is one place where shooting is being set to true; if the key operations dont work the flow of the program may never reach there and that may never happen.
So I sugest you minimize your project to a screen that will draw the graphic without depending on pressing keys.
If you can see the graphics then you can add gradually the keys
I have created a program that just moves a ball across a screen. I used to have it all in one class, but decided that it looked too messy so I split it up into three different classes: Main... initializes everything, Game... which paints everything and is a JPanel, and AL which is a KeyListener (which is also where the problem is). The problem is that I can't get the program to repaint from my AL class no matter what I try to pass into it. Can anyone help with this? Here are my three classes:
import java.awt.Color;
import javax.swing.JFrame;
public class Main {
static Game game;
static JFrame frame;
public static void main(String[] args) {
game = new Game();
frame = new JFrame();
frame.getContentPane().add(game);
frame.addKeyListener(new AL(game, frame));
frame.setTitle("Game");
frame.setSize(500, 500);
frame.setResizable(true);
frame.setVisible(true);
frame.setBackground(Color.BLACK);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
-
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Game extends JPanel implements Runnable {
int x, y, xCoord, yCoord;
private Image dbImage;
private Graphics dbg;
JFrame frame;
public void changeCoord() {
x += xCoord;
y += yCoord;
if (x <= 20) {
x = 20;
}
if (x >= 480) {
x = 480;
}
if (y <= 40) {
y = 40;
}
if (y >= 480) {
y = 480;
}
}
public void setXCoord(int xcoord) {
xCoord = xcoord;
}
public void setYCoord(int ycoord) {
yCoord = ycoord;
}
public static void main(String[] args) {
Game game = new Game();
Thread t = new Thread(game);
t.start();
}
public Game() {
x = 250;
y = 250;
}
#Override
public void paintComponent(Graphics g) {
g.setColor(Color.GREEN);
g.fillOval(x, y, 15, 15);
}
#Override
public void paint(Graphics g) {
dbImage = createImage(getWidth(), getHeight());
dbg = dbImage.getGraphics();
paintComponent(dbg);
g.drawImage(dbImage, 0, 0, this);
}
#Override
public void run() {
try {
while (true) {
changeCoord();
Thread.sleep(30);
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
-
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
public class AL extends KeyAdapter {
Game game;
JFrame frame;
public AL(Game game, JFrame frame) {
this.game = game;
this.frame = frame;
}
#Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == e.VK_LEFT) {
game.setXCoord(-1);
}
if (keyCode == e.VK_RIGHT) {
game.setXCoord(+1);
}
if (keyCode == e.VK_UP) {
game.setYCoord(-1);
}
if (keyCode == e.VK_DOWN) {
game.setYCoord(+1);
}
game.repaint();
}
#Override
public void keyReleased(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == e.VK_LEFT) {
game.setXCoord(0);
}
if (keyCode == e.VK_RIGHT) {
game.setXCoord(0);
}
if (keyCode == e.VK_UP) {
game.setYCoord(0);
}
if (keyCode == e.VK_DOWN) {
game.setYCoord(0);
}
game.repaint();
}
}
Let's start with the obvious....
This is problematic...
#Override
public void paintComponent(Graphics g) {
g.setColor(Color.GREEN);
g.fillOval(x, y, 15, 15);
}
#Override
public void paint(Graphics g) {
dbImage = createImage(getWidth(), getHeight());
dbg = dbImage.getGraphics();
paintComponent(dbg);
g.drawImage(dbImage, 0, 0, this);
}
There's no need to implement double buffering in Swing components, they already are. Also, you're breaking the painting contract, by not call the paint methods super methods
The whole thing should be...
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillOval(x, y, 15, 15);
}
See Painting in AWT and Swing and Performing Custom Painting for more details
KeyListener is well known for been problematic. It will only raise key events if the component it registered to is focuable AND has keyboard focus. A JPanel by default, is not focusable. Before you run of and try and make it focusable (and get bitterly disappointed), you should be using the Key Bindings API instead, which was designed to over come the limitations of KeyListener
As a basic example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
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.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new Game());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Game extends JPanel {
int x, y, xCoord, yCoord;
public Game() {
x = 250;
y = 250;
addKeyBinding(KeyEvent.VK_LEFT, "move.left", new MoveAction(this, -1, 0));
addKeyBinding(KeyEvent.VK_RIGHT, "move.right", new MoveAction(this, 1, 0));
addKeyBinding(KeyEvent.VK_UP, "move.up", new MoveAction(this, 0, -1));
addKeyBinding(KeyEvent.VK_DOWN, "move.down", new MoveAction(this, 0, 1));
}
protected void addKeyBinding(int keyCode, String name, Action action) {
addKeyBinding(KeyStroke.getKeyStroke(keyCode, 0), 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 void changeCoord() {
x += xCoord;
y += yCoord;
if (x <= 20) {
x = 20;
}
if (x >= 480) {
x = 480;
}
if (y <= 40) {
y = 40;
}
if (y >= 480) {
y = 480;
}
repaint();
}
public void setXCoord(int xcoord) {
xCoord = xcoord;
changeCoord();
}
public void setYCoord(int ycoord) {
yCoord = ycoord;
changeCoord();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(480, 480);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillOval(x, y, 15, 15);
}
}
public class MoveAction extends AbstractAction {
private int xDelta;
private int yDelta;
// I'd prefer an interface with just the "move" methods, but
// that's more time I don't have
private Game game;
public MoveAction(Game game, int xDelta, int yDelta) {
this.xDelta = xDelta;
this.yDelta = yDelta;
this.game = game;
}
#Override
public void actionPerformed(ActionEvent e) {
game.setXCoord(xDelta);
game.setYCoord(yDelta);
}
}
}
But, wait, that isn't exactly what you want (trust me, I'm an anoymouse person on the Internet ;)), a better example might be...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
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.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new Game());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public enum Direction {
UP,
LEFT,
DOWN,
RIGHT;
}
public class Game extends JPanel {
int x, y, xCoord, yCoord;
private Set<Direction> movement;
public Game() {
x = 250;
y = 250;
movement = new HashSet<>(4);
addKeyPressedBinding(KeyEvent.VK_LEFT, "left.pressed", new MoveAction(movement, Direction.LEFT, true));
addKeyReleasedBinding(KeyEvent.VK_LEFT, "left.released", new MoveAction(movement, Direction.LEFT, false));
addKeyPressedBinding(KeyEvent.VK_RIGHT, "right.pressed", new MoveAction(movement, Direction.RIGHT, true));
addKeyReleasedBinding(KeyEvent.VK_RIGHT, "right.released", new MoveAction(movement, Direction.RIGHT, false));
addKeyPressedBinding(KeyEvent.VK_UP, "up.pressed", new MoveAction(movement, Direction.UP, true));
addKeyReleasedBinding(KeyEvent.VK_UP, "up.released", new MoveAction(movement, Direction.UP, false));
addKeyPressedBinding(KeyEvent.VK_DOWN, "down.pressed", new MoveAction(movement, Direction.DOWN, true));
addKeyReleasedBinding(KeyEvent.VK_DOWN, "down.released", new MoveAction(movement, Direction.DOWN, false));
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
changeCoord();
}
});
timer.start();
}
protected void addKeyBinding(int keyCode, String name, Action action) {
addKeyBinding(KeyStroke.getKeyStroke(keyCode, 0), name, action);
}
protected void addKeyPressedBinding(int keyCode, String name, Action action) {
addKeyBinding(KeyStroke.getKeyStroke(keyCode, 0, false), name, action);
}
protected void addKeyReleasedBinding(int keyCode, String name, Action action) {
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 void changeCoord() {
if (movement.contains(Direction.UP)) {
y--;
} else if (movement.contains(Direction.DOWN)) {
y++;
}
if (movement.contains(Direction.LEFT)) {
x--;
} else if (movement.contains(Direction.RIGHT)) {
x++;
}
x += xCoord;
y += yCoord;
if (x <= 20) {
x = 20;
}
if (x >= 480) {
x = 480;
}
if (y <= 40) {
y = 40;
}
if (y >= 480) {
y = 480;
}
repaint();
}
public void setXCoord(int xcoord) {
xCoord = xcoord;
changeCoord();
}
public void setYCoord(int ycoord) {
yCoord = ycoord;
changeCoord();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(480, 480);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillOval(x, y, 15, 15);
}
}
public class MoveAction extends AbstractAction {
private Set<Direction> movement;
private Direction direction;
private boolean pressed;
public MoveAction(Set<Direction> movement, Direction direction, boolean pressed) {
this.movement = movement;
this.direction = direction;
this.pressed = pressed;
}
#Override
public void actionPerformed(ActionEvent e) {
if (pressed) {
movement.add(direction);
} else {
movement.remove(direction);
}
}
}
}
What this does is simply activates a flag when a key is pressed (and deactivates it when it's released), then in a Swing Timer, we check which keys are "active" and update the location of the ball.
What this does is, eliminates the key "stutter" which is caused by the OS when a key is first pressed and held. A delay is inserted between the first key and the repeated key events. Instead, we just turn the flag on and off as we like.
It also allows you to move in two directions at the same time (horizontally and vertically)
Have a look at Concurrency in Swing and How to use Swing Timers for more details