Problems with projectiles on a 2d plane - java

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);
}
}
}

Related

move dot between 2 points jframe [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed last year.
Improve this question
Step 1: Task
Okay so I'm trying to replicate this in a jframe.
https://codepen.io/allanpope/pen/LVWYYd
Problem:
I have a loading screen and over the course of 5 seconds I'm moving dots from a circle to an image. Just like the codepen. Except the problem is I'm not sure how to animate it correctly. JAVA
My Idea was to make an animate function like Move.to(Dot,Duration)
One problem I'm having is that when using decimals the dots wont be in the exact place, and some wont move at all. I'm just not sure how to make this animate function and how to end it. If anyone could help me that would be epic. My code is down below. And if someone could tell me how I could do a Bezier curve animation would also be cool
If anyone wantes to test my code and tell me whats wrong that would be epic.
So first I have a function to make the circle positions
package loadingScreen;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
public class LoadTree {
static BufferedImage tree;
static int radius =300;
public static List<Dot> dots = new ArrayList<Dot>();
public static void make() {
File e = new File("assets/tree.png");
try {
tree = ImageIO.read(e);
} catch (IOException e1) {
e1.printStackTrace();
}
for(int x = 0; x<tree.getWidth();x+=1) {
for(int y = 0; y<tree.getWidth();y+=1) {
int clr = tree.getRGB(x, y);
if(clr==0) {
}else {
int i =(y)*300+x;
int a = (i / 4) % 300;
int b = (int) Math.floor(Math.floor(i / 300) / 4);
if (( a % ((1)) == 0) && (b % ((1)) == 0)) {
double p = (double) Math.random();
int circleX = (int) (MyFrame.width/2+ radius * Math.cos(2 * Math.PI * p));
int circleY = (int) (MyFrame.height/2 + radius * Math.sin(2 * Math.PI * p));
Dot dot = new Dot(circleX, circleY, clr,
MyFrame.width/2+x-150, MyFrame.height/2+y-150,circleX,circleY);
dots.add(dot);
}
}
}
}
}
}
Here is the dot class
package loadingScreen;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
public class Dot {
public int imageY;
public int imageX;
public int color;
public double y;
public double x;
public int circleX;
public int circleY;
public Dot(int x, int y, int color, int imageX, int imageY, int circleX, int circleY){
this.x = x;
this.y = y;
this.color = color;
this.imageX = imageX;
this.imageY = imageY;
this.circleX = circleX;
this.circleY = circleY;
}
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Color c = new Color(color);
g2d.setColor(c);
g2d.fillRect((int)x,(int) y, 1, 1);
}
}
Here is the Jpanel
package loadingScreen;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;
import loadingScreen.animate.Move;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class MyPanel extends JPanel implements ActionListener{
Image background;
Timer timer;
MyPanel(){
timer = new Timer(0,this);
timer.start();
File e = new File("assets/background.png");
try {
background = ImageIO.read(e);
} catch (IOException e1) {
e1.printStackTrace();
}
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
//g2d.drawImage(background, 0, 0, MyFrame.width,MyFrame.height,null);
LoadTree.dots.forEach(dot ->{
dot.paint(g);
});
}
#Override
public void actionPerformed(ActionEvent e) {
LoadTree.dots.forEach(dot ->{
Move.to(dot, 1000, 0);
//get slope
});
repaint();
//System.out.println(LoadTree.dots.size());
}
}
And here is the Jframe
package loadingScreen;
import javax.swing.JFrame;
public class MyFrame extends JFrame{
MyPanel panel;
public static int width = 1080;
public static int height = 720;
MyFrame(){
panel = new MyPanel();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(panel);
this.setSize(width, height);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
}
You are in for a massive deep dive. Animation, good animation, animation you don't notice, is really, really hard to achieve and is a very complex subject.
You've kind of started in the right direction. You need some kind of "ticker" to tell you when the animation should update, but it kind of falls apart after that.
The first thing you want to do is move away from the concept of "linear progression". That is, on each "tick", the object is moved by a specific delta value. This doesn't produce good animation and can fall apart really quickly when you want to change the speed or duration of the animation.
A better solution is to start with a "duration based progress". This is, basically, the animation will run over a fixed period of time and on each tick of the animation, you calculate the new "state" of the object based on the amount of time which has passed and the amount of time remaining.
This has the benefit of "normalising" the timeline. That is, the animation occurs between 0-1. From this it becomes incredibly easy to calculate where a object should be along that time line. Want to make it faster or slower? Change the duration, the rest is taken care for you!
To start with, figure out how to move one dot from one point to another, if you can move one, you can move a thousand.
Duration base animation engine...
Play close attention to:
The Utilities class
The DurationAnimationEngine
The engine is backed by a Swing Timer, so it's safe to use within Swing. It's whole purpose to run (as fast as it safely can) for a specified period of time and produce "ticks" with the amount of progression has occurred (remember, normalised time)
The following is basic implementation of the animation. A lot of the "work" happens in the mouseClicked event, as it starts the engine. When the engine ticks the dots are updated. Each dot is wrapped in AnimatableDot which has a "from" and "to" point, it then, based on the normalised time, calculates it's new position and then a paint pass is executed
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class DurationTest {
public static void main(String[] args) {
new DurationTest();
}
public DurationTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Utilities {
public static Point2D pointOnCircle(double degress, double radius) {
double rads = Math.toRadians(degress - 90); // 0 becomes the top
double xPosy = Math.round((Math.cos(rads) * radius));
double yPosy = Math.round((Math.sin(rads) * radius));
return new Point2D.Double(radius + xPosy, radius + yPosy);
}
public static Point2D pointOnCircle(double xOffset, double yOffset, double degress, double radius) {
Point2D poc = pointOnCircle(degress, radius);
return new Point2D.Double(xOffset + poc.getX(), yOffset + poc.getY());
}
}
public class TestPane extends JPanel {
private List<AnimatedDot> dots = new ArrayList<>(128);
private Duration duration = Duration.ofSeconds(5);
private DurationAnimationEngine engine;
private List<Color> colors = Arrays.asList(new Color[]{
Color.RED,
Color.BLUE,
Color.CYAN,
Color.GRAY,
Color.GREEN,
Color.LIGHT_GRAY,
Color.MAGENTA,
Color.PINK,
Color.WHITE,
Color.YELLOW
});
public TestPane() {
Random rnd = new Random();
setBackground(Color.BLACK);
for (int index = 0; index < 100; index++) {
double fromAngle = 360.0 * rnd.nextDouble();
double toAngle = fromAngle + 180.0;
Collections.shuffle(colors);
Color color = colors.get(0);
dots.add(new AnimatedDot(
Utilities.pointOnCircle(fromAngle, 150),
Utilities.pointOnCircle(toAngle, 150),
color, 2));
}
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (engine != null) {
engine.stop();
engine = null;
// Reset poisitions
for (AnimatedDot dot : dots) {
dot.move(0);
}
repaint();
return;
}
engine = new DurationAnimationEngine(duration, new DurationAnimationEngine.Tickable() {
#Override
public void animationDidTick(double progress) {
for (AnimatedDot dot : dots) {
dot.move(progress);
}
repaint();
}
});
engine.start();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
int xOffset = (getWidth() - 300) / 2;
int yOffset = (getWidth() - 300) / 2;
g2d.translate(xOffset, yOffset);
g2d.setColor(Color.DARK_GRAY);
g2d.drawOval(0, 0, 300, 300);
for (AnimatedDot dot : dots) {
dot.paint(g2d);
}
g2d.dispose();
}
}
public class DurationAnimationEngine {
public interface Tickable {
public void animationDidTick(double progress);
}
private Duration duration;
private Instant timeStarted;
private Timer timer;
private Tickable tickable;
public DurationAnimationEngine(Duration duration, Tickable tickable) {
this.duration = duration;
this.tickable = tickable;
}
public void start() {
// You could create the timer lazierly and restarted it as needed
if (timer != null) {
return;
}
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (timeStarted == null) {
timeStarted = Instant.now();
}
Duration runtime = Duration.between(timeStarted, Instant.now());
double progress = Math.min(1.0, runtime.toMillis() / (double) duration.toMillis());
tickable.animationDidTick(progress);
if (progress >= 1.0) {
stop();
}
}
});
timer.start();
}
public void stop() {
if (timer == null) {
return;
}
timer.stop();
timer = null;
}
}
public class AnimatedDot {
private Dot dot;
private Point2D from;
private Point2D to;
public AnimatedDot(Point2D from, Point2D to, Color color, int radius) {
dot = new Dot(from.getX(), from.getY(), color, radius);
this.from = from;
this.to = to;
}
public void paint(Graphics2D g) {
dot.paint(g);
}
public void move(double progress) {
Point2D pointAt = pointAt(progress);
dot.setLocation(pointAt);
}
public Point2D getFrom() {
return from;
}
public Point2D getTo() {
return to;
}
protected double getFromX() {
return getFrom().getX();
}
protected double getFromY() {
return getFrom().getY();
}
public Double getXDistance() {
return getTo().getX() - getFrom().getX();
}
public Double getYDistance() {
return getTo().getY() - getFrom().getY();
}
protected Point2D pointAt(double progress) {
double xDistance = getXDistance();
double yDistance = getYDistance();
double xValue = Math.round(xDistance * progress);
double yValue = Math.round(yDistance * progress);
xValue += getFromX();
yValue += getFromY();
return new Point2D.Double(xValue, yValue);
}
}
public class Dot {
private Color color;
private double y;
private double x;
private int radius;
private Ellipse2D dot;
public Dot(double x, double y, Color color, int radius) {
this.x = x;
this.y = y;
this.color = color;
this.radius = radius;
dot = new Ellipse2D.Double(0, 0, radius * 2, radius * 2);
}
public void setLocation(Point2D point) {
setLocation(point.getX(), point.getY());
}
public void setLocation(double x, double y) {
this.x = x;
this.y = y;
}
public void paint(Graphics2D g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(color);
g2d.translate(x - radius, y - radius);
g2d.fill(dot);
g2d.dispose();
}
}
}
Okay, so when we run it, we get...
😮 ... Hmmm, I'd like to say that that was expected, but once I saw it, it was obvious what had gone wrong.
All the dots are moving at the same speed over the same time range!
So, what's the answer. Well, actually a few...
We could change the duration of each dot so that they have an individual duration. This would "randomise" the movement, but I'm not sure it would generate the exact same effect, as they'd be moving at different speeds
We could randomise the start time of the dots, so they started at different times, allowing them all to have the same duration (or even a randomised duration)
We could move only a small subset of the dots, but this would mean that you'd probably end up waiting for the current subset to finish before the next one started
A combination of 2 & 3
Individualised, randomised duration...
Okay, for simplicity (and my sanity), I'm actually going to start with 1. Each dot will have it's own, randomised duration. This means that each dot will be moving at a different speed though.
Pay close attention to LinearAnimationEngine and the AnimatedDot#move method.
This should look familiar, it's basically the same animation logic as before, just isolated for the dot itself
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class RandomIndividualDuration {
public static void main(String[] args) {
new RandomIndividualDuration();
}
public RandomIndividualDuration() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Utilities {
public static Point2D pointOnCircle(double degress, double radius) {
double rads = Math.toRadians(degress - 90); // 0 becomes the top
double xPosy = Math.round((Math.cos(rads) * radius));
double yPosy = Math.round((Math.sin(rads) * radius));
return new Point2D.Double(radius + xPosy, radius + yPosy);
}
public static Point2D pointOnCircle(double xOffset, double yOffset, double degress, double radius) {
Point2D poc = pointOnCircle(degress, radius);
return new Point2D.Double(xOffset + poc.getX(), yOffset + poc.getY());
}
}
public class DurationRange {
private Duration from;
private Duration to;
public DurationRange(Duration from, Duration to) {
this.from = from;
this.to = to;
}
public Duration getFrom() {
return from;
}
public Duration getTo() {
return to;
}
public Duration getDistance() {
return Duration.ofNanos(getTo().toNanos() - getFrom().toNanos());
}
public Duration valueAt(double progress) {
Duration distance = getDistance();
long value = (long) Math.round((double) distance.toNanos() * progress);
value += getFrom().getNano();
return Duration.ofNanos(value);
}
}
public class TestPane extends JPanel {
private List<AnimatedDot> dots = new ArrayList<>(128);
private Duration duration = Duration.ofSeconds(5);
private LinearAnimationEngine engine;
private List<Color> colors = Arrays.asList(new Color[]{
Color.RED,
Color.BLUE,
Color.CYAN,
Color.GRAY,
Color.GREEN,
Color.LIGHT_GRAY,
Color.MAGENTA,
Color.PINK,
Color.WHITE,
Color.YELLOW
});
public TestPane() {
Random rnd = new Random();
setBackground(Color.BLACK);
DurationRange range = new DurationRange(Duration.ofSeconds(1), Duration.ofSeconds(5));
for (int index = 0; index < 100; index++) {
double fromAngle = 360.0 * rnd.nextDouble();
double toAngle = fromAngle + 180.0;
Collections.shuffle(colors);
Color color = colors.get(0);
Duration duration = range.valueAt(rnd.nextDouble());
dots.add(new AnimatedDot(
Utilities.pointOnCircle(fromAngle, 150),
Utilities.pointOnCircle(toAngle, 150),
color, 2, duration));
}
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (engine != null) {
engine.stop();
engine = null;
reset();
return;
}
System.out.println("Go");
List<AnimatedDot> avaliableDots = new ArrayList<>(120);
avaliableDots.addAll(dots);
engine = new LinearAnimationEngine(new LinearAnimationEngine.Tickable() {
#Override
public void animationDidTick() {
List<AnimatedDot> completed = new ArrayList<>(128);
// Reset poisitions
for (AnimatedDot dot : avaliableDots) {
if (!dot.move()) {
completed.add(dot);
}
}
avaliableDots.removeAll(completed);
repaint();
if (avaliableDots.isEmpty()) {
engine.stop();
engine = null;
reset();
}
}
});
engine.start();
}
});
}
protected void reset() {
for (AnimatedDot dot : dots) {
dot.reset();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
int xOffset = (getWidth() - 300) / 2;
int yOffset = (getWidth() - 300) / 2;
g2d.translate(xOffset, yOffset);
g2d.setColor(Color.DARK_GRAY);
g2d.drawOval(0, 0, 300, 300);
for (AnimatedDot dot : dots) {
dot.paint(g2d);
}
g2d.dispose();
}
}
public class LinearAnimationEngine {
public interface Tickable {
public void animationDidTick();
}
private Tickable tickable;
private Timer timer;
public LinearAnimationEngine(Tickable tickable) {
this.tickable = tickable;
}
public void start() {
// You could create the timer lazierly and restarted it as needed
if (timer != null) {
return;
}
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
tickable.animationDidTick();
}
});
timer.start();
}
public void stop() {
if (timer == null) {
return;
}
timer.stop();
timer = null;
}
}
public class AnimatedDot {
private Dot dot;
private Point2D from;
private Point2D to;
private Duration duration;
private Instant timeStarted;
public AnimatedDot(Point2D from, Point2D to, Color color, int radius, Duration duration) {
dot = new Dot(from.getX(), from.getY(), color, radius);
this.from = from;
this.to = to;
this.duration = duration;
}
public void paint(Graphics2D g) {
dot.paint(g);
}
public void reset() {
Point2D futureFrom = to;
to = from;
from = futureFrom;
timeStarted = null;
}
public boolean move() {
if (timeStarted == null) {
timeStarted = Instant.now();
}
Duration runtime = Duration.between(timeStarted, Instant.now());
double progress = Math.min(1.0, runtime.toMillis() / (double) duration.toMillis());
Point2D pointAt = pointAt(progress);
dot.setLocation(pointAt);
return progress < 1.0;
}
public Point2D getFrom() {
return from;
}
public Point2D getTo() {
return to;
}
protected double getFromX() {
return getFrom().getX();
}
protected double getFromY() {
return getFrom().getY();
}
public Double getXDistance() {
return getTo().getX() - getFrom().getX();
}
public Double getYDistance() {
return getTo().getY() - getFrom().getY();
}
protected Point2D pointAt(double progress) {
double xDistance = getXDistance();
double yDistance = getYDistance();
double xValue = Math.round(xDistance * progress);
double yValue = Math.round(yDistance * progress);
xValue += getFromX();
yValue += getFromY();
return new Point2D.Double(xValue, yValue);
}
}
public class Dot {
private Color color;
private double y;
private double x;
private int radius;
private Ellipse2D dot;
public Dot(double x, double y, Color color, int radius) {
this.x = x;
this.y = y;
this.color = color;
this.radius = radius;
dot = new Ellipse2D.Double(0, 0, radius * 2, radius * 2);
}
public void setLocation(Point2D point) {
setLocation(point.getX(), point.getY());
}
public void setLocation(double x, double y) {
this.x = x;
this.y = y;
}
public void paint(Graphics2D g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(color);
g2d.translate(x - radius, y - radius);
g2d.fill(dot);
g2d.dispose();
}
}
}
Now, when we run it we get...
Well, at least it's now more "randomised", and this is where I think points 2 & 3 might be a better mix.
But they're not rebounding?!
Ah, well, actually, click the second example again! The dots will move from the current position (the original to point) and back to their original from point. Soooo, conceptually, it's doable.
But they don't from a nice picture when I click it!
😐 ... So the above examples demonstrate how to animate a object from point A to point B over a specified duration, forming the picture is just changing the target destination (assuming you know what it was to start with). Based on my observations, a moving dot is first allowed to move to its current "end" position before moving to the final picture position, as trying to calculate a curving path would make me 🤯.
What's missing...
Yes, there's something missing, you probably can't see it, but it really stands out for me.
Each dot starts out slowly, speeds up and then decelerates into position. This is known as "easement" (or, in this case, ease-in/ease-out) and it's not the simplest thing in the world to implement. If you're really interested, take a look at How can I implement easing functions with a thread
Now, what is the actual answer to your question? Unless you're completely crazy (and I am), don't try to roll this kind of thing yourself, unless you have a very specific reason for doing so. Instead, make use one of the other ready made engines, for example:
universal-tween-engine
timingframework
Much of the concepts used above are taken from my animation playground source, Super Simple Swing Animation Framework. This is where I do a lot of my playing around and experimentation
This is one of those questions that has you digging deeper and trying to figure out what you could actually achieve, to that end BounceImagePixel is an accumulation of much tinkering to see "where this could go"

Isometric tiles drawing and picking - JAVA

I'm trying to draw isometric tiles in Java and implement a tile picking system using the mouse cursor. I draw the tiles using these math formulas I found and adapted to my tile textures which you can find below. Tiles are 64x64px but flat tiles are only 32px height even if I draw them using the 64x64 sprite.
The map is a simple 2d array where my tiles are represented by their id.
Here is the class I use to convert map coordinates to screen coordinates using the toIso() function. I pass my screen coordinates which represent the cursor position on the screen to the toGrid() function to convert them to map coordinates.
public class Utils {
private static int TILE_WIDTH = Tile.TILE_WIDTH;
private static int TILE_HEIGHT = Tile.TILE_HEIGHT;
private static int TILE_WIDTH_HALF = TILE_WIDTH/2;
private static int TILE_HEIGHT_HALF = TILE_HEIGHT/2;
private static int TILE_WIDTH_QUARTER = TILE_WIDTH_HALF/2;
private static int TILE_HEIGHT_QUARTER = TILE_HEIGHT_HALF/2;
public static int[] toIso(int x, int y){
int i = (x - y) * TILE_WIDTH_HALF;
int j = (x + y) * TILE_HEIGHT_QUARTER;
//800 and 100 are temporary offsets I apply to center the map.
i+=800;
j+=100;
return new int[]{i,j};
}
public static int[] toGrid(int x, int y){
//800 and 100 are temporary offsets I apply to center the map.
x-=800;
y-=100;
int i = ( x / ( TILE_WIDTH_HALF ) + y / ( TILE_HEIGHT_QUARTER )) / 2;
int j = ( y / ( TILE_HEIGHT_QUARTER ) - ( x / ( TILE_WIDTH_HALF ))) / 2;
return new int[]{i,j};
}}
I currently render my tiles by using two for loops and converting the map coordinates to screen coordinates using the toIso() function.
public void render(Graphics g){
for(int x = 0;x<width;x++){
for(int y = 0;y<height;y++){
int[] isoCoords = Utils.toIso(x, y);
int fx = isoCoords[0];//
int fy = isoCoords[1];//
if(world[x][y] == 0){
Tile grass = new GrassTile(0);
grass.render(g, grass.getId(), fx, fy);
}else if(world[x][y] == 1){
Tile water = new WaterTile(1);
water.render(g, water.getId(), fx, fy);
}
}
}
}
I get a diamond shape as I wanted rendered on the screen.
I finally update each tick which are the actual mouse coordinates on screen.
int[] coords = Utils.toGrid(mouseManager.getMouseX(), mouseManager.getMouseY());
tileX = coords[0];
tileY = coords[1];
The selected tile is finally rendered:
BufferedImage selectedTexture = Assets.selected;
int[] coordsIsoSelected = Utils.toIso(this.tileX, this.tileY);
g.drawImage(selectedTexture, coordsIsoSelected[0], coordsIsoSelected[1], Tile.TILE_WIDTH, Tile.TILE_HEIGHT, null);
g.drawRect(Utils.toIso(tileX, tileY)[0], Utils.toIso(tileX, tileY)[1]+Tile.TILE_HEIGHT/2, Tile.TILE_WIDTH, Tile.TILE_HEIGHT/2);//I draw a rectangle to visualize what's happening.
Finally, my tile detection isn't working as expected, it isn't fitting the tiles perfectly, however it seems to be in relation with the rectangle I draw. I can't figure out the solution to this problem, I thank you in advance for reading or any advice you could give to me. If you need more precisions, I would be glad to give you more informations.
Here is a video showing what is actually happening: youtu.be/baCVIfJz2Wo
EDIT:
Here is some of my code you could use to run an application like mine. Sorry for this very messy code, but I tried to make it as short as possible without disturbing the behavior of the "game".
You will need to put the sheet provided before into a "textures" folder created into the ressource folder of the project.
The gfx package:
package fr.romainimberti.isometric.gfx;
import java.awt.image.BufferedImage;
public class Assets {
private static final int width = 64, height = 64;
public static BufferedImage grass, water, selected;
public static void init(){
//Temp
SpriteSheet tileSheet = new SpriteSheet(ImageLoader.loadImage("/textures/sheet.png"));
grass = tileSheet.crop(width*2, 0, width, height);
water = tileSheet.crop(width*9, height*5, width, height);
selected = tileSheet.crop(0, height*5, width, height);
//
}
}
package fr.romainimberti.isometric.gfx;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
public class ImageLoader {
public static BufferedImage loadImage(String path){
try {
return ImageIO.read(ImageLoader.class.getResource(path));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
return null;
}
}
package fr.romainimberti.isometric.gfx;
import java.awt.image.BufferedImage;
public class SpriteSheet {
private BufferedImage sheet;
public SpriteSheet(BufferedImage sheet){
this.sheet = sheet;
}
public BufferedImage crop(int x, int y, int width, int height){
return sheet.getSubimage(x, y, width, height);
}
}
The rest of the project:
package fr.romainimberti.isometric;
public class Launcher {
public static void main(String args[]){
System.setProperty("sun.awt.noerasebackground", "true");
Game game = new Game("Isometric", 1280, 720);
game.start();
}
}
package fr.romainimberti.isometric;
import java.awt.Canvas;
import java.awt.Dimension;
import javax.swing.JFrame;
public class Display {
private JFrame frame;
private Canvas canvas;
private String title;
private int width, height;
public Display(String title, int width, int height){
this.title = title;
this.width = width;
this.height = height;
createDisplay();
}
private void createDisplay(){
frame = new JFrame(title);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(true);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
canvas = new Canvas();
canvas.setPreferredSize(new Dimension(width, height));
canvas.setMaximumSize(new Dimension(width, height));
canvas.setMinimumSize(new Dimension(width, height));
canvas.setFocusable(true);
frame.add(canvas);
frame.pack();
}
public Canvas getCanvas(){
return canvas;
}
public JFrame getFrame(){
return frame;
}
}
package fr.romainimberti.isometric;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.util.concurrent.ThreadLocalRandom;
import javax.swing.JFrame;
import fr.romainimberti.isometric.gfx.Assets;
public class Game implements Runnable {
private Display display;
private int width, height;
public JFrame frame;
private boolean running = false;
private Thread thread;
public String title;
private BufferStrategy bs;
private Graphics g;
public int x, y;
public int[][] world;
public static final int TILE_WIDTH = 64;
public static final int TILE_HEIGHT = 64;
public static final int TILE_WIDTH_HALF = 32;
public static final int TILE_HEIGHT_HALF = 32;
public static final int TILE_WIDTH_QUARTER = 16;
public static final int TILE_HEIGHT_QUARTER = 16;
public int xOffset;
//Input
private MouseManager mouseManager;
public Game(String title, int width, int height){
this.width = width;
this.height = height;
this.mouseManager = new MouseManager(this);
this.world = new int[10][10];
}
private void init(){
display = new Display(title, width, height);
display.getFrame().addMouseListener(mouseManager);
display.getFrame().addMouseMotionListener(mouseManager);
display.getCanvas().addMouseListener(mouseManager);
display.getCanvas().addMouseMotionListener(mouseManager);
this.frame = display.getFrame();
Assets.init();
xOffset = frame.getWidth()/2;
//Fill the world
for(int i = 0;i<world.length;i++){
for(int j=0;j<world[0].length;j++){
int r = ThreadLocalRandom.current().nextInt(0,1+1);
if(r == 0)
world[i][j] = 0;
else
world[i][j] = 1;
}
}
}
private void tick(){
mouseManager.tick();
xOffset = frame.getWidth()/2;
}
private void render(){
bs = display.getCanvas().getBufferStrategy();
if(bs == null){
display.getCanvas().createBufferStrategy(3);
return;
}
g = bs.getDrawGraphics();
//Clear Screen
g.clearRect(0, 0, frame.getWidth(), frame.getHeight());
//Draw Here
//World render
for(int x = 0;x<world.length;x++){
for(int y = 0;y<world[0].length;y++){
int[] isoCoords = toIso(x, y);
int fx = isoCoords[0];//
int fy = isoCoords[1];//
if(world[x][y] == 0){
g.drawImage(Assets.grass, fx, fy, null);
}else if(world[x][y] == 1){
g.drawImage(Assets.water, fx, fy, null);
}
}
}
//Selected tile render
int[] coordsIsoSelected = toIso(x, y);
g.drawImage(Assets.selected, coordsIsoSelected[0], coordsIsoSelected[1], TILE_WIDTH, TILE_HEIGHT, null);
//End Drawing
bs.show();
g.dispose();
}
public void run(){
init();
int fps = 120;
double timePerTick = 1000000000 / fps;
double delta = 0;
long now;
long lastTime = System.nanoTime();
while(running){
now = System.nanoTime();
delta += (now - lastTime) / timePerTick;
lastTime = now;
if(delta >= 1){
tick();
render();
delta--;
}
}
stop();
}
public MouseManager getMouseManager(){
return mouseManager;
}
public int getWidth(){
return width;
}
public int getHeight(){
return height;
}
public synchronized void start(){
if(running)
return;
running = true;
thread = new Thread(this);
thread.start();
}
public synchronized void stop(){
if(!running)
return;
running = false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static int[] toIso(int x, int y){
int i = (x - y) * TILE_WIDTH_HALF;
int j = (x + y) * TILE_HEIGHT_QUARTER;
i+=xOffset;
return new int[]{i,j};
}
public static int[] toGrid(int x, int y){
x-=xOffset;
int i = ( x / ( TILE_WIDTH_HALF ) + y / ( TILE_HEIGHT_QUARTER )) / 2;
int j = ( y / ( TILE_HEIGHT_QUARTER ) - ( x / ( TILE_WIDTH_HALF ))) / 2;
return new int[]{i,j};
}
}
package fr.romainimberti.isometric;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
public class MouseManager implements MouseListener, MouseMotionListener {
private boolean leftPressed, rightPressed;
private int mouseX, mouseY;
private Game game;
public MouseManager(Game game){
this.game = game;
}
public void tick(){
game.x = game.toGrid(mouseX, mouseY)[0];
game.y = game.toGrid(mouseX, mouseY)[1];
}
// Getters
public boolean isLeftPressed(){
return leftPressed;
}
public boolean isRightPressed(){
return rightPressed;
}
public int getMouseX(){
return mouseX;
}
public int getMouseY(){
return mouseY;
}
// Implemented methods
#Override
public void mousePressed(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1)
leftPressed = true;
else if(e.getButton() == MouseEvent.BUTTON3)
rightPressed = true;
}
#Override
public void mouseReleased(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1)
leftPressed = false;
else if(e.getButton() == MouseEvent.BUTTON3)
rightPressed = false;
}
#Override
public void mouseMoved(MouseEvent e) {
mouseX = e.getX();
mouseY = e.getY();
}
#Override
public void mouseDragged(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseClicked(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
}
}
If you need it, you can find here my project architecture so you can organize all the files correctly.
Again, sorry for this very, very messy code but I had to split all the usefull parts of my game to reduce it's size. Also don't forget to download and place correctly the sheet file. Hope this will help.
128*64 tiles
just wanted to say that I finally solved it. It was just a conversion to int issue. These are the final methods that I use. Hope it will help people who are trying to work with isometric tiles. Thank you !
public static int[] toIso(int x, int y){
int i = (x - y) * TILE_WIDTH_HALF;
int j = (x + y) * TILE_HEIGHT_QUARTER;
i += xOffset-TILE_WIDTH_HALF;
j+=yOffset;
return new int[]{i,j};
}
public static int[] toGrid(double i, double j){
i-=xOffset;
j-=yOffset;
double tx = Math.ceil(((i / TILE_WIDTH_HALF) + (j / TILE_HEIGHT_QUARTER))/2);
double ty = Math.ceil(((j / TILE_HEIGHT_QUARTER) - (i / TILE_WIDTH_HALF))/2);
int x = (int) Math.ceil(tx)-1;
int y = (int) Math.ceil(ty)-1;
return new int[]{x, y};
}
After replacing the spritesheet with the new one with 128x64 pixels' tiles, I've been able to achieve the desired output partially...
Why I say "partially"? Because I've managed to get the desired result only from the half right part of the map.
I believe it could have something to do with how the map is being painted, I'm not a native English speaker so I might be misunderstanding what the "Notes" section says in OP's link:
Notice that the "origin" of the isometric tile is the top corner. But usually when we draw a sprite it's from the top-left corner
I've called the methods toGrid() and toIso() at the beginning of the program as follows:
int[] coordinates = Game.toIso(2, 1);
System.out.println(coordinates[0] + "-" + coordinates[1]);
int[] coordinates2 = Game.toGrid(coordinates[0], coordinates[1]);
System.out.println(coordinates2[0] + "-" + coordinates2[1]);
And got the following results, (Which indeed are what we were expecting), so we know the methods work correctly:
64-96
2-1
I was sure to modify the Assets file:
public static final int WIDTH = 128, HEIGHT = 64;
Where I also changed the variable names following the Java naming conventions (ALL_WORDS_UPPER_CASE_CONSTANTS) and made it public instead of private
I also changed the Game file:
public static final int TILE_WIDTH = Assets.WIDTH;
public static final int TILE_HEIGHT = Assets.HEIGHT;
public static final int TILE_WIDTH_HALF = TILE_WIDTH / 2;
public static final int TILE_HEIGHT_HALF = TILE_HEIGHT / 2;
public static final int TILE_WIDTH_QUARTER = TILE_WIDTH / 4;
public static final int TILE_HEIGHT_QUARTER = TILE_HEIGHT / 4;
To use those constants on the Assets file and calculate the HALF and QUARTER instead of hardcoding it.
I also believe xOffset shouldn't be public but private as well as some other variables on the program...
The tick() method, doesn't need to calculate the xOffset everytime, so we can get rid of this line inside it:
xOffset = frame.getWidth() / 2 - 65;
I also changed the way you paint the tile you're selecting as:
// Selected tile render
int[] coordsIsoSelected = toIso(x, y);
g.drawImage(Assets.selected, coordsIsoSelected[0], coordsIsoSelected[1], TILE_WIDTH, TILE_HEIGHT, null);
And for the Tolso equations, I changed them to:
public static int[] toIso(int x, int y) {
int i = (x - y) * TILE_WIDTH_HALF;
int j = (x + y) * TILE_HEIGHT_HALF;
i += xOffset;
return new int[] { i, j };
}
Below I adjusted the parenthesis locations:
public static int[] toGrid(int x, int y) {
x -= xOffset;
int i = ((x / TILE_WIDTH_HALF) + (y / TILE_HEIGHT_HALF)) / 2;
int j = ((y / TILE_HEIGHT_HALF) - (x / TILE_WIDTH_HALF)) / 2;
return new int[] { i, j };
}

Creating my own paint method in java

I want to create a method that creates 5 balls on a panel. Can somebody please help me get around this using the paint component method or creating my own draw method. As you can see bellow, i have a paint component method with a for loop that will loop 5 and create a ball in a random location, but unfortunately only one ball is being created.
import java.awt.*;
import java.util.Random;
import javax.swing.*;
public class AllBalls extends JPanel {
int Number_Ball=5;
int x,y;
Graphics g;
AllBalls(){
Random r = new Random();
x=r.nextInt(320);
y=r.nextInt(550);
}
public static JFrame frame;
public static void main(String[] args) {
AllBalls a= new AllBalls();
frame= new JFrame("Bouncing Balls");
frame.setSize(400,600);
frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.add(a);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for(int i=0;i<Number_Ball;i++){
g.fillOval(x, y, 30, 30);
}
repaint();
}
}
In my paintComponent method, i created a for loop in which i wanted 5 ball to be created, but only one is being created on the screen.
Recommendations as per my comments:
Get rid of the Graphics g variable as that will only hurt you.
Consider creating a non-GUI Ball class, one that does not extend any Swing component and has its own paint(Graphics) method.
If you need 5 balls, create an array of Ball objects in your AllBalls class.
In paintComponent, iterate through the Balls painting each one by calling its paint or draw method (whatever you name the method).
If you need to move the balls, do so in a Swing Timer that moves the balls and then calls repaint(). Whatever you do, do not call repaint() within the paintComponent method as this results in a bastardization of the method, and results in a completely uncontrolled animation. This method should be for painting and painting only and not for animation or code logic. Use the Swing Timer instead.
Also don't randomize your ball locations within paintComponent, another recommendation made in a now-deleted answer. Again, paintComponent should be for painting and painting only and not for program logic or to change the state of your class. The randomization should be within your Swing Timer or game loop.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.*;
#SuppressWarnings({"serial", "unused"})
public class BallImages extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = PREF_W;
private static final int BALL_COUNT = 15;
private static final Color[] COLORS = { Color.RED, Color.BLACK, Color.BLUE,
Color.CYAN, Color.GREEN, Color.ORANGE, Color.PINK, Color.WHITE,
Color.MAGENTA, Color.YELLOW };
private static final int MIN_SPEED = 2;
private static final int MAX_SPEED = 10;
private static final int MIN_WIDTH = 10;
private static final int MAX_WIDTH = 30;
private static final int TIMER_DELAY = 15;
private List<Ball> balls = new ArrayList<>();
private Random random = new Random();
public BallImages() {
for (int i = 0; i < BALL_COUNT; i++) {
int ballWidth = MIN_WIDTH + random.nextInt(MAX_WIDTH - MIN_WIDTH);
Color ballColor = COLORS[random.nextInt(COLORS.length)];
int ballX = ballWidth / 2 + random.nextInt(PREF_W - ballWidth / 2);
int ballY = ballWidth / 2 + random.nextInt(PREF_H - ballWidth / 2);
double direction = 2 * Math.PI * Math.random();
double speed = MIN_SPEED + random.nextInt(MAX_SPEED - MIN_SPEED);
Ball ball = new Ball(ballWidth, ballColor, ballX, ballY, direction,
speed);
balls.add(ball);
}
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for (Ball ball : balls) {
ball.draw(g2);
}
}
#Override
// set the component's size in a safe way
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (!BallImages.this.isDisplayable()) {
((Timer) e.getSource()).stop();
}
for (Ball ball : balls) {
ball.move(PREF_W, PREF_H);
}
repaint();
}
}
private class Ball {
private int width;
private Color color;
private double x;
private double y;
private double direction;
private double speed;
private double deltaX;
private double deltaY;
public Ball(int width, Color color, int x, int y, double direction,
double speed) {
this.width = width;
this.color = color;
this.x = x;
this.y = y;
this.direction = direction;
this.speed = speed;
deltaX = speed * Math.cos(direction);
deltaY = speed * Math.sin(direction);
}
public int getWidth() {
return width;
}
public Color getColor() {
return color;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getDirection() {
return direction;
}
public double getSpeed() {
return speed;
}
public void draw(Graphics2D g2) {
g2.setColor(color);
int x2 = (int) (x - width / 2);
int y2 = (int) (y - width / 2);
g2.fillOval(x2, y2, width, width);
}
public void move(int containerWidth, int containerHeight) {
double newX = x + deltaX;
double newY = y + deltaY;
if (newX - width / 2 < 0) {
deltaX = Math.abs(deltaX);
newX = x + deltaX;
}
if (newY - width / 2 < 0) {
deltaY = Math.abs(deltaY);
newY = y + deltaY;
}
if (newX + width / 2 > containerWidth) {
deltaX = -Math.abs(deltaX);
newX = x + deltaX;
}
if (newY + width / 2 > containerHeight) {
deltaY = -Math.abs(deltaY);
newY = y + deltaY;
}
x = newX;
y = newY;
}
}
private static void createAndShowGui() {
BallImages mainPanel = new BallImages();
JFrame frame = new JFrame("BallImages");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.setResizable(false);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}

Physics with bouncing balls

Is this the right site to ask this question, since I've been referred to another site two times by now.
I'm trying to create bouncing balls with realistic physics. At the moment, when the balls hit each other, they bounce back in the same direction they were coming from, now, I've searched the internet for how to do this, but I only find things about how to detect collision, not what to do after. I don't know a lot about physics, are their topics I should learn first before I would be able to do this? Here's an image of how I imagine balls would bounce in real life. Is this how it works?
(source: thewombatguru.nl)
Also, do I have bad practises in my code? (probably, but I'm learning a lot and I like it)
This is my current code:
Asteroids.java
package Asteroids;
import javax.swing.*;
public class Asteroids {
public static void createAndShowGui() {
GamePanel gamePanel = new GamePanel();
JFrame frame = new JFrame("Asteroids");
frame.getContentPane().add(gamePanel);
frame.pack();
frame.setVisible(true);
frame.setResizable(false);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setLocation(2000, 50);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
GamePanel.java
package Asteroids;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
public class GamePanel extends JPanel implements ActionListener {
private final int WIDTH = 1000;
private final int HEIGHT = 1000;
Timer animationTimer;
ArrayList<Rock> rocks;
public GamePanel() {
Dimension preferredDimension = new Dimension(WIDTH, HEIGHT);
setPreferredSize(preferredDimension);
animationTimer = new Timer(10, this);
setUp();
}
public void setUp() {
rocks = new ArrayList<>();
rocks.add(new Rock(475, 1000, 0, -1));
rocks.add(new Rock(0, 500, 1, 0));
//rocks.add(new Rock(300, 270, -2, 2));
//rocks.add(new Rock(400, 315, -5, -1));
animationTimer.start();
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
for (Rock rock : rocks) {
for (Rock rockToCheck : rocks) {
if (!rock.equals(rockToCheck)) {
rock.checkForCollisionWithRocks(rockToCheck);
}
}
rock.checkForCollisionWithFrame(WIDTH, HEIGHT);
rock.setxPos(rock.getxPos() + rock.getxVelocity());
rock.setyPos(rock.getyPos() + rock.getyVelocity());
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
RenderingHints mainRenderingHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHints(mainRenderingHints);
for (Rock rock : rocks) {
rock.display(g2d);
}
g2d.dispose();
}
}
Rock.java
package Asteroids;
import java.awt.*;
public class Rock {
private int xPos;
private int yPos;
private int rockWidth;
private int rockHeight;
private int xVelocity;
private int yVelocity;
private int rockRadius;
private Color rockColor;
public Rock(int xPos, int yPos, int xVelocity, int yVelocity) {
this.xPos = xPos;
this.yPos = yPos;
this.xVelocity = xVelocity;
this.yVelocity = yVelocity;
rockWidth = 50;
rockHeight = rockWidth;
rockRadius = rockWidth / 2;
rockColor = new Color((int) (Math.random() * 255),(int) (Math.random() * 255),(int) (Math.random() * 255));
}
public void setxPos(int xPos) {
this.xPos = xPos;
}
public int getxPos() {
return xPos;
}
public int getRockWidth() {
return rockWidth;
}
public void setRockWidth(int rockWidth) {
this.rockWidth = rockWidth;
}
public int getRockHeight() {
return rockHeight;
}
public void setRockHeight(int rockHeight) {
this.rockHeight = rockHeight;
}
public int getyPos() {
return yPos;
}
public void setyPos(int yPos) {
this.yPos = yPos;
}
public int getxVelocity() {
return xVelocity;
}
public void setxVelocity(int xVelocity) {
this.xVelocity = xVelocity;
}
public int getyVelocity() {
return yVelocity;
}
public void setyVelocity(int yVelocity) {
this.yVelocity = yVelocity;
}
public int getRockRadius() {
return rockRadius;
}
public void setRockRadius(int rockRadius) {
this.rockRadius = rockRadius;
}
public void checkForCollisionWithRocks(Rock rock) {
int radiusOfBoth = rock.getRockRadius() + rockRadius;
int horDistance = Math.abs((rock.getxPos() + rock.getRockRadius()) - (xPos + rockRadius));
int verDistance = Math.abs((rock.getyPos() + rock.getRockRadius()) - (yPos + rockRadius));
int diagDistance = (int) Math.sqrt(Math.pow(horDistance, 2) + Math.pow(verDistance, 2));
if (diagDistance <= radiusOfBoth) {
xVelocity = -xVelocity;
yVelocity = -yVelocity;
rock.setxVelocity(-rock.getxVelocity());
rock.setyVelocity(-rock.getyVelocity());
rock.setxPos(rock.getxPos() + rock.getxVelocity());
rock.setyPos(rock.getyPos() + rock.getyVelocity());
}
}
public void checkForCollisionWithFrame(final int WIDTH, final int HEIGHT) {
if (xPos < 0) {
xVelocity *= -1;
xPos = 0;
} else if (xPos + rockWidth > WIDTH) {
xVelocity *= -1;
xPos = WIDTH - rockWidth;
}
if (yPos < 0) {
yVelocity *= -1;
yPos = 0;
} else if (yPos + rockHeight > HEIGHT) {
yVelocity *= -1;
yPos = HEIGHT - rockHeight;
}
}
public void display(Graphics2D g2d) {
g2d.setColor(rockColor);
g2d.fillOval(xPos, yPos, rockWidth, rockHeight);
}
}
Can anyone help ?
You can start with the law of conservation of momentum. When two objects collide the total momentum will not change, you can use this link to understand the calculations behind while trying to predict the trajectories of two objects after they collide.
As for your code, you seem to be missing a crucial field mass in your Rock.java. You need mass in order to calculate the momentum of an object which later you will use to predict the trajectories after the objects collide.
EDIT: Note that the examples in the link are somewhat limited to 2 directions where the objects collide against each other with 180* between them. However it is not that hard to generalize and find the velocity of an object after the collision by breaking down the momentum/velocity vectors to 4 directions, that are 0,90,180,270 degrees. You have to use vector math to express the speed of a given object in terms of the 4 directions.
Real life physics is tricky (gravity, inertia, etc), but to start with, bouncing the balls off of each other:
When the two balls hit, there's an angle of collision. Luckily, because they are circles (assuming) you can find the angle of incidence by finding the angle of the line going through the center of the two balls. Then you want to send 1 ball off 1 way perpendicular the that line, and the other the other way.
Make sense?
Answer here:
Also there is a nice gif,you can easily find out,how to calculate the new velocities ;)
Ball to Ball Collision - Detection and Handling

Simulating rain

I am making a game in java and I want to create a simulation of a cloud that is pouring rain. The cloud is supposed to move to the right while raining. Moving the cloud is no problem. It's the rain that I am struggling with.
What I was thinking of doing was with a timer to draw a rectangle, thats supposed to look like falling rain at a random x value inside of the cloud. And then add 1 to the y value of the drop each 100 millisecond. But I don't want to create 100 different rectangles, x variables and y variables for each rain drop.
Any idea how I can accomplish this? Suggestions appreciated!
It is a 2d game.. Sorry.
One approach would be to consider a marquee on a theater. You take a series of bulbs and, by lighting and extinguishing them in sequence, you can simulate linear motion.
In the same way, rather than creating raindrops and animating their movement, why not creating multiple raindrops that are invisible and show and hide them in sequence to simulate downward motion. Then, you would have a series of arrays representing a raindrop track and you simply need to cycle through then, hiding the current one, incrementing the array pointer and displaying that one.
Is it a requirement that the rain drops be programmed? Traditionally, this would be done with a few rain sprites that you place under the cloud and animate so that it looks like the rain is falling.
I would recommend just storing the values as an ArrayList of objects.
class Raindrop {
private int x;
private int y;
public void fall() {
y--;
}
}
Then make an ArrayList with a generic type.
ArrayList<Raindrop> drops = new ArrayList<Raindrop>();
To make each drop fall,
for (int i=0; i<drops.length(); i++) {
drops.get(i).fall();
}
Here is my java (swing) implementation of 2d rain with drops, splash, wind and gravity
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main( String [] args ) {
JFrame frame = new JFrame();
frame.setSize(800, 300);
final RPanel rPanel=new RPanel();
frame.add(rPanel);
frame.setVisible( true );
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
rPanel.stop();
System.exit(0);
}
});
}
}
class RPanel extends JPanel {
//*********SETTINGS****************************
private float mWind = 2.05f;
private float mGravity = 9.8f;
private double mRainChance = 0.99; // from 0 to 1
private int mRepaintTimeMS = 16;
private float mRainWidth=1;
private double mDdropInitialVelocity = 20;
private double mDropDiam = 2;
private Color mColor=new Color(0, 0, 255);
//*********************************************
private ArrayList<Rain> rainV;
private ArrayList<Drop> dropV;
private UpdateThread mUpdateThread;
public RPanel() {
rainV = new ArrayList<>();
dropV = new ArrayList<>();
mUpdateThread=new UpdateThread();
mUpdateThread.start();
}
public void stop() {
mUpdateThread.stopped=true;
}
public int getHeight() {
return this.getSize().height;
}
public int getWidth() {
return this.getSize().width;
}
private class UpdateThread extends Thread {
public volatile boolean stopped=false;
#Override
public void run() {
while (!stopped) {
RPanel.this.repaint();
try {
Thread.sleep(mRepaintTimeMS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(mRainWidth));
g2.setColor(mColor);
//DRAW DROPS
Iterator<Drop> iterator2 = dropV.iterator();
while (iterator2.hasNext()) {
Drop drop = iterator2.next();
drop.update();
drop.draw(g2);
if (drop.y >= getHeight()) {
iterator2.remove();
}
}
//DRAW RAIN
Iterator<Rain> iterator = rainV.iterator();
while (iterator.hasNext()) {
Rain rain = iterator.next();
rain.update();
rain.draw(g2);
if (rain.y >= getHeight()) {
//create new drops (2-8)
long dropCount = 1 + Math.round(Math.random() * 4);
for (int i = 0; i < dropCount; i++) {
dropV.add(new Drop(rain.x, getHeight()));
}
iterator.remove();
}
}
//CREATE NEW RAIN
if (Math.random() < mRainChance) {
rainV.add(new Rain());
}
}
//*****************************************
class Rain {
float x;
float y;
float prevX;
float prevY;
public Rain() {
Random r = new Random();
x = r.nextInt(getWidth());
y = 0;
}
public void update() {
prevX = x;
prevY = y;
x += mWind;
y += mGravity;
}
public void draw(Graphics2D g2) {
Line2D line = new Line2D.Double(x, y, prevX, prevY);
g2.draw(line);
}
}
//*****************************************
private class Drop {
double x0;
double y0;
double v0; //initial velocity
double t; //time
double angle;
double x;
double y;
public Drop(double x0, double y0) {
super();
this.x0 = x0;
this.y0 = y0;
v0 = mDdropInitialVelocity;
angle = Math.toRadians(Math.round(Math.random() * 180)); //from 0 - 180 degrees
}
private void update() {
// double g=10;
t += mRepaintTimeMS / 100f;
x = x0 + v0 * t * Math.cos(angle);
y = y0 - (v0 * t * Math.sin(angle) - mGravity * t * t / 2);
}
public void draw(Graphics2D g2) {
Ellipse2D.Double circle = new Ellipse2D.Double(x, y, mDropDiam, mDropDiam);
g2.fill(circle);
}
}
}
You can use a particle system or use a vector of raindrops and animate them every X milliseconds. A link to a particle system library: http://code.google.com/p/jops/
example code for vector:
import java.util.Vector;
// In your class
Vector raindrops;
void animate()
{
ListIterator iter = raindrops.listIterator;
while (iter.hasNext()) {
((Raindrop)iter.next()).moveDown();
}
}

Categories

Resources