Here is the task:
Ants move in one place in the region of their residence (for example, [0; 0]) in a straight line with a speed V, and then turn back to the point of their birth with the same speed.I have problems with the moving of objects. The object must stop at the certain point and go back to starting point. How should I fix my code? Some code I have written:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
class vThread extends Thread{
public void run(){
new LabSevenFirst();
System.out.println(Thread.currentThread().getName());
}
}
public class LabSevenFirst extends JPanel implements ActionListener {
private JFrame fr;
double x = 10;
double y = 10;
double r = 10;
public static double T=0, V;
private float x1, y1, x2, y2, xc, yc;
private int t0;
private Timer timer;
private JButton start, stop, apply;
private JLabel forx1, fory1, forx2, fory2, forV;
private JTextField fx1, fy1, fx2, fy2, fV;
public static void main(String[] args) throws InterruptedException {
vThread mt = new vThread();
mt.setName("Ants-labours");
mt.start();
Thread.yield();//позволяет досрочно завершить квант времени текущей нити
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName());
}
LabSevenFirst() {
t0 = 1000/60;
timer = new Timer(t0, this);
timer.setActionCommand("timer");
fr = new JFrame("Movement of ants-labours");
fr.setLayout(null);
fr.setSize(600, 600);
fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 50, 300, 300);
start = new JButton("Start");
stop = new JButton("Stop");
apply = new JButton("Apply");
forx1 = new JLabel("x1");
fory1 = new JLabel("y1");
forx2 = new JLabel("x2");
fory2 = new JLabel("y2");
forV = new JLabel("V");
fx1 = new JTextField(x1 + "");
fy1 = new JTextField(y1 + "");
fx2 = new JTextField(x2 + "");
fy2 = new JTextField(y2 + "");
fV = new JTextField(V + "");
forx1.setBounds(5, 380, 20, 20);
fory1.setBounds(5, 400, 20, 20);
forx2.setBounds(5, 420, 20, 20);
fory2.setBounds(5, 440, 20, 20);
forV.setBounds(5, 460, 20, 20);
fx1.setBounds(30, 380, 40, 20);
fy1.setBounds(30, 400, 40, 20);
fx2.setBounds(30, 420, 40, 20);
fy2.setBounds(30, 440, 40, 20);
fV.setBounds(30, 460, 40, 20);
start.setActionCommand("start");
stop.setActionCommand("stop");
apply.setActionCommand("apply");
start.addActionListener(this);
stop.addActionListener(this);
apply.addActionListener(this);
start.setBounds(300, 430, 80, 20);
stop.setBounds(390, 430, 80, 20);
apply.setBounds(210, 430, 80, 20);
fr.add(this);
fr.add(start);
fr.add(stop);
fr.add(apply);
fr.add(forx1);
fr.add(fory1);
fr.add(forx2);
fr.add(fory2);
fr.add(forV);
fr.add(fx1);
fr.add(fy1);
fr.add(fx2);
fr.add(fy2);
fr.add(fV);
fr.setVisible(true);
}
#Override
protected void paintComponent(Graphics g) {
int width = getWidth();
int height = getHeight();
//System.out.println("width" + width);
// System.out.println("height" + height);
g.setColor(Color.yellow);
g.fillRect(0, 0, width, height);
Graphics2D g2d = (Graphics2D) g;
g2d.setStroke(new BasicStroke(3f));
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//double x = 0.5 * width;
//double y = 0.5 * height;
double r = 0.75 * Math.min(x, y);
double dx,dy;
double t,l;
l=Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2));
// System.out.println("!!l!!"+l);
t= l/V;
//System.out.println("!!t!!"+t);
g2d.setColor(Color.black);
if(T<t) {
dx = ((x2 - x1) / (Math.sqrt(Math.pow(x2 - x1, 2)) + Math.pow(y2 - y1, 2)));
//System.out.println("!!dx!!" + dx);
dy = ((y2 - y1) / (Math.sqrt(Math.pow(x2 - x1, 2)) + Math.pow(y2 - y1, 2)));
//System.out.println("!!dy!!" + dy);
x += x1 + dx * V * T;//+ dx * (V * T);
//System.out.println("!!x!!" + x);
//System.out.println("!!x1!!" + x1);
y += y1 + dy * V * T;// + dy * (V * T);
r = Math.max(0.1 * r, 5);
// System.out.println("!!y!!" + y);
//System.out.println("!!y1!!" + x1);
}
if (x==x2 && y == y2 && T>t) {
dx = ((x2 - x1) / (Math.sqrt(Math.pow(x2 - x1, 2)) + Math.pow(y2 - y1, 2)));
dy = ((y2 - y1) / (Math.sqrt(Math.pow(x2 - x1, 2)) + Math.pow(y2 - y1, 2)));
x -= x1 + dx * V * T;//+ dx * (V * T);
y -= y1 + dy * V * T;// + dy * (V * T);
r = Math.max(0.1 * r, 5);
}
g2d.fill(circle(x,y,r));
//if (x == x2 && y == y2)
// x = x1 -
}
public Shape circle(double x, double y, double R){
return new Ellipse2D.Double(x - r, y - r, 2 * r, 2 * r);
}
#Override
public void actionPerformed(ActionEvent e) {
switch (e.getActionCommand()) {
case "stop": {
timer.stop();
break;
}
case "start": {
timer.start();
break;
}
case "apply": {
float ax1, ay1, bx2, by2, cv;
try {
ax1 = Float.parseFloat(fx1.getText());
ay1 = Float.parseFloat(fy1.getText());
bx2 = Float.parseFloat(fx2.getText());
by2 = Float.parseFloat(fy2.getText());
cv = Float.parseFloat(fV.getText());
x1 = ax1;
y1 = ay1;
x2 = bx2;
y2 = by2;
V = cv;
repaint();
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(null, "Invalid input", "Error",
JOptionPane.ERROR_MESSAGE);
}
break;
}
case "timer": {
T += 0.6;
System.out.println("!!T!!"+T);
repaint();
break;
}
}
}
}
The OP defined a task:
Ants move in one place in the region of their residence (for example,
[0; 0]) in a straight line with a speed V, and then turn back to the
point of their birth with the same speed.I have problems with the
moving of objects. The object must stop at the certain point and go
back to starting point.
And then he asked?
How should I fix my code?
It's too late. There's too many lines of code to debug and test.
So let's start over.
Here's the first iteration of the new code.
import javax.swing.SwingUtilities;
public class MovingAnts implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new MovingAnts());
}
public MovingAnts() {
}
#Override
public void run() {
// TODO Auto-generated method stub
}
}
We can test this code by running it and observing that it does not abend.
So, let's add a bit more code. We know we're going to have to define one or more ants. So, let's create an Ant class.
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import javax.swing.SwingUtilities;
public class MovingAnts implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new MovingAnts());
}
private List<Ant> ants;
public MovingAnts() {
ants = new ArrayList<>();
Point origin = new Point(10, 10);
Point destination = new Point(200, 300);
Ant ant = new Ant(5.0d, origin, destination);
ants.add(ant);
}
#Override
public void run() {
// TODO Auto-generated method stub
}
public class Ant {
private final double velocity;
private Point position;
private final Point startPosition;
private final Point endPosition;
public Ant(double velocity, Point startPosition,
Point endPosition) {
this.velocity = velocity;
this.startPosition = startPosition;
this.endPosition = endPosition;
}
public double getVelocity() {
return velocity;
}
public Point getPosition() {
return position;
}
public void setPosition(Point position) {
this.position = position;
}
public Point getStartPosition() {
return startPosition;
}
public Point getEndPosition() {
return endPosition;
}
}
}
We've defined a velocity (speed), a starting position, and an ending position. According to the task description, these values don't change, so we can mark them final and define them in the constructor.
We've also defined a current position. The current position will be important later when it's time to draw the ant on a drawing JPanel.
We will probably add more to the Ant class as we develop more code. But for now, we have a class that holds the important variables for a ant.
We defined an ant (one instance of the Ant class) and saved the ant in a List<Ant> in the MovingAnts constructor. We can define more later, but let's start with one ant.
Now, we can create the JFrame and drawing JPanel for the ants.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class MovingAnts implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new MovingAnts());
}
private Dimension drawingPanelSize;
private DrawingPanel drawingPanel;
private List<Ant> ants;
public MovingAnts() {
drawingPanelSize = new Dimension(400, 400);
ants = new ArrayList<>();
Point origin = new Point(10, 10);
Point destination = new Point(200, 300);
Ant ant = new Ant(5.0d, origin, destination);
ants.add(ant);
}
#Override
public void run() {
JFrame frame = new JFrame("Moving Ants");
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(
drawingPanelSize);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
public DrawingPanel(Dimension drawingPanelSize) {
this.setPreferredSize(drawingPanelSize);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
}
}
public class Ant {
private final double velocity;
private Point position;
private final Point startPosition;
private final Point endPosition;
public Ant(double velocity, Point startPosition,
Point endPosition) {
this.velocity = velocity;
this.startPosition = startPosition;
this.endPosition = endPosition;
}
public double getVelocity() {
return velocity;
}
public Point getPosition() {
return position;
}
public void setPosition(Point position) {
this.position = position;
}
public Point getStartPosition() {
return startPosition;
}
public Point getEndPosition() {
return endPosition;
}
}
}
Notice how every method and class is short and to the point. No person can read and understand hundreds of lines of code in a single method.
We've added a little bit of code at a time and tested each bit of code by running the application. At his point, we have a GUI. We also don't have any abends. Both the GUI and the lack of abends are important.
We defined the size of the drawing panel. This is important. We don't care how big the JFrame is. We care how big the drawing JPanel is, so we can keep the ants within the bounds of the drawing panel.
We haven't put any code in the paintComponent method of the drawing panel yet. Before we can do that, we have to create an Animation class that will update the position of the ants.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class MovingAnts implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new MovingAnts());
}
private Animation animation;
private Dimension drawingPanelSize;
private DrawingPanel drawingPanel;
private List<Ant> ants;
public MovingAnts() {
drawingPanelSize = new Dimension(400, 400);
ants = new ArrayList<>();
Point origin = new Point(200, 200);
Point destination = new Point(300, 350);
Ant ant = new Ant(30.0d, origin, destination);
ants.add(ant);
}
#Override
public void run() {
JFrame frame = new JFrame("Moving Ants");
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(
drawingPanelSize);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
animation = new Animation();
new Thread(animation).start();
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
public DrawingPanel(Dimension drawingPanelSize) {
this.setPreferredSize(drawingPanelSize);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.BLACK);
for (Ant ant : ants) {
Point position = ant.getPosition();
g.fillOval(position.x - 4,
position.y - 4, 8, 8);
}
}
}
public class Animation implements Runnable {
private volatile boolean running;
public Animation() {
this.running = true;
}
#Override
public void run() {
int fps = 20;
long delay = 1000L / fps;
while (running) {
calculateAntPosition(fps);
updateDrawingPanel();
sleep(delay);
}
}
private void calculateAntPosition(int fps) {
for (Ant ant : ants) {
ant.calculatePosition(fps);
// System.out.println(ant.getPosition());
}
}
private void updateDrawingPanel() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
drawingPanel.repaint();
}
});
}
private void sleep(long duration) {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
// Deliberately left empty
}
}
public synchronized void setRunning(
boolean running) {
this.running = running;
}
}
public class Ant {
private boolean returning;
private double totalDistance;
private double traveledDistance;
private double theta;
private final double velocity;
private Point position;
private final Point startPosition;
private final Point endPosition;
public Ant(double velocity, Point startPosition,
Point endPosition) {
this.velocity = velocity;
this.startPosition = startPosition;
this.position = startPosition;
this.endPosition = endPosition;
this.returning = false;
this.theta = calculateTheta();
this.totalDistance = calculateTotalDistance();
this.traveledDistance = 0d;
}
private double calculateTheta() {
return Math.atan2((endPosition.y - startPosition.y),
endPosition.x - startPosition.x);
}
private double calculateTotalDistance() {
double diffX = endPosition.x - startPosition.x;
double diffY = endPosition.y - startPosition.y;
return Math.sqrt((diffX * diffX) + (diffY * diffY));
}
public double getVelocity() {
return velocity;
}
public Point getPosition() {
return position;
}
public void calculatePosition(int fps) {
double distance = velocity / fps;
double angle = theta;
if (returning) {
angle += Math.PI;
}
int x = (int) Math.round(
position.x + distance * Math.cos(angle));
int y = (int) Math.round(
position.y + distance * Math.sin(angle));
traveledDistance += distance;
if (traveledDistance > totalDistance) {
returning = !returning;
traveledDistance = 0d;
}
this.position = new Point(x, y);
}
public Point getStartPosition() {
return startPosition;
}
public Point getEndPosition() {
return endPosition;
}
}
}
I added way too much code this iteration, but we now have an ant that walks back and forth between two points.
The Animation class is a Runnable that runs in a Thread. You could use a Swing Timer, but it's easier for me to create the Runnable.
The Ant class grew some chest hair. All the trigonomic calculations can be found in the Ant class. Basically, I used polar coordinates to calculate the position of the ant.
The paintComponent method of the drawing panel simply draws the ants.
Every method and class is small and hopefully, easy to understand. Write short methods. Write short classes.
Hopefully, this code will provide a solid base for you to expand your project.
Related
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"
I'm trying to make my Pedestrian object move, and it moves but at a certain point it flies away from the screen. The Pedestrian moves by a List of points. First the Pedestrian is added to toDraw to paint it and in startAndCreateTimer I loop through the same list to move the Vehicles Maybe it's because of this line i = (double) diff / (double) playTime; I actually don't want to set a playtime how not to do that, could this be the problem or is it something else? Here a link with the point where the Pedestrian flies away (starts north of left roundabout) http://gyazo.com/23171a6106c88f1ba8ca438598ff4153.
class Surface extends JPanel{
Track track=new Track();
public List<Vehicle> toDraw = new ArrayList<>();
private Long startTime;
private long playTime = 4000;
private double i;
public Surface(){
startAndCreateTimer();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
//Make sure the track is painted first
track.paint(g);
for (Vehicle v : toDraw) {
v.paint(g);
}
}
public void repaintPanel(){
this.repaint();
}
private void startAndCreateTimer(){
Timer timer = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = System.currentTimeMillis();
}
long now = System.currentTimeMillis();
long diff = now - startTime;
i = (double) diff / (double) playTime;
for (Vehicle v : toDraw){
v.update(i);
}
repaintPanel();
}
});
timer.start();
}
}
Pedestrian java
public class Pedestrian extends Vehicle {
BufferedImage pedestrian;
Point pedestrianPosition;
double pedestrianRotation = 0;
int pedestrianW, pedestrianH;
int counter=0;
List<LanePoint>pedestrianPath;
boolean lockCounter=false;
public Pedestrian(int x, int y){
try {
pedestrian = ImageIO.read(Car.class.getResource("images/human.png"));
} catch (IOException e) {
System.out.println("Problem loading pedestrian images: " + e);
}
pedestrianPosition = new Point(x,y);
pedestrianW = pedestrian.getWidth();
pedestrianH = pedestrian.getHeight();
}
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.rotate(Math.toRadians(pedestrianRotation), pedestrianPosition.x, pedestrianPosition.y);
g2d.drawImage(pedestrian, pedestrianPosition.x, pedestrianPosition.y, null);
}
#Override
public void setPath(List<LanePoint> path) {
pedestrianPath=path;
}
/*Update*/
#Override
public void update(double i){
if (counter < pedestrianPath.size()) {
Point startPoint = new Point(pedestrianPosition.x, pedestrianPosition.y);
LanePoint endPoint = new LanePoint(pedestrianPath.get(counter).x, pedestrianPath.get(counter).y,pedestrianPath.get(counter).lanePointType,pedestrianPath.get(counter).lanePointToTrafficLight,pedestrianPath.get(counter).laneTrafficLightId,pedestrianPath.get(counter).degreesRotation);
pedestrianPosition.x=(int)Maths.lerp(startPoint.x,endPoint.x,i);
pedestrianPosition.y=(int)Maths.lerp(startPoint.y,endPoint.y,i);
pedestrianRotation=endPoint.degreesRotation;
if(pedestrianPosition.equals(new Point(endPoint.x,endPoint.y))){
/*PEDESTRIAN SIGN UP*/
if (endPoint.lanePointType.equals(LanePoint.PointType.TRAFFICLIGHT) && endPoint.lanePointToTrafficLight.equals(LanePoint.PointToTrafficLight.INFRONTOF)){
try {
Roundabout.client.sendBytes(new byte []{0x03,endPoint.laneTrafficLightId.byteValue(),0x01,0x00});
} catch (IOException ex) {
ex.printStackTrace();
}
}
/*PEDESTRIAN SIGN OFF*/
else if (endPoint.lanePointType.equals(LanePoint.PointType.TRAFFICLIGHT) && endPoint.lanePointToTrafficLight.equals(LanePoint.PointToTrafficLight.UNDERNEATH)) {
if (Surface.trafficLights.get(endPoint.laneTrafficLightId).red) {
lockCounter = true;
} else {
try {
Roundabout.client.sendBytes(new byte[]{0x03, endPoint.laneTrafficLightId.byteValue(), 0x00, 0x00});
lockCounter=false;
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
if (!lockCounter) {
counter++; //Increment counter > sets next point
}
}
}
}
}
Maths.java
public class Maths {
//Lineat interpolation
public static double lerp(double a, double b, double t) {
return a + (b - a) * t;
}
}
So, basically you are calculating the position of the object between to points based on the amount of time that has passed. This is good.
So at t = 0, the object will be at the start point, at t = 0.5, it will be halfway between the start and end point, at t = 1.0 it will be at the end point.
What happens when t > 1.0? Where should the object be? - hint, it should be nowhere as it should have been removed or reset...
This and this are basic examples of "time line" based animation, meaning that, over a period of time, the position of the object is determined by using different points (along a time line)
So, in order to calculate the position along a line, you need three things, the point you started at, the point you want to end at and the duration (between 0-1)
Using these, you can calculate the point along the line between these two points based on the amount of time.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class TestPane extends JPanel {
protected static final double PLAY_TIME = 4000.0;
private Point2D startAt = new Point(0, 0);
private Point2D endAt = new Point(200, 200);
private Point2D current = startAt;
private Long startTime;
public TestPane() {
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = System.currentTimeMillis();
}
long time = System.currentTimeMillis() - startTime;
double percent = (double) time / PLAY_TIME;
if (percent > 1.0) {
percent = 1.0;
((Timer) e.getSource()).stop();
}
current = calculateProgress(startAt, endAt, percent);
repaint();
}
});
timer.start();
}
protected Point2D calculateProgress(Point2D startPoint, Point2D targetPoint, double progress) {
Point2D point = new Point2D.Double();
if (startPoint != null && targetPoint != null) {
point.setLocation(
calculateProgress(startPoint.getX(), targetPoint.getY(), progress),
calculateProgress(startPoint.getX(), targetPoint.getY(), progress));
}
return point;
}
protected double calculateProgress(double startValue, double endValue, double fraction) {
return startValue + ((endValue - startValue) * fraction);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.GREEN);
g2d.draw(new Line2D.Double(startAt, endAt));
g2d.setColor(Color.RED);
g2d.fill(new Ellipse2D.Double(current.getX() - 5, current.getY() - 5, 10, 10));
g2d.dispose();
}
}
}
So, using current = calculateProgress(startAt, endAt, percent);,
you can see that the dot moves evenly between the start and end points.
If we change it to something more like what you seem to be doing, current = calculateProgress(current, endAt, percent);,
you can see that it speeds down the line and finally eases out, which isn't what you really want...
Updated with time line theory
Let's imagine you have a time line, which has a length of t and along this time line, you have 5 events (or key frames) (e1 - e5), each occurring after each other.
e1 starts at 0 and e5 ends at 1
As you can see, the events occur at irregular intervals and run for different lengths of time.
t1 runs for 25% of the time line
t2 runs for 25% of the time line
t3 runs for 12.5% of the time line
t3 runs for 37.5% of the time line
So, based on t, you need to determine which events are been executed. So when t is 0.12, we are running about half way through t1 (between e1 & e2).
You then need to calculate local time/difference between the key frames (0-0.25 along the timeline)
localTime = 1.0 - ((t - e1) / (e2 - e1))
= 1.0 - ((0.12 - 0) / (0.25 - 0))
= 1.0 - (0.12 / 0.25)
= 1.0 - 0.48
= 0.52
Where t is the time along the time line, e1 is the time of the first event (0) and e2 is the time of the second event (0.25), which gives us the duration along the t1 (in this example)
This is then the value of your linear interpolation for the given time slice.
Runnable example...
I took a look at your code, but there's a lot of work that needs to be done to get this to work.
Basically, you need to know how long the path is and the amount that each segment is of that path (as a percentage). With this, we can create a "time line" of "key frames" which determines how far along the "path" your object is based on the amount of time that has passed and the amount of time it "should" take to travel.
So, the first thing I did was create a Path class (kind of mimics your Lists, but has some additional methods)
public class Path implements Iterable<Point> {
private List<Point> points;
private double totalLength = 0;
public Path(Point... points) {
this.points = new ArrayList<>(Arrays.asList(points));
for (int index = 0; index < size() - 1; index++) {
Point a = get(index);
Point b = get(index + 1);
double length = lengthBetween(a, b);
totalLength += length;
}
}
public double getTotalLength() {
return totalLength;
}
public int size() {
return points.size();
}
public Point get(int index) {
return points.get(index);
}
public double lengthBetween(Point a, Point b) {
return Math.sqrt(
(a.getX() - b.getX()) * (a.getX() - b.getX())
+ (a.getY() - b.getY()) * (a.getY() - b.getY()));
}
#Override
public Iterator<Point> iterator() {
return points.iterator();
}
}
Mostly, this provides the totalLength of the path. We use this to calculate how much each segment takes up later
I then borrowed the TimeLine class from this previous answer
public class Timeline {
private Map<Double, KeyFrame> mapEvents;
public Timeline() {
mapEvents = new TreeMap<>();
}
public void add(double progress, Point p) {
mapEvents.put(progress, new KeyFrame(progress, p));
}
public Point getPointAt(double progress) {
if (progress < 0) {
progress = 0;
} else if (progress > 1) {
progress = 1;
}
KeyFrame[] keyFrames = getKeyFramesBetween(progress);
double max = keyFrames[1].progress - keyFrames[0].progress;
double value = progress - keyFrames[0].progress;
double weight = value / max;
return blend(keyFrames[0].getPoint(), keyFrames[1].getPoint(), 1f - weight);
}
public KeyFrame[] getKeyFramesBetween(double progress) {
KeyFrame[] frames = new KeyFrame[2];
int startAt = 0;
Double[] keyFrames = mapEvents.keySet().toArray(new Double[mapEvents.size()]);
while (startAt < keyFrames.length && keyFrames[startAt] <= progress) {
startAt++;
}
if (startAt >= keyFrames.length) {
startAt = keyFrames.length - 1;
}
frames[0] = mapEvents.get(keyFrames[startAt - 1]);
frames[1] = mapEvents.get(keyFrames[startAt]);
return frames;
}
protected Point blend(Point start, Point end, double ratio) {
Point blend = new Point();
double ir = (float) 1.0 - ratio;
blend.x = (int) (start.x * ratio + end.x * ir);
blend.y = (int) (start.y * ratio + end.y * ir);
return blend;
}
public class KeyFrame {
private double progress;
private Point point;
public KeyFrame(double progress, Point point) {
this.progress = progress;
this.point = point;
}
public double getProgress() {
return progress;
}
public Point getPoint() {
return point;
}
}
}
Now, as they stand, they are not compatible, we need to take each segment and calculate the length of the segment as a percentage of the total length of the path and create a key frame for the specified point along the time line...
double totalLength = path.getTotalLength();
timeLine = new Timeline();
timeLine.add(0, path.get(0));
// Point on time line...
double potl = 0;
for (int index = 1; index < path.size(); index++) {
Point a = path.get(index - 1);
Point b = path.get(index);
double length = path.lengthBetween(a, b);
double normalised = length / totalLength;
// Normalised gives as the percentage of this segment, we need to
// translate that to a point on the time line, so we just add
// it to the "point on time line" value to move to the next point :)
potl += normalised;
timeLine.add(potl, b);
}
I did this deliberately, to show the work you are going to need to do.
Need, I create a Ticker, which just runs a Swing Timer and reports ticks to Animations
public enum Ticker {
INSTANCE;
private Timer timer;
private List<Animation> animations;
private Ticker() {
animations = new ArrayList<>(25);
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Prevent possible mutatation issues...
Animation[] anims = animations.toArray(new Animation[animations.size()]);
for (Animation animation : anims) {
animation.tick();
}
}
});
}
public void add(Animation animation) {
animations.add(animation);
}
public void remove(Animation animation) {
animations.remove(animation);
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
}
public interface Animation {
public void tick();
}
This centralises the "clock", be allows Animations to determine what they would like to do on each tick. This should be more scalable then creating dozens of Timers
Okay, that's all fun and games, but how does it work together? Well, here's a complete runnable example.
It takes one of your own paths and creates a TimeLine out of it and animates a object moving along it.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
Path path = new Path(
new Point(440, 40),
new Point(440, 120),
new Point(465, 90),
new Point(600, 180),
new Point(940, 165),
new Point(940, 145),
new Point(1045, 105),
new Point(1080, 120),
new Point(1170, 120),
new Point(1200, 120),
new Point(1360, 123),
new Point(1365, 135),
new Point(1450, 170),
new Point(1457, 160),
new Point(1557, 160));
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane(path));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Ticker.INSTANCE.start();
}
});
}
public enum Ticker {
INSTANCE;
private Timer timer;
private List<Animation> animations;
private Ticker() {
animations = new ArrayList<>(25);
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Prevent possible mutatation issues...
Animation[] anims = animations.toArray(new Animation[animations.size()]);
for (Animation animation : anims) {
animation.tick();
}
}
});
}
public void add(Animation animation) {
animations.add(animation);
}
public void remove(Animation animation) {
animations.remove(animation);
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
}
public interface Animation {
public void tick();
}
public static final double PLAY_TIME = 4000d;
public class TestPane extends JPanel implements Animation {
private Path path;
private Path2D pathShape;
private Timeline timeLine;
private Long startTime;
private Point currentPoint;
public TestPane(Path path) {
this.path = path;
// Build the "path" shape, we can render this, but more importantally
// it allows use to determine the preferred size of the panel :P
pathShape = new Path2D.Double();
pathShape.moveTo(path.get(0).x, path.get(0).y);
for (int index = 1; index < path.size(); index++) {
Point p = path.get(index);
pathShape.lineTo(p.x, p.y);
}
// Build the time line. Each segemnt (the line between any two points)
// makes up a percentage of the time travelled, we need to calculate
// the amount of time that it would take to travel that segement as
// a percentage of the overall length of the path...this
// allows us to even out the time...
double totalLength = path.getTotalLength();
timeLine = new Timeline();
timeLine.add(0, path.get(0));
// Point on time line...
double potl = 0;
for (int index = 1; index < path.size(); index++) {
Point a = path.get(index - 1);
Point b = path.get(index);
double length = path.lengthBetween(a, b);
double normalised = length / totalLength;
// Normalised gives as the percentage of this segment, we need to
// translate that to a point on the time line, so we just add
// it to the "point on time line" value to move to the next point :)
potl += normalised;
timeLine.add(potl, b);
}
currentPoint = path.get(0);
Ticker.INSTANCE.add(this);
}
#Override
public Dimension getPreferredSize() {
Dimension size = pathShape.getBounds().getSize();
size.width += pathShape.getBounds().x;
size.height += pathShape.getBounds().y;
return size;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.GREEN);
g2d.draw(pathShape);
g2d.setColor(Color.RED);
g2d.fill(new Ellipse2D.Double(currentPoint.x - 5, currentPoint.y - 5, 10, 10));
g2d.dispose();
}
#Override
public void tick() {
if (startTime == null) {
startTime = System.currentTimeMillis();
}
long diff = System.currentTimeMillis() - startTime;
double t = (double)diff / PLAY_TIME;
if (t > 1.0) {
t = 1.0d;
// Don't call me any more, I'm already home
Ticker.INSTANCE.remove(this);
}
currentPoint = timeLine.getPointAt(t);
repaint();
}
}
public class Path implements Iterable<Point> {
private List<Point> points;
private double totalLength = 0;
public Path(Point... points) {
this.points = new ArrayList<>(Arrays.asList(points));
for (int index = 0; index < size() - 1; index++) {
Point a = get(index);
Point b = get(index + 1);
double length = lengthBetween(a, b);
totalLength += length;
}
}
public double getTotalLength() {
return totalLength;
}
public int size() {
return points.size();
}
public Point get(int index) {
return points.get(index);
}
public double lengthBetween(Point a, Point b) {
return Math.sqrt(
(a.getX() - b.getX()) * (a.getX() - b.getX())
+ (a.getY() - b.getY()) * (a.getY() - b.getY()));
}
#Override
public Iterator<Point> iterator() {
return points.iterator();
}
}
public class Timeline {
private Map<Double, KeyFrame> mapEvents;
public Timeline() {
mapEvents = new TreeMap<>();
}
public void add(double progress, Point p) {
mapEvents.put(progress, new KeyFrame(progress, p));
}
public Point getPointAt(double progress) {
if (progress < 0) {
progress = 0;
} else if (progress > 1) {
progress = 1;
}
KeyFrame[] keyFrames = getKeyFramesBetween(progress);
double max = keyFrames[1].progress - keyFrames[0].progress;
double value = progress - keyFrames[0].progress;
double weight = value / max;
return blend(keyFrames[0].getPoint(), keyFrames[1].getPoint(), 1f - weight);
}
public KeyFrame[] getKeyFramesBetween(double progress) {
KeyFrame[] frames = new KeyFrame[2];
int startAt = 0;
Double[] keyFrames = mapEvents.keySet().toArray(new Double[mapEvents.size()]);
while (startAt < keyFrames.length && keyFrames[startAt] <= progress) {
startAt++;
}
if (startAt >= keyFrames.length) {
startAt = keyFrames.length - 1;
}
frames[0] = mapEvents.get(keyFrames[startAt - 1]);
frames[1] = mapEvents.get(keyFrames[startAt]);
return frames;
}
protected Point blend(Point start, Point end, double ratio) {
Point blend = new Point();
double ir = (float) 1.0 - ratio;
blend.x = (int) (start.x * ratio + end.x * ir);
blend.y = (int) (start.y * ratio + end.y * ir);
return blend;
}
public class KeyFrame {
private double progress;
private Point point;
public KeyFrame(double progress, Point point) {
this.progress = progress;
this.point = point;
}
public double getProgress() {
return progress;
}
public Point getPoint() {
return point;
}
}
}
}
Now, if I was doing this, I would create a method either in Path or as a static utility method, that took a Path and returned a TimeLine automatically ;)
I'm working on a Java program that displays a map (inherited from JComponent) within a JScrollPane. When the MouseWheelListener fires, the map zooms and the JScrollPane's viewport is adjusted to center on the location of the mouse.
This all works fine, except that the call to setSize(Dimension d) forces the map to repaint immediately before the view is adjusted, causing a "stutter." However, I cannot adjust the view until after setSize has completed execution or the calculations for "centering" the viewport will be haywire (due to getHeight() and getWidth() calls,) therefore the viewport adjustment is within a runnable called with invokeLater.
I would like to find a way to move directly from the previous map size and viewport location to the new view, without seeing the scene repainted twice.
setIgnoreRepaint(boolean) did not work for me. Is there another way to go about this?
EDIT: Here's what I worked up from your sample code that replicates my issue, although not as noticably as there's far less computation going on in the drawing. If you scroll rapidly over the image, you'll see that there's a brief stutter between the resizing of the hexagons to their new size and the adjustment of the viewport to its new position.
You can see the hexagons being re-drawn twice. (Once when the setSize() method is called and once when the setViewPosition() method is called.)
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class ZoomWithSelectionInViewport implements MouseWheelListener{
private int zoom = 80;
JComponent b;
int hexSize = 3;
public ZoomWithSelectionInViewport() throws Exception{
b = new JComponent() {
private static final long serialVersionUID = 1L;
#Override
public Dimension getMinimumSize() {
return new Dimension(700, 700);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = ((Graphics2D) g);
int vertOffsetX, vertOffsetY, horizOffsetX, horizOffsetY;
vertOffsetX = (int)((double)hexSize* Math.sqrt(3.0f));
vertOffsetY = (int)((double)-hexSize-1* Math.sqrt(3.0f)/2.0f);
horizOffsetX = (int) ((double)hexSize* Math.sqrt(3.0f));
horizOffsetY = (int) ((double)hexSize+1* Math.sqrt(3.0f)/2.0f);
for(int x = 0; x < 50; x++)
{
for(int y = 0; y < 50; y++)
{
int[] xcoords = new int[6]; int[] ycoords = new int[6];
for(int i = 0; i < 6; i++)
{
xcoords[i] = (int)((hexSize+x * horizOffsetX + y * vertOffsetX) + (double)hexSize * Math.cos(i * 2 * Math.PI / 6));
ycoords[i] = (int)(((getSize().height /2 )+ x * horizOffsetY + y * vertOffsetY) + (double)hexSize * Math.sin(i * 2 * Math.PI / 6));
}
g2d.setStroke(new BasicStroke(hexSize/2.5f));
g2d.setColor(Color.GRAY);
g2d.drawPolygon(xcoords, ycoords, 6);
}
}
}
};
JScrollPane view = new JScrollPane(b);
b.addMouseWheelListener(this);
JFrame f = new JFrame();
f.setLocation(10, 10);
f.setDefaultCloseOperation(3);
f.add(view);
f.setSize(500,500);
f.setVisible(true);
view.setWheelScrollingEnabled(false);
}
public void mouseWheelMoved(MouseWheelEvent e) {
zoom = 100*-Integer.signum(e.getWheelRotation());
if(hexSize - Integer.signum(e.getWheelRotation()) > 0)
hexSize-= Integer.signum(e.getWheelRotation());
Dimension targetSize = new Dimension(b.getWidth()+zoom,b.getHeight()+zoom);
b.setPreferredSize(targetSize);
b.setSize(targetSize);
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JViewport tempView = (JViewport)b.getParent();
tempView.setViewPosition(new Point(b.getWidth()/2,b.getHeight()/2));
}
});
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
ZoomWithSelectionInViewport example = new ZoomWithSelectionInViewport();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
my curiosity, no idea what's happends, could you please use this SSCCE add there your issues and edit with the code your question
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class ZoomWithSelectionInViewport {
private Point startPoint = new Point(0, 0);
private Point rectLocale = new Point();
private Dimension rectSize = new Dimension();
private int zoom = 80;
private BufferedImage capture = null;
private BufferedImage raw;
public ZoomWithSelectionInViewport() throws Exception {
raw = new Robot().createScreenCapture(new Rectangle(
Toolkit.getDefaultToolkit().getScreenSize()));
MouseBehavior behavior = new MouseBehavior();
JPanel b = new JPanel() {
private static final long serialVersionUID = 1L;
#Override
public Dimension getMinimumSize() {
return new Dimension(500, 500);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = ((Graphics2D) g);
g2d.drawImage(raw, 0, 0, null);
if (capture != null) {
int width2 = (int) (rectSize.width + rectSize.width * (zoom / 500d));
int height2 = (int) (rectSize.height + rectSize.height * (zoom / 500d));
int x2 = rectLocale.x - ((width2 - rectSize.width) / 2);
int y2 = rectLocale.y - ((height2 - rectSize.height) / 2);
Image scaledInstance = capture.getScaledInstance(
width2, height2, Image.SCALE_AREA_AVERAGING);
g2d.drawImage(scaledInstance, x2, y2, null);
g2d.drawRect(x2, y2, width2, height2);
} else {
g2d.draw(new Rectangle(rectLocale, rectSize));
}
}
};
b.addMouseMotionListener(behavior);
b.addMouseListener(behavior);
b.addMouseWheelListener(behavior);
JFrame f = new JFrame();
f.setLocation(10, 10);
f.setDefaultCloseOperation(3);
f.add(b);
f.pack();
f.setVisible(true);
}
private class MouseBehavior extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
startPoint = e.getPoint();
rectLocale = new Point();
rectSize = new Dimension();
capture = null;
if (e.getSource() instanceof JComponent) {
((JComponent) e.getSource()).repaint();
}
}
#Override
public void mouseDragged(MouseEvent e) {
Point currentPoint = e.getPoint();
rectSize.width = Math.abs(currentPoint.x - startPoint.x);
rectSize.height = Math.abs(currentPoint.y - startPoint.y);
if (e.isShiftDown()) {
rectSize.width = rectSize.height = Math.min(rectSize.width, rectSize.height);
int dx = startPoint.x - rectSize.width;
int dy = startPoint.y - rectSize.height;
rectLocale.x = startPoint.x < currentPoint.x ? startPoint.x : Math.max(dx, dy);
rectLocale.y = startPoint.y < currentPoint.y ? startPoint.y : Math.min(dx, dy);
} else {
rectLocale.x = Math.min(currentPoint.x, startPoint.x);
rectLocale.y = Math.min(currentPoint.y, startPoint.y);
}
if (e.getSource() instanceof JComponent) {
((JComponent) e.getSource()).repaint();
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (rectSize.width <= 0 || rectSize.height <= 0) {
capture = null;
} else {
capture = raw.getSubimage(Math.max(0, rectLocale.x),
Math.max(0, rectLocale.y), rectSize.width, rectSize.height);
}
if (e.getSource() instanceof JComponent) {
((JComponent) e.getSource()).repaint();
}
}
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
zoom = Math.min(2000, Math.max(0, zoom + e.getUnitsToScroll() * 10));
if (e.getSource() instanceof JComponent) {
((JComponent) e.getSource()).repaint();
}
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
ZoomWithSelectionInViewport example = new ZoomWithSelectionInViewport();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
an alternative could be
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;
public class ZoomDemo {
private PaintSurface canvas = new PaintSurface();
private JFrame frame = new JFrame();
private AffineTransform aT = new AffineTransform();
private Point2D p1 = null;
private Point2D p2 = null;
public ZoomDemo() {
frame.setSize(500, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
ScaleListener scaleListener = new ScaleListener();
canvas.addMouseWheelListener(scaleListener);
canvas.addMouseListener(scaleListener);
frame.add(canvas);
frame.setVisible(true);
}
public class ScaleListener extends MouseAdapter {
private double scale = 1;
#Override
public void mouseClicked(MouseEvent e) {
p1 = e.getPoint();
try {
p2 = aT.inverseTransform(p1, new Point2D.Double());
/*
* p1 is the point relative to canvas where the user physically
* held the mouse.
*
* Since you may want to deal with a virtual mouse location
* relative to an untransformed canvas, you inverse transform p1
* to p2.
*
* For example: when the user held the mouse over, let's say,
* the displayed left upper corner of the red rectangle.
*
* p2 now will point to the upper left corner of the red
* rectangle in an untransformed canvas.
*/
applyScale();
} catch (NoninvertibleTransformException e1) {
e1.printStackTrace();
}
canvas.repaint();
}
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (p1 != null && p2 != null) {
scale -= (0.05 * e.getWheelRotation());
if (scale > 5) {
scale = 5;
}
if (scale < 1) {
scale = 1;
aT.setToIdentity();
} else {
applyScale();
}
canvas.repaint();
}
}
private void applyScale() {
aT.setToIdentity();
// *** variation one (your implementation)
aT.translate(p1.getX(), p1.getY());
aT.scale(scale, scale);
aT.translate(-p2.getX(), -p2.getY());
// *** variation two
// aT.translate(p1.getX(), p1.getY());
// aT.scale(scale, scale);
// aT.translate(-p1.getX(), -p1.getY());
// *** variation three
// aT.translate(p2.getX(), p2.getY());
// aT.scale(scale, scale);
// aT.translate(-p2.getX(), -p2.getY());
}
}
public class PaintSurface extends JComponent {
private static final long serialVersionUID = 1L;
{
this.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
}
/*
* Override paintComponent, not paint!!!
*/
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
try {
g2.setColor(Color.black);
g2.fillRect(0, 0, getWidth(), getHeight());
// g2.setTransform(aT); <<<<<<<<< !!!!!!!
/*
* A transform (translation for example) may already have been
* applied to the Graphics object by a parent. This is removed
* by setTransform.
*/
g2.transform(aT); // <<<<<<<<<< !!!!!!!
g2.setColor(Color.red);
g2.drawRect(50, 50, 100, 100);
g2.setColor(Color.blue);
g2.drawRect(200, 200, 150, 50);
if (p2 != null) {
g2.setColor(Color.green);
g2.fill(new Rectangle2D.Double(p2.getX() - 4, p2.getY() - 4, 8, 8));
}
} finally {
g2.dispose();
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
ZoomDemo zoomDemo = new ZoomDemo();
}
});
}
}
same question,
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
//http://stackoverflow.com/questions/6819243/jscrollpane-jumping-when-scrollbars-start-being-used
public class LockViewPortToPoint extends JFrame {
private static final long serialVersionUID = 1L;
public static void main(String[] arg) {
LockViewPortToPoint lockViewPortToPoint = new LockViewPortToPoint();
}
public LockViewPortToPoint() {
initComponents();
setVisible(true);
}
private void initComponents() {
setLayout(new BorderLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(600, 600);
setPreferredSize(new Dimension(600, 600));
add(new TopPanel());
}
private class TopPanel extends JPanel {
private static final long serialVersionUID = 1L;
private JScrollPane scrollPane;
TopPanel() {
setPreferredSize(new Dimension(500, 500));
scrollPane = new JScrollPane(new InteriorPanel());
scrollPane.setPreferredSize(new Dimension(500, 500));
scrollPane.getVerticalScrollBar().setPreferredSize(new Dimension(10, 490));
scrollPane.getHorizontalScrollBar().setPreferredSize(new Dimension(490, 10));
scrollPane.setWheelScrollingEnabled(false);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
add(scrollPane);
}
}
private class InteriorPanel extends JPanel {
private static final long serialVersionUID = 1L;
private double scale = 10.0;
private final double scaleModifier = 0.1;
private final int width = 10;
private Point loc = new Point(0, 0);
private final int SIZE = 10;
private Point orig = new Point(250, 250);
InteriorPanel() {
super(true);
setPreferredSize(new Dimension((int) (scale * width * SIZE), (int) (scale * width * SIZE)));
this.addMouseWheelListener(new MapMouseWheelListener());
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2D = (Graphics2D) g;
g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2D.scale(scale, scale);
for (int row = 0; row <= SIZE; row++) {
for (int col = 0; col < SIZE; col++) {
if ((col + row) % 2 == 0) {
g2D.setColor(Color.white);
} else {
g2D.setColor(Color.black);
}
g2D.fillRect(col * width, row * width, width, width);
}
}
}
private void incrementScale(int notches) {
double modifier = 0;
final double prevScale = scale;
if (notches != 0) {
modifier = 1.0 + -notches / Math.abs(notches) * scaleModifier;
}
scale *= Math.pow(modifier, Math.abs(notches));
/*if (scale * width < 1) {
scale = 1.0/width;
} else if (scale * width * 3 > parentHeight || scale * width * 3 > parentWidth) {
if (parentHeight > parentWidth) {
scale = parentWidth / 3.0 / width;
} else {
scale = parentHeight / 3.0 / width;
}
} else if (scale * width * SIZE < parentWidth) {
scale = parentWidth / (double)SIZE / width;
} else if (scale * width * SIZE < parentHeight) {
scale = parentHeight / (double)SIZE / width;
}*/
setPreferredSize(new Dimension((int) (scale * width * SIZE), (int) (scale * width * SIZE)));
orig = new Point(((int) (scale * width * SIZE)) / 2, ((int) (scale * width * SIZE) / 2));
final JViewport viewport = ((JViewport) (getParent().getParent().getComponent(0)));
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
viewport.setViewPosition(new Point(
orig.x - (int) Math.round(loc.x * (1 - scale / prevScale)),
orig.y - (int) Math.round(loc.y * (1 - scale / prevScale))));
}
});
/*viewport.scrollRectToVisible(new Rectangle(new Point(
orig.x - (int) Math.round(loc.x * (1 - scale / prevScale)),
orig.y - (int) Math.round(loc.y * (1 - scale / prevScale))))); */
System.out.println(orig + "\n " + loc + "\n " + (1 - scale / prevScale));
revalidate();
repaint();
}
private class MapMouseWheelListener implements MouseWheelListener {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
loc = e.getPoint();
incrementScale(e.getWheelRotation());
}
}
}
}
another example
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
//http://stackoverflow.com/questions/115103/how-do-you-implement-position-sensitive-zooming-inside-a-jscrollpane
public class FPanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L;
private Dimension preferredSize = new Dimension(400, 400);
private Rectangle2D[] rects = new Rectangle2D[50];
public static void main(String[] args) {
JFrame jf = new JFrame("test");
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setSize(400, 400);
jf.add(new JScrollPane(new FPanel()));
jf.setVisible(true);
}
public FPanel() {
// generate rectangles with pseudo-random coords
for (int i = 0; i < rects.length; i++) {
rects[i] = new Rectangle2D.Double(
Math.random() * .8, Math.random() * .8,
Math.random() * .2, Math.random() * .2);
}
// mouse listener to detect scrollwheel events
addMouseWheelListener(new MouseWheelListener() {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
updatePreferredSize(e.getWheelRotation(), e.getPoint());
}
});
}
private void updatePreferredSize(int wheelRotation, Point stablePoint) {
double scaleFactor = findScaleFactor(wheelRotation);
scaleBy(scaleFactor);
Point offset = findOffset(stablePoint, scaleFactor);
offsetBy(offset);
getParent().doLayout();
revalidate();
repaint();
}
private double findScaleFactor(int wheelRotation) {
double d = wheelRotation * 1.08;
return (d > 0) ? 1 / d : -d;
}
private void scaleBy(double scaleFactor) {
int w = (int) (getWidth() * scaleFactor);
int h = (int) (getHeight() * scaleFactor);
preferredSize.setSize(w, h);
}
private Point findOffset(Point stablePoint, double scaleFactor) {
int x = (int) (stablePoint.x * scaleFactor) - stablePoint.x;
int y = (int) (stablePoint.y * scaleFactor) - stablePoint.y;
return new Point(x, y);
}
private void offsetBy(Point offset) {
Point location = getLocation();
setLocation(location.x - offset.x, location.y - offset.y);
}
#Override
public Dimension getPreferredSize() {
return preferredSize;
}
private Rectangle2D r = new Rectangle2D.Float();
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.red);
int w = getWidth();
int h = getHeight();
for (Rectangle2D rect : rects) {
r.setRect(rect.getX() * w, rect.getY() * h,
rect.getWidth() * w, rect.getHeight() * h);
((Graphics2D) g).draw(r);
}
}
}
I have displayed an image(ball) inside the JApplet, now I want the image to move in a vertical way (up and down). The problem is I don't know how to do it.
Could someone has an idea about this matter?
You need to set the position of that image to some calculated value (means you caculate the vertical position using time, speed and maybe other restrictions).
How you'd set that position depends on how you draw the image.
Example, based on drawing in the applet's (or a nested component's) paint(Graphics g) method:
//first calculate the y-position
int yPos += timeSinceLastPaint * speed; //increment the position
if( (speed > 0 && yPos > someMaxY) || (speed < 0 && yPos <0 ) ) {
speed *= -1; //if the position has reached the bottom (max y) or the top invert the direction
}
//in your paint(Graphics g) method:
g.drawImage(image, yPos, x, null);
Then you'd have to constantly repaint the applet.
More information on animations in applets can be found here: http://download.oracle.com/javase/tutorial/uiswing/components/applet.html
another example for javax.swing.Timer with moving Ojbects created by paintComponent(Graphics g), and I have lots of Start, not some blurred Mikado :-)
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
public class AnimationBackground {
private Random random = new Random();
private JFrame frame = new JFrame("Animation Background");
private final MyJPanel panel = new MyJPanel();
private JLabel label = new JLabel("This is a Starry background.", JLabel.CENTER);
private JPanel stopPanel = new JPanel();
private JPanel startPanel = new JPanel();
public AnimationBackground() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
panel.setBackground(Color.BLACK);
for (int i = 0; i < 50; i++) {
Star star = new Star(new Point(random.nextInt(490), random.nextInt(490)));
star.setColor(new Color(100 + random.nextInt(155), 100 + random.nextInt(155), 100 + random.nextInt(155)));
star.setxIncr(-3 + random.nextInt(7));
star.setyIncr(-3 + random.nextInt(7));
panel.add(star);
}
panel.setLayout(new GridLayout(10, 1));
label.setForeground(Color.WHITE);
panel.add(label);
stopPanel.setOpaque(false);
stopPanel.add(new JButton(new AbstractAction("Stop this madness!!") {
private static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent e) {
panel.stopAnimation();
}
}));
panel.add(stopPanel);
startPanel.setOpaque(false);
startPanel.add(new JButton(new AbstractAction("Start moving...") {
private static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent e) {
panel.startAnimation();
}
}));
panel.add(startPanel);
frame.add(panel);
frame.pack();
frame.setLocation(150, 150);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
AnimationBackground aBg = new AnimationBackground();
}
});
}
private class Star extends Polygon {
private static final long serialVersionUID = 1L;
private Point location = null;
private Color color = Color.YELLOW;
private int xIncr, yIncr;
static final int WIDTH = 500, HEIGHT = 500;
Star(Point location) {
int x = location.x;
int y = location.y;
this.location = location;
this.addPoint(x, y + 8);
this.addPoint(x + 8, y + 8);
this.addPoint(x + 11, y);
this.addPoint(x + 14, y + 8);
this.addPoint(x + 22, y + 8);
this.addPoint(x + 17, y + 12);
this.addPoint(x + 21, y + 20);
this.addPoint(x + 11, y + 14);
this.addPoint(x + 3, y + 20);
this.addPoint(x + 6, y + 12);
}
public void setColor(Color color) {
this.color = color;
}
public void move() {
if (location.x < 0 || location.x > WIDTH) {
xIncr = -xIncr;
}
if (location.y < 0 || location.y > WIDTH) {
yIncr = -yIncr;
}
translate(xIncr, yIncr);
location.setLocation(location.x + xIncr, location.y + yIncr);
}
public void setxIncr(int xIncr) {
this.xIncr = xIncr;
}
public void setyIncr(int yIncr) {
this.yIncr = yIncr;
}
public Color getColor() {
return color;
}
}
private class MyJPanel extends JPanel {
private static final long serialVersionUID = 1L;
private ArrayList<Star> stars = new ArrayList<Star>();
private Timer timer = new Timer(20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Star star : stars) {
star.move();
}
repaint();
}
});
public void stopAnimation() {
if (timer.isRunning()) {
timer.stop();
}
}
public void startAnimation() {
if (!timer.isRunning()) {
timer.start();
}
}
#Override
public void addNotify() {
super.addNotify();
timer.start();
}
#Override
public void removeNotify() {
super.removeNotify();
timer.stop();
}
MyJPanel() {
this.setPreferredSize(new Dimension(512, 512));
}
public void add(Star star) {
stars.add(star);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (Star star : stars) {
g.setColor(star.getColor());
g.fillPolygon(star);
}
}
}
}
How to move the image inside the JApplet ..?
Pretty much exactly the same way you might do it in a JFrame, JComponent or JPanel or...
Or to put that another way, nothing to do with applets and everything to do with Graphics2D. For more details, see the 2D Graphics Trail of the Java Tutorial.
When you've figured how to move an image and paint it to a Graphics2D, implement that logic in a JComponent or JPanel's paintComponent(Graphics) method and drop the component with moving image into a JApplet or JFrame (or a JPanel etc.).
For the animation side of it, use a javax.swing.Timer as seen in this example. This example does not extend any component. Instead, it creates a BufferedImage and adds it to a JLabel that is displayed to the user. When the timer fires, the code grabs the Graphics object of the image, and proceeds from there to draw the bouncing lines.
import java.awt.image.BufferedImage;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.*;
import javax.swing.*;
import java.util.Random;
class LineAnimator {
public static void main(String[] args) {
final int w = 640;
final int h = 480;
final RenderingHints hints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
hints.put(
RenderingHints.KEY_ALPHA_INTERPOLATION,
RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY
);
final BufferedImage bi = new BufferedImage(w,h, BufferedImage.TYPE_INT_ARGB);
final JLabel l = new JLabel(new ImageIcon(bi));
final BouncingLine[] lines = new BouncingLine[100];
int factor = 1;
for (int ii=0; ii<lines.length; ii++) {
lines[ii] = new BouncingLine(w*factor,h*factor);
}
final Font font = new Font("Arial", Font.BOLD, 30);
ActionListener al = new ActionListener() {
int count = 0;
long lastTime;
String fps = "";
private final BasicStroke stroke = new BasicStroke(6);
public void actionPerformed(ActionEvent ae) {
count++;
Graphics2D g = bi.createGraphics();
g.setRenderingHints(hints);
g.setColor(new Color(55,12,59));
g.fillRect(0,0,w,h);
g.setStroke(stroke);
for (int ii=0; ii<lines.length; ii++) {
lines[ii].move();
lines[ii].paint(g);
}
if ( System.currentTimeMillis()-lastTime>1000 ) {
lastTime = System.currentTimeMillis();
fps = count + " FPS";
count = 0;
}
g.setColor(Color.YELLOW);
g.setFont(font);
g.drawString(fps,5,h-5);
l.repaint();
g.dispose();
}
};
Timer timer = new Timer(25,al);
timer.start();
JOptionPane.showMessageDialog(null, l);
//System.exit(0);
timer.stop();
}
}
class BouncingLine {
private final Color color;
private static final Random random = new Random();
Line2D line;
int w;
int h;
int x1;
int y1;
int x2;
int y2;
BouncingLine(int w, int h) {
line = new Line2D.Double(random.nextInt(w),random.nextInt(h),random.nextInt(w),random.nextInt(h));
this.w = w;
this.h = h;
this.color = new Color(
random.nextInt(255)
,random.nextInt(255)
,random.nextInt(255)
,64+random.nextInt(128)
);
x1 = (random.nextBoolean() ? 1 : -1);
y1 = (random.nextBoolean() ? 1 : -1);
x2 = -x1;
y2 = -y1;
}
public void move() {
int tx1 = 0;
if (line.getX1()+x1>0 && line.getX1()+x1<w) {
tx1 = (int)line.getX1()+x1;
} else {
x1 = -x1;
tx1 = (int)line.getX1()+x1;
}
int ty1 = 0;
if (line.getY1()+y1>0 && line.getY1()+y1<h) {
ty1 = (int)line.getY1()+y1;
} else {
y1 = -y1;
ty1 = (int)line.getY1()+y1;
}
int tx2 = 0;
if (line.getX2()+x2>0 && line.getX2()+x2<w) {
tx2 = (int)line.getX2()+x2;
} else {
x2 = -x2;
tx2 = (int)line.getX2()+x2;
}
int ty2 = 0;
if (line.getY2()+y2>0 && line.getY2()+y2<h) {
ty2 = (int)line.getY2()+y2;
} else {
y2 = -y2;
ty2 = (int)line.getY2()+y2;
}
line.setLine(tx1,ty1,tx2,ty2);
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.setColor(color);
//line.set
g2.draw(line);
}
}
Update 1
I want to do it in JApplet(1) using the image(2), is it possible(3)?
The examples by mKorbel and myself feature either an image in a JLabel or custom rendering in a JPanel. In our case, we added the components to a JOptionPane & a JFrame. Either example could be just as easily added to a JApplet, or a JDialog, or as part of another panel, or.. See the Laying Out Components Within a Container lesson & Using Top-Level Containers in the Java Tutorial for more details.
Instead of the stars or lines in our examples, ..paint your image. My example goes so far as to demonstrate how to get the position to bounce around within the bounds of the container.
Sure it is possible, but "Batteries not included". Our intention is to give you some ideas that you can then adapt to your bouncing ball applet. I doubt anyone is going to create an example for you, using balls, in an applet. Though if you post an SSCCE that shows your intent and what you tried, I (and others) would often run with that source. If you want more specific answers, ask a more specific SSCCE. ;)
I want to do it in JApplet.
Why not both? You can have a hybrid application/applet as shown in this animation.
I'm starting to learn java programming and I think it's cool to learn java through game development. I know how to draw image and listen to a keypress then move that image. But is it possible to make the image move back and forth to the window while the window is listening to a keypress? Like for example, while the image or object(like spaceship) is moving left to right in the window, then if I press space key, a laser will fire at the bottom of the screen( cool huh :D ). But basically I just want to know how to make the image move left to right while the window is listening to a keypress.
I'm thinking that I will add a key listener to my window then fire an infinite loop to move the image. Or do I need to learn about threading so that another thread will move the object?
Please advise.
Many thanks.
Yep, a Swing Timer and Key Bindings would work well. Here's another example (mine) :)
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class AnimationWithKeyBinding {
private static void createAndShowUI() {
AnimationPanel panel = new AnimationPanel(); // the drawing JPanel
JFrame frame = new JFrame("Animation With Key Binding");
frame.getContentPane().add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
#SuppressWarnings("serial")
class AnimationPanel extends JPanel {
public static final int SPRITE_WIDTH = 20;
public static final int PANEL_WIDTH = 400;
public static final int PANEL_HEIGHT = 400;
private static final int MAX_MSTATE = 25;
private static final int SPIN_TIMER_PERIOD = 16;
private static final int SPRITE_STEP = 3;
private int mState = 0;
private int mX = (PANEL_WIDTH - SPRITE_WIDTH) / 2;
private int mY = (PANEL_HEIGHT - SPRITE_WIDTH) / 2;
private int oldMX = mX;
private int oldMY = mY;
private boolean moved = false;
// an array of sprite images that are drawn sequentially
private BufferedImage[] spriteImages = new BufferedImage[MAX_MSTATE];
public AnimationPanel() {
// create and start the main animation timer
new Timer(SPIN_TIMER_PERIOD, new SpinTimerListener()).start();
setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
setBackground(Color.white);
createSprites(); // create the images
setupKeyBinding();
}
private void setupKeyBinding() {
int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
InputMap inMap = getInputMap(condition);
ActionMap actMap = getActionMap();
// this uses an enum of Direction that holds ints for the arrow keys
for (Direction direction : Direction.values()) {
int key = direction.getKey();
String name = direction.name();
// add the key bindings for arrow key and shift-arrow key
inMap.put(KeyStroke.getKeyStroke(key, 0), name);
inMap.put(KeyStroke.getKeyStroke(key, InputEvent.SHIFT_DOWN_MASK), name);
actMap.put(name, new MyKeyAction(this, direction));
}
}
// create a bunch of buffered images and place into an array,
// to be displayed sequentially
private void createSprites() {
for (int i = 0; i < spriteImages.length; i++) {
spriteImages[i] = new BufferedImage(SPRITE_WIDTH, SPRITE_WIDTH,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = spriteImages[i].createGraphics();
g2.setColor(Color.red);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
double theta = i * Math.PI / (2 * spriteImages.length);
double x = SPRITE_WIDTH * Math.abs(Math.cos(theta)) / 2.0;
double y = SPRITE_WIDTH * Math.abs(Math.sin(theta)) / 2.0;
int x1 = (int) ((SPRITE_WIDTH / 2.0) - x);
int y1 = (int) ((SPRITE_WIDTH / 2.0) - y);
int x2 = (int) ((SPRITE_WIDTH / 2.0) + x);
int y2 = (int) ((SPRITE_WIDTH / 2.0) + y);
g2.drawLine(x1, y1, x2, y2);
g2.drawLine(y1, x2, y2, x1);
g2.dispose();
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(spriteImages[mState], mX, mY, null);
}
public void incrementX(boolean right) {
oldMX = mX;
if (right) {
mX = Math.min(getWidth() - SPRITE_WIDTH, mX + SPRITE_STEP);
} else {
mX = Math.max(0, mX - SPRITE_STEP);
}
moved = true;
}
public void incrementY(boolean down) {
oldMY = mY;
if (down) {
mY = Math.min(getHeight() - SPRITE_WIDTH, mY + SPRITE_STEP);
} else {
mY = Math.max(0, mY - SPRITE_STEP);
}
moved = true;
}
public void tick() {
mState = (mState + 1) % MAX_MSTATE;
}
private class SpinTimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
tick();
int delta = 20;
int width = SPRITE_WIDTH + 2 * delta;
int height = width;
// make sure to erase the old image
if (moved) {
int x = oldMX - delta;
int y = oldMY - delta;
repaint(x, y, width, height);
}
int x = mX - delta;
int y = mY - delta;
// draw the new image
repaint(x, y, width, height);
moved = false;
}
}
}
enum Direction {
UP(KeyEvent.VK_UP), DOWN(KeyEvent.VK_DOWN), LEFT(KeyEvent.VK_LEFT), RIGHT(KeyEvent.VK_RIGHT);
private int key;
private Direction(int key) {
this.key = key;
}
public int getKey() {
return key;
}
}
// Actions for the key binding
#SuppressWarnings("serial")
class MyKeyAction extends AbstractAction {
private AnimationPanel draw;
private Direction direction;
public MyKeyAction(AnimationPanel draw, Direction direction) {
this.draw = draw;
this.direction = direction;
}
#Override
public void actionPerformed(ActionEvent e) {
switch (direction) {
case UP:
draw.incrementY(false);
break;
case DOWN:
draw.incrementY(true);
break;
case LEFT:
draw.incrementX(false);
break;
case RIGHT:
draw.incrementX(true);
break;
default:
break;
}
}
}
Here is another example that uses this sprite sheet:
obtained from this site.
Again it's an example of drawing within a JPanel's paintComponent method and using Key Bindings to tell which direction to move.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.swing.*;
#SuppressWarnings("serial")
public class Mcve3 extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = 640;
private static final int TIMER_DELAY = 50;
private int spriteX = 400;
private int spriteY = 320;
private SpriteDirection spriteDirection = SpriteDirection.RIGHT;
private MySprite sprite = null;
private Timer timer = null;
public Mcve3() {
try {
sprite = new MySprite(spriteDirection, spriteX, spriteY);
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
setBackground(Color.WHITE);
setKeyBindings(SpriteDirection.LEFT, KeyEvent.VK_LEFT);
setKeyBindings(SpriteDirection.RIGHT, KeyEvent.VK_RIGHT);
setKeyBindings(SpriteDirection.FORWARD, KeyEvent.VK_DOWN);
setKeyBindings(SpriteDirection.AWAY, KeyEvent.VK_UP);
timer = new Timer(TIMER_DELAY, new TimerListener());
timer.start();
}
private void setKeyBindings(SpriteDirection dir, int keyCode) {
int condition = WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition);
ActionMap actionMap = getActionMap();
KeyStroke keyPressed = KeyStroke.getKeyStroke(keyCode, 0, false);
KeyStroke keyReleased = KeyStroke.getKeyStroke(keyCode, 0, true);
inputMap.put(keyPressed, keyPressed.toString());
inputMap.put(keyReleased, keyReleased.toString());
actionMap.put(keyPressed.toString(), new MoveAction(dir, false));
actionMap.put(keyReleased.toString(), new MoveAction(dir, true));
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
sprite.draw(g);
}
private class MoveAction extends AbstractAction {
private SpriteDirection dir;
private boolean released;
public MoveAction(SpriteDirection dir, boolean released) {
this.dir = dir;
this.released = released;
}
#Override
public void actionPerformed(ActionEvent e) {
if (released) {
sprite.setMoving(false);
} else {
sprite.setMoving(true);
sprite.setDirection(dir);
}
}
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (sprite.isMoving()) {
sprite.tick();
}
repaint();
}
}
private static void createAndShowGui() {
Mcve3 mainPanel = new Mcve3();
JFrame frame = new JFrame("MCVE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class MySprite {
private static final String SPRITE_SHEET_PATH = "http://"
+ "orig12.deviantart.net/7db3/f/2010/338/3/3/"
+ "animated_sprite_sheet_32x32_by_digibody-d3479l2.gif";
private static final int MAX_MOVING_INDEX = 4;
private static final int DELTA = 4;
private SpriteDirection direction;
private Map<SpriteDirection, Image> standingImgMap = new EnumMap<>(SpriteDirection.class);
private Map<SpriteDirection, List<Image>> movingImgMap = new EnumMap<>(SpriteDirection.class);
private int x;
private int y;
private boolean moving = false;
private int movingIndex = 0;
public MySprite(SpriteDirection direction, int x, int y) throws IOException {
this.direction = direction;
this.x = x;
this.y = y;
createSprites();
}
public void draw(Graphics g) {
Image img = null;
if (!moving) {
img = standingImgMap.get(direction);
} else {
img = movingImgMap.get(direction).get(movingIndex);
}
g.drawImage(img, x, y, null);
}
private void createSprites() throws IOException {
URL spriteSheetUrl = new URL(SPRITE_SHEET_PATH);
BufferedImage img = ImageIO.read(spriteSheetUrl);
// get sub-images (sprites) from the sprite sheet
// magic numbers for getting sprites from sheet, all obtained by trial and error
int x0 = 0;
int y0 = 64;
int rW = 32;
int rH = 32;
for (int row = 0; row < 4; row++) {
SpriteDirection dir = SpriteDirection.values()[row];
List<Image> imgList = new ArrayList<>();
movingImgMap.put(dir, imgList);
int rY = y0 + row * rH;
for (int col = 0; col < 5; col++) {
int rX = x0 + col * rW;
BufferedImage subImg = img.getSubimage(rX, rY, rW, rH);
if (col == 0) {
// first image is standing
standingImgMap.put(dir, subImg);
} else {
// all others are moving
imgList.add(subImg);
}
}
}
}
public SpriteDirection getDirection() {
return direction;
}
public void setDirection(SpriteDirection direction) {
if (this.direction != direction) {
setMoving(false);
}
this.direction = direction;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public boolean isMoving() {
return moving;
}
public void setMoving(boolean moving) {
this.moving = moving;
if (!moving) {
movingIndex = 0;
}
}
public void tick() {
if (moving) {
switch (direction) {
case RIGHT:
x += DELTA;
break;
case LEFT:
x -= DELTA;
break;
case FORWARD:
y += DELTA;
break;
case AWAY:
y -= DELTA;
}
movingIndex++;
movingIndex %= MAX_MOVING_INDEX;
}
}
public int getMovingIndex() {
return movingIndex;
}
public void setMovingIndex(int movingIndex) {
this.movingIndex = movingIndex;
}
}
enum SpriteDirection {
FORWARD, LEFT, AWAY, RIGHT
}
As an alternative to KeyListener, consider using actions and key bindings, discussed here. Derived from this example, the program below moves a line left, down, up or right using either buttons or keys.
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
/**
* #see https://stackoverflow.com/questions/6991648
* #see https://stackoverflow.com/questions/6887296
* #see https://stackoverflow.com/questions/5797965
*/
public class LinePanel extends JPanel {
private MouseHandler mouseHandler = new MouseHandler();
private Point p1 = new Point(100, 100);
private Point p2 = new Point(540, 380);
private boolean drawing;
public LinePanel() {
this.setPreferredSize(new Dimension(640, 480));
this.addMouseListener(mouseHandler);
this.addMouseMotionListener(mouseHandler);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.blue);
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(8,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
g.drawLine(p1.x, p1.y, p2.x, p2.y);
}
private class MouseHandler extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
drawing = true;
p1 = e.getPoint();
p2 = p1;
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
drawing = false;
p2 = e.getPoint();
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
if (drawing) {
p2 = e.getPoint();
repaint();
}
}
}
private class ControlPanel extends JPanel {
private static final int DELTA = 10;
public ControlPanel() {
this.add(new MoveButton("\u2190", KeyEvent.VK_LEFT, -DELTA, 0));
this.add(new MoveButton("\u2191", KeyEvent.VK_UP, 0, -DELTA));
this.add(new MoveButton("\u2192", KeyEvent.VK_RIGHT, DELTA, 0));
this.add(new MoveButton("\u2193", KeyEvent.VK_DOWN, 0, DELTA));
}
private class MoveButton extends JButton {
KeyStroke k;
int dx, dy;
public MoveButton(String name, int code, final int dx, final int dy) {
super(name);
this.k = KeyStroke.getKeyStroke(code, 0);
this.dx = dx;
this.dy = dy;
this.setAction(new AbstractAction(this.getText()) {
#Override
public void actionPerformed(ActionEvent e) {
LinePanel.this.p1.translate(dx, dy);
LinePanel.this.p2.translate(dx, dy);
LinePanel.this.repaint();
}
});
ControlPanel.this.getInputMap(
WHEN_IN_FOCUSED_WINDOW).put(k, k.toString());
ControlPanel.this.getActionMap().put(k.toString(), new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
MoveButton.this.doClick();
}
});
}
}
}
private void display() {
JFrame f = new JFrame("LinePanel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.add(new ControlPanel(), BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new LinePanel().display();
}
});
}
}
But basically I just want to know how to make the image move left to right while the window is listening to a keypress
You can use a Swing Timer to animate an image:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TimerAnimation extends JLabel implements ActionListener
{
int deltaX = 2;
int deltaY = 3;
int directionX = 1;
int directionY = 1;
public TimerAnimation(
int startX, int startY,
int deltaX, int deltaY,
int directionX, int directionY,
int delay)
{
this.deltaX = deltaX;
this.deltaY = deltaY;
this.directionX = directionX;
this.directionY = directionY;
setIcon( new ImageIcon("dukewavered.gif") );
// setIcon( new ImageIcon("copy16.gif") );
setSize( getPreferredSize() );
setLocation(startX, startY);
new javax.swing.Timer(delay, this).start();
}
public void actionPerformed(ActionEvent e)
{
Container parent = getParent();
// Determine next X position
int nextX = getLocation().x + (deltaX * directionX);
if (nextX < 0)
{
nextX = 0;
directionX *= -1;
}
if ( nextX + getSize().width > parent.getSize().width)
{
nextX = parent.getSize().width - getSize().width;
directionX *= -1;
}
// Determine next Y position
int nextY = getLocation().y + (deltaY * directionY);
if (nextY < 0)
{
nextY = 0;
directionY *= -1;
}
if ( nextY + getSize().height > parent.getSize().height)
{
nextY = parent.getSize().height - getSize().height;
directionY *= -1;
}
// Move the label
setLocation(nextX, nextY);
}
public static void main(String[] args)
{
JPanel panel = new JPanel();
JFrame frame = new JFrame();
frame.setContentPane(panel);
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.getContentPane().setLayout(null);
// frame.getContentPane().add( new TimerAnimation(10, 10, 2, 3, 1, 1, 10) );
frame.getContentPane().add( new TimerAnimation(300, 100, 3, 2, -1, 1, 20) );
// frame.getContentPane().add( new TimerAnimation(0, 000, 5, 0, 1, 1, 20) );
frame.getContentPane().add( new TimerAnimation(0, 200, 5, 0, 1, 1, 80) );
frame.setSize(400, 400);
frame.setLocationRelativeTo( null );
frame.setVisible(true);
// frame.getContentPane().add( new TimerAnimation(10, 10, 2, 3, 1, 1, 10) );
// frame.getContentPane().add( new TimerAnimation(10, 10, 3, 0, 1, 1, 10) );
}
}
You can add a KeyListener to the panel and it will operate independently of the image animation.