以可变帧大小动态地将精灵表切割成单独的位图

Posted

技术标签:

【中文标题】以可变帧大小动态地将精灵表切割成单独的位图【英文标题】:Cutting sprite sheets into individual bitmaps dynamically with variable frame size 【发布时间】:2013-11-21 19:05:46 【问题描述】:

我想创建一个 cookie-cutter 类型的类,它接受一个源矩形并动态地遍历 sprite 表中的每一帧,直到每个图像都被剪切出来用于动画。当精灵表中的每个帧大小相同时,我可以做到这一点,但几乎不可能在具有相同帧大小的动画中找到具有任何复杂性的精灵表。例如:

按照我想要的方式进行剪辑和动画处理,但是:

由于它的帧大小可变,因此变得很有趣(因为我的程序目前假设所有帧的大小都相同)。有没有办法在某种程度上感知精灵表中每个单独帧的帧大小,或者这是一个失败的原因?

当前创建“千篇一律”源框架的代码:

// Indirect Variable Sets (Sprite Animation and Sprite Sheet Cuts)
    framesPerRow = frameCount/spriteSheetRows;
    spriteWidth = bmp.getWidth() / framesPerRow;    // cut the sheet into pieces based on 
                                                    // sprite width: frames/row ratio
    spriteHeight = bmp.getHeight()/spriteSheetRows; // cut the sheet horizontally into pieces based on
                                                    // total height : total rows ratio

    setSourceRect(new Rect(0, 0, spriteWidth, spriteHeight));
    setFramePeriod(1000 / fps);                     // set the framePeriod based on desired fps

然后在我的更新方法中使用:

public void update(long gameTimeInMillis)
    // If the game time has been longer than the frame period...
    // the reason that we need frameTicker is so that we can use our variable (frameTicker)
    // to keep track of the last time that the frame was updated (relative to our game)
    if (gameTimeInMillis > frameTicker + framePeriod)
        frameTicker = gameTimeInMillis;             // set last update time (current time)
        // increment the animation frame
        currentFrame++;

        // Get current column in sprite sheet:
        // this works like this, imagine we are at frame 20, and we have 5 frames per row
        // 20%5 = 0 so we are at the end of the row, if we are at frame 22, 22%5 = 2, etc.
        frameColumn = currentFrame%framesPerRow;

        // if we are at our max frame count (note, we start at 0) then reset our animation
        if(currentFrame >= frameCount) 
            currentFrame = 0;                   
        

        // increment the sprite sheet row if we are at the end of the row
        if(frameColumn == 0)
            currentRow++;
        

        // if we are at our max rows (note, we start at 0) then reset our animation rows
        if(currentRow >= spriteSheetRows)
            currentRow = 0;
        

        // define the "cookie cutter" sourceRectangle for our sprite sheet
        this.sourceRect.left = frameColumn * spriteWidth;
        this.sourceRect.right = this.sourceRect.left + spriteWidth;
        this.sourceRect.top = currentRow * spriteHeight;
        this.sourceRect.bottom = this.sourceRect.top + spriteHeight;
        Log.d(TAG, "Top coordinates = " + currentRow);
    

由于我不是艺术家,我的目标是使用预渲染的精灵表,以便我可以在 2D 动画环境中发挥我的技能。问题是,我发现的大多数精灵表似乎都有可变帧,这使它们对我来说相当无用,除非我能找到一种更精确地切割它们的方法(或另一种切割方法,是否有我缺少的 API 工具? )

【问题讨论】:

【参考方案1】:

您可以添加一些元数据与精灵表一起定义像素位置以及表中每个帧的宽度/高度。这比拥有一个完美的网格更费力,但它将使您能够按原样使用更多种类的精灵表。例如(不按比例)如果你去获取第 1 帧,你会发现它是从 128 像素位置开始的 16x32,你可以根据整张纸的宽度/高度来计算纹理坐标。这听起来很乱,但一旦你开始工作,它应该会给你更多的灵活性,并且可以在各种 sprite sheet 中重复使用。

否则,您可以使用 Gimp 将单个帧剪切并粘贴到网格上。或者,将帧切割成单个图像,所有图像都具有相同的宽度/高度,然后使用 ImageMagick 之类的工具将单个图像组合成一个精灵表。我不记得实际的命令,但我使用命令行中的 ImageMagick 将 Reiner 的一些 Tilesets 组装成精灵表。 ImageMagick 将连接目录中的所有图像,您甚至可以告诉它为您调整它们的大小,以便您可以从更高分辨率的图像开始,并在需要时将它们强制为 2 的幂。

【讨论】:

【参考方案2】:

您可以将精灵存储为Rects 的数组。 (说真的,您不想将它们存储在二维数组中。)

Rect[] frames = new Rect[TOTAL_NUMBER_OF_FRAMES];

现在,计算机无法知道如何剪纸。最简单的方法可能是创建一个如下所示的文本文件:

x,y,width,height
x,y,width,height
x,y,width,height

这将允许您使用具有不同宽度和高度的奇怪精灵表。这也意味着您可以拥有没有精灵的图像位(例如,当每个帧周围有边框或您不想使用的帧时)。使用文件 IO 将它们放入数组中。 (请注意,您可能希望将 BitMapFactory 选项设置为不缩放位图)。

您可以使用以下方法绘制第 n 帧:

canvas.drawBitmap(bmp, frames[n],
    new RectF(x, y, x + frames[n].width(), y + frames[n].height()),
    paint);

【讨论】:

以上是关于以可变帧大小动态地将精灵表切割成单独的位图的主要内容,如果未能解决你的问题,请参考以下文章

Spritesheet 以编程方式切割:最佳实践

Unity像素艺术多个精灵没有正确切割

是否可以在 Unity 中获取精灵表动画的当前帧?

使用 html 或 javascript 从精灵表中提取单个帧

将精灵表转换为 gif 动画

在 Java 中读取精灵表的最佳方法是啥?