在java中加载精灵图像

2023-12-04

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

这是我获取图像的方法。

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

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

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

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


好吧,我们需要知道很多事情。

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

因此,根据您上一个问题中的图像......

Sprite Sheet

我们知道有 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);
    }

}

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

现在,因为构建一个的实际过程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);
    }
}

所以,再次,根据你的精灵表,这意味着我可以构建一个SpriteSheet使用类似...

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

但它给了我建造任意数量的能力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);
            }
        }

    }

}

现在,这基本上只是 Swing 的包装类Timer,但它的作用是为我们计算循环进程。它也有不错的ActionListener支持,这样我们就可以在发生蜱虫时收到通知

好吧,但是这一切是如何协同工作的呢?

基本上,给定一个或多个SpriteSheets and 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();
    }

}

现在,好吧,这很基本,但它提供了一个想法。

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

就像是...

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

}

可以使用诸如...之类的东西来创建它

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这将改变给定进度值返回的帧...

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

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

    return new SpriteSheet(images);
}

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

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

绘画可能会使用类似的东西来完成...

@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,但精灵的动画没有变化,因为它正在研究单周期时间为 1 秒的概念。然而,这将允许您相对简单地改变对象移动的速度。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在java中加载精灵图像 的相关文章

  • 使用 Intellij 2017.2 /out 目录构建会重复 /build 目录中的文件

    更新到 Intellij 2017 2 后 构建我的项目会创建一个 out包含生成的源文件和资源文件的目录 这些文件与已包含的文件重复 build并导致duplicate class生成的类的编译器错误 关于 Gradle 或 Intell
  • 为什么byteArray的长度是22而不是20?

    我们尝试从字符串转换为Byte 使用以下 Java 代码 String source 0123456789 byte byteArray source getBytes UTF 16 我们得到一个长度为 22 字节的字节数组 我们不确定这个
  • 为什么 hibernate 在一张表中保存两个 @OneToMany 列表?

    想象一下使用 Hibernate 和 JPA 的简化代码如下 Entity class C Id GeneratedValue public long id MappedSuperclass abstract class A Id Gene
  • 具有最小刻度的图表的漂亮标签算法

    我需要手动计算图表的刻度标签和刻度范围 我知道漂亮刻度的 标准 算法 参见 我也知道这个Java实现 http erison blogspot nl 2011 07 algorithm for optimal scaling on char
  • 如何避免 Java 中的忙旋转

    我有一个多线程应用程序 其中一个线程向另一个线程发送消息 等待线程轮询消息并做出反应 处理锁 像这样 等待线程代码 while true if helloArrived System out println Got hello if bye
  • IntSummaryStatistics的summaryStatistics方法

    为什么空 IntStream 上的 summaryStatistics 方法返回整数的最大和最小值作为流中存在的最大和最小 int 值 IntStream intStream IntStream of IntSummaryStatistic
  • 解密 TLS 1.2 AES-GCM 数据包

    我正在开发一个 Java 程序来解密TLS 1 2正在使用的会话TLS RSA WITH AES 128 GCM SHA256密码 我使用wireshark 录制了一个测试会话 这大师秘密是已知的 No Time Protocol Leng
  • 将 RequestBody json 转换为对象 - Spring Boot

    我是 java 开发的初学者 但之前有 PHP 和 Python 等编程语言的经验 对于如何进行 Spring Boot 的开发几乎没有什么困惑 我正在开发一个rest API 它有以下请求 key value key1 value1 pl
  • a4j:commandLink 重新渲染后停止工作

    我创建了这个测试用例来隔离我的问题 一旦轮询执行 ajax 更新 a4j commandLink 操作就不会执行 如果我们在轮询重新渲染之前关闭 modalPanel 则会执行它 有什么建议吗 提前致谢 测试 xhtml
  • “未找到 JAVA 路径。请检查 JAVA 是否已安装。”初始化 RSelenium 时出错

    我正在尝试启动一个 RSelenium 会话到 webscrape 但是 当运行此代码时 driver lt rsDriver browser c chrome chromever 76 0 3809 126 port 4444L 我收到此
  • 如何找到 Oracle 数据库的 URL?

    如何找到 Oracle 数据库的 URL 和端口 Example jdbc oracle thin host port dbName 用户名 密码 是否有我可以查看的 SQL 命令或日志 配置文件 对于甲骨文来说 有一个tnsnames o
  • java:如何设置全局线程ID?

    是否有可能为线程设置唯一ID 在分布式系统中 线程是在许多不同的机器上创建的 例如通过 RMI 我需要它来创建日志消息 根据我的研究 我知道可以使用 log4j mdc ndc 来完成 但只能在单线程中完成 我的问题是 在创建线程时必须设置
  • 如何使用键盘上的“删除”按钮作为从 JTable 中删除行的快捷方式[重复]

    这个问题在这里已经有答案了 可能的重复 如何制作删除按钮来删除JTable中的行 https stackoverflow com questions 13236206 how to make delete button to delete
  • logcat 信息出现在 Android Studio 的“运行”选项卡中

    我的 android studio 运行选项卡很简单 然后它变得更难并给我更多信息 例如 logcat 中的信息 如何禁用或删除第二张图片中出现的更多信息并返回到第一张图片中的第一个外观 我只需要正在运行的 flutter 应用程序的日志输
  • 如何从Java中的连接获取查询字符串?

    我正在编写一个方法 尝试记录数据库调用 形成连接到它的连接 在查询之后 有很多地方调用方法 connect 来启动并调用 cleanUp 方法来结束 我不能并且不想修改每个地方 所以顺序是这样的 Connection con connect
  • Eclipse 在单独的窗口中打开代码

    我正在 eclipse 中编程 在两个显示器设置上运行 在其中一台显示器上 我只获得了项目资源管理器和编辑器作为自定义透视图 而在另一台显示器上 我获得了其他工具 例如控制台 调试 任务 变量 断点等 例如 当我单击任务视图中的任务时 这将
  • 有没有办法处理Java堆空间异常[重复]

    这个问题在这里已经有答案了 我正在寻找将文件输入流转换为大文件 文件大小为 100MB 并且抛出 java lang OutOfMemoryError Java Heap space import java io FileInputStre
  • Spring Boot中服务接口类的用途

    我的问题是关于接口类的使用 我对 Spring 还很陌生 所以如果这过于简单 请耐心等待 首先 当您可以在 BoxService 中声明 find all 时 这里拥有 IBoxService 接口有什么意义 其次 在控制器中如何使用IBo
  • 从数字列表中生成所有唯一对,n 选择 2

    我有一个元素列表 假设是整数 我需要进行所有可能的两对比较 我的方法是 O n 2 我想知道是否有更快的方法 这是我在java中的实现 public class Pair public int x y public Pair int x i
  • 混合语言源目录布局

    我们正在运行一个使用多种不同语言的大型项目 Java Python PHP SQL 和 Perl 到目前为止 人们一直在自己的私有存储库中工作 但现在我们希望将整个项目合并到一个存储库中 现在的问题是 目录结构应该是什么样的 我们应该为每种

随机推荐