JPanel图像从屏幕上飞过 [英] JPanel image flies from the screen
问题描述
我正在尝试让我的 Pedestrian
对象移动,它会移动但在某个时刻它会飞离屏幕。 行人
按点列表
移动。首先将 Pedestrian
添加到 toDraw
以绘制它并在 startAndCreateTimer $ c $中c>我循环通过相同的列表来移动
Vehicles
也许是因为这行 i =(double)diff /(double)playTime;
我实际上不想设置游戏时间如何不这样做,这可能是问题还是别的什么?这里有一个链接,其中 Pedestrian
飞走了(从左环形交叉口向北开始)上一个回答
公共课时间轴{
私人地图< 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;
}否则如果(进度> 1){
进度= 1;
}
KeyFrame [] keyFrames = getKeyFramesBetween(progress);
double max = keyFrames [1] .progress - keyFrames [0] .progress;
double value = progress - keyFrames [0] .progress;
double weight = value / max;
返回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]);
返回框架;
}
protected点混合(Point start,Point end,double ratio){
Point blend = new Point();
double ir =(float)1.0 - 比率;
blend.x =(int)(start.x * ratio + end.x * ir);
blend.y =(int)(start.y * ratio + end.y * ir);
返回混合;
}
公共类KeyFrame {
私有双重进度;
私密点数;
public KeyFrame(double progress,Point point){
this.progress = progress;
this.point = point;
}
public double getProgress(){
return progress;
}
public Point getPoint(){
return point;
}
}
}
现在,就他们而言,它们不兼容,我们需要获取每个段并计算段的长度占路径总长度的百分比,并为时间线上的指定点创建关键帧。 ..
double totalLength = path.getTotalLength();
timeLine = new Timeline();
timeLine.add(0,path.get(0));
//指向时间线...
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 normalized = length / totalLength;
//标准化给出了这个细分的百分比,我们需要
//将其转换为时间线上的某个点,所以我们只需将
//添加到点时间线移动到下一个点的值:)
potl + = normalized;
timeLine.add(potl,b);
}
我故意这样做,以展示你将需要做的工作。
需要,我创建一个 Ticker
,它只运行一个Swing 计时器
并报告勾选
s到动画
s
public enum Ticker {
INSTANCE;
私人定时器计时器;
private List< Animation>动画;
private Ticker(){
animations = new ArrayList<>(25);
timer = new Timer(5,new ActionListener(){
@Override
public void actionPerformed(ActionEvent e){
//防止可能的变异问题...
Animation [] anims = animations.toArray(new Animation [animations.size()]);
for(动画动画:动画){
animation.tick();
}
}
});
}
public void add(动画动画){
animations.add(动画);
}
public void remove(动画动画){
animations.remove(动画);
}
public void start(){
timer.start();
}
public void stop(){
timer.stop();
}
}
公共接口动画{
public void tick();
}
这会集中时钟,允许动画
用于确定他们想要在每个 tick
上做什么。这应该更具可伸缩性,然后创建几十个计时器
s
好的,这都是有趣的游戏,但是怎么做它一起工作?好吧,这是一个完整的可运行示例。
它需要你自己的一个路径,并从中创建一个 TimeLine
并动画一个沿着它移动的物体。
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 static void main(String [] args){
new Test();
}
public Test(){
EventQueue.invokeLater(new Runnable(){
@Override
public void run(){
尝试{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}赶上(ClassNotFoundException的| InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException前){
ex.printStackTrace();
}
路径路径=新路径(
新点(440,40),
新点(440,120),
新点(465,90),
新点(600,180),
新点(940,165),
新点(940,145),
新点(1045,105),
new Point(1080, 120),
新点(1170,120),
新点(1200,120),
新点(1360,123),
新点(1365,135) ,
新点(1450,170),
新点(1457,160),
新点(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 List< Animation>动画;
private Ticker(){
animations = new ArrayList<>(25);
timer = new Timer(5,new ActionListener(){
@Override
public void actionPerformed(ActionEvent e){
//防止可能的变异问题...
Animation [] anims = animations.toArray(new Animation [animations.size()]);
for(动画动画:动画){
animation.tick();
}
}
});
}
public void add(动画动画){
animations.add(动画);
}
public void remove(动画动画){
animations.remove(动画);
}
public void start(){
timer.start();
}
public void stop(){
timer.stop();
}
}
公共接口动画{
public void tick();
}
public static final double PLAY_TIME = 4000d;
公共类TestPane扩展JPanel实现动画{
私有路径;
private Path2D pathShape;
私人时间线timeLine;
private Long startTime;
private Point currentPoint;
public TestPane(Path path){
this.path = path;
//构建路径形状,我们可以渲染它,但更重要的是
//它允许用来确定面板的首选大小: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);
}
//建立时间线。每个segemnt(任意两点之间的线)
//构成了旅行时间的百分比,我们需要计算
//将该段传输所需的时间量为
//路径总长度的百分比...这个
//允许我们平均时间...
double totalLength = path.getTotalLength();
timeLine = new Timeline();
timeLine.add(0,path.get(0));
//指向时间线...
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 normalized = length / totalLength;
//标准化给出了这个细分的百分比,我们需要
//将其转换为时间线上的某个点,所以我们只需将
//添加到点时间线移动到下一个点的值:)
potl + = normalized;
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;
返回大小;
}
@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;
//不要再打给我了,我已经回家
Ticker.INSTANCE.remove(this);
}
currentPoint = timeLine.getPointAt(t);
repaint();
}
}
公共类路径实现Iterable< Point> {
private List< Point>点;
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);
点b = get(指数+ 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){
返回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();
}
}
公共课时间线{
私人地图< 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;
}否则如果(进度> 1){
进度= 1;
}
KeyFrame [] keyFrames = getKeyFramesBetween(progress);
double max = keyFrames [1] .progress - keyFrames [0] .progress;
double value = progress - keyFrames [0] .progress;
double weight = value / max;
返回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]);
返回框架;
}
protected点混合(Point start,Point end,double ratio){
Point blend = new Point();
double ir =(float)1.0 - 比率;
blend.x =(int)(start.x * ratio + end.x * ir);
blend.y =(int)(start.y * ratio + end.y * ir);
返回混合;
}
公共类KeyFrame {
私有双重进度;
私密点数;
public KeyFrame(double progress,Point point){
this.progress = progress;
this.point = point;
}
public double getProgress(){
return progress;
}
public Point getPoint(){
return point;
}
}
}
}
现在,如果我这样做,我会在 Path
或 static $ c中创建一个方法$ c>实用程序方法,它采用
路径
并自动返回 TimeLine
;)
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 List
s, 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 tick
s to Animation
s
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 Animation
s to determine what they would like to do on each tick
. This should be more scalable then creating dozens of Timer
s
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 ;)
这篇关于JPanel图像从屏幕上飞过的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!