My Smaller Modules [Open Source!]

I’ve made many large projects that each got their own threads. However, I’ve also made smaller things that I just dropped into the What Are You Working On thread. They get lost in the thousands of replies, so I’ll be putting them all here for easier access, and any new ones will go here instead.


ClickDetectors

November 2019

The ClickDetector Instances have very shoddy behavior, and I found them to be unusable.
A MouseHoverEnter event doesn’t always fire a MouseHoverLeave event afterwards. Additionally, sometimes Enter fired on the next detector before the previous one fired Leave.

Differences between my custom Lua ClickDetector vs the Instance version:

  • Mine has stable and reliable behavior. Every Enter is sure to have a Leave fired after (unless you never leave :stuck_out_tongue:) and Leave will always fire before the Enter fires on the next one.

  • Mine has a read-only .Hovering property. Instead of having to hook Enter and Leave and track it in a variable, you can just check this property when needed. I found this useful, hope you agree.

  • Mine has a .Enabled property, so you can just set that to false for enter events to stop firing. For some reason, the Instance versions don’t have a way to be disabled.

  • Mine does not put Instances inside the part, which I find nice for avoiding clutter.

  • The Instance ClickDetector APIs are named differently than everything else. They’re verbose and old. I hate that. Mine follows the API of a GuiButton. Instead of MouseHoverEnter, it’s MouseEnter.

  • Mine doesn’t replicate to the server. It’s entirely clientside. Worth noting, however, that the Instance is super insecure and people have to write modules to be able to use it on the server reasonably.

It’s not entirely complete. Here’s what missing (will be added later):

  • MouseButton1Click (You can use MouseButton1Down and MouseButton1Up for now)
  • MouseButton2Click (You can use MouseButton2Down and MouseButton2Up for now)
Source code:
--[=[
	
	Example usage:
	
local ClickDetector = require(script.ClickDetector)

local Part = ClickDetector.new(workspace:WaitForChild("Part"))
	Part.MaxActivationDistance = 15
	Part.CursorIcon = "http://www.roblox.com/asset/?id=4317746479"

	Part.MouseEnter:Connect(function()
		print("Entered part")
	end)
	Part.MouseLeave:Connect(function()
		print("Left part")
	end)
	
	Part.MouseButton1Down:Connect(function()
		print("Clicked part")
	end)
	
	
--]=]


local ClickDetector = {}


local UIS = game:GetService("UserInputService")

local BINDABLE = Instance.new("BindableEvent")

local Cam = workspace.CurrentCamera
local Plr = game.Players.LocalPlayer
local Mouse = Plr:GetMouse()
local Char = Plr.Character or Plr.CharacterAdded:Wait()
local HRP = Char:WaitForChild("HumanoidRootPart")



local ActiveDetectors,CurrentHover,last_t,last_i = {},nil,nil,Mouse.Icon

Cam:GetPropertyChangedSignal("CFrame"):Connect(function()
	
	local h = ActiveDetectors[last_t]
	
	if h then
		if (HRP.Position - h._Internal.Adornee.Position).Magnitude <=h.MaxActivationDistance and h.Enabled then
			--Within distance
			if not h.Hovering then
				-- not yet set to hover, do it
				rawset(h, "Hovering", true)
				h._Internal.EnterEvent:Fire()
				
				Mouse.Icon = h.CursorIcon or ""
				
				CurrentHover = h
			end
		else
			-- Far away
			if h.Hovering then
				-- set to hover, undo
				rawset(h, "Hovering", false)
				h._Internal.LeaveEvent:Fire()
				
				Mouse.Icon = ""
				
				if CurrentHover == h then
					CurrentHover = nil
				end
			end
		end
	end
	
end)

UIS.InputChanged:Connect(function(Input)
	if Input.UserInputType == Enum.UserInputType.MouseMovement then
		
		-- Handle hover detection
		local t = Mouse.Target
		
		if t ~= last_t then last_t = t
			local h = ActiveDetectors[t]
			
			-- Leave old
			if CurrentHover and CurrentHover._Internal.Adornee ~= t then
				rawset(CurrentHover, "Hovering", false)
				CurrentHover._Internal.LeaveEvent:Fire()
				
				Mouse.Icon = ""
			end
			
			-- Enter new
			if h and (HRP.Position - h._Internal.Adornee.Position).Magnitude <=h.MaxActivationDistance and h.Enabled then
				rawset(h, "Hovering", true)
				h._Internal.EnterEvent:Fire()
				
				Mouse.Icon = h.CursorIcon or ""
				
				CurrentHover = h
			else
				CurrentHover =  nil
			end
		end
		
	end
end)
UIS.InputBegan:Connect(function(Input,GP)
	
	if not CurrentHover then return end
	
	if Input.UserInputType == Enum.UserInputType.MouseButton1 then
		
		CurrentHover._Internal.Button1DownEvent:Fire()
		
	elseif Input.UserInputType == Enum.UserInputType.MouseButton2 then
		
		CurrentHover._Internal.Button2DownEvent:Fire()
	
	end
end)
UIS.InputEnded:Connect(function(Input,GP)
	
	if not CurrentHover then return end
	
	if Input.UserInputType == Enum.UserInputType.MouseButton1 then
		CurrentHover._Internal.Button1UpEvent:Fire()
	elseif Input.UserInputType == Enum.UserInputType.MouseButton2 then
		CurrentHover._Internal.Button2UpEvent:Fire()
	end
end)


function ClickDetector.new(BasePart)
	
	--Type check
	if typeof(BasePart)~="Instance" or not BasePart:IsA("BasePart") then
		error("Invalid BasePart for click detection "..BasePart,2)
	end
	
	local EnterEvent = BINDABLE:Clone()
	local LeaveEvent = BINDABLE:Clone()
	
	local Button1DownEvent = BINDABLE:Clone()
	local Button2DownEvent = BINDABLE:Clone()
	
	local Button1UpEvent = BINDABLE:Clone()
	local Button2UpEvent = BINDABLE:Clone()
	
	local Detector = {
		
		Enabled		= true;
		MaxActivationDistance = 32;
		CursorIcon	= "";
		
		Hovering	= false;
		
		
		MouseEnter	= EnterEvent.Event;
		MouseLeave	= LeaveEvent.Event;
		
		MouseButton1Down	= Button1DownEvent.Event;
		MouseButton2Down	= Button2DownEvent.Event;
		
		MouseButton1Up	= Button1UpEvent.Event;
		MouseButton2Up	= Button2UpEvent.Event;
		
		
		
		_Internal	= {
			EnterEvent = EnterEvent;
			LeaveEvent = LeaveEvent;
			
			Button1DownEvent	= Button1DownEvent;
			Button2DownEvent	= Button2DownEvent;
			
			Button1UpEvent	= Button1UpEvent;
			Button2UpEvent	= Button2UpEvent;
			
			Adornee		= BasePart;
		};
		
	}
	
	function Detector:Destroy()
		
		ActiveDetectors[self._Internal.Adornee] = nil
		
		EnterEvent:Destroy()
		LeaveEvent:Destroy()
		
		Button1UpEvent:Destroy()
		Button2UpEvent:Destroy()
		
		if self.Hovering then
			Mouse.Icon = ""
		end
		
		self._Internal.Adornee = nil
	end
	
	-- Catch inputs
	ActiveDetectors[BasePart] = Detector
	
	return Detector
end

return ClickDetector


Film Grain

October 2019

Wrote a short and simple script to add a film grain effect to your game, with easy customization.

Really doesn’t show up in a compressed video. :frowning:

Source code
--[=[
	
:::::::::: ::::::::::: :::        ::::    ::::    ::::::::  :::::::::      :::     ::::::::::: ::::    ::: 
:+:            :+:     :+:        +:+:+: :+:+:+  :+:    :+: :+:    :+:   :+: :+:       :+:     :+:+:   :+: 
+:+            +:+     +:+        +:+ +:+:+ +:+  +:+        +:+    +:+  +:+   +:+      +:+     :+:+:+  +:+ 
:#::+::#       +#+     +#+        +#+  +:+  +#+  :#:        +#++:++#:  +#++:++#++:     +#+     +#+ +:+ +#+ 
+#+            +#+     +#+        +#+       +#+  +#+   +#+# +#+    +#+ +#+     +#+     +#+     +#+  +#+#+# 
#+#            #+#     #+#        #+#       #+#  #+#    #+# #+#    #+# #+#     #+#     #+#     #+#   #+#+# 
###        ########### ########## ###       ###   ########  ###    ### ###     ### ########### ###    #### 


by boatbomber, 2019

--]=]
-- Put this as a LocalScript and tweak the settings to your liking.


-- Settings

local GRAIN_SIZE		= 50	-- A number 1 to 100
local GRAIN_SPEED		= 50	-- A number 1 to 100
local GRAIN_VISIBILITY	= 15	-- A number 1 to 100

---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------

local udim2	= UDim2.new
local rand	= math.random
local plr	= game.Players.LocalPlayer
local plrGui= plr:WaitForChild("PlayerGui")

local baseSize	= 300*(GRAIN_SIZE*0.02)
local speed		= 1/(40*(GRAIN_SPEED*0.02))
local vis		= 1.05-(GRAIN_VISIBILITY*0.01)

local GrainGui = Instance.new("ScreenGui")
	GrainGui.Name = "FilmGrain"
	GrainGui.IgnoreGuiInset = true
	GrainGui.DisplayOrder = 99
	GrainGui.Parent = plrGui

local GrainImage = Instance.new("ImageLabel")
	GrainImage.Size = udim2(1,0,1,0)
	GrainImage.BackgroundTransparency = 1
	GrainImage.ImageTransparency = vis
	GrainImage.ScaleType = Enum.ScaleType.Tile
	GrainImage.Image = "http://www.roblox.com/asset/?id=28756351"
	GrainImage.Parent = GrainGui


local last = 0
game:GetService("RunService").Heartbeat:Connect(function()
	if tick()-last < speed then return end
	
	last = tick()	
	GrainImage.TileSize = udim2(rand(baseSize*0.89,baseSize*1.11)/1000,0,rand(baseSize*0.89,baseSize*1.11)/1000,0)
end)

Filters

June 2019

It quickly and easily applies filters to your world, on-the-fly.
A while after I made this, someone made a wonderful plugin called LightPlus+ that serves the same purpose but only in studio. Mine is more for a “photo mode” in game that allows filters. It comes with a few filters, and you can easily add your own.

Demo World:

Source code
local LightingService	= game:GetService("Lighting")
local Applying			= false
local Objects			= {}

local module = {
	
	-- To create your own filter, just add another table like these
	-- and give it a unique key so it can be called in module.ApplyFilter
	Filters = {
		
		['None']	= {
			["BloomEffect"]				= {Intensity = 0, Size = 0, Threshold = 5};
			["BlurEffect"]				= {Size = 0};
			["ColorCorrectionEffect"]	= {Brightness = 0, Contrast = 0, Saturation = 0, TintColor = Color3.fromRGB(255,255,255)};
			["SunRaysEffect"]			= {Intensity = 0, Spread = 0};
		};
		
		['Vintage']	= {
			["BloomEffect"]				= {Intensity = 2, Size = 50, Threshold = 1};
			["BlurEffect"]				= {Size = 3};
			["ColorCorrectionEffect"]	= {Brightness = 0.04, Contrast = -0.1, Saturation = -0.05, TintColor = Color3.fromRGB(252,255,233)};
			["SunRaysEffect"]			= {Intensity = 0.1, Spread = 0.1};
		};
		
		['Warm']	= {
			["BloomEffect"]				= {Intensity = 2, Size = 50, Threshold = 1};
			["BlurEffect"]				= {Size = 2};
			["ColorCorrectionEffect"]	= {Brightness = 0.01, Contrast = 0.01, Saturation = 0.1, TintColor = Color3.fromRGB(255,227,180)};
			["SunRaysEffect"]			= {Intensity = 0.1, Spread = 0.1};
		};
		
		['Cool']	= {
			["BloomEffect"]				= {Intensity = 5, Size = 40, Threshold = 1};
			["BlurEffect"]				= {Size = 2};
			["ColorCorrectionEffect"]	= {Brightness = -0.03, Contrast = -0.1, Saturation = -0.1, TintColor = Color3.fromRGB(212,240,255)};
			["SunRaysEffect"]			= {Intensity = 0.05, Spread = 0.03};
		};
		
		['Negative']	= {
			["BloomEffect"]				= {Intensity = 10, Size = 50, Threshold = 1};
			["BlurEffect"]				= {Size = 0};
			["ColorCorrectionEffect"]	= {Brightness = 0.1, Contrast = -1.8, Saturation = -1, TintColor = Color3.fromRGB(235,246,255)};
			["SunRaysEffect"]			= {Intensity = 0.2, Spread = 0.1};
		};
		
		['Realistic']	= {
			["BloomEffect"]				= {Intensity = 1, Size = 38, Threshold = 0.8};
			["BlurEffect"]				= {Size = 2};
			["ColorCorrectionEffect"]	= {Brightness = -0.01, Contrast = 0.06, Saturation = -0.06, TintColor = Color3.fromRGB(255,253,252)};
			["SunRaysEffect"]			= {Intensity = 0.2, Spread = 0.1};
		};
		
		['Western']		= {
			["BloomEffect"]				= {Intensity = 2, Size = 40, Threshold = 1};
			["BlurEffect"]				= {Size = 1};
			["ColorCorrectionEffect"]	= {Brightness = 0.02, Contrast = -0.03, Saturation = -0.4, TintColor = Color3.fromRGB(255,205,123)};
			["SunRaysEffect"]			= {Intensity = 0.1, Spread = 0.7};
		};
		
		['Old School']	= {
			["BloomEffect"]				= {Intensity = 3, Size = 50, Threshold = 1};
			["BlurEffect"]				= {Size = 2};
			["ColorCorrectionEffect"]	= {Brightness = 0.05, Contrast = 0.06, Saturation = -0.95, TintColor = Color3.fromRGB(249,255,255)};
			["SunRaysEffect"]			= {Intensity = 0.5, Spread = 0.02};
		};
		
		['Meme']		= {
			["BloomEffect"]				= {Intensity = 10, Size = 80, Threshold = 1};
			["BlurEffect"]				= {Size = 1};
			["ColorCorrectionEffect"]	= {Brightness = 0.2, Contrast = 1, Saturation = 30, TintColor = Color3.fromRGB(255,255,255)};
			["SunRaysEffect"]			= {Intensity = 0.01, Spread = 0.12};
		};
	
	};
}

function module:ApplyFilter(FilterType)
	
	--Avoid doing two applications at once and making a mess
	if Applying then repeat wait(0.1) until not Applying end Applying = true
	
	--Remove old filter
	for i=1,#Objects do
		Objects[i]:Destroy()
	end
	
	--Find chosen filter
	local Filter = self.Filters[FilterType] or self.Filters.Realistic
	
	--Apply filter
	for obj, props in pairs(Filter) do
		local effect = Instance.new(obj)
		
		for p,v in pairs(props) do
			effect[p] = v
		end
		
		Objects[#Objects+1] = effect
		
		effect.Parent = LightingService
		
	end
	
	--Allow new application
	Applying = false
	
end

return module

53 Likes

Does adding a film grain effect take a toll on client/server performance? It looks amazing but I’m not sure how practical it is.

It shouldn’t cause any performance issues. All the module does is apply color correction effects to Lightning to create the desired filters.

@mircostaff He asked about the grain, not the filters.

@FragmentFour I don’t remember the numbers, and cant check rn. You can run it yourself and just check the Script Performance numbers. I can’t imagine it being that bad though.

I can definitely optimize more if needed.

1 Like

That one is definitely on me. I had the source for the filter code open while reading their question. :confused:

2 Likes

No worries. Easy mistake to make, as they are both visual effects.

Boat “Gui Guy” Bomber

In terms of the ClickDetector API, how does it stack up in terms of performance to the standard ClickDetector?

1 Like

Definitely worse performance than the Instance. The Instance gets handled by the engine whereas mine runs on Lua in-engine. I can’t win that competition.
However, mine is useful in cases where the stable behavior is necessary. It’s trading a bit of performance for more functionality and use.

1 Like

I figured that’d be the case, but it does function as the click detector would for standard purposes, just with added features?

I updated the OP to have a clear dif list after this reply. He didn’t ask before reading, so don’t think his question was redundant. :slight_smile:

I used it in place of a ClickDetector in a secret project of mine, if that’s what you’re asking.

The end behavior is just a stable ClickDetector, even if the internals are different.

You can refer to the dif list in the OP.

Other than those things, they serve the same purpose. They give hover and click detection for BaseParts, with a MaxActivationDistance. Mine is more reliable, while the Instance is more performant.

1 Like

That Subway station doesn’t look like roblox…Anyways thanks for Film grain

2 Likes

I love your lighting module! Any chance you could add in the Warm, Cool, Negative, and Vintage settings as well?

2 Likes

Oh gosh, I didn’t realize I was missing those! I’ll definitely add them in when I have a chance.

EDIT: Added them.

2 Likes

Thanks for sharing these modules. I edited your Filter script to tween between filters instead of deleting old filter and I think you should do that as an option
edited version :

function module:ApplyFilter(FilterType)
	
	if Applying then repeat wait(0.1) until not Applying end Applying = true
	
	local Filter = self.Filters[FilterType] or self.Filters.Realistic
	

local TS = game:GetService("TweenService")
local TI = TweenInfo.new(.5)
	for i,v in pairs(game.Lighting:GetChildren()) do
		if v:IsA("PostEffect") then
			for o,p in pairs(Filter) do
				if v.ClassName == o then
					local goal = {}
						for l,vv in pairs(p) do
						goal[l] = vv
						end
					local a =  TS:Create(v,TI,goal)
					a:Play()
				end
			end
		end 
	end
	
	Applying = false
	
end
1 Like

This is a great idea, but this implementation would accidentally mess with your other in game effects!

You’d need to track the filter module’s Instance effects, and loop through those to only apply to them.

Maybe you can give special name for your filter effect I made like this because I dont have pp-effect other than these and I change it with region3

:slight_smile:

2 Likes