I am working on a 2D platformer in Java for an assignment. The assignment specifies I must use an abstract shape class to draw shapes. The problem I am having is getting my collision detection to work with multiple Rectangle Objects which I am using as platforms - I store these in a list. At the moment my collision detection will move my player regardless of which platform I have collided with, so if I collide with a platform from the right it will move me to the top of that platform because it's still checking another platform below me, and will assume I've hit that, therefore moving me to the top of the platform. I was wondering how I can change this so that my collision detection also detects which platform I have collided with and collide relative to that platform (as opposed to every platform).
Here's what is currently happening:
Expected Output:
My player should stop where it is when colliding with platforms.
My App class:
package A2;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
public class App extends JFrame {
public App() {
final Player player = new Player(200, 100);
final ArrayList<Rectangle> platforms = new ArrayList<>();
platforms.add(new Rectangle(100, 500, 400 ,10));
platforms.add(new Rectangle(500, 100, 10 ,400));
JPanel mainPanel = new JPanel() {
public void paintComponent(Graphics g) {
super.paintComponent(g);
player.draw(g);
for(Rectangle r : platforms){
r.draw(g);
}
}
};
mainPanel.addKeyListener(new InputControl(this, player, platforms));
mainPanel.setFocusable(true);
add(mainPanel);
setLayout(new GridLayout());
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setSize(600, 600);
}
public static void main(String[] args) {
App app = new App();
app.setVisible(true);
}
}
abstract class Shape {
public void draw(Graphics g) { }
}
My input handling class:
package A2;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import static java.awt.event.KeyEvent.*;
public class InputControl implements ActionListener, KeyListener {
App app;
Player player;
Timer time = new Timer(5, this);
ArrayList<Rectangle> platforms;
ArrayList<Integer> keyList = new ArrayList<>();
boolean keyReleased = false;
boolean keyPressed = false;
boolean[] keyUp = new boolean[256];
boolean[] keyDown = new boolean[256];
boolean topCollision = false;
boolean rightCollision = false;
boolean leftCollision = false;
boolean botCollision = false;
boolean onGround = false;
private static final double GRAVITY = 2;
private static final int MAX_FALL_SPEED = 5;
double Xoverlap = 0;
double Yoverlap = 0;
boolean collision = false;
public InputControl(App app, Player player, ArrayList<Rectangle> platforms) {
this.app = app;
this.player = player;
this.platforms = platforms;
time.start();
}
public void gravity(){
if(player.yVelocity > MAX_FALL_SPEED){
player.yVelocity = MAX_FALL_SPEED;
}
player.yVelocity += GRAVITY;
}
public void momentum(){}
public void actionPerformed(ActionEvent e) {
Rectangle2D offset = player.getOffsetBounds();
for(int i = 0; i < platforms.size(); i++) {
Rectangle r = platforms.get(i);
//If collision
if (offset.intersects(r.obj.getBounds2D())) {
//Collision on Y axis
if (offset.getX() + offset.getWidth() > r.obj.getX() &&
offset.getX() < r.obj.getX() + r.obj.getWidth() &&
offset.getY() + offset.getHeight() > r.obj.getY() &&
offset.getY() + player.yVelocity < r.obj.getY() + r.obj.getHeight()) {
player.y -= (offset.getY() + offset.getHeight()) - r.obj.getY();
}
//Collision on X axis
if (offset.getX() + offset.getWidth() + player.xVelocity > r.obj.getX() &&
offset.getX() + player.xVelocity < r.obj.getX() + r.obj.getWidth() &&
offset.getY() + offset.getHeight() > r.obj.getY() &&
offset.getY() < r.obj.getY() + r.obj.getHeight()) {
player.x -= (offset.getX() + offset.getHeight()) - r.obj.getX();
}
}
else {
player.x += player.xVelocity;
player.y += player.yVelocity;
}
}
player.x += player.xVelocity;
player.y += player.yVelocity;
app.repaint();
}
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() >= 0 && e.getKeyCode() < 256) {
keyDown[e.getKeyCode()] = true;
keyUp[e.getKeyCode()] = false;
keyPressed = true;
keyReleased = false;
}
if (keyDown[VK_UP]) {
player.yVelocity = -1;
}
if (keyDown[VK_RIGHT]){
player.xVelocity = 1;
}
if (keyDown[VK_LEFT]) {
player.xVelocity = -1;
}
if (keyDown[VK_DOWN]) {
player.yVelocity = 1;
}
}
public void keyReleased(KeyEvent e) {
if(e.getKeyCode() >= 0 && e.getKeyCode() < 256) {
keyDown[e.getKeyCode()] = false;
keyUp[e.getKeyCode()] = true;
keyPressed = false;
keyReleased = true;
}
if(keyUp[VK_RIGHT] || keyUp[VK_LEFT]){
player.xVelocity = 0;
}
if(keyUp[VK_UP]){
player.yVelocity = 0;
}
}
public void keyTyped(KeyEvent e) { }
}
My Player class:
package A2;
import java.awt.*;
import java.awt.geom.Rectangle2D;
public class Player extends Shape {
public double xVelocity;
public double yVelocity;
public double x;
public double y;
public double width = 60;
public double height = 60;
public double weight = 5;
public Rectangle2D obj;
public Player(double x, double y) {
this.x = x;
this.y = y;
obj = new Rectangle2D.Double(x, y, width, height);
}
public Rectangle2D getOffsetBounds(){
return new Rectangle2D.Double( x, y, width, height);
}
public void draw(Graphics g) {
Color c =new Color(1f,0f,0f,0.2f );
obj = new Rectangle2D.Double(x, y, width, height);
g.setColor(Color.BLACK);
Graphics2D g2 = (Graphics2D)g;
g2.fill(obj);
g.drawString("(" + String.valueOf(x) + ", " + String.valueOf(y) + ")", 10, 20);
g.drawString("X Speed: " + String.valueOf(xVelocity) + " Y Speed: " + String.valueOf(yVelocity) + ")", 10, 35);
g.setColor(c);
}
}
My Rectangle class:
package A2;
import java.awt.*;
import java.awt.geom.Rectangle2D;
public class Rectangle extends Shape {
public double width;
public double height;
public double x;
public double y;
boolean hasCollided = false;
public Rectangle2D obj;
public Rectangle(double x, double y, double width, double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
obj = new Rectangle2D.Double(x, y, width, height);
}
public void draw(Graphics g) {
g.setColor(Color.ORANGE);
Graphics2D g2 = (Graphics2D)g;
g2.fill(obj);
}
}
First, define ColorRectangle class that extends Shape and provide logic for drawing:
// extends java.awt.Shape
abstract class ColorRectangle extends Rectangle {
private static final long serialVersionUID = -3626687047605407698L;
private final Color color;
protected ColorRectangle(int x, int y, int width, int height, Color color) {
super(x, y, width, height);
this.color = color;
}
public void draw(Graphics2D g) {
g.setColor(color);
g.fillRect(x, y, width, height);
}
}
Second, define separate class for represent Player and Platform:
public final class Player extends ColorRectangle {
private static final long serialVersionUID = -3909362955417024742L;
public Player(int width, int height, Color color) {
super(0, 0, width, height, color);
}
}
public final class Platform extends ColorRectangle {
private static final long serialVersionUID = 6602359551348037628L;
public Platform(int x, int y, int width, int height, Color color) {
super(x, y, width, height, color);
}
public boolean intersects(Player player, int offsX, int offsY) {
return intersects(player.x + offsX, player.y + offsY, player.width, player.height);
}
}
Third, define class that contains logic of demo application. Actually, you could split logic for demo and logic for board (including actiona and key listeners), but these classes are very simple, so in given case, no need to split it.
public class CollisionDetectionDemo extends JFrame {
public CollisionDetectionDemo() {
setLayout(new GridLayout());
add(new MainPanel());
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setSize(600, 600);
}
private static class MainPanel extends JPanel implements ActionListener, KeyListener {
private static final long serialVersionUID = 8771401446680969350L;
private static final int OFFS = 5;
private final Player player = new Player(60, 60, Color.BLACK);
private final List<Platform> platforms = Arrays.asList(
new Platform(100, 500, 400, 10, Color.ORANGE),
new Platform(500, 100, 10, 410, Color.RED),
new Platform(150, 300, 100, 10, Color.BLUE),
new Platform(150, 100, 100, 10, Color.GREEN));
private MainPanel() {
player.x = 200;
player.y = 200;
new Timer(5, this).start();
setFocusable(true);
addKeyListener(this);
}
private void drawPlayerPosition(Graphics g) {
g.setColor(Color.BLACK);
g.drawString(String.format("(%d, %d)", player.x, player.y), 10, 20);
}
private void movePlayer(int offsX, int offsY) {
if (!intersects(player, offsX, offsY)) {
player.x += offsX;
player.y += offsY;
}
}
private boolean intersects(Player player, int offsX, int offsY) {
for (Platform platform : platforms)
if (platform.intersects(player, offsX, offsY))
return true;
return false;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Color color = g.getColor();
drawPlayerPosition(g);
player.draw((Graphics2D)g);
platforms.forEach(platform -> platform.draw((Graphics2D)g));
g.setColor(color);
}
#Override
public void actionPerformed(ActionEvent event) {
repaint();
}
#Override
public void keyTyped(KeyEvent event) {
}
#Override
public void keyPressed(KeyEvent event) {
if (event.getKeyCode() == VK_UP)
movePlayer(0, -OFFS);
else if (event.getKeyCode() == VK_DOWN)
movePlayer(0, OFFS);
else if (event.getKeyCode() == VK_LEFT)
movePlayer(-OFFS, 0);
else if (event.getKeyCode() == VK_RIGHT)
movePlayer(OFFS, 0);
}
#Override
public void keyReleased(KeyEvent event) {
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new CollisionDetectionDemo().setVisible(true));
}
}
Related
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'm trying to create Circles in JFrame using JComponent. Here's what I'm trying to achieve:
And here's what's happening with my code:
I have no idea what's causing this problem. Here's my code:
CircleViewer.java
import javax.swing.JFrame;
import java.awt.event.*;
public class CircleViewer
{
public static void main(String[] args)
{
final CirclePanel panel = new CirclePanel();
class MousePressListener implements MouseListener, MouseMotionListener
{
public void mouseClicked(MouseEvent event) { }
public void mouseEntered(MouseEvent event) { }
public void mouseExited(MouseEvent event) { }
public void mouseWheelMoved(MouseWheelEvent event) { }
public void mouseMoved(MouseEvent event) { }
public void mousePressed(MouseEvent event)
{
var x = event.getX();
var y = event.getY();
panel.addCircle(x, y);
}
public void mouseDragged(MouseEvent event)
{
var x = event.getX();
var y = event.getY();
panel.moveTo(x, y);
}
public void mouseReleased(MouseEvent event)
{
panel.finalMove();
}
}
MousePressListener listener = new MousePressListener();
panel.addMouseListener(listener);
panel.addMouseMotionListener(listener);
JFrame frame = new JFrame("Circle Shapes");
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.setVisible(true);
}
private static final int FRAME_WIDTH = 700;
private static final int FRAME_HEIGHT = 500;
}
CirclePanel.java
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JComponent;
import java.util.ArrayList;
import java.awt.Color;
import java.lang.Math;
import java.awt.BasicStroke;
import java.awt.Stroke;
public class CirclePanel extends JComponent
{
private int lineX;
private int lineY;
private boolean isDraged;
private ArrayList<Circle> circleList;
private BasicStroke dashLine;
public CirclePanel()
{
this.circleList = new ArrayList<Circle>();
this.isDraged = false;
this.lineX = 0;
this.lineY = 0;
this.dashLine = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{6}, 0);
}
public void addCircle(int x, int y)
{
lineX = x;
lineY = y;
isDraged = true;
circleList.add(new Circle(x, y, 0, Color.RED));
repaint();
}
public void moveTo(int x, int y)
{
var circleTemp = circleList.get(circleList.size() - 1);
isDraged = true;
var tempR = (int)Math.sqrt(Math.pow(x - circleTemp.get(0), 2) + Math.pow(y - circleTemp.get(1), 2));
System.out.println(tempR);
var tempX = circleTemp.get(0) - (tempR / 2);
var tempY = circleTemp.get(1) - (tempR / 2);
circleList.get(circleList.size() - 1).setCords(tempX, tempY, tempR);
lineX = x;
lineY = y;
repaint();
}
public void finalMove()
{
isDraged = false;
circleList.get(circleList.size() - 1).setColor(Color.BLUE);
repaint();
}
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D)g;
Stroke defaultStroke;
defaultStroke = g2.getStroke();
if (!circleList.isEmpty())
{
if (isDraged)
{
g2.setColor(Color.RED);
g2.setStroke(dashLine);
g2.drawLine(circleList.get(circleList.size() - 1).get(0), circleList.get(circleList.size() - 1).get(1), lineX, lineY);
}
for (Circle circle : circleList)
{
g2.setStroke(defaultStroke);
circle.draw(g2);
isDraged = false; //this is prob reduntant
}
}
}
}
And finally, Circle.java
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
public class Circle
{
private int x;
private int y;
private int radius;
private Color color;
public Circle(int x, int y, int radius, Color color)
{
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
}
public int get(int option)
{
switch (option)
{
case 0:
return this.x;
case 1:
return this.y;
case 2:
return this.radius;
}
return 0;
}
public Color get()
{
return this.color;
}
public void setCords(int x, int y, int r)
{
this.x = x;
this.y = y;
this.radius = r;
}
public void set(int option, int value)
{
switch (option)
{
case 0: //set x
this.x = value;
break;
case 1:
this.y = value;
break;
case 2:
radius = value;
break;
}
}
public void setColor(Color color)
{
this.color = color;
}
public void draw(Graphics2D g2)
{
g2.setColor(color);
g2.draw(new Ellipse2D.Double(x, y, radius, radius));
}
}
Rather than finding the specific problem in you code, I just decided to make a cleaner implementation. Using this circle class
class Circle {
final int x;
final int y;
int radius;
public Circle(int x, int y, int radius) {
this.x = x;
this.y = y;
this.radius = radius;
}
}
You can implement CirclePanel like this:
public class CirclePanel extends JPanel {
private static final Stroke DASHED = new BasicStroke(1,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_BEVEL,
0, new float[]{6}, 0);
public Color oldCircleColor = Color.BLUE;
public Color newCircleColor = Color.RED;
public Color backgroundColor = Color.LIGHT_GRAY;
private final List<Circle> oldCircles = new ArrayList<>();
private Circle newCircle = null;
private int mouseX = 0;
private int mouseY = 0;
private class MouseHelper implements MouseListener, MouseMotionListener {
#Override public void mouseMoved(MouseEvent e) {}
#Override public void mouseClicked(MouseEvent e) {}
#Override public void mousePressed(MouseEvent e) {}
#Override public void mouseEntered(MouseEvent e) {}
#Override public void mouseExited(MouseEvent e) {}
#Override
public void mouseDragged(MouseEvent e) {
mouseX = e.getX();
mouseY = e.getY();
if (newCircle == null) {
newCircle = new Circle(mouseX, mouseY, 0);
} else {
int dX = newCircle.x - mouseX;
int dY = newCircle.y - mouseY;
newCircle.radius = (int) Math.sqrt(dX*dX + dY*dY);
}
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
if (newCircle != null) {
oldCircles.add(newCircle);
newCircle = null;
repaint();
}
}
}
public CirclePanel() {
MouseHelper helper = new MouseHelper();
addMouseListener(helper);
addMouseMotionListener(helper);
setPreferredSize(new Dimension(400, 400));
}
#Override
public void paint(Graphics g) {
g.setColor(backgroundColor);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(oldCircleColor);
for (Circle c : oldCircles) {
drawCircle(g, c);
}
Circle c = newCircle;
if (c != null) {
g.setColor(newCircleColor);
drawCircle(g, c);
Graphics2D g2 = (Graphics2D) g.create();
g2.setStroke(DASHED);
g2.drawLine(c.x, c.y, mouseX, mouseY);
g2.dispose();
}
}
private void drawCircle(Graphics g, Circle c) {
// note: drawOval takes top-left corner and diameter, NOT center and radius
g.drawOval(c.x - c.radius, c.y - c.radius, c.radius * 2, c.radius * 2);
}
}
And test to see that it works
public static void main(String[] args) {
JFrame frame = new JFrame();
CirclePanel panel = new CirclePanel();
frame.add(panel);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
Like #SirLenz0rlot mentioned in the comments there is a bug in your moveTo method. The problem is the call circleList.get(circleList.size() - 1).setCords(tempX, tempY, tempR); which is setting the position of the circle to tempX, tempY, but you probably only want to set the radius of the circle, while the coords are not changed.
Changing the moveTo method to the code below should help:
public void moveTo(int x, int y)
{
var circleTemp = circleList.get(circleList.size() - 1);
isDraged = true;
var tempR = (int)Math.sqrt(Math.pow(x - circleTemp.get(0), 2) + Math.pow(y - circleTemp.get(1), 2));
System.out.println(tempR);
var tempX = circleTemp.get(0) - (tempR / 2);
var tempY = circleTemp.get(1) - (tempR / 2);
//EDITED HERE
circleList.get(circleList.size() - 1).setCords(circleTemp.get(0), circleTemp.get(1), tempR);//using getX() and getY() on your circle would be easier to understand here, but this should also work...
lineX = x;
lineY = y;
repaint();
}
For some reason your circleTemp.get(0) and (1) doesn't give you the (x,y) coordinates
circleTemp.get(0)
What you should do is save the circle's (x,y) coordinates, and use them in the moveTo method.
Correct Change
And in the end for the draw line, you should again, use the saved (x,y) coordinates and the new coordinated of the mouse. (not the circleList.size() -1).get(0) - Line 67)
(Ex. I added the drawLineX and drawLineY)
So I created a simple simple simulation where squares are spawned randomly with random vectors and bounce of the edges of the window.
I wanted it to take into account the window being resized. So that if I change the dimensions of the window from 600x600 to 1200x600 the squares will bounce of the new border rather than 600x600.
I tried doing getWidth() getHeight() but it would return 0.
So I put it in the pain() (since it gets called on window resize) method and saved the return values as local variables. But I cannot call getjpWidth() from the Rect class.
So basically what I need is to get new window dimension into the move() method in the Rect class.
Please feel free to point out any other mistakes and things that can be done better. I'm new to 2D programming (studying Computer Science)
Application
import javax.swing.*;
public class Application {
private Application(){
//create a JFrame window
JFrame frame = new JFrame("Moving Squares");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//add a JPanel
GamePanel gamePanel = new GamePanel();
frame.add(gamePanel);
//pack the window around the content
frame.pack();
//center
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String args[]){
new Application();
}
}
GamePanel
import java.awt.*;
import java.util.ArrayList;
import javax.swing.*;
public class GamePanel extends JPanel implements Runnable{
private int jpWidth=0, jpHeight=0;
//set JPanel size
private static final Dimension DESIRED_SIZE = new Dimension(600,600);
#Override
public Dimension getPreferredSize(){
return DESIRED_SIZE;
}
//constructor
GamePanel(){
Thread t = new Thread(this);
t.start();
}
private ArrayList <Rect> rect=new ArrayList<>();
public void run(){
for(int i=0; i<15; i++){
rect.add(new Rect());
}
while(true){
for(Rect rect:rect){
rect.move();
}
//repaint still image for better frames
//should be 100fps instead it's >144fps
repaint();
try{Thread.sleep(10);}
catch(InterruptedException e){/**/};
repaint();
try{Thread.sleep(10);}
catch(InterruptedException e){/**/};
repaint();
try{Thread.sleep(10);}
catch(InterruptedException e){/**/};
}
}
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D) g.create();
jpWidth=getWidth();
jpHeight=getHeight();
g2d.setColor(Color.white);
g2d.fillRect(0,0,jpWidth,jpHeight);
for(Rect rect:rect) {
g2d.setColor(Color.black);
g2d.fillRect(rect.getXcord()-1, rect.getYcord()-1, rect.getWidth()+2, rect.getHeight()+2);
g2d.setColor(Color.getHSBColor(rect.getR(), rect.getG(), rect.getB()));
g2d.fillRect(rect.getXcord(), rect.getYcord(), rect.getWidth(), rect.getHeight());
}
}
public int getJpWidth() {
return jpWidth;
}
public int getJpHeight() {
return jpHeight;
}
}
Rect
import java.util.Random;
public class Rect {
//properties
private int width=30, height=30;
private int R, G, B;
//movement
private int xCord, yCord;
private int xVector, yVector;
private int xSlope, ySlope;
public Rect(){
Random rand = new Random();
//random color
R=rand.nextInt(255);
G=rand.nextInt(255);
B=rand.nextInt(255);
//random spawn position
xCord=rand.nextInt(600-width);
yCord=rand.nextInt(600-height);
//direction
do{
xVector=rand.nextInt(3) - 1;
yVector=rand.nextInt(3) - 1;
}while(xVector==0 || yVector==0);
//slope
do{
xSlope=rand.nextInt(3);
ySlope=rand.nextInt(3);
}while(xSlope==0 || ySlope==0);
xVector*=xSlope;
yVector*=ySlope;
}
public void move(){
//if(xCord>=//how to get screen width ? ){}
if((xCord>=600-width) || (xCord<=0)){
bounceX();
}
if((yCord>=600-height) || (yCord<=0)) {
bounceY();
}
xCord+=xVector;
yCord+=yVector;
}
public void bounceX(){
xVector*=-1;
}
public void bounceY(){
yVector*=-1;
}
public int getR() {
return R;
}
public int getG() {
return G;
}
public int getB() {
return B;
}
public int getXcord() {
return xCord;
}
public int getYcord() {
return yCord;
}
public int getWidth(){
return width;
}
public int getHeight(){
return height;
}
}
So basically what I need is to get new window dimension into the move() method in the Rect class.
Don't know if it is the best design but I pass the "panel" as a parameter to the "move()" method so its width/height can be used.
Here is some old code I have lying around that shows this approach:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
public class BallAnimation4
{
private static void createAndShowUI()
{
BallPanel panel = new BallPanel();
JFrame frame = new JFrame("BallAnimation4");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( panel );
frame.pack();
frame.setLocationRelativeTo( null );
//frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setVisible( true );
panel.addBalls(5);
panel.startAnimation();
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
class BallPanel extends JPanel implements ActionListener
{
private ArrayList<Ball> balls = new ArrayList<Ball>();
public BallPanel()
{
setLayout( null );
// setBackground( Color.BLACK );
}
public void addBalls(int ballCount)
{
Random random = new Random();
for (int i = 0; i < ballCount; i++)
{
Ball ball = new Ball();
ball.setRandomColor(true);
ball.setLocation(random.nextInt(getWidth()), random.nextInt(getHeight()));
// ball.setMoveRate(32, 32, 1, 1, true);
ball.setMoveRate(16, 16, 1, 1, true);
// ball.setSize(32, 32);
ball.setSize(64, 64);
balls.add( ball );
}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
for (Ball ball: balls)
{
ball.draw(g);
}
}
#Override
public Dimension getPreferredSize()
{
return new Dimension(800, 600);
}
public void startAnimation()
{
Timer timer = new Timer(1000/60, this);
timer.start();
}
public void actionPerformed(ActionEvent e)
{
move();
repaint();
}
private void move()
{
for (Ball ball : balls)
{
ball.move(this);
}
}
class Ball
{
public Color color = Color.BLACK;
public int x = 0;
public int y = 0;
public int width = 1;
public int height = 1;
private int moveX = 1;
private int moveY = 1;
private int directionX = 1;
private int directionY = 1;
private int xScale = moveX;
private int yScale = moveY;
private boolean randomMove = false;
private boolean randomColor = false;
private Random myRand = null;
public Ball()
{
myRand = new Random();
setRandomColor(randomColor);
}
public void move(JPanel parent)
{
int iRight = parent.getSize().width;
int iBottom = parent.getSize().height;
x += 5 + (xScale * directionX);
y += 5 + (yScale * directionY);
if (x <= 0)
{
x = 0;
directionX *= (-1);
xScale = randomMove ? myRand.nextInt(moveX) : moveX;
if (randomColor) setRandomColor(randomColor);
}
if (x >= iRight - width)
{
x = iRight - width;
directionX *= (-1);
xScale = randomMove ? myRand.nextInt(moveX) : moveX;
if (randomColor) setRandomColor(randomColor);
}
if (y <= 0)
{
y = 0;
directionY *= (-1);
yScale = randomMove ? myRand.nextInt(moveY) : moveY;
if (randomColor) setRandomColor(randomColor);
}
if (y >= iBottom - height)
{
y = iBottom - height;
directionY *= (-1);
yScale = randomMove ? myRand.nextInt(moveY) : moveY;
if (randomColor) setRandomColor(randomColor);
}
}
public void draw(Graphics g)
{
g.setColor(color);
g.fillOval(x, y, width, height);
}
public void setColor(Color c)
{
color = c;
}
public void setLocation(int x, int y)
{
this.x = x;
this.y = y;
}
public void setMoveRate(int xMove, int yMove, int xDir, int yDir, boolean randMove)
{
this.moveX = xMove;
this.moveY = yMove;
directionX = xDir;
directionY = yDir;
randomMove = randMove;
}
public void setRandomColor(boolean randomColor)
{
this.randomColor = randomColor;
switch (myRand.nextInt(3))
{
case 0: color = Color.BLUE;
break;
case 1: color = Color.GREEN;
break;
case 2: color = Color.RED;
break;
default: color = Color.BLACK;
break;
}
}
public void setSize(int width, int height)
{
this.width = width;
this.height = height;
}
}
}
Also, note that for animation you should be using a Swing Timer to schedule the animation. Updates to Swing components should be done on the Event Dispatch Thread (EDT). While not likely to cause a problem with this simple application it is a good habit to make sure this basic rule is followed otherwise you can have random problems and it is never easy to debug a random problem.
I'm all new to this site and to Java, so please be lenient.
I'm writing a program that allows to draw different type of shapes with a button click and after hitting another button move/stop/reset them.
I've made already the most part I think (the shapes are correctly creating and storing in an arraylist, the same with the reset, which clear the screen), but I can't figure out how to make them move.I got a function for movement but can't find a way to make the shapes form the arraylist to move. Can anyone give me a little advise.
Thanks
P.S. If there is something wrong/bad coding and needs to be fixed I'll be grateful if you will point at them.
Here is my code:
MyShape class is for creating different shapes.
import java.awt.*;
import java.util.Random;
public abstract class MyShape extends Component {
protected Color color;
private int x, y, dimX, dimY;
public Random random = new Random();
public MyShape(int x, int y, int dimX, int dimY){
this.x = x;
this.y = y;
this.dimX = dimX;
this.dimY = dimY;
color = new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255));
}
public abstract void draw(Graphics g);
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getDimX() {
return dimX;
}
public void setDimX(int dimX) {
this.dimX = dimX;
}
public int getDimY() {
return dimY;
}
public void setDimY(int dimY) {
this.dimY = dimY;
}
}
CircleShape - creating circles.
import java.awt.*;
public class CircleShape extends MyShape {
public CircleShape(int x, int y, int dimX, int dimY) {
super(x, y, dimX, dimY);
}
#Override
public void draw(Graphics g) {
g.setColor(color);
g.fillOval(getX(), getY(), getDimX(), getDimY());
}
}
RectangleShape - rectangles
import java.awt.*;
public class RectangleShape extends MyShape {
public RectangleShape(int x, int y, int dimX, int dimY) {
super(x, y, dimX, dimY);
}
#Override
public void draw(Graphics g) {
g.setColor(color);
g.fillRect(getX(), getY(), getDimX(), getDimY());
}
}
and the DrawShape class which handles pretty much everything
public class DrawShapes extends JPanel {
private JButton addButton, resumeAllButton, stopAllButton, resetButton;
private final int FRAME_WIDTH = 800;
private final int FRAME_HEIGHT = 530;
private int x, y, dimX, dimY;
private Random random = new Random();
public List<MyShape> myShapeList = new CopyOnWriteArrayList<MyShape>();
private Timer timer = null;
public boolean move = false;
public DrawShapes() {
this.setLayout(null);
addButton = new JButton("Add Shape");
resumeAllButton = new JButton("Resume Shapes");
stopAllButton = new JButton("Stop All Shapes");
resetButton = new JButton("Reset");
addButton.setBounds(40, 20, 150, 30);
resumeAllButton.setBounds(230, 20, 150, 30);
stopAllButton.setBounds(420, 20, 150, 30);
resetButton.setBounds(610, 20, 150, 30);
this.add(addButton);
this.add(resumeAllButton);
this.add(stopAllButton);
this.add(resetButton);
stopAllButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
move = false;
}
});
resumeAllButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
move = true;
}
});
addButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Thread thread = new Thread() {
public void run() {
init();
}
};
thread.start();
}
});
resetButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < myShapeList.size(); i++) {
myShapeList.clear();
repaint();
}
}
});
}
public void moveIt() {
boolean directionUp = random.nextBoolean();
boolean directionLeft = random.nextBoolean();
boolean directionDown = !directionUp;
boolean directionRight = !directionLeft;
while (move) {
if (x <= 0) {
directionRight = true;
directionLeft = false;
}
if (x >= FRAME_WIDTH - dimX) {
directionRight = false;
directionLeft = true;
}
if (y <= 70) {
directionUp = false;
directionDown = true;
}
if (y >= FRAME_HEIGHT + 50 - dimY) {
directionUp = true;
directionDown = false;
}
if (directionUp)
y--;
if (directionDown)
y++;
if (directionLeft)
x--;
if (directionRight)
x++;
}
}
public void init() {
x = 0;
y = 0;
dimX = (random.nextInt(FRAME_WIDTH) + 100) / 2;
dimY = (random.nextInt(FRAME_HEIGHT) + 100) / 2;
while (x <= 0)
x = (random.nextInt(FRAME_WIDTH) - dimX);
while (y <= 70)
y = (random.nextInt(FRAME_HEIGHT) - dimY);
int choice = 0;
choice = random.nextInt(2) + 1;
switch (choice) {
case 1:
RectangleShape rectangleShape = new RectangleShape(x, y, dimX, dimY);
myShapeList.add(rectangleShape);
timer.start();
repaint();
break;
case 2:
CircleShape circleShape = new CircleShape(x, y, dimX, dimY);
myShapeList.add(circleShape);
repaint();
break;
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.WHITE);
g.fillRect(0, 70, 800, 530);
for (MyShape aMyShapeList : myShapeList) {
aMyShapeList.draw(g);
}
}
public static void main(String args[]) {
JFrame jFrame = new JFrame();
jFrame.add(new DrawShapes());
jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
jFrame.setSize(800, 600);
jFrame.setResizable(false);
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
jFrame.setLocation(dim.width / 2 - jFrame.getSize().width / 2, dim.height / 2 - jFrame.getSize().height / 2);
jFrame.setLocationRelativeTo(null);
jFrame.setVisible(true);
}
}
From the code you posted I can see that you are not calling your moveIt() method anywhere.
You have the right idea of how to move things around. The basic algorithm is:
Calculate new positions
Repaint the view
I can recommend you do the following:
You are currently calling your init method in a thread. I am not sure this is needed. Remove the thread logic and just call the method on the main thread.
Introduce another button tho start the actual animation. When clicking, create a thread that will call your moveIt() method.
I'm starting to learn java programming and I think it's cool to learn java through game development. I know how to draw image and listen to a keypress then move that image. But is it possible to make the image move back and forth to the window while the window is listening to a keypress? Like for example, while the image or object(like spaceship) is moving left to right in the window, then if I press space key, a laser will fire at the bottom of the screen( cool huh :D ). But basically I just want to know how to make the image move left to right while the window is listening to a keypress.
I'm thinking that I will add a key listener to my window then fire an infinite loop to move the image. Or do I need to learn about threading so that another thread will move the object?
Please advise.
Many thanks.
Yep, a Swing Timer and Key Bindings would work well. Here's another example (mine) :)
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class AnimationWithKeyBinding {
private static void createAndShowUI() {
AnimationPanel panel = new AnimationPanel(); // the drawing JPanel
JFrame frame = new JFrame("Animation With Key Binding");
frame.getContentPane().add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
#SuppressWarnings("serial")
class AnimationPanel extends JPanel {
public static final int SPRITE_WIDTH = 20;
public static final int PANEL_WIDTH = 400;
public static final int PANEL_HEIGHT = 400;
private static final int MAX_MSTATE = 25;
private static final int SPIN_TIMER_PERIOD = 16;
private static final int SPRITE_STEP = 3;
private int mState = 0;
private int mX = (PANEL_WIDTH - SPRITE_WIDTH) / 2;
private int mY = (PANEL_HEIGHT - SPRITE_WIDTH) / 2;
private int oldMX = mX;
private int oldMY = mY;
private boolean moved = false;
// an array of sprite images that are drawn sequentially
private BufferedImage[] spriteImages = new BufferedImage[MAX_MSTATE];
public AnimationPanel() {
// create and start the main animation timer
new Timer(SPIN_TIMER_PERIOD, new SpinTimerListener()).start();
setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
setBackground(Color.white);
createSprites(); // create the images
setupKeyBinding();
}
private void setupKeyBinding() {
int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
InputMap inMap = getInputMap(condition);
ActionMap actMap = getActionMap();
// this uses an enum of Direction that holds ints for the arrow keys
for (Direction direction : Direction.values()) {
int key = direction.getKey();
String name = direction.name();
// add the key bindings for arrow key and shift-arrow key
inMap.put(KeyStroke.getKeyStroke(key, 0), name);
inMap.put(KeyStroke.getKeyStroke(key, InputEvent.SHIFT_DOWN_MASK), name);
actMap.put(name, new MyKeyAction(this, direction));
}
}
// create a bunch of buffered images and place into an array,
// to be displayed sequentially
private void createSprites() {
for (int i = 0; i < spriteImages.length; i++) {
spriteImages[i] = new BufferedImage(SPRITE_WIDTH, SPRITE_WIDTH,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = spriteImages[i].createGraphics();
g2.setColor(Color.red);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
double theta = i * Math.PI / (2 * spriteImages.length);
double x = SPRITE_WIDTH * Math.abs(Math.cos(theta)) / 2.0;
double y = SPRITE_WIDTH * Math.abs(Math.sin(theta)) / 2.0;
int x1 = (int) ((SPRITE_WIDTH / 2.0) - x);
int y1 = (int) ((SPRITE_WIDTH / 2.0) - y);
int x2 = (int) ((SPRITE_WIDTH / 2.0) + x);
int y2 = (int) ((SPRITE_WIDTH / 2.0) + y);
g2.drawLine(x1, y1, x2, y2);
g2.drawLine(y1, x2, y2, x1);
g2.dispose();
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(spriteImages[mState], mX, mY, null);
}
public void incrementX(boolean right) {
oldMX = mX;
if (right) {
mX = Math.min(getWidth() - SPRITE_WIDTH, mX + SPRITE_STEP);
} else {
mX = Math.max(0, mX - SPRITE_STEP);
}
moved = true;
}
public void incrementY(boolean down) {
oldMY = mY;
if (down) {
mY = Math.min(getHeight() - SPRITE_WIDTH, mY + SPRITE_STEP);
} else {
mY = Math.max(0, mY - SPRITE_STEP);
}
moved = true;
}
public void tick() {
mState = (mState + 1) % MAX_MSTATE;
}
private class SpinTimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
tick();
int delta = 20;
int width = SPRITE_WIDTH + 2 * delta;
int height = width;
// make sure to erase the old image
if (moved) {
int x = oldMX - delta;
int y = oldMY - delta;
repaint(x, y, width, height);
}
int x = mX - delta;
int y = mY - delta;
// draw the new image
repaint(x, y, width, height);
moved = false;
}
}
}
enum Direction {
UP(KeyEvent.VK_UP), DOWN(KeyEvent.VK_DOWN), LEFT(KeyEvent.VK_LEFT), RIGHT(KeyEvent.VK_RIGHT);
private int key;
private Direction(int key) {
this.key = key;
}
public int getKey() {
return key;
}
}
// Actions for the key binding
#SuppressWarnings("serial")
class MyKeyAction extends AbstractAction {
private AnimationPanel draw;
private Direction direction;
public MyKeyAction(AnimationPanel draw, Direction direction) {
this.draw = draw;
this.direction = direction;
}
#Override
public void actionPerformed(ActionEvent e) {
switch (direction) {
case UP:
draw.incrementY(false);
break;
case DOWN:
draw.incrementY(true);
break;
case LEFT:
draw.incrementX(false);
break;
case RIGHT:
draw.incrementX(true);
break;
default:
break;
}
}
}
Here is another example that uses this sprite sheet:
obtained from this site.
Again it's an example of drawing within a JPanel's paintComponent method and using Key Bindings to tell which direction to move.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.swing.*;
#SuppressWarnings("serial")
public class Mcve3 extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = 640;
private static final int TIMER_DELAY = 50;
private int spriteX = 400;
private int spriteY = 320;
private SpriteDirection spriteDirection = SpriteDirection.RIGHT;
private MySprite sprite = null;
private Timer timer = null;
public Mcve3() {
try {
sprite = new MySprite(spriteDirection, spriteX, spriteY);
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
setBackground(Color.WHITE);
setKeyBindings(SpriteDirection.LEFT, KeyEvent.VK_LEFT);
setKeyBindings(SpriteDirection.RIGHT, KeyEvent.VK_RIGHT);
setKeyBindings(SpriteDirection.FORWARD, KeyEvent.VK_DOWN);
setKeyBindings(SpriteDirection.AWAY, KeyEvent.VK_UP);
timer = new Timer(TIMER_DELAY, new TimerListener());
timer.start();
}
private void setKeyBindings(SpriteDirection dir, int keyCode) {
int condition = WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition);
ActionMap actionMap = getActionMap();
KeyStroke keyPressed = KeyStroke.getKeyStroke(keyCode, 0, false);
KeyStroke keyReleased = KeyStroke.getKeyStroke(keyCode, 0, true);
inputMap.put(keyPressed, keyPressed.toString());
inputMap.put(keyReleased, keyReleased.toString());
actionMap.put(keyPressed.toString(), new MoveAction(dir, false));
actionMap.put(keyReleased.toString(), new MoveAction(dir, true));
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
sprite.draw(g);
}
private class MoveAction extends AbstractAction {
private SpriteDirection dir;
private boolean released;
public MoveAction(SpriteDirection dir, boolean released) {
this.dir = dir;
this.released = released;
}
#Override
public void actionPerformed(ActionEvent e) {
if (released) {
sprite.setMoving(false);
} else {
sprite.setMoving(true);
sprite.setDirection(dir);
}
}
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (sprite.isMoving()) {
sprite.tick();
}
repaint();
}
}
private static void createAndShowGui() {
Mcve3 mainPanel = new Mcve3();
JFrame frame = new JFrame("MCVE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class MySprite {
private static final String SPRITE_SHEET_PATH = "http://"
+ "orig12.deviantart.net/7db3/f/2010/338/3/3/"
+ "animated_sprite_sheet_32x32_by_digibody-d3479l2.gif";
private static final int MAX_MOVING_INDEX = 4;
private static final int DELTA = 4;
private SpriteDirection direction;
private Map<SpriteDirection, Image> standingImgMap = new EnumMap<>(SpriteDirection.class);
private Map<SpriteDirection, List<Image>> movingImgMap = new EnumMap<>(SpriteDirection.class);
private int x;
private int y;
private boolean moving = false;
private int movingIndex = 0;
public MySprite(SpriteDirection direction, int x, int y) throws IOException {
this.direction = direction;
this.x = x;
this.y = y;
createSprites();
}
public void draw(Graphics g) {
Image img = null;
if (!moving) {
img = standingImgMap.get(direction);
} else {
img = movingImgMap.get(direction).get(movingIndex);
}
g.drawImage(img, x, y, null);
}
private void createSprites() throws IOException {
URL spriteSheetUrl = new URL(SPRITE_SHEET_PATH);
BufferedImage img = ImageIO.read(spriteSheetUrl);
// get sub-images (sprites) from the sprite sheet
// magic numbers for getting sprites from sheet, all obtained by trial and error
int x0 = 0;
int y0 = 64;
int rW = 32;
int rH = 32;
for (int row = 0; row < 4; row++) {
SpriteDirection dir = SpriteDirection.values()[row];
List<Image> imgList = new ArrayList<>();
movingImgMap.put(dir, imgList);
int rY = y0 + row * rH;
for (int col = 0; col < 5; col++) {
int rX = x0 + col * rW;
BufferedImage subImg = img.getSubimage(rX, rY, rW, rH);
if (col == 0) {
// first image is standing
standingImgMap.put(dir, subImg);
} else {
// all others are moving
imgList.add(subImg);
}
}
}
}
public SpriteDirection getDirection() {
return direction;
}
public void setDirection(SpriteDirection direction) {
if (this.direction != direction) {
setMoving(false);
}
this.direction = direction;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public boolean isMoving() {
return moving;
}
public void setMoving(boolean moving) {
this.moving = moving;
if (!moving) {
movingIndex = 0;
}
}
public void tick() {
if (moving) {
switch (direction) {
case RIGHT:
x += DELTA;
break;
case LEFT:
x -= DELTA;
break;
case FORWARD:
y += DELTA;
break;
case AWAY:
y -= DELTA;
}
movingIndex++;
movingIndex %= MAX_MOVING_INDEX;
}
}
public int getMovingIndex() {
return movingIndex;
}
public void setMovingIndex(int movingIndex) {
this.movingIndex = movingIndex;
}
}
enum SpriteDirection {
FORWARD, LEFT, AWAY, RIGHT
}
As an alternative to KeyListener, consider using actions and key bindings, discussed here. Derived from this example, the program below moves a line left, down, up or right using either buttons or keys.
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
/**
* #see https://stackoverflow.com/questions/6991648
* #see https://stackoverflow.com/questions/6887296
* #see https://stackoverflow.com/questions/5797965
*/
public class LinePanel extends JPanel {
private MouseHandler mouseHandler = new MouseHandler();
private Point p1 = new Point(100, 100);
private Point p2 = new Point(540, 380);
private boolean drawing;
public LinePanel() {
this.setPreferredSize(new Dimension(640, 480));
this.addMouseListener(mouseHandler);
this.addMouseMotionListener(mouseHandler);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.blue);
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(8,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
g.drawLine(p1.x, p1.y, p2.x, p2.y);
}
private class MouseHandler extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
drawing = true;
p1 = e.getPoint();
p2 = p1;
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
drawing = false;
p2 = e.getPoint();
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
if (drawing) {
p2 = e.getPoint();
repaint();
}
}
}
private class ControlPanel extends JPanel {
private static final int DELTA = 10;
public ControlPanel() {
this.add(new MoveButton("\u2190", KeyEvent.VK_LEFT, -DELTA, 0));
this.add(new MoveButton("\u2191", KeyEvent.VK_UP, 0, -DELTA));
this.add(new MoveButton("\u2192", KeyEvent.VK_RIGHT, DELTA, 0));
this.add(new MoveButton("\u2193", KeyEvent.VK_DOWN, 0, DELTA));
}
private class MoveButton extends JButton {
KeyStroke k;
int dx, dy;
public MoveButton(String name, int code, final int dx, final int dy) {
super(name);
this.k = KeyStroke.getKeyStroke(code, 0);
this.dx = dx;
this.dy = dy;
this.setAction(new AbstractAction(this.getText()) {
#Override
public void actionPerformed(ActionEvent e) {
LinePanel.this.p1.translate(dx, dy);
LinePanel.this.p2.translate(dx, dy);
LinePanel.this.repaint();
}
});
ControlPanel.this.getInputMap(
WHEN_IN_FOCUSED_WINDOW).put(k, k.toString());
ControlPanel.this.getActionMap().put(k.toString(), new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
MoveButton.this.doClick();
}
});
}
}
}
private void display() {
JFrame f = new JFrame("LinePanel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.add(new ControlPanel(), BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new LinePanel().display();
}
});
}
}
But basically I just want to know how to make the image move left to right while the window is listening to a keypress
You can use a Swing Timer to animate an image:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TimerAnimation extends JLabel implements ActionListener
{
int deltaX = 2;
int deltaY = 3;
int directionX = 1;
int directionY = 1;
public TimerAnimation(
int startX, int startY,
int deltaX, int deltaY,
int directionX, int directionY,
int delay)
{
this.deltaX = deltaX;
this.deltaY = deltaY;
this.directionX = directionX;
this.directionY = directionY;
setIcon( new ImageIcon("dukewavered.gif") );
// setIcon( new ImageIcon("copy16.gif") );
setSize( getPreferredSize() );
setLocation(startX, startY);
new javax.swing.Timer(delay, this).start();
}
public void actionPerformed(ActionEvent e)
{
Container parent = getParent();
// Determine next X position
int nextX = getLocation().x + (deltaX * directionX);
if (nextX < 0)
{
nextX = 0;
directionX *= -1;
}
if ( nextX + getSize().width > parent.getSize().width)
{
nextX = parent.getSize().width - getSize().width;
directionX *= -1;
}
// Determine next Y position
int nextY = getLocation().y + (deltaY * directionY);
if (nextY < 0)
{
nextY = 0;
directionY *= -1;
}
if ( nextY + getSize().height > parent.getSize().height)
{
nextY = parent.getSize().height - getSize().height;
directionY *= -1;
}
// Move the label
setLocation(nextX, nextY);
}
public static void main(String[] args)
{
JPanel panel = new JPanel();
JFrame frame = new JFrame();
frame.setContentPane(panel);
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.getContentPane().setLayout(null);
// frame.getContentPane().add( new TimerAnimation(10, 10, 2, 3, 1, 1, 10) );
frame.getContentPane().add( new TimerAnimation(300, 100, 3, 2, -1, 1, 20) );
// frame.getContentPane().add( new TimerAnimation(0, 000, 5, 0, 1, 1, 20) );
frame.getContentPane().add( new TimerAnimation(0, 200, 5, 0, 1, 1, 80) );
frame.setSize(400, 400);
frame.setLocationRelativeTo( null );
frame.setVisible(true);
// frame.getContentPane().add( new TimerAnimation(10, 10, 2, 3, 1, 1, 10) );
// frame.getContentPane().add( new TimerAnimation(10, 10, 3, 0, 1, 1, 10) );
}
}
You can add a KeyListener to the panel and it will operate independently of the image animation.