我正在用Java制作一个简单的动画,我试图让它尽可能顺利。
我只使用每个Shape对象的* .Double内部类,并在Graphics2D对象中设置抗锯齿。只要我只使用fill()方法,但是如果我也使用draw()方法在同一个Shape周围绘制线条,这些线条的动画也是不连贯的 - 逐个像素。
画布上的每个矩形都有这种绘制方法。它每隔20ms移动一次,整个画布使用Timer和TimerListener重新绘制。
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class AnimationTest {
public static void main(String[] args) {
JFrame frm = new JFrame("Test");
frm.setBounds(200, 200, 400, 400);
frm.setResizable(false);
frm.setLocationRelativeTo(null);
AnimationCanvas a = new AnimationCanvas();
frm.add(a);
frm.setVisible(true);
a.startAnimation();
}
}
class AnimationCanvas extends JPanel {
SimpleSquare[] squares = new SimpleSquare[2];
AnimationCanvas() {
squares[0] = new SimpleSquare(50, 80, true);
squares[1] = new SimpleSquare(160, 80, false);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (SimpleSquare c : squares) {
c.paintSquare(g);
}
}
Timer t;
public void startAnimation() {
t = new Timer(30, new Animator());
t.start();
}
private class Animator implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
squares[0].y += 0.10;
squares[1].y += 0.10;
repaint();
}
}
}
class SimpleSquare {
double x;
double y;
Color color = Color.black;
boolean fill;
SimpleSquare(double x, double y, boolean fill) {
this.x = x;
this.y = y;
this.fill = fill;
}
void paintSquare(Graphics g) {
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Shape s = new Rectangle.Double(x, y, 100, 100);
g.setColor(color);
((Graphics2D) g).setStroke(new BasicStroke(2));
if (fill) {
((Graphics2D) g).fill(s);
} else {
((Graphics2D) g).draw(s);
}
}
}
有没有办法解决这个问题?我环顾了一会儿。
答案 0 :(得分:5)
我把这个小测试放在一起并没有明显的问题,我基本上能够保持50fps,即使1000个矩形都以随机方向随机移动。
public class SimpleAnimationEngine {
public static void main(String[] args) {
new SimpleAnimationEngine();
}
public SimpleAnimationEngine() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
AnimationPane pane = new AnimationPane();
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(pane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
pane.init();
pane.start();
}
});
}
public static class AnimationPane extends JPanel implements AnimationCanvas {
private AnimationModel model;
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
public AnimationModel getModel() {
return model;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
for (Animatable animatable : getModel().getAnimatables()) {
animatable.paint(g2d);
}
g2d.dispose();
}
@Override
public synchronized void updateState() {
Runnable update = new Runnable() {
@Override
public void run() {
AnimationModel model = getModel();
for (Animatable animatable : model.getAnimatables()) {
animatable.copy();
}
repaint();
}
};
if (EventQueue.isDispatchThread()) {
update.run();
} else {
try {
EventQueue.invokeAndWait(update);
} catch (InterruptedException | InvocationTargetException ex) {
ex.printStackTrace();
}
}
}
public void init() {
model = new DefaultAnimationModel();
for (int index = 0; index < 1000; index++) {
model.add(new AnimatableRectangle(this));
}
updateState();
}
public void start() {
AnimationEngine engine = new AnimationEngine(this, getModel());
engine.start();
}
}
public static interface Animatable {
public void copy();
public void update(AnimationCanvas canvas, float progress);
public void paint(Graphics2D g2d);
}
public static class AnimationEngine extends Thread {
private AnimationModel model;
private AnimationCanvas canvas;
public AnimationEngine(AnimationCanvas canvas, AnimationModel model) {
setDaemon(true);
setName("AnimationThread");
this.model = model;
this.canvas = canvas;
}
public AnimationCanvas getCanvas() {
return canvas;
}
public AnimationModel getModel() {
return model;
}
@Override
public void run() {
float progress = 0;
long cylceStartTime = System.currentTimeMillis();
long cylceEndTime = cylceStartTime + 1000;
int updateCount = 0;
while (true) {
long frameStartTime = System.currentTimeMillis();
getModel().update(getCanvas(), progress);
getCanvas().updateState();
long frameEndTime = System.currentTimeMillis();
long delay = 20 - (frameEndTime - frameStartTime);
if (delay > 0) {
try {
sleep(delay);
} catch (InterruptedException ex) {
}
}
long now = System.currentTimeMillis();
long runtime = now - cylceStartTime;
progress = (float)runtime / (float)(1000);
updateCount++;
if (progress > 1.0) {
progress = 0f;
cylceStartTime = System.currentTimeMillis();
cylceEndTime = cylceStartTime + 1000;
System.out.println(updateCount + " updates in this cycle");
updateCount = 0;
}
}
}
}
public interface AnimationCanvas {
public void updateState();
public Rectangle getBounds();
}
public static interface AnimationModel {
public void update(AnimationCanvas canvas, float progress);
public void add(Animatable animatable);
public void remove(Animatable animatable);
public Animatable[] getAnimatables();
}
public static class AnimatableRectangle implements Animatable {
private Rectangle bounds;
private int dx, dy;
private Rectangle copyBounds;
private Color foreground;
private Color backColor;
public AnimatableRectangle(AnimationCanvas canvas) {
bounds = new Rectangle(10, 10);
Rectangle canvasBounds = canvas.getBounds();
bounds.x = canvasBounds.x + ((canvasBounds.width - bounds.width) / 2);
bounds.y = canvasBounds.y + ((canvasBounds.height - bounds.height) / 2);
dx = (getRandomNumber(10) + 1) - 5;
dy = (getRandomNumber(10) + 1) - 5;
dx = dx == 0 ? 1 : dx;
dy = dy == 0 ? 1 : dy;
foreground = getRandomColor();
backColor = getRandomColor();
}
protected int getRandomNumber(int range) {
return (int) Math.round(Math.random() * range);
}
protected Color getRandomColor() {
return new Color(getRandomNumber(255), getRandomNumber(255), getRandomNumber(255));
}
@Override
public void copy() {
copyBounds = new Rectangle(bounds);
}
@Override
public void update(AnimationCanvas canvas, float progress) {
bounds.x += dx;
bounds.y += dy;
Rectangle canvasBounds = canvas.getBounds();
if (bounds.x + bounds.width > canvasBounds.x + canvasBounds.width) {
bounds.x = canvasBounds.x + canvasBounds.width - bounds.width;
dx *= -1;
}
if (bounds.y + bounds.height > canvasBounds.y + canvasBounds.height) {
bounds.y = canvasBounds.y + canvasBounds.height - bounds.height;
dy *= -1;
}
if (bounds.x < canvasBounds.x) {
bounds.x = canvasBounds.x;
dx *= -1;
}
if (bounds.y < canvasBounds.y) {
bounds.y = canvasBounds.y;
dy *= -1;
}
}
@Override
public void paint(Graphics2D g2d) {
g2d.setColor(backColor);
g2d.fill(copyBounds);
g2d.setColor(foreground);
g2d.draw(copyBounds);
}
}
public static class DefaultAnimationModel implements AnimationModel {
private List<Animatable> animatables;
public DefaultAnimationModel() {
animatables = new ArrayList<>(25);
}
@Override
public synchronized void update(AnimationCanvas canvas, float progress) {
for (Animatable animatable : animatables) {
animatable.update(canvas, progress);
}
}
@Override
public synchronized void add(Animatable animatable) {
animatables.add(animatable);
}
@Override
public synchronized void remove(Animatable animatable) {
animatables.remove(animatable);
}
@Override
public synchronized Animatable[] getAnimatables() {
return animatables.toArray(new Animatable[animatables.size()]);
}
}
}
<强>更新强>
您要面对的最大问题是屏幕只能以整数运行......
private class Animator implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
squares[0].y += 1;
squares[1].y += 1;
repaint();
}
}
我相信这两个方块实际上都是“抖动”,但由于平方广场有明显缺乏的身体,它更突出。我以大约24fps的速度运行此测试而没有任何问题。
答案 1 :(得分:0)
你有:
private class Animator implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
squares[0].y += 0.10;
squares[1].y += 0.10;
repaint();
}
}
和
public void startAnimation() {
t = new Timer(30, new Animator());
t.start();
}
但你可以通过改为
来实现同样的目标private class Animator implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
squares[0].y += 1;
squares[1].y += 1;
repaint();
}
}
和
public void startAnimation() {
t = new Timer(300, new Animator());
t.start();
}
哪个相同,但循环次数减少了10倍。
我没有测试代码,但值得一试。
MadProgrammer提供了正确的解决方案,他通过它来测量延迟和偏移。