在java中加载sprite图像

Tie*_*yen 2 java bufferedimage sprite

我想问一下为什么错误地将任何精灵图像加载到对象中

这是我如何获得图像.

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);
            }
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

这是我如何实现它

    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;
    }
Run Code Online (Sandbox Code Playgroud)

我也想问一下我使用sprite数组的方法.我希望通过更改当前精灵图像来更改动作事件绘制的图像,从而在计时器中使用

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

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

用这个方法

    public void changeSprite(){
        if(ds==12)
            ds=0;
        ds++;
        currentSprite = sprite[ds];
    }
Run Code Online (Sandbox Code Playgroud)

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

Mad*_*mer 7

好的,所以我们需要了解很多东西.

  • 如果有不均匀的图像数量(count != rows * cols)甚至可能是每个精灵的大小,那么精灵表中有多少图像,它们是如何布局的(行/列)
  • 我们在给定周期内走了多远(让我们说一秒钟)

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

雪碧表

基于此,我们知道有5列,4行但只有19个图像.现在你可以花很多时间,为每个可能的精灵表写下大量的代码,或者你可以尝试和共生这些问题......

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);
    }

}
Run Code Online (Sandbox Code Playgroud)

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

现在,因为构建a的实际过程SpriteSheet涉及许多可能的变量,所以构建器将是个好主意......

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,再次,基于你的精灵表,这意味着我可以建立一个SpriteSheet使用像...

spriteSheet = new SpriteSheetBuilder().
        withSheet(sheet).
        withColumns(5).
        withRows(4).
        withSpriteCount(19).
        build();
Run Code Online (Sandbox Code Playgroud)

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

但是现在我们有了一个SpriteSheet,我们需要一些方法来动画它们,但我们真正需要的是一些方法来计算一个给定周期的进展(让我们说第二个是一个周期),我们可以使用一个简单的"引擎",就像是...

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);
            }
        }

    }

}
Run Code Online (Sandbox Code Playgroud)

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

好的,但这一切如何一起工作?

基本上,给定一个或多个SpriteSheets和a SpriteEngine,我们可以绘制表格,如...

Spinny

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();
    }

}
Run Code Online (Sandbox Code Playgroud)

现在,好吧,这是非常基本的,但它提供了一个想法.

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

就像是...

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);
    }

}
Run Code Online (Sandbox Code Playgroud)

可以使用类似的东西创建

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();
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

    return new SpriteSheet(images);
}
Run Code Online (Sandbox Code Playgroud)

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

public AstroidEntity(SpriteSheet spriteSheet) {
    this.spriteSheet = spriteSheet.offsetBy((int) (Math.random() * spriteSheet.count()));
Run Code Online (Sandbox Code Playgroud)

绘画可能会使用类似......

@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();
}
Run Code Online (Sandbox Code Playgroud)

基本上,这里的关键因素是,尝试将代码与"时间"概念分离.

例如,我将引擎使用的每秒帧数改为60,并且没有看到精灵动画的变化,因为它正在研究单个循环时间为1秒的概念.但是,这可以让您更改对象相对简单移动的速度.

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