I'm making a simple Java program to bounce a ball up and down. The problem is that the ball bounces up higher than its starting point with each bounce. I expect the ball to bounce back up exactly to the height that it started from.
The ball physics can be found in the circle class in the doPhysics() method where I suspect the problem can be found
import java.awt.*;
import java.util.*;
public class Main{
public static Frame frame = new Frame();
public static Physics physics = new Physics();
public static ArrayList<Circle> circles = new ArrayList<Circle>(); //array for the points
public static void main(String args[]) {
Circle circle = new Circle(100, 300, 50, Color.BLACK);
circles.add(circle);
run();
}
public static void run() {
physics.timer.start();
}
}
import java.awt.*;
public class Circle {
private int x;
private int y;
private double xAccel= 0;
private double yAccel = 0;
private double xVel= 0;
private double yVel = 0;
private Color colour;
private int radius;
public Circle(int x, int y, int radius, Color colour) {
setX(x);
setY(y);
setRadius(radius);
setColour(colour);
}
public void draw(Graphics2D g2d) {
g2d.setColor(colour);
g2d.fillOval(x, y, radius*2, radius*2);
}
public void doPhysics() {
hitGround();
System.out.println(yVel);
yVel += Physics.getGravity();
y -= yVel;
}
public void hitGround() {
if(y + radius*2 > Frame.panel.h ) {
yVel = -yVel;
}
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setColour(Color colour) {
this.colour = colour;
}
public void setRadius(int radius) {
this.radius = radius;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Color getColour() {
return colour;
}
public int getRadius() {
return radius;
}
}
import java.awt.*;
import javax.swing.*;
class Frame extends JFrame {
public static Panel panel;
public Frame() {
panel = new Panel();
this.setTitle("Fun");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(panel);
this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
}
}
class Panel extends JPanel {
public int w = 500;
public int h = 500;
public Panel() {
this.setPreferredSize(new Dimension(w, h));
this.setBackground(Color.red);
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
for(Circle circle : Main.circles) {
circle.draw(g2d);
}
}
}
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
public class Physics implements ActionListener {
private static double gravity = -.1;
public Timer timer;
public Physics() {
timer = new Timer(1, this);
}
public static double getGravity() {
return gravity;
}
#Override
public void actionPerformed(ActionEvent e) {
for(Circle circle : Main.circles) {
circle.doPhysics();
}
Main.frame.repaint();
}
}
The problem is mainly caused by using integer values for position (x and y). On each iteration the values are rounded and the errors get accumulated.
Solution: declare double x and double y and only use the rounded integer values for drawing.
Above should reduce the problem, but not completely solve it. The code is doing a rough integration over timeĀ¹ by using the velocity calculated after the time interval (see Numerical Integration). This can be improved by doing an average of the velocities before and after it was changed. Roughly:
double preVel = yVel;
yVel += Physics.getGravity();
y -= (preVel + yVel)/2;
which can be simplified (pure math) to:
yVel += Physics.getGravity();
y -= yVel - Physics.getGravity()/2;
This should work fine since the acceleration is constant. Not the case if the acceleration is also changing. And it is also susceptible to precision errors being accumulated over time.
1 - see Numerical integration and Temporal discretization
Here I have two classes, one including main function. I want to draw a rectangle which moves automatically. But the starting point of rectangle is not the same as the point i clicked with mouse. I could not figure out this problem. Can you help me?
This is the first class including main function
import java.awt.Graphics;
import java.awt.event.*;
import javax.swing.*;
public class BuyuyenSuDamlalari extends JPanel implements ActionListener {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(600,400);
BuyuyenSuDamlalari bsd1 = new BuyuyenSuDamlalari();
frame.setContentPane(bsd1);
BuyuyenSuDamlalariClickListener bscl = new BuyuyenSuDamlalariClickListener(bsd1);
bsd1.addMouseListener(bscl);
frame.setResizable(false);
frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
int x;
int y;
int radius;
int click;
public BuyuyenSuDamlalari() {
super();
setFocusable(true);
Timer zaman = new Timer(40, this); // bir saniyede 25 resim oluyor
zaman.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (click>0) {
g.drawRect(x, y, radius, radius);
}
}
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 void actionPerformed(ActionEvent arg0) {
radius++;
repaint();
}
public void setClick(int click) {
this.click = click;
}
}
This is the second class wihich includes motionListener
import java.awt.event.*;
public class BuyuyenSuDamlalariClickListener extends MouseAdapter {
private BuyuyenSuDamlalari bsd = new BuyuyenSuDamlalari();
private int x;
private int y;
public BuyuyenSuDamlalariClickListener(BuyuyenSuDamlalari bsd) {
super();
this.bsd = bsd;
}
public void mousePressed(MouseEvent e) {
if (e.getClickCount()>0) {
bsd.setX(e.getX()-25);
bsd.setY(e.getY()-25);
}
bsd.setClick(1);
}
}
I want to draw circle only after mouse gets click. As paintComponent method called itself, so first circle draw without click.
public class DrawPanel extends JPanel implements MouseListener {
private static final long serialVersionUID = 1L;
int x, y;
public DrawPanel() {
setBackground(Color.WHITE);
addMouseListener(this);
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.red);
g2.fillOval(x, y, 20, 20);
}
#Override
public void mouseClicked(MouseEvent e) {
x = e.getX();
y = e.getY();
repaint();
}
}
There are a few issues with your code:
You never call super.paintComponent();
You only have one x and y
Note how when you resize the frame, some circles will disappear and it overall behaves in a strange way.
I would store all the Points where the user has clicked in an ArrayList and then loop through that list inside the paintComponent method. This way you can call super.paintComponent(); without the circles disappearing.
Changed, working code:
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class DrawPanel extends JPanel {
private static final long serialVersionUID = 1L;
private ArrayList<Point> points;
public DrawPanel() {
points = new ArrayList<Point>();
setBackground(Color.WHITE);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
points.add(new Point(e.getX(), e.getY()));
repaint();
}
});
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.red);
for (Point point : points) {
g2.fillOval(point.x, point.y, 20, 20);
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new DrawPanel());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.setVisible(true);
}
});
}
}
You should place your circle inside its own class. That class will hold information on its location, radius, and its color. You can abstract your shape and have a list of shapes to draw on the panel. This will make it easy to implement a triangle, square, hexagon, etc. later.
You can add more methods and attributes to your shape objects later and only have to change their internal implementation of THEIR OWN paintComponent(g) method. This makes the DrawPanel depend on how each Shape does its own drawing.
App.java
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class App {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame("Circle Click Application");
DrawPanel p = new DrawPanel(10f);
p.setPreferredSize(new Dimension(300, 200));;
f.setContentPane(p);
f.pack();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
}
DrawPanel.java
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JPanel;
public class DrawPanel extends JPanel implements MouseListener {
private static final long serialVersionUID = -6817035652787391530L;
private List<Shape> shapes;
protected float radius;
private float sat = 0.7f;
private float bri = 0.8f;
public DrawPanel(float radius) {
this.shapes = new ArrayList<Shape>();
this.radius = radius;
setBackground(Color.WHITE);
addMouseListener(this);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (Shape shape : shapes) {
shape.paintComponent(g);
}
}
#Override
public void mouseClicked(MouseEvent e) {
shapes.add(new Circle(e.getX(), e.getY(), radius, ColorUtils.randHue(sat, bri)));
repaint();
}
#Override
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
}
Shape.java
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
public interface Shape {
Point getOrigin();
void setOrigin(Point origin);
Color getColor();
void setColor(Color color);
void paintComponent(Graphics g);
}
Circle.java
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
public class Circle implements Shape {
private Point origin;
private float radius;
private Color color;
public Circle() {
this(0, 0, 0.5f, Color.BLACK);
}
public Circle(int x, int y, float radius, Color color) {
this(new Point(x, y), radius, color);
}
public Circle(Point origin, float radius, Color color) {
this.origin = origin;
this.radius = radius;
this.color = color;
}
#Override
public Point getOrigin() {
return origin;
}
#Override
public void setOrigin(Point origin) {
this.origin = origin;
}
public float getRadius() {
return radius;
}
public void setRadius(float radius) {
this.radius = radius;
}
#Override
public Color getColor() {
return color;
}
#Override
public void setColor(Color color) {
this.color = color;
}
#Override
public void paintComponent(Graphics g) {
int diameter = (int) (this.radius * 2);
int x = (int) (origin.x - this.radius);
int y = (int) (origin.y - this.radius);
g.setColor(this.color);
g.fillOval(x, y, diameter, diameter);
}
}
ColorUtils.java
import java.awt.Color;
import java.util.Random;
public class ColorUtils {
private static final Random RAND;
static {
RAND = new Random(System.currentTimeMillis());
}
public static Color randHue(float saturation, float brightness) {
return Color.getHSBColor(RAND.nextFloat(), saturation, brightness);
}
}
Extension
You can easily add a class such as a Triangle just by implementing the Shape interface.
DrawPanel#mouseClicked
#Override
public void mouseClicked(MouseEvent e) {
long time = System.currentTimeMillis();
boolean isEven = time % 2 == 0;
if (isEven) {
shapes.add(new Circle(e.getX(), e.getY(), radius, ColorUtils.randHue(sat, bri)));
} else {
shapes.add(new Triangle(e.getX(), e.getY(), radius * 2, ColorUtils.randHue(sat, bri)));
}
repaint();
}
Triangle.java
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.geom.Point2D;
public class Triangle implements Shape {
private Point origin;
private float side;
private Color color;
private Polygon poly;
public Triangle(int x, int y, float side, Color color) {
this(new Point(x, y), side, color);
}
public Triangle(Point origin, float side, Color color) {
this.origin = origin;
this.side = side;
this.color = color;
recalculate();
}
protected void recalculate() {
this.poly = createPolygon((float) origin.getX(), (float) origin.getY(), side, false);
}
protected Polygon createPolygon(float x, float y, float side, boolean invert) {
float xOff = side / 2f;
float yOff = (float) (xOff * Math.sqrt(3));
float r1 = 1f / 3f;
float r2 = 2f / 3f;
if (invert) {
yOff *= -1;
}
return createPolygon(new Point2D.Float[] {
new Point2D.Float(x, y - (yOff * r2)), // Top
new Point2D.Float(x - xOff, y + (yOff * r1)), // Left
new Point2D.Float(x + xOff, y + (yOff * r1)) // Right
});
}
protected Polygon createPolygon(Point2D.Float[] points) {
int nPoints = points.length + 1;
int xCoords[] = new int[nPoints];
int yCoords[] = new int[nPoints];
for (int i = 0; i < nPoints; i++) {
xCoords[i] = (int) points[i % points.length].x;
yCoords[i] = (int) points[i % points.length].y;
}
return new Polygon(xCoords, yCoords, nPoints);
}
#Override
public Point getOrigin() {
return origin;
}
#Override
public void setOrigin(Point origin) {
this.origin = origin;
recalculate();
}
public float getSide() {
return side;
}
public void setSide(float side) {
this.side = side;
recalculate();
}
#Override
public Color getColor() {
return color;
}
#Override
public void setColor(Color color) {
this.color = color;
}
#Override
public void paintComponent(Graphics g) {
g.setColor(this.color);
g.fillPolygon(poly);
}
}
You should have some initial state, so you know not to draw when it is set.
This can be done with an easy boolean variable, that you set to true when the user has pressed on the screen.
public class DrawPanel extends JPanel implements MouseListener {
private static final long serialVersionUID = 1L;
int x, y;
boolean mustDraw = false;
public DrawPanel() {
setBackground(Color.WHITE);
addMouseListener(this);
}
public void paintComponent(Graphics g) {
if(!mustDraw) return;
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.red);
g2.fillOval(x, y, 20, 20);
}
#Override
public void mouseClicked(MouseEvent e) {
x = e.getX();
y = e.getY();
mustDraw = true;
repaint();
}
}
I have this code:
public Juego() {
setFocusable(true);
loop = new Timer(10, this);
loop.start();
jugador = new Jugador(400, 400);
}
public void pintar(Graphics g) {
super.paint(g);
Graphics2D g2D = (Graphics2D) g;
jugador.dibujar(g2D);
}
that is supposed to draw the player into the screen, and this is the code for the actual player:
public class Jugador extends PosicionGlobal {
private String imagendejugador = "/imagenes/jugador.png";
public Jugador(int x, int y) {
super(x, y);
}
public void actualizar() {
}
public void dibujar(Graphics2D g2D) {
g2D.drawImage(imagendejugador(), x, y, null);
}
public Image imagendejugador(){
ImageIcon icono = new ImageIcon(getClass().getResource(imagendejugador));
return icono.getImage();
}
}
When I run it the player doesn't appear its just the same white screen as before. PD: I do have a JFrame and I already add this class to it.
In case its is needed here is the PosicionGlobal class:
public class PosicionGlobal {
public int x;
public int y;
public PosicionGlobal(int x, int y) {
this.x = x;
this.y = y;
}
}
You need to be overriding paint() not pintar()..
change
public void pintar(Graphics g) {
to
#Override public void paint(Graphics g) {
and make sure you call super.paint(g);
I have created a program which draws a circle when I click on the screen. I have it working so that I can draw as many circles as I want. I can even drag one circle and not the others if I hard code which circle I am dragging. The code is:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.List;
import java.util.ArrayList;
public class DrawBall extends JPanel implements MouseListener, MouseMotionListener {
private List<Ball> balls;
private int x, y;
private int numBalls = 0;
boolean drag = false;
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("Draw Ball");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JComponent newContentPane = new DrawBall();
newContentPane.setOpaque(true);
frame.setContentPane(newContentPane);
frame.setSize(300, 300);
frame.setVisible(true);
}
public DrawBall() {
super(new BorderLayout());
balls = new ArrayList<Ball>(10);
addMouseListener(this);
addMouseMotionListener(this);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RederingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for(Ball ball: balls) {
ball.paint(g2d);
}
g2d.dispose();
}
public void mouseClicked(MouseEvent event) {
x = (int) event.getPoint().getX();
y = (int) event.getPoint().getY();
Ball ball = new Ball(Color.red, numBalls, 30);
ball.setX(x);
ball.setY(y);
balls.add(ball);
numBalls = numBalls + 1;
repaint();
}
public void mousePressed(MouseEvent event) {
drag = true;
}
public void mouseReleased(MouseEvent event) {
drag = false;
}
public void mouseEntered(MouseEvent event) {}
public void mouseExited(MouseEvent event) {}
public void mouseDragged(MouseEvent event) {
if(drag == true) {
x = (int) event.getPoint().getX();
y = (int) event.getPoint().getY();
if(event.getSource() == balls.get(0)) {
Ball ball = balls.get(0);
ball.setX(x);
ball.setY(y);
balls.set(0,ball);
}
repaing();
}
}
public void mouseMoved(MouseEvent event) {}
public class Ball {
private Color color;
private int x, y, diameter, id;
public Ball(Color color, int id, int diameter) {
setColor(color);
setID(id);
setDiameter(diameter);
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setID(int id) {
this.id = id;
}
public void setDiameter(int diameter) {
this.diameter = diameter;
}
public void setColor(Color color) {
this.color = color;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getID() {
return id;
}
public int getDiameter() {
return diameter;
}
public Color getColor() {
return color;
}
protected void paint(Graphics2D g2d) {
int x = getX();
int y = getY();
g2d.setColor(getColor());
g2d.fillOval(x, y, getDiameter(), getDiameter());
}
}
}
My question is this: In my mouseDragged method, is there an easy way to tell which circle I am hovering over? I have played around with the event.getSource() but it doesn't seem to be working for my circles, at least not in the way I would expect. Thanks for any help.
Modify your Ball class so it creates a circle based on the center point and the radius, rather than the upper left point and the diameter.
Then, you can calculate if you click inside of a circle by applying the distance formula to the point you've clicked and the center point of each of the balls in turn.
The first ball where the distance is less than the radius is the ball you've clicked on. Or, if you want to be more sophisticated, the ball with the smallest distance less than the radius is the ball you've clicked on.
In this case, the event source is the JPanel, not the Ball. Try adding a System.out.println(event.getSource()); in your mouseDragged() method to see for yourself. As others have suggested, just calculate distance from the mouse pressed point to your circles. Basing it on center/radius will make the math easier.
You could modify you Ball class to take advantage of the Shape's API and use an Ellipse2D to maintain the coordinates of the circle and render it.
You could then simply use the Ellipse2D#contains to determine if the shape was clicked or not.
You will have to loop through the collection of balls checking each one until you either reach the end or find the ball you are looking for