JPanel图像从屏幕上飞过

时间:2015-05-25 07:18:16

标签: java swing jpanel paintcomponent

我正在尝试让我的Pedestrian物体移动,它会移动,但在某个点上它会飞离屏幕。 Pedestrian移动List点。首先将Pedestrian添加到toDraw进行绘制,然后在startAndCreateTimer中循环显示相同的列表以移动Vehicles可能因为此行i = (double) diff / (double) playTime;我实际上不想设置游戏时间如何不这样做,这可能是问题还是别的什么?这里有一个链接,指向Pedestrian飞走的点(从左环形交叉口向北开始)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;
    }

}

1 个答案:

答案 0 :(得分:2)

所以,基本上你是根据已经过的时间计算对象在点之间的位置。这很好。

所以在t = 0处,对象将位于起始点,t = 0.5,它将位于起点和终点之间,t = 1.0它将在最后点。

t > 1.0时会发生什么?对象应该在哪里? - 提示,应该没有它应该被删除或重置...

Thisthis是基于“时间线”的动画的基本示例,这意味着,在一段时间内,通过使用不同的点(沿着时间线)确定对象的位置)

因此,为了计算沿线的位置,你需要三件事,你开始的点,你要结束的点和持续时间(0-1之间)

使用这些,您可以根据时间计算这两点之间的直线点。

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

    }

}

所以,使用current = calculateProgress(startAt, endAt, percent);

NiceMove

你可以看到点在起点和终点之间均匀移动。

如果我们将其更改为您似乎正在做的事情,current = calculateProgress(current, endAt, percent);

BadMove

你可以看到它加速了线路并最终缓解了,这不是你真正想要的......

更新了时间线理论

假设您有一个时间线,其长度为t,沿此时间线,您有5个事件(或关键帧)(e1 - e5),每个都发生在彼此之后。

e10开始,e51结尾

Time Line

如您所见,事件以不规则的间隔发生,并且运行的时间不同。

  • t1运行时间线的25%
  • t2运行25%的时间线
  • t3运行12.5%的时间线
  • t3运行37.5%的时间线

因此,基于t,您需要确定已执行的事件。因此,当t0.12时,我们正在t1的一半(在e1和e2之间)运行。

然后,您需要计算关键帧之间的本地时间/差异(沿时间轴0-0.25)

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

t是时间线上的时间,e1是第一个事件的时间(0),e2是第二个事件的时间( 0.25),它给出了t1(在此示例中)

的持续时间

这是给定时间片的线性插值值。

可运行的示例......

我看了你的代码,但是要完成这项工作还需要做很多工作。

基本上,您需要知道路径的长度以及每个段的路径数量(以百分比表示)。有了这个,我们就可以创建一个“关键帧”的“时间线”,它根据已经过去的时间和“应该”旅行所需的时间来确定你的对象沿着“路径”的距离。

所以,我做的第一件事是创建一个Path类(模仿你的List,但还有一些额外的方法)

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

}

大多数情况下,这会提供路径的totalLength。我们使用它来计算每个段稍后占用的数量

然后我从this上一个回答中借用了TimeLine课程

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

    }

}

现在,他们认为,它们不兼容,我们需要获取每个段并计算段的长度占路径总长度的百分比,并为时间线上的指定点创建关键帧...

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

我故意这样做,以展示你将要做的工作。

需要,我创建一个Ticker,只需运行Swing Timer并将tick报告给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();

}

这会集中“时钟”,允许Animation确定他们想要对每个tick做什么。这应该更具可扩展性,然后创建数十个Timer s

好的,这都是有趣的游戏,但它是如何协同工作的?好吧,这是一个完整的可运行的例子。

它需要您自己的一条路径,并从中创建一个TimeLine,并为沿其移动的对象设置动画。

FollowMeHome

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

        }

    }
}

现在,如果我这样做,我会在Pathstatic实用程序方法中创建一个方法,该方法需要Path并返回TimeLine自动;)