Perclean (v2.5) - For easy implementation of low-quality mode!

Why would someone want a toggle for all effects instead of a settings menu where you can enable or disable specific effects to fit your liking?

1 Like

Well, that’s no easy task and that would take forever for me to do so

1 Like

I’ve done it, it wasn’t that hard.

2 Likes

Oh sorry, I’m just a game designer who doesn’t know a lot of coding, because I’m kinda new here, I’m a solo dev btw.

1 Like

Take for example, games that on-join let you pick PC or Mobile performance.

If you were using a phone (which can’t handle the heavy game), you would probably wanna get the best optimization for your phone to handle a heavy game. So it’s easier to just toggle all effects rather than toggle lots of options.

And plus, you’re missing the point. It’s fine if you want to make a settings GUI with multiple options, go ahead! But I made this for people who want to make a single low-quality button.

3 Likes

PERCLEAN - V2.0 (NEW UPDATE)


  • Added the feature of being able to toggle specific effects instead of all effects
  • Added new function perclean.SetLowQualityType(...) for new feature above (more information on documentation inside Module)
  • Fixed some small bugs
  • Added more documentation to functions
2 Likes

Woah, a new update and a new logo, I’ll try it out, thanks for updating us.

1 Like

PERCLEAN - V2.5 (NEW UPDATE)


  • NEW FUNCTION! You can now implement a RenderDistance system to your game
    (with optional part whitelists)
  • Completely rewrote the entire module
  • Put everything in two functions ( EnableLowQuality() & DisableLowQuality())
  • Optimized the module (removed useless functions and made the code cleaner)
  • Better enable/disable system for easier implementation of new toggleable effects
  • Made a GitHub documentation for the module

For this update, I completely went from scratch, rewriting the entire module. I wrote Perclean a pretty long time ago, and back then I wasn’t as good of a programmer as I am now. And plus, the code was pretty messy so yeah.

I’ll probably add more documentation for each and every single toggleable effect included in the module.

Anyways yeah, this may or may not be the last update (unless I think of something else to add in this module, feel free to suggest!)

3 Likes

Yo bro, its a nice thing, i have still a question.
How to use the

“renderdistance”

and what exactly is (parameter 1, parameter 2) are the Numbers right ?

Is this right ?

1 Like

Actually, nah.

The second parameter is the render distance value itself, and the third parameter is optional. Basically, you could make a table of parts that you want to whitelist. So no matter how far you are you can see those specific parts.

Documentation for all functions and how to use them can be read here.

Speaking of render distance, I accidentally forgot about making the function work whenever you DONT have a whitelist table. I guess it wasn’t actually optional before LOL-

Fixed the issue, should be working now.

1 Like

Okay so now i understand render distance.
But i still don’t understand the module with:

local conState = {
castshadow = false or true??? what does false or true do?
lighting = ??
etc….

What i mean is, what if it’s on true on what if it’s on false?
What is changing if i change it?

Maybe im just dumb but e idk…

1 Like

You don’t have to change anything in the module, that table just makes my life easier for when toggling.

1 Like

I like the idea of this module but I wish there was a gui button and everything was client because there high end pcs that want to use full graphics.

1 Like

You could easily implement it to a client GUI yourself. I’ve done something like this before for a friend of mine.

You can use the module for server-side AND client-side.


Example:

-- example localscript is located under a guibutton
local p = require(workspace.PercleanModule)
local button = script.Parent

button.MouseButton1Click:Connect(function()
     p.EnableLowQuality("lighting")
end)

When disabling the shadows low quality, it seems to set every part’s CastShadow to true regardless of their setting before. Is there someway to exclude parts from this, as there are some parts that shouldn’t cast shadows regardless of the shadow setting.

1 Like

Why not one function; ToggleLowQuality(true)?

1 Like

Hey everyone!

I made a small fork to this module for usage in my game and decided to release it in case anyone else here wants to use it.

My fork adds the option HideParticles to the effects, letting you disable all particles, beams, trails, etc from the game.

Usage is pretty simple, the one argument is has is a whitelist table. Usage example below:

local p = require(script.PercleanModule)

-- enabling
p.EnableLowQuality("hideparticles", {workspace.Baseplate.ParticleEmitter}) -- 2nd argument is an optional whitelist.

-- disabling
p.DisableLowQuality("hideparticles")

Quick note: You can also add a particles parent, so for example {workspace.Tool.Handle} and any particles inside the handle will be ignored.

I did add checks to make sure newly added particles and/or particles that move around a lot (for example a tool with particles) are still functional with this fork.

Get the model on the Roblox website!

Or

View The Source
--[[
@author: Downrest
@author: SpiralAPI
@fork: spiralapi/perclean
@description: A simple performance module with multiple options that help optimize or make a game "low-quality" for potato devices
@documentation: https://github.com/Downrest/Perclean
]]

local PercleanModule = {}
local RS = game:GetService("ReplicatedStorage")
local RNS = game:GetService("RunService")
local PS = game:GetService("Players")
local LT = game:GetService("Lighting")
local conState = {
	castshadow = false,
	lighting = false,
	partrendering = false,
	renderdistance = false,
	hideparticles = false
}

local getWorkspace = workspace
local newInstance = Instance.new
local stringMatch = string.match
local mathHuge = math.huge
local taskWait = task.wait
local taskSpawn = task.spawn
local affectedParticles = {}

function PercleanModule.EnableLowQuality(effectType, optionalParameter, optionalParameter2)
	local percleanCon

	if not RS:FindFirstChild("PercleanConfigurables") then
		percleanCon = newInstance("Folder")
		percleanCon.Name = "PercleanConfigurables"
		percleanCon.Parent = RS
	else
		percleanCon = RS:FindFirstChild("PercleanConfigurables")
	end

	if effectType then
		if stringMatch(effectType, "castshadow") then
			if conState.castshadow == false then
				conState.castshadow = true

				for i,v in ipairs(getWorkspace:GetDescendants()) do
					if v:IsA("UnionOperation") or v:IsA("PartOperation") or v:IsA("Part") or v:IsA("TrussPart") or v:IsA("WedgePart") then
						v.CastShadow = false
					end
				end
			else
				warn("[PERCLEAN] CASTSHADOW HAS ALREADY BEEN ENABLED")
			end
		elseif stringMatch(effectType, "lighting") then
			if conState.lighting == false then
				conState.lighting = true

				local lightingCon
				local originalFog

				if not percleanCon:FindFirstChild("Lighting") then
					lightingCon = newInstance("Folder")
					lightingCon.Name = "Lighting"
					lightingCon.Parent = percleanCon

					originalFog = newInstance("NumberValue")
					originalFog.Name = "OriginalFog"
					originalFog.Parent = lightingCon

					if not LT:FindFirstChildWhichIsA("Atmosphere") then
						originalFog.Value = LT.FogEnd
						LT.FogEnd = mathHuge
					else
						warn("[PERCLEAN] FOG CANNOT BE CHANGED DUE TO ATMOSPHERE INSTANCE INSIDE LIGHTING")
					end
				else
					lightingCon = percleanCon:FindFirstChild("Lighting")
					originalFog = lightingCon:FindFirstChild("OriginalFog")

					if not LT:FindFirstChildWhichIsA("Atmosphere") then
						originalFog.Value = LT.FogEnd
						LT.FogEnd = mathHuge
					else
						warn("[PERCLEAN] FOG CANNOT BE CHANGED DUE TO ATMOSPHERE INSTANCE INSIDE LIGHTING")
					end
				end

				for i,v in ipairs(LT:GetChildren()) do
					if not v:IsA("Sky") and not v:IsA("NumberValue") then
						v:Clone().Parent = lightingCon
						v:Destroy()
					end
				end

				LT.GlobalShadows = false
				LT.EnvironmentDiffuseScale = 0
				LT.EnvironmentSpecularScale = 0
			else
				warn("[PERCLEAN] LIGHTING HAS ALREADY BEEN ENABLED")
			end
		elseif stringMatch(effectType, "partrendering") then
			if conState.partrendering == false then
				conState.partrendering = true

				for i,v in ipairs(getWorkspace:GetDescendants()) do
					if v:IsA("PartOperation") or v:IsA("Part") or v:IsA("TrussPart") or v:IsA("WedgePart") then
						if v.Material == Enum.Material.Glass then
							v.Transparency = 1
							v.Reflectance = 0
						end
					elseif v:IsA("UnionOperation") then
						if v.Material == Enum.Material.Glass then
							v.Transparency = 1
							v.Reflectance = 0
						end
						v.SmoothingAngle = 1	
						v.RenderFidelity = Enum.RenderFidelity.Automatic
					end
				end
			else
				warn("PERCLEAN] PARTRENDERING HAS ALREADY BEEN ENABLED")
			end
		elseif stringMatch(effectType, "renderdistance") then
			if conState.renderdistance == false then
				conState.renderdistance = true

				if RNS:IsClient() then
					local player = PS.LocalPlayer
					local character = player.Character or player.CharacterAdded:Wait()

					if optionalParameter then
						taskSpawn(function()
							while taskWait(.2) do
								if conState.renderdistance == false then
									break
								end

								for i,v in ipairs(workspace:GetDescendants()) do
									if v:IsA("Part") or v:IsA("MeshPart") or v:IsA("TrussPart") or v:IsA("WedgePart") or v:IsA("CornerWedgePart") or v:IsA("PartOperation") or v:IsA("UnionOperation") and not v:IsDescendantOf(character) then
										if optionalParameter2 then
											for i = 1, #optionalParameter2, 1 do
												optionalParameter2[i].Transparency = 0
											end

											if (character.Head.Position - v.Position).Magnitude > optionalParameter then
												v.Transparency = 1
											else
												v.Transparency = 0
											end
										else
											if (character.Head.Position - v.Position).Magnitude > optionalParameter then
												v.Transparency = 1
											else
												v.Transparency = 0
											end
										end
									end
								end
							end
						end)
					else
						error("[PERCLEAN] PLEASE INSERT A PARAMETER FOR THE DISTANCE")
					end
				else
					warn("[PERCLEAN] YOU CANNOT RUN THIS FUNCTION IN SERVER, ONLY IN CLIENT")
				end
			else
				warn("[PERCLEAN] RENDERDISTANCE HAS ALREADY BEEN ENABLED")
			end
		elseif stringMatch(effectType, "hideparticles") then
			if conState.hideparticles == false then
				conState.renderdistance = true

				if RNS:IsClient() then
					local whitelist
					if optionalParameter then
						if type(optionalParameter) == "table" then
							whitelist = optionalParameter
						else
							warn("[PERCLEAN] PLEASE INSERT A TABLE FOR PARTICLE WHITELIST")
						end
					else
						whitelist = {}
					end

					local function checkParticle(particle)
						if particle:IsA("ParticleEmitter") or particle:IsA("Beam") or particle:IsA("Trail") or particle:IsA("Sparkles") or particle:IsA("Smoke") or particle:IsA("Fire") then
							if whitelist[particle] == nil and whitelist[particle.Parent] == nil then
								particle.Enabled = false
								table.insert(affectedParticles, particle)
							end
						end
					end

					for i, v in pairs(workspace:GetDescendants()) do
						checkParticle(v)
					end

					_G.ParticleConnection = game.Workspace.DescendantAdded:Connect(function(desc)
						checkParticle(desc)
					end)
				else
					warn("[PERCLEAN] YOU CANNOT RUN THIS FUNCTION IN SERVER, ONLY IN CLIENT")
				end
			else
				warn("[PERCLEAN] HIDEPARTICLES HAS ALREADY BEEN ENABLED")
			end
		end
	end
end

function PercleanModule.DisableLowQuality(effectType)
	local percleanCon

	if not RS:FindFirstChild("PercleanConfigurables") then
		percleanCon = newInstance("Folder")
		percleanCon.Name = "PercleanConfigurables"
		percleanCon.Parent = RS
	else
		percleanCon = RS:FindFirstChild("PercleanConfigurables")
	end

	if effectType then
		if stringMatch(effectType, "castshadow") then
			if conState.castshadow == true then
				conState.castshadow = false

				for i,v in ipairs(getWorkspace:GetDescendants()) do
					if v:IsA("UnionOperation") or v:IsA("PartOperation") or v:IsA("Part") or v:IsA("TrussPart") or v:IsA("WedgePart") then
						v.CastShadow = true
					end
				end
			else
				warn("[PERCLEAN] CASTSHADOW HAS ALREADY BEEN DISABLED")
			end
		elseif stringMatch(effectType, "lighting") then
			if conState.lighting == true then
				conState.lighting = false
				local lightingCon
				local originalFog

				if not percleanCon:FindFirstChild("Lighting") then
					lightingCon = newInstance("Folder")
					lightingCon.Name = "Lighting"
					lightingCon.Parent = percleanCon

					if not LT:FindFirstChildWhichIsA("Atmosphere") then
						if originalFog then
							LT.FogEnd = originalFog.Value
						else
							warn("[PERCLEAN] ORIGINAL FOG VALUE CANNOT BE FOUND, UNABLE TO CHANGE FOG")
						end
					else
						warn("[PERCLEAN] FOG CANNOT BE CHANGED DUE TO ATMOSPHERE INSTANCE INSIDE LIGHTING")
					end
				else
					lightingCon = percleanCon:FindFirstChild("Lighting")
					originalFog = lightingCon:FindFirstChild("OriginalFog")

					if not LT:FindFirstChildWhichIsA("Atmosphere") then
						if originalFog then
							LT.FogEnd = originalFog.Value
						else
							warn("[PERCLEAN] ORIGINAL FOG VALUE CANNOT BE FOUND, UNABLE TO CHANGE FOG")
						end
					else
						warn("[PERCLEAN] FOG CANNOT BE CHANGED DUE TO ATMOSPHERE INSTANCE INSIDE LIGHTING")
					end
				end

				for i,v in ipairs(lightingCon:GetChildren()) do
					if not v:IsA("Sky") and not v:IsA("NumberValue") then
						v:Clone().Parent = LT
						v:Destroy()
					end
				end

				LT.GlobalShadows = true
				LT.EnvironmentDiffuseScale = 1
				LT.EnvironmentSpecularScale = 1
			else
				warn("[PERCLEAN] LIGHTING HAS ALREADY BEEN DISABLED")
			end
		elseif stringMatch(effectType, "partrendering") then
			if conState.partrendering == true then
				conState.partrendering = false

				for i,v in ipairs(getWorkspace:GetDescendants()) do
					if v:IsA("PartOperation") or v:IsA("Part") or v:IsA("TrussPart") or v:IsA("WedgePart") then
						if v.Material == Enum.Material.Glass then
							v.Transparency = .5
							v.Reflectance = 1
						end
					elseif v:IsA("UnionOperation") then
						if v.Material == Enum.Material.Glass then
							v.Transparency = .5
							v.Reflectance = 1
						end
						v.SmoothingAngle = 1
						v.RenderFidelity = Enum.RenderFidelity.Performance
					end
				end
			else
				warn("PERCLEAN] PARTRENDERING HAS ALREADY BEEN DISABLED")
			end
		elseif stringMatch(effectType, "renderdistance") then
			if conState.renderdistance == true then

				if RNS:IsClient() then
					local player = PS.LocalPlayer
					local character = player.Character or player.CharacterAdded:Wait()

					for i,v in ipairs(game:GetDescendants()) do
						if v:IsA("Part") and not v:IsDescendantOf(character) then
							v.Transparency = 0
						end
					end

					conState.renderdistance = false
				else
					warn("[PERCLEAN] YOU CANNOT RUN THIS FUNCTION IN SERVER, ONLY IN CLIENT")
				end
			else
				warn("[PERCLEAN] RENDERDISTANCE HAS ALREADY BEEN DISABLED")
			end
		elseif stringMatch(effectType, "hideparticles") then
			if conState.renderdistance == true then

				if RNS:IsClient() then
					
					for i, particle in pairs(workspace:GetDescendants()) do
						if particle:IsA("ParticleEmitter") or particle:IsA("Beam") or particle:IsA("Trail")  or particle:IsA("Sparkles") or particle:IsA("Smoke") or particle:IsA("Fire") then
							particle.Enabled = true
						end
					end
					
					for i, particle in pairs(affectedParticles) do -- if your wondering why bother its because it stops particles that were removed during a period of less detail from hiding when it is turned off.
						particle.Enabled = true
						affectedParticles[particle] = nil
					end
					
					_G.ParticleConnection:Disconnect() -- gotta keep your game tidy
											
					conState.renderdistance = false
				else
					warn("[PERCLEAN] YOU CANNOT RUN THIS FUNCTION IN SERVER, ONLY IN CLIENT")
				end
			else
				warn("[PERCLEAN] HIDEPARTICLES HAS ALREADY BEEN DISABLED")
			end
		end
	end
end

return PercleanModule

4 Likes

Oh hey, that’s a pretty cool fork you got there.

2 Likes

New Version Out!


image

I present to you… GraphicsEditor!

GraphicsEditor supersedes Perclean. The difference is, that I rewrote the module from scratch. Unlike Perclean, this utilizes OOP instead of folders for its inner workings. It is also more efficient, has more options, is easier to use, and the code is more organized which makes it easier to update this module.

You can think of this as an update to Perclean, only now it has a different name.

I WILL NOT BE MAINTANING PERCLEAN ANYMORE, SWITCH TO GRAPHICSEDITOR FOR FREQUENT UPDATES!

Do note that it still functions the exact way as Perclean and has the same purpose!

2 Likes