Hello, today I’ve been trying to finish work on my animated textures script, which is supposed to play a tagged texture atlas as an animation. But I’m currently having trouble trying to get it to sync properly. At the moment individual parts, despite containing the same tags and textureid, are a few frames off from one another creating an effect that’s too distracting to ignore. I thought that its possible to change multiple parts properties at the same time in a script, but I’ve been told otherwise and I am now a little bit lost on how to handle this feature. Below will be the current script, any and all help figuring this out will be greatly appreciated <3
local AnimatedTextureService = {}
-- Services
local RunService = game:GetService("RunService")
local CollectionService = game:GetService("CollectionService")
-- Variables
local frames = 1 -- Number of frames in a spritesheet
local rows = 1 -- Number of rows in a spritesheet
local columns = 1 -- Number of columns in a spritesheet
local targetRate = 0.1 -- The smaller this number, the faster the animations will play
local accumulatedTime = 0
local connections: { [Texture]: RBXScriptConnection } = {}
local shouldUpdate = false
local needsUpdates: { () -> () } = {}
local amountOfTexturesNeedingUpdates = 0
local amountOfTextures = 0
-- Cleanup function
local function cleanup(texture: Texture)
connections[texture]:Disconnect()
connections[texture] = nil
end
-- Connection function
local function connect(texture: Texture)
local currentFrame = 1
local currentRow = 1
local currentColumn = 1
connections[texture] = RunService.Heartbeat:Connect(function(deltaTime)
if not shouldUpdate then
return
end
if texture.Parent == nil then
cleanup(texture)
return
end
if amountOfTexturesNeedingUpdates >= amountOfTextures then
for _, update in needsUpdates do
update()
end
table.clear(needsUpdates)
end
local size = texture.Parent.Size -- The spritesheet should be on the FRONT of the part
accumulatedTime = accumulatedTime + deltaTime
if accumulatedTime >= targetRate then
if texture:HasTag("Water") then -- Water
frames = 32
rows = 6
columns = 6
texture.StudsPerTileU = columns * size.X
texture.StudsPerTileV = rows * size.Y
currentColumn = currentColumn + 1
if currentColumn > columns then
currentColumn = 1
currentRow = currentRow + 1
end
if currentFrame > frames then
currentRow = 1
currentColumn = 1
currentFrame = 1
end
texture.OffsetStudsU = size.X * (currentColumn - 1)
texture.OffsetStudsV = size.Y * (currentRow - 1)
currentFrame = currentFrame + 1
end
if texture:HasTag("Seafoam") then -- Seafoam
frames = 27
rows = 9
columns = 3
texture.StudsPerTileU = columns * size.X
texture.StudsPerTileV = rows * size.Y
currentColumn = currentColumn + 1
if currentColumn > columns then
currentColumn = 1
currentRow = currentRow + 1
end
if currentFrame > frames then
currentRow = 1
currentColumn = 1
currentFrame = 1
end
texture.OffsetStudsU = size.X * (currentColumn - 1)
texture.OffsetStudsV = size.Y * (currentRow - 1)
currentFrame = currentFrame + 1
end
accumulatedTime -= targetRate
end
end)
end
-- CollectionService instances
CollectionService:GetInstanceAddedSignal("AnimatedTexture"):Connect(function(texture)
if texture:IsA("Texture") then
amountOfTextures += 1
connect(texture)
else
texture:RemoveTag("AnimatedTexture")
end
end)
CollectionService:GetInstanceRemovedSignal("AnimatedTexture"):Connect(cleanup)
-- For texture loop
for _, texture in CollectionService:GetTagged("AnimatedTexture") do
connect(texture)
end
-- Initialization and start functions
function AnimatedTextureService.Init()
return
end
function AnimatedTextureService.Start()
shouldUpdate = true
return
end
return AnimatedTextureService
You’re off a bit and it’s adding up. These need to be right down to the pixel. Always takes me a lot of testing to get it right. The picture has to be perfect also..
One of my templates set to a texture for this..
Summary
root = workspace:WaitForChild("Omega"):WaitForChild("Parts")
:WaitForChild("PodC"):WaitForChild("Faces"):WaitForChild("Com")
local frame01 = root.Bridge01L.Display.AniTexture
function AnimA(frame, xF, yF)
local oU, oV = 0, 0
task.wait(0.7)
task.spawn(function()
while true do
for stepY = 1, 4 do
local oV = ((yF * stepY) - yF) frame.OffsetStudsV = oV
for stepX = 1, 2 do
local oU = ((xF * stepX) - xF) frame.OffsetStudsU = oU - 0.075
task.wait(0.3)
end
end
end
end)
end
AnimA(frame01, 8.875, 6)
Hey! I see your template here but don’t fully understand it yet. Do you mind elaborating on it a bit further? Also, what how are your animated texture images setup? Is it the same as mine, one big image with different frames, or are you somehow using separate texture images?
Well, the picture is many shots lined up in a few rows. This one is 4 by 2
OOOO
OOOO
Then I’m using two for loops to run through them all and loop back.
This is more about that picture being perfect than anything else.
As you see it’s down to 8.875 moving horizontally for this shot,
It can’t be 0.001 off, or it will show.
For these, the trim offsets are local oU, oV = 0, 0. Looking at yours, it looks like your oU would need to be more than 0. These were re edited like 30 times to hit that perfect 0, 0. That is not really needed as long as the offset is right for the pictures. The move offset is 8.875 horizontally and 6 vertically.
I hadn’t considered moving the frames along by pixels, I’ll try that out next. Using for loops to run through the frames over run service should be worth looking into as well. If I successfully model my code off of yours as you’ve described it, I will need to separate each texture into their own module scripts, which will actually be better for organization’s sake! Below is the water texture I’m using, which has a resolution of 1000 x 1000.
I’m not even sure how I can get it perfect, since I used programs to create this spritesheet from a gif and resize it to the proper resolution. Do you have any tips on how I should go about this? Sorry if this is an odd request, I’m just unfamiliar with this process xD
Looking at it, it’s perfectly off. That may not be off at all. Maybe that is six rows. It’s different than mine. I actually make the clips (frames) one by one to match each other, with the moment of course. This is a very old school way of doing a cycle animation.. frames, loops, and math.
I think what we are looking at here is 6 rows cut into 36 frames.
The one I’m showing is 2 rows cut into 8 frames.
Considering you used a sprite sheet here. I’ll assume it is perfect..
If it is, this maybe right.
function AnimA(frame, xF, yF)
task.wait(0.7)
task.spawn(function()
while true do
for stepY = 1, 6 do
frame.OffsetStudsV = (yF * stepY) - yF
for stepX = 1, 6 do
frame.OffsetStudsU = (xF * stepX) - xF
task.wait(0.3)
end
end
end
end)
end
AnimA(frame, 1000/6, 1000/6)
No problem, this is good stuff. Good luck, it’s worth learning. Hard to go through all the steps here on the forums. I’m sure there is some tutorial showing how to do this. It’s been around forever.
Also I’ll take a shot at your script ..
Summary
local AnimatedTextureService = {}
local RunService = game:GetService("RunService")
local CollectionService = game:GetService("CollectionService")
local animations = {
Water = {
frames = 32,
rows = 6,
columns = 6,
fps = 10,
},
Seafoam = {
frames = 27,
rows = 9,
columns = 3,
fps = 10,
}
}
local currentTime = 0
local shouldUpdate = false
local textures = {}
local connections = {}
local animationConnection
local function updateTexture(texture, config, size, frameIndex)
local frameZeroIndex = frameIndex - 1
local column = (frameZeroIndex % config.columns) + 1
local row = math.floor(frameZeroIndex / config.columns) + 1
texture.StudsPerTileU = config.columns * size.X
texture.StudsPerTileV = config.rows * size.Y
texture.OffsetStudsU = size.X * (column - 1)
texture.OffsetStudsV = size.Y * (row - 1)
end
animationConnection = RunService.Heartbeat:Connect(function(deltaTime)
if not shouldUpdate then return end
currentTime = currentTime + deltaTime
for texture, data in textures do
if not texture.Parent then
textures[texture] = nil
if connections[texture] then
connections[texture]:Disconnect()
connections[texture] = nil
end
continue
end
local config = animations[data.type]
if not config then continue end
local fps = config.fps
local totalFrames = config.frames
local frameTime = 1 / fps
local animationTime = currentTime % (totalFrames * frameTime)
local frameIndex = math.floor(animationTime / frameTime) + 1
frameIndex = math.clamp(frameIndex, 1, totalFrames)
updateTexture(texture, config, data.size, frameIndex)
end
end)
local function cleanup(texture)
if connections[texture] then
connections[texture]:Disconnect()
connections[texture] = nil
end
textures[texture] = nil
end
local function setupTexture(texture)
if not texture:IsA("Texture") then return end
local animType = nil
if texture:HasTag("Water") then
animType = "Water"
elseif texture:HasTag("Seafoam") then
animType = "Seafoam"
end
if not animType then return end
textures[texture] = {
type = animType,
size = texture.Parent and texture.Parent.Size or Vector3.new(1, 1, 1)
}
local parent = texture.Parent
if parent then
connections[texture] = parent:GetPropertyChangedSignal("Size"):Connect(function()
if textures[texture] then
textures[texture].size = parent.Size
end
end)
end
end
CollectionService:GetInstanceAddedSignal("AnimatedTexture"):Connect(function(texture)
setupTexture(texture)
end)
CollectionService:GetInstanceRemovedSignal("AnimatedTexture"):Connect(cleanup)
for _, texture in CollectionService:GetTagged("AnimatedTexture") do
setupTexture(texture)
end
function AnimatedTextureService.Init()
return
end
function AnimatedTextureService.Start()
shouldUpdate = true
return
end
function AnimatedTextureService.Stop()
shouldUpdate = false
end
function AnimatedTextureService.Cleanup()
animationConnection:Disconnect()
for texture in pairs(textures) do
cleanup(texture)
end
table.clear(textures)
end
return AnimatedTextureService
Tried for a synchronized animation state: Instead of each texture having its own frame counter.
Your take on my script ended up working better than my original version, thank you! I’m sure I’ll be able to learn a new practice or to by studying it for my future scripts. Although in regards to a tutorial, I’ve searched a bit online but couldn’t find anything concrete on how to create this feature. In matter of fact it was a resource here on the forum that gave me the code to work into the script I originally posted. Maybe I’m just looking in the wrong places lol.
Not too sure about these seams between the textures, but I have a few ideas on how I can hide them so I’m not too worried about them atm.