JPanel图像从屏幕上飞过

我正试图让我的Pedestrian物体移动,它会移动,但在某一点它会飞离屏幕。 Pedestrian按点List移动。 首先将Pedestrian添加到toDraw以绘制它,并在startAndCreateTimer循环通过相同的列表来移动startAndCreateTimer可能是因为这行i = (double) diff / (double) playTime; 我实际上不想设置游戏时间如何不这样做,这可能是问题还是别的什么? 这里有一个与Pedestrian飞走的地方的链接(从左环岛的北边开始) http://gyazo.com/23171a6106c88f1ba8ca438598ff4153 。

 class Surface extends JPanel{ Track track=new Track(); public List 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(); } } 

步行java

 public class Pedestrian extends Vehicle { BufferedImage pedestrian; Point pedestrianPosition; double pedestrianRotation = 0; int pedestrianW, pedestrianH; int counter=0; ListpedestrianPath; 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 path) { pedestrianPath=path; } /*Update*/ @Override public void update(double i){ if (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; } } 

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

因此,在t = 0 ,对象将位于起始点,在t = 0.5 ,它将位于起点和终点之间,在t = 1.0 ,它将位于终点。

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

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

因此,为了计算沿线的位置,你需要三件事,你开始的点,你要结束的点和持续时间(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);

好身手

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

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

BadMove

你可以看到它加快了速度,终于减轻了,这不是你真正想要的……

更新了时间线理论

让我们假设你有一个时间线,其长度为t ,沿着这个时间线,你有5个事件(或关键帧)( e1e5 ),每个都发生在彼此之后。

e10开始, e51结束

时间线

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

  • 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 { private List 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 iterator() { return points.iterator(); } } 

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

然后我从之前的答案中借用了TimeLine

 public class Timeline { private Map 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); } 

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

需要,我创建一个TickerTicker运行一个Swing Timer并向Animation s报告tick

 public enum Ticker { INSTANCE; private Timer timer; private List 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 s确定他们想要在每个tick上做什么。 这应该更具可扩展性,然后创建几十个Timer

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

它采用您自己的一条路径并从中创建一个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 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 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(px, py); } // 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 { private List 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 iterator() { return points.iterator(); } } public class Timeline { private Map 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; } } } } 

现在,如果我这样做,我会在Path创建一个方法,或者作为static实用工具方法创建一个方法,它接受一个Path并自动返回一个TimeLine ;)