Scale Spritesheet Iteration

I’ve noticed from looking through the DevForum that there isn’t a lot of information on spritesheet iteration. There are some which use ImageOffset, however this isn’t great if your UIs are based off scale. A while ago, I was interested in iterating through spritesheets for generating GIFs and I managed to create some iterator code which can do this based off 4 variables: the image, time (in seconds) per frame, the number of columns and the total amount of frames.

Getting the assets/information

The first step is to get a spritesheet and upload it to Roblox (as a decal). If you do not have a spritesheet but you have a GIF, then you can turn the GIF into a spritesheet. I personally use the following website for this, although there are probably better ways:

GIF to Spritesheet Website Link

Online GIF to sprite sheet converter

This website also provides video into GIF and a bunch of other features. Looking at the spritesheet, you should be able to get the number of columns and number of frames.

In the above image, there are 5 columns and 20 frames (5 * 4).

You can get the timePerFrame from the GIF, although if your timePerFrame is lower than 1/30, then it may not be exactly the same in terms of speed (although it won’t be noticeable!)

Iterator

The iterator works by setting the image size so that each frame in the spritesheet has a size of {1,0}{1,0} which would mean that each frame would fit the UI perfectly.

Spritesheet pre-sizing

Spritesheet post-sizing

Then, the code simply iterates through and sets the position every timePerFrame so that each frame in the spritesheet is shown in the UI.

local imageLabel
 
local GIFs = {
    ['Example'] = {
		timePerFrame = .1;
		image = 'http://www.roblox.com/asset/?id=3671846574';
		columns = 5;
		frames = 20;
	};
}
 
local currentGIF
 
local function calculateRows(GIF_Data)
    return math.ceil(GIF_Data.frames/GIF_Data.columns)
end
 
local function calculateFrames(rows,columns,finalPanel)
    local frames = {}
   
    for row = 0,rows-1 do
        for column = 0,columns-1 do
            frames[#frames + 1] = UDim2.new(-column,0,-row,0)
            if #frames == finalPanel then break end
        end
    end
   
    return frames
end
 
 
local function loadGIF(name)
    local GIF_Data = GIFs[name]
    if GIF_Data then
        local a = calculateRows(GIF_Data)
        imageLabel.Image = GIF_Data.image
        imageLabel.Size = UDim2.new(GIF_Data.columns,0,a,0)
       
        currentGIF = name
        local i = 0
        local frames = calculateFrames(a,GIF_Data.columns,GIF_Data.frames)
       
        spawn(function()
            while currentGIF == name do
                i = (i % GIF_Data.frames) + 1
                imageLabel.Position = frames[i]
                wait(GIF_Data.timePerFrame)
            end
        end)
    end
end
 
loadGIF('Example')
 
--[[ Iterate through all the GIFs
for name,data in pairs(GIFs) do
    loadGIF(name)
    wait(10)
end
--]]
Roblox Spritesheet Example Place (LocalScript in ReplicatedFirst!)

spritesheet_example.rbxl (18.2 KB)

72 Likes

"Necro’ing a 3-year-old post here I realize. I’ve been using this guide and the example posted to experiment with GIFs in-game. This is an incredible technique. Very efficient and works well. Lots of potential for using 2d short videos as effects in-game. To the OP, if you’re still around, thank you for making this guide and sharing the resources.

My question: Would this be able to work with more frames than would fit on a single image? Put another way, could the code be modified to iterate through multiple images (each with multiple frames on them) in order to play longer clips? I’ve experimented with this a bit by adding multiple image IDs and calling the loadGIF function sequentially (with a wait in between), but I believe this is set up to loop through the multiple frames on the individual image, which is causing studdering and overlap for me.

2 Likes

Hey!

Sorry for the super super late reply; I haven’t been on Roblox in a while. Thanks so much for your comment, I love hearing how much it’s helped you.

The code was a bit poorly designed I now realise and can be rewritten to include multiple images with smooth transitions and also to work with more than one GIF playing at a time. You can make my code more efficient (for example by referencing the original data for example instead of copying it) but here’s an example:

local GIFs = {
	['Example'] = {
		timePerFrame = .1;
		images = {
			{'http://www.roblox.com/asset/?id=3671846574',5,20}, -- image source, column no, n frames
			{"http://www.roblox.com/asset/?id=3670059307", 5, 36}
		};
	};
}

local currentGIF

local function calculateRows(GIF_Data)
	return math.ceil(GIF_Data.frames/GIF_Data.columns)
end

local function calculateFrames(rows,columns,finalPanel)
	local frames = {}

	for row = 0,rows-1 do
		for column = 0,columns-1 do
			frames[#frames + 1] = UDim2.new(-column,0,-row,0)
			if #frames == finalPanel then break end
		end
	end

	return frames
end


local function loadGIF(name, imageLabel)
	local GIF_Data = GIFs[name]
	
	if GIF_Data then
		local loadedAssets = {}
		
		for i, imageData in ipairs(GIF_Data.images) do
			local a = math.ceil(imageData[3]/imageData[2]) -- just does calculateRows
			loadedAssets[i] = {imageData[1], imageData[2], a, calculateFrames(a,imageData[2], imageData[3])}
		end
		
		return {
			running = false,
			imageIndex = 1,
			frameIndex = 0,
			imageLabel = imageLabel,
			assets = loadedAssets,
			timePerFrame = GIF_Data.timePerFrame
		}
	end
end

local function runGIF(GIFState)
	GIFState.running = true
	
	
	while GIFState.running do 
		GIFState.frameIndex += 1
		
		if GIFState.frameIndex > #GIFState.assets[GIFState.imageIndex][4] then
			GIFState.frameIndex = 1
			GIFState.imageIndex = (GIFState.imageIndex % #GIFState.assets) + 1

			GIFState.imageLabel.Image = GIFState.assets[GIFState.imageIndex][1]
			GIFState.imageLabel.Size = UDim2.new(GIFState.assets[GIFState.imageIndex][2],0,GIFState.assets[GIFState.imageIndex][3],0)
		end
		
		GIFState.imageLabel.Position = GIFState.assets[GIFState.imageIndex][4][GIFState.frameIndex]
		
		task.wait(GIFState.timePerFrame)
	end
end

local GIFState = loadGIF("Example", workspace.Part.SurfaceGui.ImageLabel)
runGIF(GIFState)

Please let me know if there are any problems!

Edit: if lag between image switches happens then it might be worth cloning the original label and making visible/invisible whenever necessary however it probably won’t be an issue.

1 Like