在java中加载sprite图像 [英] Load a sprites image in java

查看:140
本文介绍了在java中加载sprite图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想询问为什么在将任何精灵图像加载到对象中时出错

I want to ask if why am getting error loading any sprite images into the object

这里是我获取图像的方式。

here is how I get the image in.

import java.awt.image.BufferedImage;
import java.io.IOException;

public class SpriteSheet {
    public BufferedImage sprite;
    public BufferedImage[] sprites;
    int width;
    int height;
    int rows;
    int columns;
    public SpriteSheet(int width, int height, int rows, int columns, BufferedImage ss) throws IOException {
        this.width = width;
        this.height = height;
        this.rows = rows;
        this.columns = columns;
        this.sprite = ss;
        for(int i = 0; i < rows; i++) {
            for(int j = 0; j < columns; j++) {
                sprites[(i * columns) + j] = ss.getSubimage(i * width, j * height, width, height);
            }
        }
    }

}

这是我如何实现它

    public BufferedImage[] init(){
        BufferedImageLoader loader = new BufferedImageLoader();
        BufferedImage spriteSheet = null;
        SpriteSheet ss = null;
        try {
            spriteSheet = loader.loadImage("planet.png");
            ss = new SpriteSheet(72,72,4,5,spriteSheet);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return ss.sprites;
    }

我还想问一下我使用sprites数组的方式。我希望通过更改当前精灵图像来更改动作事件绘制的图像来在计时器中使用

I also want to ask about my way of using the sprites array. I want to use in the timer by changing the image drawn by action event by changing the current sprite image

        tmr = new Timer(20, new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                for(Rock r:rocks){
                    r.move();
                    r.changeSprite();
                }
                repaint();
            }
        });

使用方法

    public void changeSprite(){
        if(ds==12)
            ds=0;
        ds++;
        currentSprite = sprite[ds];
    }

每个摇滚对象都有一个sprite数组,其中包含从精灵图像接收的缓冲图像加载并绘制当前图像。计时器将更改当前图像并在对象上重新绘制它,以便绘制整个精灵但它似乎不起作用。所以我的loadingSpriteImage有问题或我的绘制方式导致问题?

Each rock object has a sprite array full of Buffered image received from the sprite image loaded in and a current image which get drawn. the timer will change the current image and redrawn it on the object so that the whole sprite get drawn but it doesn't seem to work. So it is my loadingSpriteImage that have the problem or my way of drawing it causing the problem?

推荐答案

好的,所以有很多我们需要知道的事情。

Okay, so there a lots of things we need to know.


  • 构成精灵表的图像数量,它们的布局方式(行/列),如果图像数量不均匀( count!= rows * cols )甚至可能是每个精灵的大小

  • 我们走了多远是通过一个给定的周期(让我们说一秒钟)

  • How many images make up the sprite sheet, how they are laid out (rows/cols), if there is an uneven number of images (count != rows * cols) and possibly even the size of each sprite
  • How far we are through a given cycle (let's say a second)

所以基于你上一个问题的图像......

So based on you image from a previous question...

基于此,我们知道有5列,但是4行但是只有19张图片。现在你可以花很多时间,为每个可能的精灵表写下大量的代码,或者你可以试着和其他一些问题共存......

Based on this, we know there are 5 columns, 4 rows but only 19 images. Now you could spend a lot of time, write lots of code for each possible sprite sheet, or you could try and commensalism some of those problems...

public class SpriteSheet {

    private final List<BufferedImage> sprites;

    public SpriteSheet(List<BufferedImage> sprites) {
        this.sprites = new ArrayList<>(sprites);
    }

    public int count() {
        return sprites.size();
    }

    public BufferedImage getSprite(double progress) {
        int frame = (int) (count() * progress);
        return sprites.get(frame);
    }

}

所以,这是非常基本的,它只是一个图像列表。特殊部分是 getSprite 方法,该方法在当前动画周期中进行,并根据您可用的图像数返回图像。这基本上将时间概念与精灵分离,并允许您在外部定义循环的含义。

So, this is pretty basic, it's simply a list of images. The special part is the getSprite method, which takes a progression through the current animation cycle and returns an image based on the number of images you have available. This basically decouples the concept of time from the sprite and allows you to define the meaning of a "cycle" externally.

现在,因为构建<$的实际过程c $ c> SpriteSheet 涉及很多可能的变量,建造者是个好主意......

Now, because the actual process of building a SpriteSheet involves a lot of possible variables, a builder would be a good idea...

public class SpriteSheetBuilder {

    private BufferedImage spriteSheet;
    private int rows, cols;
    private int spriteWidth, spriteHeight;
    private int spriteCount;

    public SpriteSheetBuilder withSheet(BufferedImage img) {
        spriteSheet = img;
        return this;
    }

    public SpriteSheetBuilder withRows(int rows) {
        this.rows = rows;
        return this;
    }

    public SpriteSheetBuilder withColumns(int cols) {
        this.cols = cols;
        return this;
    }

    public SpriteSheetBuilder withSpriteSize(int width, int height) {
        this.spriteWidth = width;
        this.spriteHeight = height;
        return this;
    }

    public SpriteSheetBuilder withSpriteCount(int count) {
        this.spriteCount = count;
        return this;
    }

    protected int getSpriteCount() {
        return spriteCount;
    }

    protected int getCols() {
        return cols;
    }

    protected int getRows() {
        return rows;
    }

    protected int getSpriteHeight() {
        return spriteHeight;
    }

    protected BufferedImage getSpriteSheet() {
        return spriteSheet;
    }

    protected int getSpriteWidth() {
        return spriteWidth;
    }

    public SpriteSheet build() {
        int count = getSpriteCount();
        int rows = getRows();
        int cols = getCols();
        if (count == 0) {
            count = rows * cols;
        }

        BufferedImage sheet = getSpriteSheet();

        int width = getSpriteWidth();
        int height = getSpriteHeight();
        if (width == 0) {
            width = sheet.getWidth() / cols;
        }
        if (height == 0) {
            height = sheet.getHeight() / rows;
        }

        int x = 0;
        int y = 0;
        List<BufferedImage> sprites = new ArrayList<>(count);

        for (int index = 0; index < count; index++) {
            sprites.add(sheet.getSubimage(x, y, width, height));
            x += width;
            if (x >= width * cols) {
                x = 0;
                y += height;
            }
        }

        return new SpriteSheet(sprites);
    }
}

所以,再次,基于你的精灵表,这个意味着我可以使用类似的东西构建一个 SpriteSheet

So, again, based on your sprite sheet, this means I could build a SpriteSheet using something like...

spriteSheet = new SpriteSheetBuilder().
        withSheet(sheet).
        withColumns(5).
        withRows(4).
        withSpriteCount(19).
        build();

但它让我能够构建任意数量的 SpriteSheets ,所有这些都可能由不同的矩阵组成

but it gives me the power to build any number of SpriteSheets, all of which might be made up of different matrices

但现在我们有一个 SpriteSheet ,我们需要一些方法来动画它们,但我们真正需要的是一些计算通过给定周期的进展的方法(假设第二个是循环),我们可以使用一个简单的引擎,类似...... / p>

But now that we have a SpriteSheet, we need some way to animate them, but what we really need is some way to calculate the progression through a given cycle (let's say a second is a cycle), we could use a simple "engine", something like...

public class SpriteEngine {

    private Timer timer;
    private int framesPerSecond;
    private Long cycleStartTime;
    private TimerHandler timerHandler;

    private double cycleProgress;

    private List<ActionListener> listeners;

    public SpriteEngine(int fps) {
        framesPerSecond = fps;
        timerHandler = new TimerHandler();
        listeners = new ArrayList<>(25);
    }

    public int getFramesPerSecond() {
        return framesPerSecond;
    }

    public double getCycleProgress() {
        return cycleProgress;
    }

    protected void invaldiate() {
        cycleProgress = 0;
        cycleStartTime = null;
    }

    public void stop() {
        if (timer != null) {
            timer.stop();
        }
        invaldiate();
    }

    public void start() {
        stop();
        timer = new Timer(1000 / framesPerSecond, timerHandler);
        timer.start();
    }

    public void addActionListener(ActionListener actionListener) {
        listeners.add(actionListener);
    }

    public void removeActionListener(ActionListener actionListener) {
        listeners.remove(actionListener);
    }

    protected class TimerHandler implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            if (cycleStartTime == null) {
                cycleStartTime = System.currentTimeMillis();
            }
            long diff = (System.currentTimeMillis() - cycleStartTime) % 1000;
            cycleProgress = diff / 1000.0;
            ActionEvent ae = new ActionEvent(SpriteEngine.this, ActionEvent.ACTION_PERFORMED, e.getActionCommand());
            for (ActionListener listener : listeners) {
                listener.actionPerformed(ae);
            }
        }

    }

}

现在,这基本上只是Swing Timer 的包装类,但它的作用是计算我们的循环进度。它还具有很好的 ActionListener 支持,因此我们可以在发生勾号时收到通知

Now, this is basically just a wrapper class for a Swing Timer, but what it does is it calculates the cycle progression for us. It also has nice ActionListener support, so we can be notified when a tick occurs

好的,但是怎么做一起工作?

Okay, but how does that all work together?

基本上,给定一个或多个 SpriteSheet s和 SpriteEngine ,我们可以将表格描绘成类似......

Basically, given one or more SpriteSheets and a SpriteEngine, we can paint the sheets doing something like...

public class TestPane extends JPanel {

    private SpriteSheet spriteSheet;
    private SpriteEngine spriteEngine;

    public TestPane() {
        try {
            BufferedImage sheet = ImageIO.read(...);
            spriteSheet = new SpriteSheetBuilder().
                    withSheet(sheet).
                    withColumns(5).
                    withRows(4).
                    withSpriteCount(19).
                    build();

            spriteEngine = new SpriteEngine(25);
            spriteEngine.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    repaint();
                }
            });
            spriteEngine.start();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(200, 200);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        BufferedImage sprite = spriteSheet.getSprite(spriteEngine.getCycleProgress());
        int x = (getWidth() - sprite.getWidth()) / 2;
        int y = (getHeight() - sprite.getHeight()) / 2;
        g2d.drawImage(sprite, x, y, this);
        g2d.dispose();
    }

}

现在,好的,这是非常基本的,但它给出了一个想法。

Now, okay, that's pretty basic, but it gives an idea.

对于你想要移动(或旋转)的实体,我会创建另一个包含该信息的类和 spriteSheet 。这可能包含一个paint方法,或者您可以使用该对象的属性然后绘制各个帧......

For entities you want to move (or rotate), I would create another class which contained that information AND the spriteSheet. This might contain a "paint" method or you could use the properties of the object to then paint the individual frames...

类似于......

public interface PaintableEntity {
    public void paint(Graphics2D g2d, double progress);
}

public class AstroidEntity implements PaintableEntity {

    private SpriteSheet spriteSheet;
    private Point location;
    private double angel;

    public AstroidEntity(SpriteSheet spriteSheet) {
        this.spriteSheet = spriteSheet;
        location = new Point(0, 0);
        angel = 0;
    }

    public void update() {
        // Apply movement and rotation deltas...
    }

    public void paint(Graphics2D g2d, double progress) {
        g2d.drawImage(
                spriteSheet.getSprite(progress), 
                location.x, 
                location.y, 
                null);
    }

}

可以使用类似的东西创建...

Which could be created using something like...

private List<PaintableEntity> entities;
//...
entities = new ArrayList<>(10);
try {
    BufferedImage sheet = ImageIO.read(new File("..."));
    SpriteSheet spriteSheet = new SpriteSheetBuilder().
            withSheet(sheet).
            withColumns(5).
            withRows(4).
            withSpriteCount(19).
            build();

    for (int index = 0; index < 10; index++) {
         entities.add(new AstroidEntity(spriteSheet));
    }

} catch (IOException ex) {
    ex.printStackTrace();
}

注意,我只创建了一次精灵表。这可能要求您为 AstroidEntity 提供一个随机的偏移量,这将改变它为给定进度值返回的帧...

Note, I only created the sprite sheet once. This might require you to supply a random "offset" to the AstroidEntity which will change which frame it returns for a given progress value...

一种简单的方法可能是添加......

A simple way might be to add...

public SpriteSheet offsetBy(int amount) {
    List<BufferedImage> images = new ArrayList<>(sprites);
    Collections.rotate(images, amount);

    return new SpriteSheet(images);
}

SpriteSheet ,然后在 AstroidEntity 中你可以使用类似的东西创建一个偏移 SpriteSheet

to SpriteSheet, then in AstroidEntity you could create an offset SpriteSheet using something like...

public AstroidEntity(SpriteSheet spriteSheet) {
    this.spriteSheet = spriteSheet.offsetBy((int) (Math.random() * spriteSheet.count()));

可以使用类似的东西进行绘画......

Painting might be done using something like...

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g.create();
    for (PaintableEntity entity : entities) {
        entity.paint(g2d, spriteEngine.getCycleProgress());
    }
    g2d.dispose();
}

基本上,这里的关键因素是,尝试将代码与概念分离例如,我将引擎使用的每秒帧数改为60,并且没有看到精灵动画的变化,因为它是时间。

Basically, the key factor here is, try and decouple your code from a concept of "time".

正在研究单个循环时间的概念是1秒。但是,这将允许您相对简单地更改对象移动的速度。

For example, I changed the frames-per-second the engine was using to 60 and saw no change in the animation of the sprites, because it was working on the concept of a single cycle of time been 1 second. This would, however allow you to change the speed at which the objects moved relatively simply.

您还可以将引擎设置为具有周期长度的概念并使其半秒或5秒,这将影响动画速度 - 但你的其余代码将保持不变!

You could also set the engine up to have a concept of "cycle-length" and make it half a second or 5 seconds which would then affect the animation speed - but the rest of you code would remain unchanged!

这篇关于在java中加载sprite图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆