我尝试使用java.awt渲染简单的动画(在光标位置单击鼠标就会发生简单爆炸)。
首先是我的动画类:
package animation;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Animation {
private BufferedImage spriteSheet;
private int currentStateNum;
private int spriteSize;
private int spritsInWidth; // number of sprites in one row of the spritesheet
private long startTime;
private long delay; // delay between each sprite
private long sicleTime;
private long lifeTime;
private boolean alive;
public Animation(String spriteSheetPath, int spriteSize,
int spritsCount, int spritsInWidth, long delay,
long lifeTime) {
try {
this.spriteSheet = ImageIO.read(new File(spriteSheetPath));
this.currentStateNum = 0;
this.spriteSize = spriteSize;
this.delay = delay;
this.spritsInWidth = spritsInWidth;
this.sicleTime = delay * spritsCount;
this.lifeTime = lifeTime;
this.alive = true;
this.startTime = System.currentTimeMillis();
} catch (IOException e) {
e.printStackTrace();
}
}
public BufferedImage getCurrentState() {
int x = (currentStateNum % spritsInWidth) * spriteSize;
int y = (currentStateNum / spritsInWidth) * spriteSize;
return spriteSheet.getSubimage(x, y, spriteSize, spriteSize);
}
public void update() {
long currTime = System.currentTimeMillis();
long passedTime = currTime - startTime;
alive = passedTime < lifeTime;
long currSicleTime = passedTime % sicleTime;
currentStateNum = (int) (currSicleTime / delay);
}
public boolean isAlive() {
return alive;
}
}
现在,我实际渲染动画的第一次尝试类似于:
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;
import animation.Animation;
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame("Animation test");
Canvas canvas = new Canvas();
canvas.setPreferredSize(new Dimension(600, 400));
canvas.setBackground(Color.white);
canvas.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
Animation a = new Animation("res/explosion.png", 64, 16,
4, 40, 40 * 16);
BufferStrategy bs;
Graphics g;
while (a.isAlive()) {
bs = canvas.getBufferStrategy();
g = bs.getDrawGraphics();
g.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
g.drawImage(a.getCurrentState(), e.getX(), e.getY(), null);
g.dispose();
bs.show();
a.update();
}
bs = canvas.getBufferStrategy();
g = bs.getDrawGraphics();
g.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
g.dispose();
bs.show();
super.mouseReleased(e);
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(canvas);
frame.pack();
frame.setVisible(true);
canvas.createBufferStrategy(3);
}
}
虽然这种方法适用于屏幕上的单个动画,但不用说这个
多次点击失败了。
我很快意识到MouseListener
将事件放入队列中导致动画一个接一个地延迟渲染而不是一起渲染,几分钟后我意识到它实际上很好,因为它可能是一个坏主意同时编辑同一个栅格形成多个线程。
所以我的下一个方法是在mouseReleased(MouseEvent e)
内渲染动画而不是
方法是创建一个动画地图,然后将它们添加到地图中,并使用其他地图进行渲染。
代码:
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JFrame;
import animation.Animation;
public class Main {
static boolean running = true;
public static void main(String[] args) {
JFrame frame = new JFrame("Animation test");
Canvas canvas = new Canvas();
Map<Animation, Point> explosions = new HashMap<Animation, Point>();
canvas.setPreferredSize(new Dimension(600, 400));
canvas.setBackground(Color.white);
canvas.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
explosions.put(new Animation("res/explosion.png", 64, 16,
4, 25, 25 * 16),
new Point(e.getX() - 32, e.getY() - 32));
super.mouseReleased(e);
}
});
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent we) {
running = false;
explosions.clear();
System.exit(0);
}
});
frame.getContentPane().add(canvas);
frame.pack();
frame.setVisible(true);
while (running) {
BufferStrategy bs = canvas.getBufferStrategy();
if (bs == null) {
canvas.createBufferStrategy(3);
continue;
}
Graphics g = bs.getDrawGraphics();
g.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
for (Animation a : explosions.keySet()) {
Point p = explosions.get(a);
if (a.isAlive()) {
g.drawImage(a.getCurrentState(), p.x, p.y, null);
a.update();
} else {
explosions.remove(a);
}
}
g.dispose();
bs.show();
}
}
}
这种(有点)有效,当它正确地渲染所有动画并且没有延迟时,它经常与java.util.ConcurrentModificationException
堆栈跟踪是:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(Unknown Source)
at java.util.HashMap$KeyIterator.next(Unknown Source)
at Main.main(Main.java:65)
我的第一个假设是问题是我在迭代它时从地图中删除了键,所以我删除了explosions.remove(a)
但仍然遇到了同样的问题,实际上我仍然认为问题正在改变迭代时映射,因为event
可以触发,而我将从地图渲染,这将为其添加新密钥。
我需要的是洞察我的假设是否正确,如果不是,那么问题是什么? 虽然(如果我的假设是正确的)我可以用一些讨厌的方式来解决这个问题,但我更倾向于为这个问题提供一个很好的优雅解决方案。
答案 0 :(得分:0)
好的,似乎我的假设确实是正确的,问题只是简单地解决了:
synchronized (explosions) {
Iterator<Map.Entry<Animation, Point>> it = explosions.entrySet().iterator();
for (; it.hasNext();) {
Map.Entry<Animation, Point> entry = it.next();
if (entry.getKey().isAlive()) {
g.drawImage(entry.getKey().getCurrentState(), entry.getValue().x, entry.getValue().y, null);
entry.getKey().update();
} else {
it.remove();
}
}
}
答案 1 :(得分:0)
并发哈希映射应该提供更好的性能,特别是因为只有删除或插入操作才会实际阻止映射