Skip to content

Instantly share code, notes, and snippets.

@Gabys2005
Last active February 21, 2023 08:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Gabys2005/8e5f4c0626061fd2d8b60065749ee05d to your computer and use it in GitHub Desktop.
Save Gabys2005/8e5f4c0626061fd2d8b60065749ee05d to your computer and use it in GitHub Desktop.
--!strict
local run = game:GetService("RunService")
local http = game:GetService("HttpService")
local ts = game:GetService("TweenService")
local DontOffsetPartTypes = {
Part = true,
WedgePart = true,
CornerWedgePart = true
}
local GIFPlayer: GIFPlayer = {
_AllGifs = {},
--// I use emptyFunction so much because roblox's type checking screams at me otherwise
--// I'll probably try to find a better way to do it later
new = emptyFunction,
DestroyAllGifsIn = emptyFunction,
GetById = emptyFunction
}
type GIFPlayer = {
_AllGifs: {[string]: GIF},
new: (GIFSettings) -> GIF,
DestroyAllGifsIn: (ins: Instance) -> (),
GetById: (id: string) -> GIF?
}
type FadeEffect = {
Name: "Fade",
Blocking: boolean?,
Time: number?
}
type NoEffect = {
Name: "None"
}
type Effect = FadeEffect | NoEffect
type Screen = ImageLabel | Texture
type ScreenContainer = Texture | GuiBase
type GIF = {
FPS: number,
FramesPerRow: number,
Image: string,
Frames: number,
LastFrame: number,
SpriteSheets: {any}?,
Size: UDim2,
ID: string,
Screens: {Screen},
Frame: number,
Row: number,
TotalFrames: number,
SpriteSheetIndex: number,
UpdateTextureSize: boolean,
Effect: Effect,
Player: RBXScriptConnection?,
Play: (GIF) -> (),
Pause: (GIF) -> (),
Destroy: (GIF) -> (),
Reset: (GIF) -> (),
}
type SpriteSheet = {
FPS: number,
Image: string,
Frames: number,
FramesPerRow: number,
}
type GIFSettings = {
Screen: ScreenContainer,
Screens: {ScreenContainer},
ID: string?,
FPS: number?,
Image: string?,
Frames: number?,
FramesPerRow: number?,
Effect: Effect?,
UpdateTextureSize: boolean?,
SpriteSheets: {SpriteSheet}?
}
local function convertFaceToAxes(face: Enum.NormalId)
if face == Enum.NormalId.Right or face == Enum.NormalId.Left then
return "Z","Y"
elseif face == Enum.NormalId.Front or face == Enum.NormalId.Back then
return "X","Y"
elseif face == Enum.NormalId.Top or face == Enum.NormalId.Bottom then
return "X","Z"
end
return "X", "Z"
end
function emptyFunction(...: any) end
function GIFPlayer.new(setting: GIFSettings): GIF
if setting.Screen then
setting.Screens = {setting.Screen}
end
local emptyGif: GIF = {
FPS = 0,
FramesPerRow = 0,
Image = "",
Frames = 0,
LastFrame = 0,
SpriteSheets = {},
Size = UDim2.new(),
ID = "",
Screens = {},
UpdateTextureSize = false,
Effect = {Name = "None"},
Frame = 0,
Row = 0,
TotalFrames = 0,
SpriteSheetIndex = 0,
Play = emptyFunction,
Pause = emptyFunction,
Destroy = emptyFunction,
Reset = emptyFunction,
}
local self: GIF = emptyGif
if setting.ID and GIFPlayer._AllGifs[setting.ID] then
GIFPlayer._AllGifs[setting.ID]:Destroy()
end
self.FPS = (if setting.SpriteSheets then setting.SpriteSheets[1].FPS else setting.FPS) or 1
self.FramesPerRow = (if setting.SpriteSheets then setting.SpriteSheets[1].FramesPerRow else setting.FramesPerRow) or 1
self.Image = (if setting.SpriteSheets then setting.SpriteSheets[1].Image else setting.Image) or ""
self.Frames = (if setting.SpriteSheets then setting.SpriteSheets[1].Frames else setting.Frames) or 1
self.LastFrame = tick()
self.SpriteSheets = setting.SpriteSheets
self.Size = UDim2.new()
self.ID = setting.ID or http:GenerateGUID(false)
self.Screens = {}
--// Frame = frame on current row
self.Frame = 1
self.Row = 1
--// Total Frames = frame in current sprite sheet
self.TotalFrames = 1
self.SpriteSheetIndex = 1
self.Size = UDim2.fromScale(self.FramesPerRow, math.ceil(self.Frames / self.FramesPerRow))
self.UpdateTextureSize = if setting.UpdateTextureSize == nil then false else setting.UpdateTextureSize
self.Effect = if setting.Effect == nil then { Name = "None" } else setting.Effect
if self.Effect.Name == "Fade" then
if self.Effect.Blocking == nil then
self.Effect.Blocking = true
end
if self.Effect.Time == nil then
self.Effect.Time = 0.25
end
end
for _, guiOrTexture in setting.Screens do
if guiOrTexture:IsA("GuiBase") then
local big = Instance.new("Frame")
big.ClipsDescendants = true
big.BackgroundTransparency = 1
big.Size = UDim2.fromScale(1,1)
big.Name = "GIFPlayer"
big.Parent = guiOrTexture
local img = Instance.new("ImageLabel")
img.Name = "Img"
img.BackgroundTransparency = 1
img.Image = self.Image
img.Size = self.Size
img.Parent = big
if self.Effect.Name == "Fade" then
img.ImageTransparency = 1
ts:Create(img, TweenInfo.new(self.Effect.Time), { ImageTransparency = 0 }):Play()
end
table.insert(self.Screens, img)
elseif guiOrTexture:IsA("Texture") and guiOrTexture.Parent and guiOrTexture.Parent:IsA("BasePart") then
local widthAxis, heightAxis = convertFaceToAxes(guiOrTexture.Face)
--// I have to cast size to any because roblox thinks that if x[y] then x must be a table
local size: any = guiOrTexture.Parent.Size
guiOrTexture.StudsPerTileU = size[widthAxis] * self.FramesPerRow
guiOrTexture.StudsPerTileV = size[heightAxis] * math.ceil(self.Frames / self.FramesPerRow)
guiOrTexture.Texture = self.Image
if self.Effect.Name == "Fade" then
guiOrTexture.Transparency = 1
ts:Create(guiOrTexture, TweenInfo.new(self.Effect.Time), { Transparency = 0 }):Play()
end
table.insert(self.Screens, guiOrTexture)
end
end
--table.insert(GIFPlayer._AllGifs, self) then
GIFPlayer._AllGifs[self.ID] = self
function self:Play()
self.LastFrame = tick() - (1/self.FPS)
local event = run.Heartbeat
if run:IsClient() then
event = run.RenderStepped
end
self.Player = event:Connect(function()
if self.LastFrame + (1/self.FPS) <= tick() then
--// If there are multiple sprite sheets and the last one just ended
--// Basically next sprite sheet
--// (Total Frames == 1 because it gets set to 0 and then ++ on the last frame)
if self.SpriteSheets and self.TotalFrames == 1 then
for _, img in self.Screens do
if img:IsA("ImageLabel") then
img.Size = self.Size
img.Image = self.SpriteSheets[self.SpriteSheetIndex].Image
elseif img.Parent and img.Parent:IsA("BasePart") then
local widthAxis, heightAxis = convertFaceToAxes(img.Face)
local size: any = img.Parent.Size
img.StudsPerTileU = self.Size.X.Scale * size[widthAxis]
img.StudsPerTileV = self.Size.Y.Scale * size[heightAxis]
img.Texture = self.SpriteSheets[self.SpriteSheetIndex].Image
end
end
self.FPS = self.SpriteSheets[self.SpriteSheetIndex].FPS
end
--// Update the positions to show current frame
for _, img in self.Screens do
if img:IsA("ImageLabel") then
img.Position = UDim2.fromScale(1-self.Frame, 1-self.Row)
elseif img.Parent and img.Parent:IsA("BasePart") then
local widthAxis, heightAxis = convertFaceToAxes(img.Face)
local offsetX, offsetY = 0, 0
local size: any = img.Parent.Size
if self.UpdateTextureSize then
img.StudsPerTileU = self.Size.X.Scale * size[widthAxis]
img.StudsPerTileV = self.Size.Y.Scale * size[heightAxis]
end
if DontOffsetPartTypes[img.Parent.ClassName] == nil then
--// Cylinders, spheres, etc. have a different offset for textures because reasons
offsetX = size[widthAxis] / 2
offsetY = size[heightAxis] / 2
end
img.OffsetStudsU = (self.Frame-1) * size[widthAxis] + offsetX
img.OffsetStudsV = (self.Row-1) * size[heightAxis] + offsetY
end
end
--// Move to the next frame (or row)
if self.Frame == self.FramesPerRow then
self.Row += 1
self.Frame = 1
else
self.Frame += 1
end
--// If the current sprite sheet has ended
if self.TotalFrames == self.Frames then
self.Row = 1
self.Frame = 1
self.TotalFrames = 0
--// If there are multiple sprite sheets, move to the next one
if self.SpriteSheets then
self.SpriteSheetIndex += 1
if self.SpriteSheetIndex > #self.SpriteSheets then
self.SpriteSheetIndex = 1
end
self.FramesPerRow = self.SpriteSheets[self.SpriteSheetIndex].FramesPerRow
self.Frames = self.SpriteSheets[self.SpriteSheetIndex].Frames
self.Size = UDim2.fromScale(self.FramesPerRow, math.ceil(self.Frames/self.FramesPerRow))
end
end
self.TotalFrames = self.TotalFrames + 1
self.LastFrame = tick()
end
end)
end
function self:Pause()
if self.Player then
self.Player:Disconnect()
end
end
function self:Destroy()
self:Pause()
for _, img in self.Screens do
if img:IsA("ImageLabel") and img.Parent then
if self.Effect.Name == "None" then
img.Parent:Destroy()
elseif self.Effect.Name == "Fade" then
local info = TweenInfo.new(self.Effect.Time)
ts:Create(img, info, { ImageTransparency = 1 }):Play()
task.delay(info.Time, function()
img.Parent:Destroy()
end)
end
elseif img:IsA("Texture") then
if self.Effect.Name == "None" then
img.Texture = "rbxassetid://0"
elseif self.Effect.Name == "Fade" then
if self.Effect.Blocking == true then
local info = TweenInfo.new(self.Effect.Time)
ts:Create(img, info, { Transparency = 1 }):Play()
task.delay(info.Time, function()
img.Texture = "rbxassetid://0"
end)
else
img.Texture = "rbxassetid://0"
end
end
end
end
if self.Effect.Name == "Fade" and self.Effect.Blocking then
task.wait(self.Effect.Time)
end
GIFPlayer._AllGifs[self.ID] = nil
end
function self:Reset()
self.TotalFrames = 1
self.Frame = 1
self.Row = 1
for _, img in self.Screens do
if img:IsA("ImageLabel") then
img.Position = UDim2.new(0,0,0,0)
else
img.OffsetStudsU = 0
img.OffsetStudsV = 0
end
end
end
return self
end
function GIFPlayer.DestroyAllGifsIn(ins: Instance)
for _, gif in GIFPlayer._AllGifs do
if gif.Screens[1]:IsDescendantOf(ins) or gif.Screens[1] == ins then
gif:Destroy()
end
end
end
function GIFPlayer.GetById(id: string)
if GIFPlayer._AllGifs[id] then
return GIFPlayer._AllGifs[id]
end
return
end
return GIFPlayer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment