JPanel图像从屏幕上飞过 [英] JPanel image flies from the screen

查看:148
本文介绍了JPanel图像从屏幕上飞过的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试让我的 Pedestrian 对象移动,它会移动但在某个时刻它会飞离屏幕。 行人按点列表移动。首先将 Pedestrian 添加到 toDraw 以绘制它并在 startAndCreateTimer 我循环通过相同的列表来移动 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 实用程序方法,它采用路径并自动返回 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 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 ;)

这篇关于JPanel图像从屏幕上飞过的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆