Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
I draw a (co)sinusoid signal in the time domain using a JPanel and overriding its paintComponent(Graphics g).
When i do a resize my application starts flickering and appears a black background until the resizing is not finished.
Is there a way to prevent this sort of flickering?
Thanks in advance.
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Dimension;
public class AnotherSignalGraph extends JPanel {
private double offset = 0.0;
private double scaleX = 0.0;
private double scaleY = 0.0;
protected double maxX = 0.0;
private double minX = 0.0;
private double maxY = 0.0;
private double minY = 0.0;
private Point[] points = null;
private Signal s = null;
private void initPoints(int pointsToGenerate) {
this.points = new Point[pointsToGenerate];
double t = 0;
double dt = this.maxX / pointsToGenerate;
for (int i=0; i<pointsToGenerate; i++, t += dt)
points[i] = new Point(t, this.s.v(t));
}
public AnotherSignalGraph(int pointsToGenerate, int width, int height) {
super.setPreferredSize(new Dimension(width, height));
this.s = new Sinusoid(1000, 10, 0);
this.maxX = (1 / this.s.frequency) * 2;
this.maxY = this.s.amplitude;
this.minY = -this.maxY;
this.offset = super.getHeight() / 2.0;
this.initPoints(pointsToGenerate);
this.updateScale();
}
private void updateScale() {
this.scaleX = super.getWidth() / (this.maxX - this.minX);
this.scaleY = super.getHeight() / (this.maxY - this.minY);
this.offset = super.getHeight() / 2.0;
}
private void drawSignal(Graphics2D g2D) {
int x1, x2, y1, y2;
g2D.setColor(Color.blue);
g2D.setClip(0, (int)(this.offset - (this.s.amplitude * this.scaleY)), (int)(super.getWidth()), (int)((this.s.amplitude * 2 * this.scaleY) + 2));
for (int i=0; i<this.points.length - 1; i++) {
x1 = (int)(this.scaleX * this.points[i].getX());
y1 = (int)(this.offset - (this.scaleY * this.points[i].getY()));
x2 = (int)(scaleX * this.points[i + 1].getX());
y2 = (int)(this.offset - (this.scaleY * this.points[i + 1].getY()));
g2D.drawLine(x1, y1, x2, y2);
}
}
private void graphIt(Graphics2D g2D) {
this.updateScale();
this.drawSignal(g2D);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2D = (Graphics2D) g;
this.graphIt(g2D);
}
private class Point {
private double x;
private double y;
public Point(double x, double y){
this.x = x;
this.y = y;
}
public double getX(){
return this.x;
}
public double getY(){
return this.y;
}
}
private abstract class Signal {
double frequency; //Signal frequency in Hz
double amplitude; //Signal amplitude in Volt
double initPhase; //Signal initial phase in radians
public Signal(double frequency, double amplitude, double degInitPhase) throws IllegalArgumentException{ //degInitPhase parameter specifies the initial angle phase in degrees
this.frequency = frequency;
this.amplitude = amplitude;
this.initPhase = degInitPhase;
}
public abstract double v(double t);
}
private class Sinusoid extends Signal {
public Sinusoid(double frequency, double amplitude, double degInitPhase){ //degInitPhase parameter specifies the initial angle phase in degrees
super(frequency, amplitude, degInitPhase);
}
#Override
public double v(double t){ //t parameter indicates the specific time frame in seconds
return super.amplitude * Math.sin((super.frequency * Math.PI * t) + super.initPhase);
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.add(new AnotherSignalGraph(50000, 600, 600));
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Related
I am trying to make a program where object1 movement is warped by the gravity of object2. It should get flung. However, for some reason the velocity never decreases or goes negative. I expect for the velocity to change in both directions, but this does not happen. Can anyone tell me why? Here is the code.
Sim.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Sim extends JPanel {
private static final long serialVersionUID = -2669101810074157675L;
public static final int PREF_W = 800, PREF_H = 600;
private Mass object1, object2;
private Sim() {
this.setFocusable(true);
this.setBackground(Color.WHITE);
double[] vect1 = {1, 0}, vect2 = {0, 0};
object1 = new Mass(new Point(PREF_W / 2 - 100, PREF_H / 2 - 100), vect1, 10);
object2 = new Mass(new Point(PREF_W / 2 + 100, PREF_H / 2 + 100), vect2, 30);
object2.lock();
gameTimer.start();
}
private Timer gameTimer = new Timer(1000 / 30, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
double[] v = Mass.vector(object1, object2);
object1.dx += v[0];
object1.dy += v[1];
Point p = new Point(
(int) (object1.center.x + object1.dx),
(int) (object1.center.y + object1.dy)
);
object1.center = p;
System.out.println("[" + object1.center.x + "," + object1.dy + "]");
}
});
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.fillOval(
(int) object1.center.x - (int) object1.radius,
(int) object1.center.y - (int) object1.radius,
(int) object1.radius,
(int) object1.radius
);
g2.fillOval(
(int) object2.center.x - (int) object2.radius,
(int) object2.center.y - (int) object2.radius,
(int) object2.radius,
(int) object2.radius
);
g2.drawLine(object1.center.x, object1.center.y, object2.center.x, object2.center.y);
repaint();
}
/* METHODS FOR CREATING JFRAME AND JPANEL */
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Gravity Simulation");
JPanel gamePanel = new Sim();
frame.getContentPane().add(gamePanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
Mass.java
import java.awt.Point;
public class Mass {
public static double G = 1f;
public static double deltaTime = 1;
public Point center;
public double mass;
public double radius;
public double dx = 0;
public double dy = 0;
public boolean locked = false;
public Mass(Point center, double[] vect, double mass) {
this.center = center;
this.dx = vect[0];
this.dy = vect[1];
this.mass = mass;
this.radius = mass;
}
public void lock() {
this.locked = true;
}
public void unlock() {
this.locked = false;
}
public static double distance(Mass obj1, Mass obj2) {
double dX = obj1.center.x - obj2.center.x;
double dY = obj1.center.y - obj2.center.y;
double ans = Math.sqrt(Math.pow(dX, 2) + Math.pow(dY, 2));
return (double) ans;
}
public static double force(Mass obj1, Mass obj2) {
double ans = ((obj1.mass * obj2.mass) / Math.pow(distance(obj1, obj2), 2)) * G;
return (double) ans;
}
public static double[] vector(Mass obj1, Mass obj2) {
double force = force(obj1, obj2);
double dX = Math.abs(obj1.center.x - obj2.center.x);
double dY = Math.abs(obj1.center.y - obj2.center.y);
double udX = dX / distance(obj1, obj2);
double udY = dY / distance(obj1, obj2);
double fx = -(udX * force);
double fy = -(udY * force);
double x = obj1.dx + fx / obj1.mass * deltaTime;
double y = obj1.dy + fy / obj1.mass * deltaTime;
double[] v = {x, y};
return v;
}
}
Thank you in advance. I derived my formula from F = G(m1*m2)/r^2.
So I want to make a number line class that I can use to display single points along a single axis, but I want it to respond to the size of the container it's in at the moment and to change its size relative to that. Unfortunately, I'm unable to use getWidth() and getHeight() correctly to get the number line I want. This is the code I have written so far:
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
public class NumberLine extends JPanel {
private int value;
private Color green1 = new Color(32, 77, 2);
#Override
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
int maxXValue = getWidth();
int maxYValue = getHeight();
Line2D.Float xline = new Line2D.Float((float) maxXValue/6, (float) maxYValue/2, (float) maxXValue * (5/6), (float) maxYValue/2);
Line2D.Float yline = new Line2D.Float( (float) maxXValue/ 2, (float) maxYValue * (9/20), (float) maxXValue/2, (float) maxYValue *(11/20));
g2.draw(xline);
g2.draw(yline);
Ellipse2D.Float cir = new Ellipse2D.Float((float) (maxXValue/10 + (8 * value/1000) * (maxXValue)), (float) (maxYValue/2), 10F, 10F );
g2.setColor(green1);
g2.fill(cir);
}
public NumberLine(int val0) {
value = val0;
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(150,100);
NumberLine num = new NumberLine(5);
frame.setContentPane(num);
frame.setVisible(true);
}
}
Ideally, I would like something such that if I were to do
NumberLine num = new NumberLine(5);
I would get something that looks like:
Instead, I'm getting:
I think that your problem is one of basic geometry. If you're trying to center the circle within the line, then you need to subtract half its width and height from its location. That's it:
Ellipse2D.Float cir = new Ellipse2D.Float(
(float) (maxXValue / 10 + (8 * value / 1000) * (maxXValue)) - 5,
(float) (maxYValue / 2) - 5, 10F, 10F);
Also you're doing int division and that is returning 0 values where you don't want them. Change
Line2D.Float yline = new Line2D.Float((float) maxXValue / 2, (float) maxYValue * (9 / 20),
(float) maxXValue / 2, (float) maxYValue * (11 / 20));
to
Line2D.Float yline = new Line2D.Float((float) maxXValue / 2, (float) maxYValue * (9f / 20f),
(float) maxXValue / 2f, (float) maxYValue * (11f / 20f));
Unrelated issues:
Don't forget to call the super's paintComponent method:
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g); // !! don't forget this!
And avoid "magic" numbers in your program as they make debugging a bear.
Use RenderingHints to smooth out your Graphics2D drawing:
// rendering hints to smooth out your drawing
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Start your Swing GUI on the EDT:
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
For example, something like:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import javax.swing.JPanel;
public #SuppressWarnings("serial")
class NumberLine3 extends JPanel {
private static final double X_GAP = 1.0 / 20.0;
private static final double MAJOR_TIC_HT = 0.4;
private static final int PREF_W = 600;
private static final int PREF_H = 50;
private static final Stroke MAIN_STROKE = new BasicStroke(5f);
private static final Stroke MAJOR_TIC_STOKE = new BasicStroke(3f);
private static final int CIRCLE_WIDTH = 20;
private static final Color VALUE_COLOR = new Color(32, 230, 2);
private int maxX;
private int majorTickCount;
private int minorTicksPerMajor;
private double value;
public NumberLine3(int maxX, int majorTickCount, int minorTicksPerMajor, double value) {
this.maxX = maxX;
this.majorTickCount = majorTickCount;
this.minorTicksPerMajor = minorTicksPerMajor;
this.value = value;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// rendering hints to smooth out your drawing
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Graphics2D g2b = (Graphics2D) g2.create(); // so we can change stroke without problems
g2b.setStroke(MAIN_STROKE);
int x1 = (int) xValueToScreen(-maxX);
int y1 = getHeight() / 2;
int x2 = (int) xValueToScreen(maxX);
int y2 = y1;
g2b.drawLine(x1, y1, x2, y2);
g2b.setStroke(MAJOR_TIC_STOKE);
for (int i = 0; i <= 2 * majorTickCount; i++) {
double xVal = ((double) i * maxX) / majorTickCount - maxX;
x1 = (int) xValueToScreen(xVal);
x2 = x1;
double dY1 = getHeight() * (1 - MAJOR_TIC_HT) / 2.0;
if (i == majorTickCount) {
dY1 = 0.5 * dY1;
}
double dY2 = getHeight() - dY1;
g2b.drawLine(x1, (int) dY1, x2, (int) dY2);
}
g2b.dispose();
g2.setColor(VALUE_COLOR);
x1 = (int) (xValueToScreen(value) - CIRCLE_WIDTH / 2.0);
y1 = (int) (getHeight() - CIRCLE_WIDTH) / 2;
g2.fillOval(x1, y1, CIRCLE_WIDTH, CIRCLE_WIDTH);
}
private double xValueToScreen(double xValue) {
double gap = getWidth() * X_GAP;
double scale = (double) (getWidth() - 2 * gap) / (2 * maxX);
return (xValue + maxX) * scale + gap;
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
public double getValue() {
return value;
}
public void setValue(double value) {
this.value = value;
repaint();
}
public int getMaxX() {
return maxX;
}
public int getMajorTickCount() {
return majorTickCount;
}
public int getMinorTicksPerMajor() {
return minorTicksPerMajor;
}
}
Which can be tested with:
import java.awt.BorderLayout;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
#SuppressWarnings("serial")
public class NumberLine3Test extends JPanel {
private static final int MAX_X = 40;
private static final int MAJOR_TICS = 4;
private static final int MINOR_TICS = 5;
private double value = 0.0;
private NumberLine3 numberLine3 = new NumberLine3(MAX_X, MAJOR_TICS, MINOR_TICS, value);
private JSlider slider = new JSlider(-MAX_X, MAX_X, 0);
public NumberLine3Test() {
slider.setPaintTicks(true);
slider.setMajorTickSpacing(10);
slider.setMinorTickSpacing(5);
slider.addChangeListener(ce -> {
value = slider.getValue();
numberLine3.setValue(value);
});
JPanel sliderPanel = new JPanel();
sliderPanel.add(slider);
int ebGap = 40;
sliderPanel.setBorder(BorderFactory.createEmptyBorder(ebGap, ebGap, ebGap, ebGap));
setLayout(new BorderLayout());
add(numberLine3, BorderLayout.PAGE_START);
add(sliderPanel);
}
private static void createAndShowGui() {
NumberLine3Test mainPanel = new NumberLine3Test();
JFrame frame = new JFrame("NumberLine3");
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());
}
}
This question already has answers here:
Create a Trailing line of blood behind a player
(2 answers)
Closed 6 years ago.
I have a program that takes information on two planets/bodies in space and animates their orbits around each other realistically. It works, though when I repaint, it clears the screen each time and does not leave a trail.
My problem is that I want to leave a trail, though any answer I can find online only explains how to get rid of a trail. However, I don't have that problem.
On other computers, I can leave out the super.paintComponent() in my paint method and that causes it to leave a trail, but on this computer, it doesn't do that, it seems to clear the screen automatically. So then how can I efficiently draw a trail behind my orbiting planets? My code follows.
JPanel class first:
import javax.swing.*;
import java.awt.*;
/**
* Created by chris on 3/2/16.
*/
public class SpacePanel2 extends JPanel{
private Body2[] planets;
public static final Dimension SCREENSIZE = Toolkit.getDefaultToolkit().getScreenSize();
public static double scale = 5e6; //m/p
public static Color[] colors = {Color.black, Color.red};
public SpacePanel2(Body2[] planets) {
this.planets = planets;
this.setPreferredSize(SCREENSIZE);
}
#Override
public void paint(Graphics g){
for (int i = 0; i < planets.length; i++) {
g.setColor(colors[i]);
int r = planets[i].getPixelRadius()/2;
int x = planets[i].getPixelX();
int y = planets[i].getPixelY();
g.fillOval(x, y, r, r);
}
}
}
Body class:
/**
* Created by chris on 3/2/16.
*/
public class Body2 {
private double mass; //in kilograms
private double radius; //in meters
private static final double GRAVITATIONAL_CONSTANT = 6.67408e-11;
private static final double AVERAGE_DENSITY = 5515; //kg/m^3
/**
* Movement variables
*/
private double dx; //in m/s
private double dy; //in m/s
private double x; //in m
private double y; //in m
public Body2() {
radius = 1;
mass = AVERAGE_DENSITY;
x = 0;
y = 0;
dx = 0;
dy = 0;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getMass() {
return mass;
}
public double getRadius() {
return radius;
}
public int getPixelX() {
return (int)((this.x-radius)/SpacePanel2.scale);
}
public int getPixelY() {
return (int)((this.y-radius)/SpacePanel2.scale);
}
public int getPixelRadius(){
return (int)(this.radius/SpacePanel2.scale);
}
public void setMass(double mass) {
this.mass = mass;
}
public void setRadius(double radius) {
this.radius = radius;
}
public void setDx(double dx) {
this.dx = dx;
}
public void setDy(double dy) {
this.dy = dy;
}
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
public void exertForce2(double diffY, double diffX, double F){
double dist = Math.sqrt(diffY*diffY + diffX*diffX);
double ratio = F / dist;
this.dy = this.dy + ratio*diffY/this.mass;
this.dx = this.dx + ratio*diffX/this.mass;
}
public void tick(double timeScale) {
x+=(dx/1000.0)*timeScale;
y+=(dy/1000.0)*timeScale;
}
public static double getForce(Body2 a, Body2 b){
double dX = a.getX() - b.getX();
double dY = a.getY() - b.getY();
double distance = Math.sqrt(Math.pow(dX,2)+Math.pow(dY,2));
return (a.getMass()*b.getMass()*GRAVITATIONAL_CONSTANT)/(distance*distance);
}
public static double getStandardMass(double radius){
return (4.0/3.0)*Math.pow(radius, 3) * Math.PI;
}
public double getDy() {
return dy;
}
public double getDx() {
return dx;
}
public static double predictCentripetalForce(Body2 sun, Body2 planet){
return Math.sqrt(getForce(planet, sun)*(sun.getY()-planet.getY())/planet.mass);
}
}
Main class:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
/**
* Created by chris on 3/2/16.
*/
public class MainSpace2 {
static JFrame frame;
static SpacePanel2 panel;
static int fps = 60;
static boolean getLarger = false;
static boolean getSmaller = false;
static Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
public static void main(String[] args) {
Body2[] test = new Body2[2];
Body2 sun = new Body2();
sun.setRadius(696300000);
sun.setMass(1.989e30);
sun.setX(getScope('x') / 2);
sun.setY(getScope('y') / 2);
sun.setDx(0);
sun.setDy(0);
test[0] = sun;
int literalSizeSun = (int)(sun.getRadius()/SpacePanel2.scale);
Body2 mercury = new Body2();
mercury.setRadius(24400000);
mercury.setMass(Body2.getStandardMass(mercury.getRadius()));
mercury.setDx(Body2.predictCentripetalForce(sun, mercury)*2);
mercury.setDy(0);
mercury.setX(sun.getX());
mercury.setY(sun.getY() + 2 * sun.getRadius());
test[1] = mercury;
int literalSizeMercury = (int)(mercury.getRadius()/SpacePanel2.scale);
frame = new JFrame();
frame.setPreferredSize(size);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel = new SpacePanel2(test);
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyChar()) {
case '-':
getSmaller = true;
getLarger = false;
break;
case '=':
getLarger = true;
getSmaller = false;
break;
}
}
#Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyChar()) {
case '-':
getSmaller = false;
break;
case '=':
getLarger = false;
break;
}
}
});
double timeScale = 60*24;
Timer time = new Timer((int) (1000.0 / fps), new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
double F = Body2.getForce(test[0], test[1]);
double dY = test[1].getY() - test[0].getY();
double dX = test[1].getX() - test[0].getX();
test[0].exertForce2(dY, dX, F);
test[1].exertForce2(-dY, -dX, F);
for (int j = 0; j < test.length; j++) {
test[j].tick(timeScale);
}
panel.repaint(sun.getPixelX(), sun.getPixelY(), literalSizeSun, literalSizeSun);
panel.repaint(mercury.getPixelX(), mercury.getPixelY(), literalSizeMercury, literalSizeMercury);
}
});
frame.add(panel);
frame.pack();
frame.setVisible(true);
time.start();
}
public static double getScope(char k) {
switch (k) {
case 'x':
return size.width * SpacePanel2.scale;
case 'y':
return size.height * SpacePanel2.scale;
default:
return 0;
}
}
}
Custom painting is done by overriding the paintComponent() method, not paint().
if you leave a continuous trail, once you do a 360 rotation you won't see any more animation, so I would think you need to clear the screen eventually.
If you want to leave a trail you can keep an ArrayList of Objects you want to paint. Then in the paintComponent() method you can iterate through the List. This will allow you to add/remove Object from the list so you can control the number of Objects you want to paint each animation.
Check out the DrawOnComponent example from Custom Painting Approaches for an example of this approach. So your animation logic would basically add a new Object (and potentially remove one once you reach a certain limit?) to the List. Then you just invoke repaint() on the panel and all the Objects will be painted.
You already have List to paint each planet. So each animation you would need to add the new location of each planet to the List.
Or, the link shows how you can paint to a BufferedImage. But this approach doesn't allow you to remove a painting once it is done. So it depends an your exact requirement which approach you use.
I am wring the bouncing ball program in java. And I Now have one bouncing ball, I would like to have at least five bouncing balls. I have tried a few ways to do it, however, I only end up with one ball or error.
Do you have any suggestions on how to proceed? This in the piece of code used for the one ball, is it possible to rewrite this piece of code to get multiple balls in a neat way?
import javafx.scene.shape.Rectangle;
public class World {
private final double width, height;
private Ball[] balls;
private final Rectangle pad;
public World(double width, double height) {
this.width = width;
this.height = height;
balls = new Ball[1];
balls[0] = new Ball(10, 10);
balls[0].setVelocity(75.0, 100.0);
pad = new Rectangle(width / 2, 0.9 * height,
width / 8, height / 32);
}
public void move(long elapsedTimeNs) {
balls[0].move(elapsedTimeNs);
constrainBall(balls[0]);
checkForCollisionWithPad(balls[0]);
}
public Ball[] getBalls() {
return (Ball[]) balls.clone();
}
public Rectangle getPad() {
return pad;
}
public void setPadX(double x) {
if (x > width) {
x = width;
}
if (x < 0) {
x = 0;
}
pad.setX(x);
}
private void constrainBall(Ball ball) {
double x = ball.getX(), y = ball.getY();
double dx = ball.getDx(), dy = ball.getDy();
double radius = ball.getRadius();
if (x < radius) {
dx = Math.abs(dx);
} else if (x > width - radius) {
dx = -Math.abs(dx);
}
if (y < radius) {
dy = Math.abs(dy);
} else if (y > height - radius) {
dy = -Math.abs(dy);
}
ball.setVelocity(dx, dy);
}
private void checkForCollisionWithPad(Ball ball) {
if (ball.intersectsArea(
pad.getX(), pad.getY(), pad.getWidth(), pad.getHeight())) {
double dx = ball.getDx();
// set dy negative, i.e. moving "up"
double newDy = -Math.abs(ball.getDy());
ball.setVelocity(dx, newDy);
}
}
}
Main
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Alert;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Bounce extends Application {
private World world;
private Canvas canvas;
private AnimationTimer timer;
protected class BounceTimer extends AnimationTimer {
private long previousNs = 0;
#Override
public void handle(long nowNs) {
if (previousNs == 0) {
previousNs = nowNs;
}
world.move(nowNs - previousNs);
previousNs = nowNs;
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.WHITESMOKE);
gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
Rectangle pad = world.getPad();
gc.setFill(Color.BLACK);
double x = pad.getX(), y = pad.getY(),
w = pad.getWidth(), h = pad.getHeight();
gc.fillRoundRect(x, y, w, h, h, h);
for (Ball b : world.getBalls()) {
b.paint(gc);
}
}
}
#Override
public void start(Stage stage) {
Group root = new Group();
Scene scene = new Scene(root, 300, 300, Color.WHITESMOKE);
canvas = new Canvas(scene.getWidth(), scene.getHeight());
root.getChildren().add(canvas);
stage.setTitle("Bounce");
stage.setScene(scene);
stage.setResizable(false);
stage.sizeToScene();
stage.show();
world = new World(canvas.getWidth(), canvas.getHeight());
timer = new BounceTimer();
timer.start();
canvas.addEventHandler(MouseEvent.MOUSE_DRAGGED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent me) {
world.setPadX(me.getX());
}
});
}
public static void main(String[] args) {
launch(args);
}
private void showAlert(String message) {
alert.setHeaderText("");
alert.setTitle("Alert!");
alert.setContentText(message);
alert.show();
}
private final Alert alert = new Alert(Alert.AlertType.INFORMATION);
}
Ball
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
public class Ball {
public static final double BILLION = 1_000_000_000.0;
private double x, y; // position of the balls center
private double dx, dy; // velocity measured in pixels/second
private double radius;
private Color color;
public Ball(double x0, double y0) {
x = x0;
y = y0;
radius = 10;
color = Color.MAGENTA;
}
public Ball(double x0, double y0, double rad, Color col) {
x = x0;
y = y0;
radius = rad;
color = col;
}
Ball(int i, int i0, Color BLUEVIOLET) {
throw new UnsupportedOperationException("Not supported yet.");
}
public void setColor(Color col) { // setColor
color = col; }
public double getX() {
return x;
}
public double getY() {
return y;
}
public void setX(double newX) {
x = newX;
}
public void setY(double newY) {
y = newY;
}
public double getRadius() {
return radius;
}
public double getDx() {
return dx;
}
public double getDy() {
return dy;
}
public void setVelocity(double newDx, double newDy) {
dx = newDx;
dy = newDy;
}
public void moveTo(double newX, double newY) {
x = newX;
y = newY;
}
public void move(long elapsedTimeNs) {
x += dx * elapsedTimeNs / BILLION;
y += dy * elapsedTimeNs / BILLION;
}
public void paint(GraphicsContext gc) {
gc.setFill(color);
// arguments to fillOval: see the javadoc for GraphicsContext
gc.fillOval(x - radius, y - radius, radius * 2, radius * 2);
}
public boolean intersectsArea(
double rectX, double rectY,
double rectWidth, double rectHeight) {
double closestX = clamp(x, rectX, rectX + rectWidth);
double closestY = clamp(y, rectY, rectY + rectHeight);
double distanceX = x - closestX;
double distanceY = y - closestY;
return (distanceX * distanceX) + (distanceY * distanceY)
< (radius * radius);
}
private double clamp(double value, double lower, double upper) {
if (value < lower) {
return lower;
}
if (value > upper) {
return upper;
}
return value;
}
}
As Stormblessed said, you are only targeting one ball in your move method.
You should do:
public void move(Ball ball, long elapsedTimeNs) {
ball.move(elapsedTimeNs);
constrainBall(ball);
checkForCollisionWithPad(ball);
}
Edit: Since you want the handler method to accept only the elapsedTimeNs argument, do:
public void move(long elapsedTimeNs) {
for (Ball ball : balls) {
ball.move(elapsedTimeNs);
constrainBall(ball);
checkForCollisionWithPad(ball);
}
}
Edit 2: You should probably have a method that creates a new ball, for convenience:
public Ball newBall(double x, double y, double velocity1, double velocity2) {
Ball tmp = new Ball(x, y);
tmp.setVelocity(velocity1, velocity2);
balls.add(tmp);
return tmp;
}
Edit 3: The reason it throws an error is that you designated balls to have only one index position by using balls = new Ball[1]. You should use an ArrayList (java.util.ArrayList) instead, like so:
import java.util.ArrayList;
ArrayList<Ball> balls = new ArrayList<>;
You should now use balls.add and balls.get instead of = and []. References have been updated accordingly.
I'm having trouble getting my bullets to go where they want and I was hoping I could get some help identifying where my calculations are off.
Basically I'm making a 2d game and where ever you click on the screen a bullet should shoot at that location that you clicked, but the problem is that my bullets are not going where I want them to. Now I believe my problem is somewhere in the checkScreenForTouch() method and how I'm calculating what should be added to the bullets location. I'm not the best at trigonometry but I do understand it, so if any one has any advice on how to fix the following code that would be super cool :).
private void updateBullets(float dt){
batch.begin();
for(int i = 0; i < bullets.size(); i++){
Bullet b = bullets.get(i);
b.velocity.x += b.deltaX * b.speed;
b.velocity.y += b.deltaY * b.speed;
b.position.x += b.velocity.x * dt;
b.position.y += b.velocity.y * dt;
batch.draw(bullTex, b.position.x, b.position.y, bullTex.getWidth(), bullTex.getHeight());
if(b.position.x < 0 || b.position.x > sWidth || b.position.y < 0 || b.position.y > sHeight){
bullets.remove(b);
continue;
}
}
batch.end();
}
private void checkForScreenTouch(float dt){
if(Gdx.input.isTouched()){
Bullet b = new Bullet();
double angle = Math.atan((p.pos.y - Gdx.input.getY())/(p.pos.x - Gdx.input.getX()));
b.deltaX = Math.cos(angle);
b.deltaY = Math.sin(angle);
b.position.x = p.pos.x; //sets bullet start position equal to the players
b.position.y = p.pos.y;
bullets.add(b); // array list of bullets
}
}
If you need anything clarified just let me know. Thanks.
I see you still haven't really gotten any advice on this. Had some time, so I thought I'd whip something up.
The problems you were having I was able to reproduce by using your code.
Bullets only shoot to the right - This was solved by using atan2(dy, dx).
Opposite angle of what they are supposed to be - This was caused because the calculation p.pos.y - Gdx.input.getY() should really have been negated: Gdx.input.getY() - p.pos.y. Just a simple mix up. When the mouse has a larger y coordinate than the player location, you'd want the change in y to be positive.
Notes about the application:
Some issue with frame resizing. Tends to crop out bullets mid-trajectory.
I divide the change in time by 1000 to signify I want the velocity to be of pixels/second. Just a habit thing from physics 20.
The application is in swing. The code you will really want is within the makeProjectile function at the top.
Beyond that, I just used a synchronized list to avoid concurrency issues with the updates and the rendering.
If you have any questions, feel free to ask :)
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
/**
* #author Obicere
*/
public class ProjectileTest {
// It's dangerous to go alone! Take this.
public void makeProjectile(final MouseEvent e, final int width, final int height){
final int x = width / 2;
final int y = height / 2;
final double angle = Math.atan2(e.getY() - y, e.getX() - x);
final double deltaX = Math.cos(angle);
final double deltaY = Math.sin(angle);
projectiles.add(new Projectile(x, y, deltaX, deltaY));
}
private static final double PROJECTILE_VELOCITY = 100; // Pixels per second
private final Collection<Projectile> projectiles = Collections.synchronizedCollection(new LinkedList<>());
public static void main(final String[] args){
SwingUtilities.invokeLater(ProjectileTest::new);
}
public ProjectileTest(){
final JFrame frame = new JFrame("Projectile Test");
final JPanel content = new JPanel(){
private final Dimension size = new Dimension(500, 500);
#Override
public void paintComponent(final Graphics g){
super.paintComponent(g);
g.setColor(Color.BLACK);
g.drawOval(getWidth() / 2 - 2, getHeight() / 2 - 2, 4, 4);
projectiles.forEach((e) -> e.render(g));
}
#Override
public Dimension getPreferredSize(){
return size;
}
};
content.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(final MouseEvent e) {
makeProjectile(e, content.getWidth(), content.getHeight());
}
});
content.addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseDragged(final MouseEvent e) {
makeProjectile(e, content.getWidth(), content.getHeight());
}
});
final Timer repaint = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
final Iterator<Projectile> iter = projectiles.iterator();
while(iter.hasNext()){
final Projectile next = iter.next();
if(!next.valid()){
iter.remove();
}
next.step(content.getWidth(), content.getHeight());
}
frame.repaint();
}
});
frame.add(content);
frame.pack();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
repaint.start();
}
public class Projectile {
private final double velocityX;
private final double velocityY;
private double x;
private double y;
private long lastUpdate;
private boolean valid = true;
public Projectile(final double x, final double y, final double vx, final double vy){
this.x = x;
this.y = y;
this.velocityX = vx;
this.velocityY = vy;
this.lastUpdate = System.currentTimeMillis();
}
public boolean valid(){
return valid;
}
public void destroy(){
this.valid = false;
}
public void step(final int width, final int height){
final long time = System.currentTimeMillis();
final long change = time - lastUpdate;
this.x += (change / 1000D) * (velocityX * PROJECTILE_VELOCITY);
this.y += (change / 1000D) * (velocityY * PROJECTILE_VELOCITY);
this.lastUpdate = time;
if(x < 0 || y < 0 || x > width || y > height){
destroy();
}
}
public void render(final Graphics g){
g.setColor(Color.RED);
g.drawOval((int) x - 2, (int) y - 2, 4, 4);
}
}
}