Officially deprecated
I released SpriteClip2 which effectively replaces this module.
Introduction
Sprite sheets can be very useful for bypassing Roblox’s limits regarding detailed GUI animations, but most creators tend to avoid them due to them being hard to set up.
This module is my attempt to bypass this issue by packing this feature into a single easily manipulable class. The module itself only contains a single .new function which returns a SpriteClip object, to which any Instance with an “Image” property can be assigned.
There is no limit to how many sprite sheets can be run at the same time; however, the module will iterate through all created SpriteClips, so remember to use :Destroy() when they are no longer required.
Documentation
The documentation is also found in the module.
Properties:
<Instance> Adornee Def: nil Desc: The image Instance to work on.
<string> SpriteSheet Def: nil Desc: The asset URL of the sprite sheet. Check value below.
<bool> InheritSpriteSheet Def: true Desc: Whether the SpriteSheet value will automatically take the Image value of a GUI object when the Adornee is set.
<number> CurrentFrame Def: 1
<Vector2> SpriteSizePixel Def: (100,100) Desc: Size of individual sprite in pixels
<Vector2> SpriteOffsetPixel Def: (0,0) Desc: Offset between sprites
<Vector2> EdgeOffsetPixel Def: (0,0) Desc: Offset from sprite sheet's edge
<number> SpriteCount Def: 25 Desc: Global sprite count
<number> SpriteCountX Def: 5 Desc: Horizontal sprite count
<number> FrameRate Def: 15 Desc: Framerate that gets turned into FrameTime, needs to be a divisor of 60
<number> FrameTime Def: 4 Desc: How many frames to skip-1
<bool> Looped Def: true Desc: If the CurrentFrame will reset after each cycle
<bool> State Def: false Desc: Whether the animation is playing
Functions:
(<bool>success) :Play() Desc: Sets the State property to true
(<bool>success) :Pause() Desc: Sets the State property to false
(<bool>success) :Stop() Desc: Pauses the animation and resets the frames
() :Advance(<number>count) Desc: Increments animation by 1 frame
() :Destroy() Desc: Removes the animation from the list and clears its metatable.
(<SpriteClip>clone) :Clone() Desc: Creates a new SpriteClip with the same properties as the original. Doesn't copy Adornee.
NOTE: Roblox will automatically resize large images, so you will have to download the asset after uploading to check the real resolution.
UPDATE:
I found an error that would cause the first frame to get skipped after one cycle. I reuploaded the module as a new asset to avoid potentially breaking any existing games and updated the example model.
UPDATE 2:
Roblox changed the link used to download assets. There are many threads describing how to do this that are obsolete. Use this link instead: https://assetdelivery.roblox.com/v1/asset/?id=assetidhere
As it has four frames, I put the SpriteSizePixel to (6400/4, 1600), but then it just wasn’t visible (I made changes directly to your example code after making sure that the Instance References or the API wasn’t the problem)
Now, the code works, and I have verified this through checking the RectOffset for the ImageLabel, and that it is under a ScreenGui in StarterGui. Though I’m not sure how to fix this issue. You’ve written that large files gets downscaled, maybe that is it?
Thanks! It works very well! Another question though, is it possible to flip a spritesheet through this module? Or will I have to import a flipped spritesheet?
I was using this module and I needed to flip my sprites on the X axis so I made some minor changes that allowed for that here they are if you want them. Apologies if its not the best way of doing it just thought I would share it with you after reading this reply.
In methods.Advance
-- Before
img.ImageRectOffset = newVec2(x,y)
img.ImageRectSize = newVec2(sizex,sizey)
-- After
if not self.FlipX then
img.ImageRectOffset = newVec2(x,y)
img.ImageRectSize = newVec2(sizex,sizey)
else
img.ImageRectOffset = newVec2(sizex + x,y)
img.ImageRectSize = newVec2(-sizex,sizey)
end
In the SpriteClip.new tab table
FlipX = false,
}
In the SpriteClip.new __newindex function
elseif i=="FlipX" then
tab:Advance(tab.CurrentFrame)
end
I am about to give multiple images a go! I have 84 or so frames over 4 images. Since the point of sprites is that they don’t overlap, it looks like the best way to go is to modify your “spriteclip example” to enable/disable multiple spriteclips as needed.
This is just me overcomplicating things as always. I wanted a fancy loading wheel, ended up with a module. The example could definitely be done better, just like the module. I wanted to make a revised version that can combine multiple images, but abandoned the idea since I’d only use it once if ever and it turned out to be a lot more complicated than I thought.
Nah, the module is great just as it is! First attempt, I got one sprite running. A little tinkering I had 4 running in sequence. Good work!
What I really want, and why I’m here, is to turn this into a health bar script that will fast forward and rewind on command. I will be using sprites everywhere in my GUI for the best reason…
because I can. Lol. There is a lot of untapped potential here!
Should have something to contribute in the next day or two.
That was also one of the planned features. Even if you can’t play the animations back and forth towards a fixed value, you should be able to use the module as a weird 9-slice helper and bind a custom function to heartbeat.
I fell into a classic trap! Going from frame 1 to 25 is only 24 jumps… I was blaming visibility!
Programming is done for the time being (this is missing a revised “Play” but I don’t yet have any looped animations I can test). SpriteClip is untouched. I wrote a new HandlerScript (see below). My coding skills are improving!
Here is my revised folder setup. Handler automatically processes it.
And here is the code. My contribution to the SpriteClip Fan Club! Does not work without the SpriteClip module.
local SpriteClip = require(script.SpriteClip)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local healthUpdate = ReplicatedStorage:WaitForChild("HealthUpdate")
local SpriteClipObject = {}
local Label = {}
local framePosition ={}
function loadSprites ()
for i, folder in pairs(script.Parent.SpriteSheets:GetChildren()) do
if folder:IsA("Folder") then
local spriteTask = folder.Name
SpriteClipObject[spriteTask] = {}
Label[spriteTask] = {}
framePosition[spriteTask] = 1
for j, sheet in ipairs (folder:GetChildren()) do
if sheet:IsA("ImageLabel") then
SpriteClipObject[spriteTask][j] = SpriteClip.new()
Label[spriteTask][j] = sheet
SpriteClipObject[spriteTask][j].InheritSpriteSheet = true
SpriteClipObject[spriteTask][j].Adornee = Label[spriteTask][j]
SpriteClipObject[spriteTask][j].SpriteSizePixel = Vector2.new(Label[spriteTask][j].AbsoluteSize.X,Label[spriteTask][j].AbsoluteSize.Y)
SpriteClipObject[spriteTask][j].SpriteCountX = folder.SpriteCountX.Value
if Label[spriteTask][j]:FindFirstChild("PartialSpriteSheet") then
SpriteClipObject[spriteTask][j].SpriteCount = Label[spriteTask][j].PartialSpriteSheet.Value
else
SpriteClipObject[spriteTask][j].SpriteCount = folder.SpriteCount.Value
end
SpriteClipObject[spriteTask][j].FrameRate = folder.FrameRate.Value
Label[spriteTask][j].Visible = false
end
end
end
end
end
function parseFrames(spriteTask, frame) -- returns which sheet and sprite belong to requested frame
local frameCount = 0
local frameSheet = 0
local frameIndex = 0
for i = 1, #SpriteClipObject[spriteTask] do
local increment = SpriteClipObject[spriteTask][i].SpriteCount
if increment >= frame then
frameSheet = i
frameIndex = frame
break
else
frame -= increment
end
end
return frameSheet, frameIndex
end
function switchClips (spriteTask, priorSheet, newSheet, clipDelay) --unhides new sheet before hiding old one, does not interrupt advanceSprite
Label[spriteTask][newSheet].Visible = true
wait(clipDelay)
Label[spriteTask][priorSheet].Visible = false
end
function advanceSprite (spriteTask,toFrame) --primary function, current frame to requested frame
local fromFrame = framePosition[spriteTask]
local fromSheet, fromIndex = parseFrames (spriteTask, fromFrame)
local toSheet, toIndex = parseFrames(spriteTask, toFrame)
-- Reverse for loop code
local advance = 1
if toFrame < fromFrame then advance = -1 end
--
local priorSheet = fromSheet
for i = fromSheet, toSheet, advance do
local iClip = SpriteClipObject[spriteTask][i]
local iClipDelay = 1/iClip.FrameRate
if i ~= priorSheet then
switchClips(spriteTask,priorSheet,i,iClipDelay)
end
local startFrame = 0
local endFrame = 0
if i == fromSheet then
startFrame = fromIndex
else
if advance == -1 then
startFrame = iClip.SpriteCount
else
startFrame = 1
end
end
if i == toSheet then
endFrame = toIndex
else
if advance == -1 then
endFrame = 1
else
endFrame = iClip.SpriteCount
end
end
for j = startFrame+advance, endFrame, advance do
iClip:Advance(advance)
wait(iClipDelay)
end
priorSheet = i
framePosition[spriteTask] = toFrame
end
end
loadSprites()
-- everything below this line is game specific. In my case, filling the health gauge then waiting for damage
wait(10)
Label["HealthBar"][1].Visible = true
advanceSprite("HealthBar", 70)
healthUpdate.OnClientEvent:Connect(function (percentHealth)
advanceSprite("HealthBar", math.floor(percentHealth * 70))
end)
You can finally change the resample mode in ui objects so now it wont get blurry when you make small images, that can help a lot with this module for pixel art stuff
this is the bob from the fnf mod, (credits to vs bob )
but thats what i get
althought when i uploaded the image it resized to 420,210
local SpriteClip = require(script.SpriteClip)
local SpriteClipObject = SpriteClip.new()
local Label = script.Parent.SpriteLabel
--We will make the SpriteClipObject take its Adornee's Image property.
--A custom image asset can be applied manually to the SpriteClip class itself through the SpriteSheet property.
--SpriteClipObject.InheritSpriteSheet = true
SpriteClipObject.Adornee = Label
--It will be a file without an extension, so you will have to add .png to its end.
SpriteClipObject.SpriteSizePixel = Vector2.new(420/48,210)
SpriteClipObject.SpriteCountX = 48
SpriteClipObject.SpriteCount = 48
--The frame rate is set to 15 by default. It can range from 1 to 60, but the most important part is that it has to be a divisor of 60 (60%FR == 0).
--While setting a frame rate that isn't valid won't cause visible issues, it will clamp to the next higher valid value.
SpriteClipObject.FrameRate = 30
--Finally we can play our animation. You can also pause it and stop it with :Pause() and :Stop().
--Play will return true if the animation isn't playing, the reverse goes for pausing and stopping.
SpriteClipObject:Play()
--You can also manually increment the animation with the :Advance(FrameCount) method and set the current frame with the CurrentFrame property. Note that
--you'll have to run :Advance(0) after setting it.
i tried this but it didnt work so i need ur help
heres the resized image btw
as what i know it has 48 frames
also sorry for 2 year delay
This module was originally for a friend of mine who wanted a simple way to set up loading wheels but had literally 0 experience touching anything script related. The API is simple enough a kid with basic understanding of what a pixel is managed to figure it out on his own. If you’re having issues, ask an actual question I can give an answer to instead of whatever this is.