我有一个名为Player的类,该类可扩展线程,其参数为JPanel
(称为DrawPanel
),坐标为x,y。
在Player
构造函数中,我在面板上的位置x,y处绘制了一个圆
(我不知道它的正确性。)
在Player
运行功能中,我想启动一个动画,该动画将小红色圆圈从玩家坐标移动到另一点。
喜欢这个动画。
我该怎么做?
public class Player extends Thread {
private Graphics graphic;
private Graphics2D g2;
private int x;
private int y;
private DrawPanel panel;
public Player(int x, int y,DrawPanel panel)
{
this.x = x;
this.y = y;
this.panel = panel;
graphic = panel.getGraphics();
g2 = (Graphics2D) graphic;
g2.fillOval( column,row, 10, 10);
}
public void run()
{
//startAnimation(this.x,this.y,destination.x,destination.y)
}
}
答案 0 :(得分:1)
我只是想开始,动画并不容易,好的动画很难。有很多理论可以使动画“看起来”更好,在此不做介绍,为此有更好的人才和资源。
我要讨论的是如何在Swing的基础上完成“好”动画。
第一个问题似乎是您对绘画在Swing中的工作方式没有很好的了解。您应该先阅读Performing Custom Painting in Swing和Painting in Swing
接下来,您似乎没有意识到Swing实际上不是线程安全的(并且是单线程的)。这意味着您永远都不应从事件调度线程的上下文外部更新UI或UI依赖的任何状态。有关更多详细信息,请参见Concurrency in Swing。
解决此问题的最简单方法是使用Swing Timer
,有关更多详细信息,请参见How to Use Swing Timers。
现在,您可以简单地运行Timer
并进行直线平移,直到所有点都达到目标,但这并不总是最好的解决方案,因为它的缩放比例不好,并且看起来会有所不同在不同的PC上,并基于各自的功能。
在大多数情况下,基于持续时间的动画效果更好。这样,当PC无法跟上时,算法就可以“丢弃”帧。它的伸缩性更好(在时间和距离上),并且可以高度配置。
我喜欢产生可重复使用的代码块,因此我将从一个简单的“基于持续时间的动画引擎”开始...
// Self contained, duration based, animation engine...
public class AnimationEngine {
private Instant startTime;
private Duration duration;
private Timer timer;
private AnimationEngineListener listener;
public AnimationEngine(Duration duration) {
this.duration = duration;
}
public void start() {
if (timer != null) {
return;
}
startTime = null;
timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
tick();
}
});
timer.start();
}
public void stop() {
timer.stop();
timer = null;
startTime = null;
}
public void setListener(AnimationEngineListener listener) {
this.listener = listener;
}
public AnimationEngineListener getListener() {
return listener;
}
public Duration getDuration() {
return duration;
}
public double getRawProgress() {
if (startTime == null) {
return 0.0;
}
Duration duration = getDuration();
Duration runningTime = Duration.between(startTime, Instant.now());
double progress = (runningTime.toMillis() / (double) duration.toMillis());
return Math.min(1.0, Math.max(0.0, progress));
}
protected void tick() {
if (startTime == null) {
startTime = Instant.now();
}
double rawProgress = getRawProgress();
if (rawProgress >= 1.0) {
rawProgress = 1.0;
}
AnimationEngineListener listener = getListener();
if (listener != null) {
listener.animationEngineTicked(this, rawProgress);
}
// This is done so if you wish to expand the
// animation listener to include start/stop events
// this won't interfer with the tick event
if (rawProgress >= 1.0) {
rawProgress = 1.0;
stop();
}
}
public static interface AnimationEngineListener {
public void animationEngineTicked(AnimationEngine source, double progress);
}
}
它并不太复杂,它有duration
的时间,可以运行。它将以固定间隔(不少于5毫秒)tick
生成tick
事件,报告动画的当前进度(归一化的值,介于0和1之间)。>
这里的想法是我们将“引擎”与使用它的那些元素分离。这使我们可以将其用于更广泛的可能性。
接下来,我需要某种方式来跟踪移动对象的位置...
public class Ping {
private Point point;
private Point from;
private Point to;
private Color fillColor;
private Shape dot;
public Ping(Point from, Point to, Color fillColor) {
this.from = from;
this.to = to;
this.fillColor = fillColor;
point = new Point(from);
dot = new Ellipse2D.Double(0, 0, 6, 6);
}
public void paint(Container parent, Graphics2D g2d) {
Graphics2D copy = (Graphics2D) g2d.create();
int width = dot.getBounds().width / 2;
int height = dot.getBounds().height / 2;
copy.translate(point.x - width, point.y - height);
copy.setColor(fillColor);
copy.fill(dot);
copy.dispose();
}
public Rectangle getBounds() {
int width = dot.getBounds().width;
int height = dot.getBounds().height;
return new Rectangle(point, new Dimension(width, height));
}
public void update(double progress) {
int x = update(progress, from.x, to.x);
int y = update(progress, from.y, to.y);
point.x = x;
point.y = y;
}
protected int update(double progress, int from, int to) {
int distance = to - from;
int value = (int) Math.round((double) distance * progress);
value += from;
if (from < to) {
value = Math.max(from, Math.min(to, value));
} else {
value = Math.max(to, Math.min(from, value));
}
return value;
}
}
这是一个简单的对象,它获取起点和终点,然后根据进度计算对象在这些点之间的位置。可以在需要时自行涂漆。
现在,我们只需要一些方法将其组合在一起...
public class TestPane extends JPanel {
private Point source;
private Shape sourceShape;
private List<Ping> pings;
private List<Shape> destinations;
private Color[] colors = new Color[]{Color.BLACK, Color.BLUE, Color.CYAN, Color.DARK_GRAY, Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.RED, Color.YELLOW};
private AnimationEngine engine;
public TestPane() {
source = new Point(10, 10);
sourceShape = new Ellipse2D.Double(source.x - 5, source.y - 5, 10, 10);
Dimension size = getPreferredSize();
Random rnd = new Random();
int quantity = 1 + rnd.nextInt(10);
pings = new ArrayList<>(quantity);
destinations = new ArrayList<>(quantity);
for (int index = 0; index < quantity; index++) {
int x = 20 + rnd.nextInt(size.width - 25);
int y = 20 + rnd.nextInt(size.height - 25);
Point toPoint = new Point(x, y);
// Create the "ping"
Color color = colors[rnd.nextInt(colors.length)];
Ping ping = new Ping(source, toPoint, color);
pings.add(ping);
// Create the destination shape...
Rectangle bounds = ping.getBounds();
Shape destination = new Ellipse2D.Double(toPoint.x - (bounds.width / 2d), toPoint.y - (bounds.height / 2d), 10, 10);
destinations.add(destination);
}
engine = new AnimationEngine(Duration.ofSeconds(10));
engine.setListener(new AnimationEngine.AnimationEngineListener() {
@Override
public void animationEngineTicked(AnimationEngine source, double progress) {
for (Ping ping : pings) {
ping.update(progress);
}
repaint();
}
});
engine.start();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
// This is probably overkill, but it will make the output look nicer ;)
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
// Lines first, these could be cached
g2d.setColor(Color.LIGHT_GRAY);
double fromX = sourceShape.getBounds2D().getCenterX();
double fromY = sourceShape.getBounds2D().getCenterY();
for (Shape destination : destinations) {
double toX = destination.getBounds2D().getCenterX();
double toY = destination.getBounds2D().getCenterY();
g2d.draw(new Line2D.Double(fromX, fromY, toX, toY));
}
// Pings, so they appear above the line, but under the points
for (Ping ping : pings) {
ping.paint(this, g2d);
}
// Destination and source
g2d.setColor(Color.BLACK);
for (Shape destination : destinations) {
g2d.fill(destination);
}
g2d.fill(sourceShape);
g2d.dispose();
}
}
好的,这个“看起来”很复杂,但是确实很简单。
然后,动画引擎将遍历所有Ping
并根据当前进度值对其进行更新,并触发新的绘制过程,然后绘制源点和目标点之间的线,绘制{{ 1}} s,最后是源和所有目标点。很简单。
嗯,这要复杂得多,并且需要更复杂的动画引擎。
通常来说,您可以建立“可动画”的概念。然后,将由一个中央“引擎”对其进行更新,该引擎会连续“滴答”(本身不受持续时间的限制)。
然后,每个“动画”都需要决定如何更新或报告其状态,并允许其他对象被更新。
在这种情况下,我将寻求一种更现成的解决方案,例如...
Ping
正如我所说,好的动画很难。要做好,需要大量的精力和计划。我什至没有谈论过缓动,链接或混合算法,所以当我说这是一个简单,可重用的解决方案时,请相信我
答案 1 :(得分:-2)
尝试一下:
public class Player extends Thread {
private Graphics graphic;
private Graphics2D g2;
private int x;
private int y;
private DrawPanel panel;
private numberOfIteration=5;
private currentNumberOfIteration=0;
public Player(int x, int y,DrawPanel panel)
{
this.x = x;
this.y = y;
this.panel = panel;
graphic = panel.getGraphics();
g2 = (Graphics2D) graphic;
g2.fillOval( column,row, 10, 10);
}
public Player(int x, int y,DrawPanel panel,int numberOfIteration)
{
this.x = x;
this.y = y;
this.panel = panel;
this.numberOfIteration=numberOfIterarion;
graphic = panel.getGraphics();
g2 = (Graphics2D) graphic;
g2.fillOval( column,row, 10, 10);
}
public void run()
{
//startAnimation(this.x,this.y,destination.x,destination.y)
currentNumberOfIteration=(++currentNumberOfIteration)%numberOfIteration;
currentX=(int)((destinationX*currentNumberOfIteration+this.x)/(currentNumberOfIteration+1));
currentY=(int)((destinationY*currentNumberOfIteration+this.y)/(currentNumberOfIteration+1));
g2.fillOval( currentX,currentY, 10, 10);
}
}
我看不到destinationX
和destinationY
的任何声明部分。