Unable to prepare decals in RAM

Quick Explanation

I currently am trying to create a nice 2D animated dialogue system. I’ve got multiple sprite sheets I iterate through in order give the illusion of animation. I have a few “Idle” Animations for the character, and then when the player picks a dialogue choice the character stops the Idle animation and instantly plays the associated “Action” Animation. Then starts going through new “Idle” Animations associated with the new ending pose of the “Action”.

The problem I’m having is getting decals to be instantly ready to play the next animation.

What I’ve tried:

  • I’ve set up a few decal “Preparers” Next to the image, and preloaded the sprite sheets on there.
Video of example.

Screenshot of problem.


(At the end of the 2nd second in the video)

Explanation

Near the end of the 2nd second in the video, the decal goes blank for a frame or two while it loads the image.

  • When that didn’t work I was suggested to have multiple Decals and toggle between them.
Video of example.

Screenshot of problem.


(At the end of 7 seconds through the vid)

Explanation

In the finished system all of the images will be stacked ontop of eachother, and when they’re transparent in this version, they’ll be completely invisible in the real version.

If you take a look at the middle decal just as the video begins, there’s a few blank frames before it’s loaded, despite the image not being changed, just the ImageRectOffset.

This happens a few times throughout the video. (7 seconds in at the bottom), (13 seconds in at the middle), and (17 seconds in at the top).

Thank you in advance for helping :slight_smile:

Have you tried using PreloadAsync?

If so, please provide code samples of attempts in your scripting support topics. Explanation and video are not sufficient for conveying what specific solutions you have and have not attempted.

1 Like

I’ve avoided posting my code because:

  • There’s a lot, I don’t want to scare people away with a giant wall of code, and it’s not obvious what snippets might have the problem.
  • The problem doesn’t seem to lie within my code, It seems like there may be an unknown function or strategy to deal with this sort of problem.

Anyways here’s what I believe to be important:

PreloadAsync Part

-- At the top of my code:
local ContentProvider = game:GetService("ContentProvider")
local Assets = {"rbxassetid://00000", "rbxassetid://00001", "rbxassetid://00002", "rbxassetid://00003", "rbxassetid://00004", "rbxassetid://00005", "rbxassetid://00006", "rbxassetid://00007", "rbxassetid://00008", "rbxassetid://00009"} 
local Decal = script.Parent.ImageLabel
ContentProvider:PreloadAsync(Assets)

Playing/Preparing next animations

local PlayImage = function(Decal, Or, ReplacementDecal)
	local finished = false
	local Current = Or -- Override/Or is so I can stop previous decals animations 1/2 way through, and stop idle animation loops
	Decal.Distance.Value = 0 -- Distance refers to the % through the animation (Value/36)
	local CurrentDecal = script.Parent.Pictures.Playing  -- Takes the previously played decal, and sets it preloading the next image.
	CurrentDecal.Image = ReplacementDecal 
	CurrentDecal.Name = Decal.Name
	CurrentDecal.ImageRectSize = Vector2.new(0,0)
	CurrentDecal.ImageRectOffset = Vector2.new(0,0)
	CurrentDecal.ImageTransparency = 0.999 -- 0.999 because I've noticed images won't preload if transparent
	Decal.ImageTransparency = 0
	Decal.Name = "Playing" 
	Decal.ImageRectSize = Vector2.new(offset, offset)
	TweenService:Create(Decal.Distance, TweenInfo.new(2, Enum.EasingStyle.Linear, Enum.EasingDirection.Out), {Value = 36}):Play()
	while (Current == OverRide) and (not finished) do
		RunService.RenderStepped:Wait()
		local ImageNumber = math.ceil(Decal.Distance.Value)
		local x = (ImageNumber -1)%6
		local y = math.floor((ImageNumber-1)/6) 
		Decal.ImageRectOffset = Vector2.new(offset * x, offset * y)
		if Decal.Distance.Value == 36 then
			finished = true
		end
	end
	Decal.ImageRectOffset = Vector2.new(0, 0)
end

Could be the case, but it’s weird to suggest PreloadAsync even with the following :

1 - exposing the images A to B, means they’ve been loaded already
image
2 - video shows the transition from A to B “multiple times”, there is no way it would’ve delay if it’s already downloaded

Not entirely sure, but RunService.RenderStepped:Wait() is a guarantee 1 frame, which would make sense that the load keeps being the same duration

Because you have

-- set
Decal.ImageRectSize = Vector2.new(offset, offset)
-- 1 frame wait
RunService.RenderStepped:Wait()
-- Animation, but runs after Wait()
Decal.ImageRectOffset = Vector2.new(offset * x, offset * y)

Could means nothing, but you did not defined Decal’s ImageRectOffset on the spot, and it’s possibly capturing a zone outside of the content.

Try out printing Decal.ImageRectOffset before RunService.RenderStepped:Wait() is played

1 Like

I had/have the same problem - i tried using async method it made it better but it still occurs occasionally… will try render steppe method, but i still dont understand why either method would have an effect on it… It must be to do with rendering order and failing to process the full stack before the next frame… or something like that… I’d say leave it and hopefully roblox will fix soon… Its not gamebreaking and 90% of people wont even notice, since it seems to only happen like once every 1000 frames…

1 Like

It’s printing out 0,0 every time.

For some reason placing RenderStepped:Wait() After offsetting, made blank a frame before every new animation, not just actions.

Let me know if this sounds like it might be the problem:

Roblox might have optimisations to collect unneeded RAM, like forgetting what’s outside a Decals visible range (RectOffset, RectSize). And it forces it to re-download the full image in order to update it’s rect size/offset.

I just tried using Runservice.Heartbeat:Wait() and It seemed to work properly. Maybe it has something to do with the order of events in a frame. I’m not sure whether it’s a consistent solution or not.

Edit:

I’ve figured it out and decided to put it here just for people in the future who might have the same problem.

It matters which event you use to resume your function. Drawing of the screen happens directly after .RenderStepped making it the best option, though .Heartbeat works as well, because there’s less stuff to do in-between the two.

My problem specifically was because the first frame of the animation began on the .Heartbeat loop or (wait() loop), then continued on the .RenderStepped loop.
I just yielded the function to start on the .RenderStepped loop using RenderStepped:Wait() at the top of my function.

local PlayImage = function(Decal, Or, ReplacementDecal)
    RenderStepped:Wait()
    -- rest of code
end

You we’re right, I didn’t give the proper information in order to solve the problem.