Last active
February 21, 2023 08:49
-
-
Save Gabys2005/8e5f4c0626061fd2d8b60065749ee05d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--!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