In my program I'm adding a JPannel that I'm using as canvas to a JFrame. The problem is that the size of the JPannel#setSize() is not what it is displayed.
I first had a problem more or less like this one and I asked here
Java questions about coordinates with Graphics
They toldme to add the JPannel and use the pack() method and it worked but I can't get it to work now because the JFrame is larger than the JPannel where I am drawing.
I have look at some threads to see if I coud fiand an answer but I wasn't succesfull. Here are the threads I looked:
Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?
Why the JProgressBar doesn't respect the setSize()?
Component setSize method in FlowLayout object
setSize() not working?
setSize not influencing size of button
Why does the JFrame setSize() method not set the size correctly?
java.AWT - setSize() method
Java JPannel not Visible
I create a JFrame with setSize(600, 400); and I add the pane. Here is the code
import java.awt.Dimension;
import javax.swing.JFrame;
import me.nemo_64.particlessimulation.util.Canvas;
public class Frame extends JFrame {
private Frame(String title) {
super(title);
setResizable(false);
setSize(600, 400);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setLayout(null);
init();
}
private void init() {
canvas = new Canvas();
canvas.setSize(400, 400);
canvas.setLocation(0, 0);
canvas.setPreferredSize(new Dimension(400, 400));
canvas.startDrawing();
add(canvas);
}
private Canvas canvas;
public static Frame getInstance() {
if (instance == null)
throw new IllegalAccessError("instanceFrame must be callen at least ones before this method");
return instance;
}
public static Frame instanceFrame(String title) {
if (instance != null)
return getInstance();
instance = new Frame(title);
return getInstance();
}
private static Frame instance;
private static final long serialVersionUID = 1L;
}
And here is the canvas class
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
public class Canvas extends ACanvas {
Vector p, dir;
float v;
public Canvas() {
p = new Vector(0, getHeight() / 2);
dir = new Vector(50f, 50f);
v = 50;
}
#Override
public void update(float delta) {
if (p.x <= 0 && dir.x < 0)
dir.x *= -1;
if (p.y <= 0 && dir.y < 0)
dir.y *= -1;
if (p.x + 100 >= getWidth() && dir.x > 0)
dir.x *= -1;
if (p.y + 100 >= getWidth() && dir.y > 0)
dir.y *= -1;
Vector a = dir.clone().multiply(delta);
p.add(a);
}
#Override
public void mouseClicked(MouseEvent e) {
System.out.println(e.getX() + " " + e.getY());
}
#Override
public void draw(Graphics2D g) {
System.out.println(g);
fill(255, 0, 0);
fillRectangle(p.x, p.y, 100, 100);
}
}
And here is the ACanvas class
package me.nemo_64.particlessimulation.util;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.JPanel;
import javax.swing.event.MouseInputListener;
public abstract class ACanvas extends JPanel
implements KeyListener, MouseInputListener, MouseWheelListener, Drawable, Updateable, FigureDrawer {
private Graphics2D g;
private Thread drawThread;
private Color backgroundColor;
private Color actualColor;
private long lastTime = 0;
private float delta = 0;
public ACanvas() {
setFocusable(true);
addKeyListener(this);
addMouseListener(this);
addMouseWheelListener(this);
lastTime = System.currentTimeMillis();
drawThread = new Thread(() -> {
while (true)
repaint();
}, "Drawing thread");
}
#Override
/**
* Used to update all the components to be drawn
*/
public abstract void update(float delta);
/**
* Draws all the comsponents
*/
public void draw(Graphics2D g) {}
#Override
public Graphics2D getGraphics2D() {
return this.g;
}
#Override
/**
* Draws all the comsponents
*/
public void draw(Graphics g) {
this.g = (Graphics2D) g;
draw(this.g);
}
#Override
public void paint(Graphics g) {
super.paint(g);
delta = (System.currentTimeMillis() - lastTime) * 0.001f;
lastTime = System.currentTimeMillis();
clearBackground(g);
update(delta);
g.setColor(actualColor);
draw(g);
}
public void clearBackground(Graphics g) {
g.setColor(backgroundColor);
g.fillRect(0, 0, getWidth(), getHeight());
}
public void startDrawing() {
if (!drawThread.isAlive())
drawThread.start();
}
public void stopDrawing() {
if (drawThread.isAlive())
drawThread.interrupt();
}
public void background(Color c) {
backgroundColor = c;
}
public void background(int c) {
backgroundColor = new Color(c);
}
public void background(int r, int g, int b) {
backgroundColor = new Color(r, g, b);
}
public void background(float r, float g, float b) {
backgroundColor = new Color(r, g, b);
}
public void fill(Color c) {
actualColor = c;
g.setColor(c);
}
public void fill(int c) {
fill(new Color(c));
}
public void fill(float r, float g, float b) {
fill(new Color(r, g, b));
}
public void fill(int r, int g, int b) {
fill(new Color(r, g, b));
}
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseDragged(MouseEvent e) {}
#Override
public void mouseMoved(MouseEvent e) {}
#Override
public void mouseWheelMoved(MouseWheelEvent e) {}
#Override
public void keyPressed(KeyEvent e) {}
#Override
public void keyReleased(KeyEvent e) {}
#Override
public void keyTyped(KeyEvent e) {}
}
It doen't seem to work in the height. In the canvas class I have a mouse click event that displays the position of the click. The width I click and the output is a coordenade like (0, ?) for the left and (400, ?) for the right. The problem is the height, at the top I get (?, 0) but at the bottom the highest value I could get is (?, 370) and not (?, 400). My question is: why the bottom doesn't go to (?, 400)?
Thanks for the help, if something is not clear just ask it
EDIT:
Other clases of the program are:
A vector class:
public class Vector {
public float x, y, z;
public Vector(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
}
public Vector(float x, float y) {
this(x, y, 0);
}
public Vector() {
this(0f, 0f, 0f);
}
public Vector multiply(float... values) {
for (float f : values) {
x *= f;
y *= f;
z *= f;
}
return this;
}
public Vector divide(float... values) {
for (float f : values) {
x /= f;
y /= f;
z /= f;
}
return this;
}
public Vector add(Vector... vectors) {
for (Vector v : vectors)
add(v.x, v.y, v.z);
return this;
}
public Vector add(float x, float y, float z) {
this.x += x;
this.y += y;
this.z += z;
return this;
}
public Vector add(float x, float y) {
return add(x, y, 0);
}
public Vector remove(Vector... vectors) {
for (Vector v : vectors)
remove(v.x, v.y, v.z);
return this;
}
public Vector remove(float x, float y, float z) {
this.x -= x;
this.y -= y;
this.z -= z;
return this;
}
public Vector remove(float x, float y) {
return remove(x, y, 0);
}
public Vector normalize() {
double mod = module();
this.x /= mod;
this.y /= mod;
this.z /= mod;
return this;
}
public double module() {
return Math.sqrt(moduleSquared());
}
public double moduleSquared() {
return (double) (x * x + y * y + z * z);
}
public Vector clone() {
return new Vector(x, y, z);
}
#Override
public String toString() {
return "[ " + x + ", " + y + ", " + z + "]";
}
}
A drawing interface:
package me.nemo_64.particlessimulation.util;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
public interface FigureDrawer {
default public void fillRectangle(float x, float y, float width, float height) {
getGraphics2D().fill(new Rectangle2D.Float(x, y, width, height));
}
default public void drawRectangle(float x, float y, float width, float height) {
getGraphics2D().draw(new Rectangle2D.Float(x, y, width, height));
}
default public void fillCircle(float x, float y, float r) {
fillCircle(x, y, r, r);
}
default public void drawCircle(float x, float y, float r) {
drawCircle(x, y, r, r);
}
default public void fillCircle(float x, float y, float r1, float r2) {
getGraphics2D().fill(new Ellipse2D.Float(x, y, r1, r2));
}
default public void drawCircle(float x, float y, float r1, float r2) {
getGraphics2D().draw(new Ellipse2D.Float(x, y, r1, r2));
}
public Graphics2D getGraphics2D();
}
And the Drawable and Updateable interfaces:
package me.nemo_64.particlessimulation.util;
public interface Updateable {
/**
* Called when the component must be updated
* #param delta The seconds that past between calls
*/
public void update(float delta);
}
package me.nemo_64.particlessimulation.util;
import java.awt.Graphics;
public interface Drawable {
/**
* Called when the component must be drawn
*/
public void draw(Graphics g);
}
I see a lot of comments here (all valid) but the question you're asking is why the canvas is showing something like ~ 370 instead of 400 on the right hand side (if I read this correctly).
Your canvas is probably x,y(400,400) but the parent container is only 400px wide based on your initial call. There's a good chance your window border and other operating system specific elements are adding quite a few pixels (on Windows Vista/7 I seem to remember this was ~30px) so there's a really good chance that's the problem.
As a side note (having written some particle simulations in Java/Processing) I can say you might want to look at the OpenGL style of positioning using floats from -1.0f/1.0f representing your canvas rather than trying to do everything in integers. First, you're going to have a lot of choppy "bounces" working with integers as you're going to want to represent values between pixels. Secondly, this makes resizing your simulation to an arbitrary x/y width very easy.
https://learnopengl.com/Getting-started/Coordinate-Systems
Good luck with it, I stopped writing GUIs in Java five or six years ago so I'm a bit rusty. Null layouts can be problematic, I would recommend picking a layout manager and using setPreferredSize (GridBag was my favorite).
Related
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
I have been working on a simple animation using a Timer on a JComponent. However, I experience incredibly choppy motion when I view the animation. What steps should I take to optimize this code?
MyAnimationFrame
import javax.swing.*;
public class MyAnimationFrame extends JFrame {
public MyAnimationFrame() {
super("My animation frame!");
setSize(300,300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(new AnimationComponent(0,0,50,50));
setVisible(true);
}
public static void main(String[] args) {
MyAnimationFrame f = new MyAnimationFrame();
}
}
AnimationComponent
import javax.swing.*;
import java.awt.*;
public class AnimationComponent extends JComponent implements ActionListener {
private Timer animTimer;
private int x;
private int y;
private int xVel;
private int yVel;
private int width;
private int height;
private int oldX;
private int oldY;
public AnimationComponent(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
animTimer = new Timer(25, this);
xVel = 5;
yVel = 5;
animTimer.start();
}
#Override
public void paintComponent(Graphics g) {
g.fillOval(x,y,width,height);
}
#Override
public void actionPerformed(ActionEvent e) {
oldX = x;
oldY = y;
if(x + width > getParent().getWidth() || x < 0) {
xVel *= -1;
}
if(y + height > getParent().getHeight() || y < 0) {
yVel *= -1;
}
x += xVel;
y += yVel;
repaint();
}
}
Not sure if this matters, but I am using OpenJDK version 1.8.0_121.
Any help is appreciated.
After a wonderful discussion with Yago it occurred to me that the issue revolves around number of areas, alot comes down to the ability for Java to sync the updates with the OS and the hardware, some things you can control, some you can't.
Inspired by Yago's example and my "memory" of how the Timing Framework works, I tested you code by increasing the framerate (to 5 milliseconds, ~= 200fps) and decreasing the change delta, which gave the same results as using the Timing Framework, but which leaves you with the flexibility of your original design.
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Test");
frame.add(new AnimationComponent(0, 0, 50, 50));
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class AnimationComponent extends JComponent implements ActionListener {
private Timer animTimer;
private int x;
private int y;
private int xVel;
private int yVel;
private int width;
private int height;
private int oldX;
private int oldY;
public AnimationComponent(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
animTimer = new Timer(5, this);
xVel = 1;
yVel = 1;
animTimer.start();
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
RenderingHints hints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON
);
g2d.setRenderingHints(hints);
g2d.fillOval(x, y, width, height);
}
#Override
public void actionPerformed(ActionEvent e) {
oldX = x;
oldY = y;
if (x + width > getParent().getWidth() || x < 0) {
xVel *= -1;
}
if (y + height > getParent().getHeight() || y < 0) {
yVel *= -1;
}
x += xVel;
y += yVel;
repaint();
}
}
}
If you need to slow down the speed more, then decrease the change delta more, this will mean you have to use doubles instead, which will lead into the Shape's API which supports double values
Which should you use? That's up to you. The Timing Framework is really great for linear animations over a period of time, where you know you want to go from one state to another. It's not so good for things like games, where the state of the object can change from my cycle to another. I'm sure you could do it, but it'd be a lot easier with a simple "main loop" concept - IMHO
Timing Framework offers a way to provide animations highly optimized which may help in this case.
MyAnimationFrame
import javax.swing.*;
public class MyAnimationFrame extends JFrame {
public MyAnimationFrame() {
super("My animation frame!");
setSize(300,300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(new AnimationComponent(0,0,50,50));
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
MyAnimationFrame f = new MyAnimationFrame();
}
});
}
}
AnimationComponent
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.TimeUnit;
import org.jdesktop.core.animation.rendering.*;
import org.jdesktop.core.animation.timing.*;
import org.jdesktop.core.animation.timing.interpolators.*;
import org.jdesktop.swing.animation.rendering.*;
import org.jdesktop.swing.animation.timing.sources.*;
#SuppressWarnings("serial")
public class AnimationComponent extends JRendererPanel {
protected int x;
protected int y;
protected int width;
protected int height;
protected Animator xAnimator;
protected Animator yAnimator;
public AnimationComponent(int x, int y, int width, int height) {
setOpaque(true);
this.x = x;
this.y = y;
this.height = height;
this.width = width;
JRendererFactory.getDefaultRenderer(this,
new JRendererTarget<GraphicsConfiguration, Graphics2D>() {
#Override
public void renderSetup(GraphicsConfiguration gc) {
// Nothing to do
}
#Override
public void renderUpdate() {
// Nothing to do
}
#Override
public void render(Graphics2D g, int w, int h) {
Color c = g.getColor();
g.setColor(g.getBackground());
g.fillRect(0, 0, w, h);
g.setColor(c);
g.fillOval(AnimationComponent.this.x, AnimationComponent.this.y,
AnimationComponent.this.width, AnimationComponent.this.height);
}
#Override
public void renderShutdown() {
// Nothing to do
}
}, false);
this.xAnimator = new Animator.Builder(new SwingTimerTimingSource())
.addTargets(new TimingTargetAdapter() {
#Override
public void timingEvent(Animator source, double fraction) {
AnimationComponent.this.x = (int) ((getWidth() - AnimationComponent.this.width) * fraction);
}})
.setRepeatCount(Animator.INFINITE)
.setRepeatBehavior(Animator.RepeatBehavior.REVERSE)
.setInterpolator(LinearInterpolator.getInstance()).build();
this.yAnimator = new Animator.Builder(new SwingTimerTimingSource())
.addTargets(new TimingTargetAdapter() {
#Override
public void timingEvent(Animator source, double fraction) {
AnimationComponent.this.y = (int) ((getHeight() - AnimationComponent.this.height) * fraction);
}})
.setRepeatCount(Animator.INFINITE)
.setRepeatBehavior(Animator.RepeatBehavior.REVERSE)
.setInterpolator(LinearInterpolator.getInstance()).build();
addComponentListener(new ComponentAdapter() {
private int oldWidth = 0;
private int oldHeight = 0;
#Override
public void componentResized(ComponentEvent event) {
Component c = event.getComponent();
int w = c.getWidth();
int h = c.getHeight();
if (w != this.oldWidth) {
AnimationComponent.this.xAnimator.stop();
AnimationComponent.this.xAnimator = new Animator.Builder()
.copy(AnimationComponent.this.xAnimator)
.setDuration(w * 5, TimeUnit.MILLISECONDS) // Original speed was 200 px/s
.build();
AnimationComponent.this.xAnimator.start();
}
if (h != this.oldHeight) {
AnimationComponent.this.yAnimator.stop();
AnimationComponent.this.yAnimator = new Animator.Builder()
.copy(AnimationComponent.this.yAnimator)
.setDuration(h * 5, TimeUnit.MILLISECONDS) // Original speed was 200 px/s
.build();
AnimationComponent.this.yAnimator.start();
}
this.oldWidth = w;
this.oldHeight = h;
}
});
}
}
I'm getting good results but has one issue: any item you resize, the animation is reset.
I'm working on a game project. The aim is clicking the balls and drop them into the basket which is below in the JPanel. I created some ways to do that but I can't achieve it. In my opinion, when the user click the ball's center with a margin of error (because the program can't catch the real point of balls), the program understands the action and runs the function of this issue. After clicking the ball it should be dropped down straight but the other balls should be continued. I use MouseListener#mousePressed method, but it doesn't work or I'm missing some parts. In addition, I made some changes in my source code, but I want to listen your advices so I am writing this topic.
I wrote a method which finds the mouse and ball coordinates. So when I make some changes to it, I can achieve my project goal. You can see the editing in the picture.
This is my source code ;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Game {
public Game() {
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("MultipleBallApp");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new BallControl());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class BallControl extends JPanel {
/**
*
*/
private static final long serialVersionUID = 1L;
private BallPanel ballPanel = new BallPanel();
private JButton Suspend = new JButton("Suspend");
private JButton Resume = new JButton("Resume");
private JButton Add = new JButton("+1");
private JButton Subtract = new JButton("-1");
private JScrollBar Delay = new JScrollBar();
public BallControl() {
// Group buttons in a panel
JPanel panel = new JPanel();
panel.add(Suspend);
panel.add(Resume);
panel.add(Add);
panel.add(Subtract);
// Add ball and buttons to the panel
ballPanel.setBorder(new javax.swing.border.LineBorder(Color.red));
Delay.setOrientation(JScrollBar.HORIZONTAL);
ballPanel.setDelay(Delay.getMaximum());
setLayout(new BorderLayout());
add(Delay, BorderLayout.NORTH);
add(ballPanel, BorderLayout.CENTER);
add(panel, BorderLayout.SOUTH);
this.addMouseListener(new MouseListener() {
#Override
public void mouseReleased(MouseEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void mousePressed(MouseEvent clickEvent) {
// TODO Auto-generated method stub
System.out.println("X coordinate =" + clickEvent.getX());
System.out.println("Y coordinate = " + clickEvent.getY());
double radius1;
int x = 0;
double y = 0;
int radius = 15;
double xM = clickEvent.getX();
double yM = clickEvent.getY();
radius1 = Math.sqrt((xM - x) * (xM - x) + (yM - y)
* (yM - y));
System.out.println("Radius1 =" + radius1);
// ballPanel.list.get(0).setD
}
#Override
public void mouseExited(MouseEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void mouseEntered(MouseEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void mouseClicked(MouseEvent arg0) {
// TODO Auto-generated method stub
}
});
// Register listeners
Suspend.addActionListener(new Listener());
Resume.addActionListener(new Listener());
Add.addActionListener(new Listener());
Subtract.addActionListener(new Listener());
Delay.addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent e) {
ballPanel.setDelay(Delay.getMaximum() - e.getValue());
}
});
}
class Listener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (e.getSource() == Suspend) {
ballPanel.suspend();
} else if (e.getSource() == Resume) {
ballPanel.resume();
} else if (e.getSource() == Add) {
ballPanel.add();
} else if (e.getSource() == Subtract) {
ballPanel.subtract();
}
}
}
}
class BallPanel extends JPanel {
/**
*
*/
private static final long serialVersionUID = 1L;
private int delay = 30;
public ArrayList<AnimatedShape> list = new ArrayList<AnimatedShape>();
private AnimatedRectange rectangle;
public BallPanel() {
this.rectangle = new AnimatedRectange(-25, 373, 50, 25, Color.BLACK);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
// Create a timer with the initial delay
protected Timer timer = new Timer(delay, new ActionListener() {
/**
* Handle the action event
*/
#Override
public void actionPerformed(ActionEvent e) {
for (AnimatedShape ball : list) {
ball.update(getBounds());
}
rectangle.update(getBounds());
repaint();
}
});
public void add() {
int radius = 15;
// Randomised position
int x = (int) (Math.random() * (getWidth() - (radius * 2)))
+ radius;
int y = (int) (Math.random() * (getHeight() - (radius * 2)))
+ radius;
Color color = new Color((int) (Math.random() * 256),
(int) (Math.random() * 256), (int) (Math.random() * 256));
AnimatedBall ball = new AnimatedBall(x, y, radius, color);
list.add(ball);
}
// public void formula(MouseEvent clickEvent) {
// double radius1;
// int x = 0;
// double y = 0;
// int radius = 15;
// double xM = clickEvent.getX();
// double yM = clickEvent.getY();
// radius1 = Math.sqrt((xM - x) * (xM - x) + (yM - y) * (yM - y));
// System.out.println("Radius1 =" + radius1);
// }
public void subtract() {
if (list.size() > 0) {
list.remove(list.size() - 1); // Remove the last ball
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (AnimatedShape ball : list) {
ball.paint(this, g2d);
}
rectangle.paint(this, g2d);
}
public void suspend() {
timer.stop();
}
public void resume() {
timer.start();
}
public void setDelay(int delay) {
this.delay = delay;
timer.setDelay(delay);
}
}
public interface AnimatedShape {
public void update(Rectangle bounds);
public void paint(JComponent parent, Graphics2D g2d);
}
public abstract class AbstractAnimatedShape implements AnimatedShape {
private Rectangle bounds;
private int dx, dy;
public AbstractAnimatedShape() {
}
public void setBounds(Rectangle bounds) {
this.bounds = bounds;
}
public Rectangle getBounds() {
return bounds;
}
public int getDx() {
return dx;
}
public int getDy() {
return dy;
}
public void setDx(int dx) {
this.dx = dx;
}
public void setDy(int dy) {
this.dy = dy;
}
#Override
public void update(Rectangle parentBounds) {// ball
Rectangle bounds = getBounds();
int dx = getDx();
int dy = getDy();
bounds.x += dx;
bounds.y += dy;
if (bounds.x < parentBounds.x) {
bounds.x = parentBounds.x;
setDx(dx *= -1);
} else if (bounds.x + bounds.width > parentBounds.x
+ parentBounds.width) {
bounds.x = parentBounds.x + (parentBounds.width - bounds.width);
setDx(dx *= -1);
}
if (bounds.y < parentBounds.y) {
bounds.y = parentBounds.y;
setDy(dy *= -1);
} else if (bounds.y + bounds.height > parentBounds.y
+ parentBounds.height) {
bounds.y = parentBounds.y
+ (parentBounds.height - bounds.height);
setDy(dy *= -1);
}
}
}
public class AnimatedBall extends AbstractAnimatedShape {
private Color color;
public AnimatedBall(int x, int y, int radius, Color color) {
setBounds(new Rectangle(x, y / 2, radius * 2, radius * 2));
this.color = color;
setDx(Math.random() > 0.5 ? 2 : -2);
// setDy(Math.random() > 0.5 ? 2 : -2);
}
public Color getColor() {
return color;
}
#Override
public void paint(JComponent parent, Graphics2D g2d) {
Rectangle bounds = getBounds();
g2d.setColor(getColor());
g2d.fillOval(bounds.x, bounds.y, bounds.width, bounds.height);
}
}
public class AnimatedRectange extends AbstractAnimatedShape {
private Color color;
public AnimatedRectange(int x, int y, int width, int height, Color color) {
setBounds(new Rectangle(x, y, width, height));
this.color = color;
setDx(2);
}
// Don't want to adjust the vertical speed
#Override
public void setDy(int dy) {
}
#Override
public void paint(JComponent parent, Graphics2D g2d) {
Rectangle bounds = getBounds();
g2d.setColor(color);
g2d.fill(bounds);
}
}
/**
* Main method
*/
public static void main(String[] args) {
new Game();
}
}
At the end of your mousePressed method, you can go though all AnimatedShape objects, check whether they are an AnimatedBall. When the object is a ball, you can test whether the mouse click hits the ball. The mouse click hits the ball when the distance between the center of the ball and the mouse position is smaller than the ball radius. When the ball is hit, you can set its horizontal speed to 0, and the vertical speed to 5 or so.
for (AnimatedShape as : ballPanel.list)
{
if (as instanceof AnimatedBall)
{
AnimatedBall ball = (AnimatedBall)as;
Rectangle b = ball.getBounds();
int ballCenterX = b.x + b.width / 2;
int ballCenterY = b.y + b.height / 2;
Point p = new Point(ballCenterX, ballCenterY);
double d = p.distance(clickEvent.getPoint());
if (d < radius)
{
ball.setDx(0);
ball.setDy(5);
}
}
}
Note
In order to make this work properly, you have to attach this listener to the ballPanel. Originally, you had the line
this.addMouseListener(new MouseListener() {
in your code. You have to change this to
ballPanel.addMouseListener(new MouseListener() {
Otherwise, the mouse coordinates will refer to the wrong component!
Concerning the question from the comment
how can I stop and disapper clickedball's when they crash the bound
You may probably insert method to check this, and call this method in the actionPerformed method of your timer. Further explainations are probably beyond the scope of an anser on a Q&A site. Stackoverflow is not a homework-solution-generator.
here is the entire code for the classes Ship,Asteroids,BaseShapeClass. Ship Class inherits from the BaseShapeClass for its shape. Asteroid class is the main source code which declares the Graphics2D object,AffineTransform(for identity creation),declares double image buffer...
Code for BaseShapeClass..
package baseshapeclass;
import java.awt.Shape;
public class BaseShapeClass {
private Shape shape;
private double x, y;
private double velX, velY;
private double moveAngle, faceAngle;
private boolean alive;
//accessors and mutators
public Shape getShape(){return shape;}
public void setShape(Shape shape){ this.shape = shape; }
public double getX() { return x; }
public void setX(double x) { this.x = x; }
public void incX(double ix) { this.x += ix; }
public double getY() { return y; }
public void setY(double y) { this.y = y; }
public void incY(double iy) { this.y += iy; }
public double getVelX() { return velX; }
public void setVelX(double velX) { this.velX = velX; }
public void incVelX(double ivX) { this.velX += ivX; }
public double getVelY() { return velY; }
public void setVelY(double velY) { this.velY = velY; }
public void incVelY(double ivY) { this.velY += ivY; }
//MoveAngle refers to the objects angular movement
public double getMoveAngle() { return moveAngle; }
public void setMoveAngle(double mAngle) { this.moveAngle = mAngle; }
public void incMoveAngle(double imAngle) { this.moveAngle += imAngle; }
//FaceAngle refers to the objects face/heads angular movement
public double getFaceAngle() { return faceAngle; }
public void setFaceAngle(double fAngle) { this.faceAngle = fAngle; }
public void incFaceAngle(double ifAngle) { this.faceAngle += ifAngle; }
public boolean isAlive() { return alive; }
public void setAlive(boolean alive) { this.alive = alive; }
//default constructor everything will be set to original state
//when update is called everything will start to move
BaseShapeClass(){
setShape(null);
setAlive(false);
//all of them are set to '0' representing their initial position,
//which will be called during the update() Event of the graphics objects
setX(0.0);
setY(0.0);
setVelX(0.0);
setVelY(0.0);
setMoveAngle(0.0);
setFaceAngle(0.0);
}
}
Code for Ship class...
package baseshapeclass;
import java.awt.Rectangle;
import java.awt.Polygon;
public class Ship extends BaseShapeClass {
//ships shape along the x and y cordinates
private final int[] shipx = {-6,3,0,3,6,0};
private final int[] shipy = {6,7,7,7,6,-7};
public Rectangle getBounds(){
Rectangle r = new Rectangle((int)getX()-6, (int)getY()-6, 12, 12);
return r;
}
Ship(){
setShape(new Polygon(shipx, shipy, shipx.length));
setAlive(true);
}
}
Code for Asteroid(Main source code)...
package baseshapeclass;
import java.awt.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.applet.*;
import java.util.*;
public abstract class Asteroid extends Applet implements Runnable, KeyListener {
BufferedImage backbuffer;
Graphics2D g2d;
Ship ship = new Ship();
boolean showBounds= true;
AffineTransform identity = new AffineTransform();
#Override public void init(){
backbuffer = new BufferedImage(640,480,BufferedImage.TYPE_INT_RGB);
g2d = backbuffer.createGraphics();
ship.setX(320);
ship.setY(240);
addKeyListener(this);
}
#Override public void update(Graphics g){
g2d.setTransform(identity);
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getSize().width, getSize().height);
g2d.setColor(Color.WHITE);
g2d.drawString("Ship: "+Math.round(ship.getX())+" , "+Math.round(ship.getY()),2, 150);
g2d.drawString("Face Angle: "+Math.toRadians(ship.getFaceAngle()),5, 30);
g2d.drawString("Move Angle: "+Math.toRadians(ship.getMoveAngle())+90,5,50);
drawShip();
paint(g);
}
public void drawShip(){
g2d.setTransform(identity);
g2d.translate(ship.getX(),ship.getY());
g2d.rotate(Math.toRadians(ship.getFaceAngle()));
g2d.setColor(Color.ORANGE);
g2d.fill(ship.getShape());
}
}
I hope you guys get a better idea with all the code in place. Just wanted to know on the part of Ship class why are the ships x and y cordinates such as under:
public class ship extends BaseShapeClass{
private int[] shipx = {-6,3,0,3,6,0};
private int[] shipy = {6,7,7,7,6,-7};
}
I cant follow on how those values will make upto a Polygon??
Ship(){
setShape(new Polygon(shipx,shipy,shipx.length));
setAlive(true);
}
You can see that the two arrays you are confused about go into the initialization of a Polygon. These two arrays, taken as a pair, give the x and y coordinates of each point in the Polygon.
This post is in answer to your comment in Kronion's answer; I was going to post it as a comment, but there is too much to say and I wanted to show you some code, which is not as legible in the comments.
As Kronion said, the Polygon class does indeed accept an array of X coordinates, and an array of Y coordinates. The reason for this is that the X and Y coordinate are stored at the same position in both arrays. So if int index = 0, then that X,Y coordinate pair would be xArray[index] and yArray[index].
If that doesn't make any sense, examine the Polygon class source code. For example, you'll see this happening in the contains method, here:
for (int i = 0; i < npoints; lastx = curx, lasty = cury, i++) {
curx = xpoints[i];
cury = ypoints[i];
// remainder of loop
}
So in short, they are assigned in this manner because the X and Y are paired by their index positions.
Hope that helps.
Here i have a code which draws a rectangle on the mouseClicked position using the paintComponent.I can get the output message but anything related to graphics and .draw() wont work.
Code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public final class testclass extends JFrame {
static JPanel p;
Timer t;
int x = 1;
int y = 1;
int xspeed = 1;
int yspeed = 1;
public testclass() {
initComponents();
this.setBounds(100, 300, 500, 500);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
t.start();
this.add(p);
}
public void initComponents() {
final ActionListener action = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
System.out.println("Hello!");
p.repaint();
}
};
t = new Timer(50, action);
p = new JPanel() {
public void paintComponent(Graphics g) {
super.paintComponent(g);
final Graphics2D gD = (Graphics2D) g;
moveBALL();
gD.drawOval(x, y, 25, 25);
p.addMouseListener(new MouseListener() {
#Override
public void mouseReleased(MouseEvent e) {
System.out.println("a");
}
#Override
public void mousePressed(MouseEvent e) {
System.out.println("b");
}
#Override
public void mouseExited(MouseEvent e) {
System.out.println("c");
}
#Override
public void mouseEntered(MouseEvent e) {
System.out.println("d");
}
#Override
public void mouseClicked(MouseEvent e) {
gD.drawRect(e.getX(), e.getY(), 10, 60);
gD.setColor(Color.green);
System.out.println("clicked");
}
});
}
void moveBALL() {
x = x + xspeed;
y = y + yspeed;
if (x < 0) {
x = 0;
xspeed = -xspeed;
} else if (x > p.getWidth() - 20) {
x = p.getWidth() - 20;
xspeed = -xspeed;
}
if (y < 0) {
y = 0;
yspeed = -yspeed;
} else if (y > p.getHeight() - 20) {
y = p.getHeight() - 20;
yspeed = -yspeed;
}
}
};
}
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new testclass().setVisible(true);
p.setBackground(Color.WHITE);
}
});
}
}
What is the proper way to implement a mouseListener() in this program?
Thanks.
Some suggestions on current code:
Watch class naming scheme i.e testclass should be TestClass or even better Test (but thats nit picking). All class names begin with capital letter and each new word thereafter is capitalized.
Dont extend JFrame unnecessarily.
Dont call setBounds on JFrame rather use appropriate LayoutManager and/or override getPreferredSize() of JPanel and return dimensions which fits its content.
Always call pack() on JFrame before setting it visible (taking above into consideration).
Use MouseAdapter vs MouseListener
Dont call moveBall() in paintComponent rather call it in your Timer which repaints the screen, not only slightly better design but we also should not do possibly long running tasks in paint methods.
As for your problem I think your logic is a bit skewed.
One approach would see the Rectangle (or Rectangle2D) get replaced by its own custom class (which will allow us to store attributes like color etc). Your ball would also have its own class which has the method moveBall() and its attributes like x and y position etc. On every repaint() your JPanel would call the method to move the ball, the JPanel itself could wrap the moveBall() in its own public method which we could than call from the timer which repaints the screen.
Here is an example of your code with above fixes implemented (please analyze it and if you have any questions let me know):
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import javax.swing.*;
public class Test {
private MyPanel p;
private Timer t;
public Test() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initComponents();
frame.add(p);
frame.pack();
frame.setVisible(true);
t.start();
}
private void initComponents() {
final ActionListener action = new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
p.moveEntities();//moves ball etc
p.repaint();
}
};
t = new Timer(50, action);
p = new MyPanel();
p.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
p.addEntity(e.getX(), e.getY(), 10, 50, Color.GREEN);
System.out.println("clicked");
}
});
p.setBackground(Color.WHITE);
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Test();
}
});
}
}
class MyPanel extends JPanel {
int width = 300, height = 300;
ArrayList<MyRectangle> entities = new ArrayList<>();
MyBall ball = new MyBall(10, 10, 25, 25, Color.RED, width, height);
void addEntity(int x, int y, int w, int h, Color c) {
entities.add(new MyRectangle(x, y, w, h, c));
}
void moveEntities() {
ball.moveBALL();
}
#Override
protected void paintComponent(Graphics grphcs) {
super.paintComponent(grphcs);
Graphics2D g2d = (Graphics2D) grphcs;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(ball.getColor());
g2d.fillOval((int) ball.x, (int) ball.y, (int) ball.width, (int) ball.height);
for (MyRectangle entity : entities) {
g2d.setColor(entity.getColor());
g2d.fillRect((int) entity.x, (int) entity.y, (int) entity.width, (int) entity.height);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(width, height);
}
}
class MyRectangle extends Rectangle2D.Double {
Color color;
public MyRectangle(double x, double y, double w, double h, Color c) {
super(x, y, w, h);
color = c;
}
public void setColor(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
}
class MyBall extends Ellipse2D.Double {
int xspeed = 1;
int yspeed = 1;
Color color;
private final int maxWidth;
private final int maxHeight;
public MyBall(double x, double y, double w, double h, Color c, int maxWidth, int maxHeight) {
super(x, y, w, h);
color = c;
this.width = w;//set width and height of Rectangle2D
this.height = h;
//set max width and height ball can move
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
}
public void setColor(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
void moveBALL() {
x = x + xspeed;
y = y + yspeed;
if (x < 0) {
x = 0;
xspeed = -xspeed;
} else if (x > maxWidth - ((int) getWidth() / 2)) {// i dont like hard coding values its not good oractice and resuaibilty is diminshed
x = maxWidth - ((int) getWidth() / 2);
xspeed = -xspeed;
}
if (y < 0) {
y = 0;
yspeed = -yspeed;
} else if (y > maxHeight - ((int) getHeight() / 2)) {
y = maxHeight - ((int) getHeight() / 2);
yspeed = -yspeed;
}
}
}
First of all the paint component is called every time swing needs to redraw the component.
And you are adding a new instance of mouse listener to the panel every time the paint is called.
Just move the line
p.addMouseListener(new MouseListener() {...}
out of the paint component, preferably after the initialization of the panel.
default template is
JPanel p = new JPanel(){
#Override
public void paintComponent(Graphics g) {
}
};
p.addMouseListener(new MouseListener() or new MouseAdapter()
//Your overridden methods
});
Hope this helps.