如何将BufferedImage绘制到JPanel

我试图使用某种绘制方法将精灵图像绘制到我的JPanel子类名为AnimationPanel。 我创建了一个Spritesheet类,它可以生成一个包含工作表中所有精灵的BufferedImage []。 在我的AnimationPanel类中,它实现了Runnable,我从AnimationPanel构造函数中实例化的spritesheet中获取BufferedImage []。 我希望能够循环遍历此数组并将每个精灵显示到屏幕上。 我该怎么办? 这是我的AnimationPanel和Spritesheet类。

AnimationPanel

 package com.kahl.animation; import javax.swing.JPanel; public class AnimationPanel extends JPanel implements Runnable { //Instance Variables private Spritesheet sheet; private int currentFrame; private Thread animationThread; private BufferedImage image; public AnimationPanel(Spritesheet aSheet) { super(); sheet = aSheet; setPreferredSize(new Dimension(128,128)); setFocusable(true); requestFocus(); } public void run() { BufferedImage[] frames = sheet.getAllSprites(); currentFrame = 0; while (true) { frames[currentFrame].draw(); //some implementation still necessary here currentFrame++; if (currentFrame >= frames.length) { currentFrame = 0; } } } public void addNotify() { super.addNotify(); if (animationThread == null) { animationThread = new Thread(this); animationThread.start(); } } } 

Spritesheet

 package com.kahl.animation; import java.awt.image.BufferedImage; import java.imageio.ImageIO; import java.io.IOException; import java.io.File; public class Spritesheet { //Instance Variables private String path; private int frameWidth; private int frameHeight; private int framesPerRow; private int frames; private BufferedImage sheet = null; //Constructors public Spritesheet(String aPath,int width,int height,int fpr, int numOfFrames) { path = aPath; frameWidth = width; frameHeight = height; framesPerRow = fpr; frames = numOfFrames; try { sheet = ImageIO.read(getClass().getResourceAsStream()); } catch (IOException e) { e.printStackTrace(); } } //Methods public int getHeight() { return frameWidth; } public int getWidth() { return frameWidth; } public int getFramesPerRow() { return framesPerRow; } private BufferedImage getSprite(int x, int y, int h, int w) { BufferedImage sprite = sheet.getSubimage(x,y,h,w); } public BufferedImage[] getAllSprites() { BufferedImage[] sprites = new BufferedImage[frames]; int y = 0; for (int i = 0; i < frames; i++) { x = i * frameWidth; currentSprite = sheet.getSprite(x,y,frameHeight,frameWidth); sprites.add(currentSprite); } return sprites; } } 

  1. 我鼓励使用javax.swing.Timer来控制帧速率,而不是一个不受控制的循环
  2. 一旦计时器“滴答”,你需要增加当前帧,获取当前图像以及在JPanel上调用repaint
  3. 使用Graphics#drawImage渲染图像。

看到…

  • 在AWT和Swing中绘画
  • 执行自定义绘画
  • 如何使用Swing Timers
  • Graphics#drawImage(Image, int, int, ImageObserver)

更多细节

您的Spritesheet类存在一系列级联问题,除了它实际上不会编译之外,还有一些问题从某些方法返回错误的值并依赖于更好计算的值…

我不得不修改你的代码,我不记得其中的大部分

 public int getHeight() { return frameWidth; } 

 public BufferedImage[] getAllSprites() { BufferedImage[] sprites = new BufferedImage[frames]; int y = 0; for (int i = 0; i < frames; i++) { x = i * frameWidth; currentSprite = sheet.getSprite(x,y,frameHeight,frameWidth); sprites.add(currentSprite); } return sprites; } 

脱颖而出是两个主要的例子......

跑

 import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.io.IOException; import javax.imageio.ImageIO; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.Timer; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; public class TestSpriteSheet { public static void main(String[] args) { new TestSpriteSheet(); } public TestSpriteSheet() { 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 class TestPane extends JPanel { private Spritesheet spritesheet; private BufferedImage currentFrame; private int frame; public TestPane() { spritesheet = new Spritesheet("/Sheet02.gif", 240, 220); Timer timer = new Timer(100, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { currentFrame = spritesheet.getSprite(frame % spritesheet.getFrameCount()); repaint(); frame++; } }); timer.start(); } @Override public Dimension getPreferredSize() { return new Dimension(240, 220); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); if (currentFrame != null) { Graphics2D g2d = (Graphics2D) g.create(); int x = (getWidth() - currentFrame.getWidth()) / 2; int y = (getHeight() - currentFrame.getHeight()) / 2; g2d.drawImage(currentFrame, x, y, this); g2d.dispose(); } } } public class Spritesheet { //Instance Variables private String path; private int frameWidth; private int frameHeight; private BufferedImage sheet = null; private BufferedImage[] frameImages; //Constructors public Spritesheet(String aPath, int width, int height) { path = aPath; frameWidth = width; frameHeight = height; try { sheet = ImageIO.read(getClass().getResourceAsStream(path)); frameImages = getAllSprites(); } catch (IOException e) { e.printStackTrace(); } } public BufferedImage getSprite(int frame) { return frameImages[frame]; } //Methods public int getHeight() { return frameHeight; } public int getWidth() { return frameWidth; } public int getColumnCount() { return sheet.getWidth() / getWidth(); } public int getRowCount() { return sheet.getHeight() / getHeight(); } public int getFrameCount() { int cols = getColumnCount(); int rows = getRowCount(); return cols * rows; } private BufferedImage getSprite(int x, int y, int h, int w) { BufferedImage sprite = sheet.getSubimage(x, y, h, w); return sprite; } public BufferedImage[] getAllSprites() { int cols = getColumnCount(); int rows = getRowCount(); int frameCount = getFrameCount(); BufferedImage[] sprites = new BufferedImage[frameCount]; int index = 0; System.out.println("cols = " + cols); System.out.println("rows = " + rows); System.out.println("frameCount = " + frameCount); for (int row = 0; row < getRowCount(); row++) { for (int col = 0; col < getColumnCount(); col++) { int x = col * getWidth(); int y = row * getHeight(); System.out.println(index + " " + x + "x" + y); BufferedImage currentSprite = getSprite(x, y, getWidth(), getHeight()); sprites[index] = currentSprite; index++; } } return sprites; } } } 

请记住,动画是随着时间的推移变化的错觉。 您需要在动画的每个帧之间提供一段延迟,以便用户识别它,但足够短以使动画看起来流畅。

在上面的例子中,我使用了100毫秒,只是一个任意值。 可以使用更像1000 / spritesheet.getFrameCount() ,这将允许整个动画的整秒(一秒内的所有帧)。

根据您的需要,您可能需要使用不同的值,以获取更长或更短的动画

这是一些用于将图像绘制到JPanel的通用代码。 调用此方法来绘制JPanel组件。

 public void paintComponent (Graphics g) { super.paintComponent(g); //I would have image be a class variable that gets updated in your run() method g.drawImage(image, 0, 0, this); } 

我也可以修改run()看起来像这样:

 public void run() { BufferedImage[] frames = sheet.getAllSprites(); currentFrame = 0; while (true) { image = frames[currentFrame]; this.repaint(); //explicitly added "this" for clarity, not necessary. currentFrame++; if (currentFrame >= frames.length) { currentFrame = 0; } } } 

关于仅重新绘制部件的一部分,它会变得更复杂一些

 public void run() { BufferedImage[] frames = sheet.getAllSprites(); currentFrame = 0; while (true) { image = frames[currentFrame]; Rectangle r = this.getDirtyRect(); this.repaint(r); currentFrame++; if (currentFrame >= frames.length) { currentFrame = 0; } } } public Rectangle getDirtyRect() { int minX=0; //calculate smallest x value affected int maxX=0; //calculate largest x value affected int minY=0; //calculate smallest y value affected int maxY=0; //calculate largest y value affected return new Rectangle(minX,minY,maxX,maxY); }