High-performance dissolve / disintegration effect system for Roblox using EditableImage.
Designed for:
- Characters
- Meshes
- UI Images
- Decals / Textures
- Large dissolve sequences
- Reusable baked effects
The module supports multiple bake modes, glow edges, animated dissolve masks, cached frame generation, and reversible appear/disappear playback.
This module only works on the client.
EditableImage does not exist on the server, so the module must be required from a LocalScript or from a ModuleScript used by a LocalScript.
Roblox disables EditableImage APIs by default in published experiences.
To enable it:
- Verify your Roblox account (13+ and ID verified)
- Open
Game Settings - Go to
Security - Enable:
Allow Mesh / Image APIs
If this setting is disabled:
- Baking will fail
- EditableImage creation may fail
- The dissolve effect may appear invisible or broken
This is a Roblox platform restriction.
ReplicatedStorage
└── Packages
└── DissolveEffect
├── Signal
├── JobManager
├── RuntimeWorker
├── Actor
└── Mask
Example require:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Dissolve = require(
ReplicatedStorage.Packages.DissolveEffect
)The glow and edge colors look significantly better when Bloom is enabled.
Add a Bloom instance (BloomEffect) to "Lighting" with the following properties:
Intensity = 3
Size = 100
Threshold = 3.5Using the Command Bar:
local Lighting = game:GetService("Lighting")
local bloom = Instance.new("BloomEffect")
bloom.Intensity = 3
bloom.Size = 100
bloom.Threshold = 3.5
bloom.Parent = LightingYou create a dissolve object using:
local dissolve = Dissolve.new({ object }, config)The module scans supported objects and prepares internal image data.
This preparation phase is called:
Baking
While preparing:
State = "Baking"
After finishing:
State = "Ready"
You can:
- Use
Ready - Use
StateChanged - Poll
IsReady()
:dissolve:Start()The original object becomes hidden.
Generated dissolve masks animate frame-by-frame until the effect completes.
:dissolve:Start(true)Passing true plays the animation backwards.
Instead of dissolving away, the object reconstructs itself.
:dissolve:Destroy()This removes:
- EditableImages
- Masks
- Signals
- Cached references
- Running jobs
Always destroy effects you no longer use.
Dissolve.new({ instances }, config?)
:dissolve:Start(appear?)
:dissolve:Reset()
:dissolve:Destroy()
:dissolve:SetConfig(config)
:dissolve:IsReady()
:dissolve:IsRunning()
:dissolve:GetState()
:dissolve:GetProgress()
:dissolve:GetBakeProgress():dissolve.Ready
:dissolve.StateChanged
:dissolve.Completed| State | Description |
|---|---|
Idle |
Created but not prepared yet |
Baking |
Preparing dissolve frame data |
Ready |
Safe to start |
Running |
Animation currently playing |
Destroyed |
Effect no longer usable |
- Part
- MeshPart
- Decal
- Texture
- SurfaceAppearance (
ColorMap only)
- ImageLabel
- ImageButton
Material
MaterialVariant
These are blocked by Roblox security restrictions.
EditableImage can only load textures that Roblox allows the experience to access.
If a texture is unavailable:
- The module may fallback to solid colors
- Some effects may appear simplified
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Dissolve = require(
ReplicatedStorage.Packages.DissolveEffect
)
local part = workspace.Part
local dissolve = Dissolve.new({ part }, {
Bake = "Mask",
})
if dissolve:IsReady() then
dissolve:Start()
else
local connection
connection = dissolve.Ready:Connect(function()
connection:Disconnect()
dissolve:Start()
end)
endUsed in multiple examples below.
local function startWhenReady(dissolve, appear: boolean?)
if dissolve:IsReady() then
dissolve:Start(appear)
return
end
local connection
connection = dissolve.Ready:Connect(function()
connection:Disconnect()
dissolve:Start(appear)
end)
endlocal Players = game:GetService("Players")
local function dissolveCharacter(character: Model)
local dissolve = Dissolve.new({ character }, {
Speed = 0.2,
Bake = "Mask",
Behavior = {
AutoDiscover = "All",
},
})
startWhenReady(dissolve)
dissolve.Completed:Connect(function()
dissolve:Destroy()
character:Destroy()
end)
end
Players.LocalPlayer.CharacterAdded:Connect(function(character)
task.delay(3, function()
dissolveCharacter(character)
end)
end)local player = game:GetService("Players").LocalPlayer
local imageLabel = player.PlayerGui.ScreenGui.ImageLabel
local dissolve = Dissolve.new({ imageLabel }, {
Speed = 0.25,
Bake = "Mask",
})
startWhenReady(dissolve)local dissolve = Dissolve.new({ workspace.Part }, {
Bake = "Mask",
})
startWhenReady(dissolve, true)local dissolve = Dissolve.new({ workspace.Part }, {
Bake = "Full",
})
while not dissolve:IsReady() do
task.wait()
end
:dissolve:Start()
while dissolve:IsRunning() do
task.wait()
endRunService.RenderStepped:Connect(function()
print(dissolve:GetProgress())
print(dissolve:GetBakeProgress())
end)Fully pre-renders all frames.
- Fastest playback
- Lowest runtime cost
- Highest memory usage
- Longer preparation time
Best for:
- Small objects
- Cinematics
- Repeated effects
Precomputes dissolve masks only.
- Lower memory usage
- Good performance balance
- Recommended mode
- Slightly more runtime processing
Best for:
- General gameplay
- Characters
- Most projects
No baking.
Frames are generated in real time.
- Instant startup
- Minimal preparation wait
- Highest runtime CPU cost
Best for:
- Quick previews
- Development tools
- Editor workflows
Controls how aggressively baking work yields.
| Mode | Description |
|---|---|
Aggressive |
Fastest bake, may freeze frames more |
Balanced |
Recommended balance |
Relaxed |
Smoothest gameplay during baking |
Controls descendant scanning.
| Mode | Description |
|---|---|
All |
Recursive descendant scan |
Children |
Direct children only |
None |
Exact instances only |
| Setting | Description |
|---|---|
Color |
Edge highlight color |
GlowColor |
Glow behind edges |
Speed |
Animation speed |
EdgeWidth |
Hard edge thickness |
GlowWidth |
Glow size |
NoiseScale |
Dissolve pattern frequency |
NoiseResolution |
Internal noise texture size |
BakeFrames |
Amount of baked frames |
Size |
EditableImage resolution |
RegionFrequency |
Dissolve band count |
ThicknessGain |
Edge amplification |
ThicknessBias |
Edge threshold offset |
WarpStrength |
Boundary distortion amount |
NoiseMap |
Custom dissolve texture |
TransformAutoUpdateEnabled |
Updates moving targets |
EmissiveStrength |
SurfaceAppearance glow strength |
local dissolve = Dissolve.new({ workspace.Part }, {
Color = Color3.fromRGB(0, 255, 255),
GlowColor = Color3.fromRGB(0, 120, 255),
Speed = 0.15,
EdgeWidth = 1,
GlowWidth = 2,
NoiseScale = 0.5,
NoiseResolution = 64,
BakeFrames = 60,
Size = Vector2.new(128, 128),
WarpStrength = 0.5,
Bake = "Mask",
YieldMode = "Balanced",
Behavior = {
AutoDiscover = "All",
},
})The module internally reuses baked frame data when:
- The same textures are used
- The same configuration is used
This makes repeated dissolves significantly cheaper after the first bake.
Useful for:
- Enemy waves
- Rhythm gameplay
- Repeated VFX
- Character respawns
Bake = "Mask"
YieldMode = "Balanced"
BakeFrames = 60
Size = Vector2.new(128, 128)For large meshes or characters:
YieldMode = "Relaxed"This reduces frame spikes while baking.
Avoid simultaneously animating:
ImageTransparencyduring dissolve playback.
dissolve:Destroy()Do not leave unused effects alive.
Add the module directly from the Creator Store:
Creator Store Model:
https://create.roblox.com/store/asset/126151482111558
[dependencies]
DissolveEffect = "y-workplace/dissolveeffect@1.0.4"
Wally Package:
https://wally.run/package/y-workplace/dissolveeffect
MIT License.