Skip to content

Instantly share code, notes, and snippets.

@mvyasu
Created May 17, 2026 01:44
Show Gist options
  • Select an option

  • Save mvyasu/3123c07817f0011e3f650f95de4e11a5 to your computer and use it in GitHub Desktop.

Select an option

Save mvyasu/3123c07817f0011e3f650f95de4e11a5 to your computer and use it in GitHub Desktop.
an applyLighting function that can handle tweens and complex setups
const TweenService = game:GetService("TweenService")
const Lighting = game:GetService("Lighting")
const TWEENABLE_TYPES: { [string]: boolean } = {
number = true,
Color3 = true,
Vector2 = true,
Vector3 = true,
CFrame = true,
UDim = true,
UDim2 = true,
Rect = true,
Vector2int16 = true,
Vector3int16 = true,
}
const CONTAINER_POOL_CLASSES: { [string]: { string } } = {
Lighting = { "Sky", "Atmosphere", "PostEffect" },
Camera = { "PostEffect" },
Terrain = { "Clouds" },
}
type InstanceProperties = { [string]: any }
type ContainerClassName = "Lighting" | "Camera"
type Preset = {
Lighting: InstanceProperties,
Atmosphere: InstanceProperties?,
Sky: InstanceProperties?,
Clouds: InstanceProperties?,
PostEffects: {
[ContainerClassName]: {
[string]: { InstanceProperties } | InstanceProperties,
},
}?,
}
local activeTweens: { Tween } = {}
local function cancelActiveTweens()
for _, tween in activeTweens do
tween:Cancel()
end
table.clear(activeTweens)
end
local function separateProperties(properties: InstanceProperties): (InstanceProperties, InstanceProperties)
local tweenableProps: InstanceProperties = {}
local immediateProps: InstanceProperties = {}
for key, value in properties do
if key == "Parent" then
continue
end
if TWEENABLE_TYPES[typeof(value)] then
tweenableProps[key] = value
else
immediateProps[key] = value
end
end
return tweenableProps, immediateProps
end
local function applyProperties(instance: Instance, properties: InstanceProperties, tweenInfo: TweenInfo?)
local tweenableProps, immediateProps = separateProperties(properties)
for key, value in immediateProps do
(instance :: any)[key] = value
end
if tweenInfo ~= nil and next(tweenableProps) ~= nil then
local tween = TweenService:Create(instance, tweenInfo, tweenableProps)
table.insert(activeTweens, tween)
tween:Play()
else
for key, value in tweenableProps do
(instance :: any)[key] = value
end
end
end
local function buildPool(container: Instance, relevantClasses: { string }): { Instance }
local pool: { Instance } = {}
for _, child in container:GetChildren() do
for _, className in relevantClasses do
if child:IsA(className) then
table.insert(pool, child)
break
end
end
end
return pool
end
local function findBestMatch(
pool: { Instance },
className: string,
properties: InstanceProperties?
): (Instance?, number?)
local nameMatch: Instance? = nil
local nameMatchIndex: number? = nil
local classMatch: Instance? = nil
local classMatchIndex: number? = nil
for index, object in pool do
local classMatches = object.ClassName == className
local nameMatches = properties ~= nil and (object :: any).Name == properties.Name
if classMatches and nameMatches then
return object, index
end
if nameMatches and nameMatch == nil then
nameMatch = object
nameMatchIndex = index
end
if classMatches and classMatch == nil then
classMatch = object
classMatchIndex = index
end
end
if nameMatch ~= nil then
return nameMatch, nameMatchIndex
end
return classMatch, classMatchIndex
end
local function setupObject(
container: Instance,
pool: { Instance },
className: string,
properties: InstanceProperties?,
tweenInfo: TweenInfo?
)
if properties == nil then
return
end
local existingInstance, poolIndex = findBestMatch(pool, className, properties)
local instance: Instance
if existingInstance ~= nil then
instance = existingInstance
table.remove(pool, poolIndex :: number)
else
instance = Instance.new(className)
end
applyProperties(instance, properties, tweenInfo)
if instance.Parent ~= container then
instance.Parent = container
end
end
local function cleanupPool(pool: { Instance })
for _, instance in pool do
instance.Parent = nil
end
end
local function resolveContainer(containerName: ContainerClassName, camera: Camera): Instance?
if containerName == "Lighting" then
return Lighting
elseif containerName == "Camera" then
return camera
end
return nil
end
local function applyPostEffects(
postEffects: { [ContainerClassName]: { [string]: { InstanceProperties } | InstanceProperties } },
containerPools: { [Instance]: { Instance } },
camera: Camera,
tweenInfo: TweenInfo?
)
for containerName, containerDescendants in postEffects do
local container = resolveContainer(containerName, camera)
if container == nil then
continue
end
local pool = containerPools[container]
if pool == nil then
continue
end
for postEffectClass, postEffectData in containerDescendants do
local _, firstValue = next(postEffectData)
if typeof(firstValue) == "table" then
for _, properties in postEffectData :: { InstanceProperties } do
setupObject(container, pool, postEffectClass, properties, tweenInfo)
end
else
setupObject(container, pool, postEffectClass, postEffectData :: InstanceProperties, tweenInfo)
end
end
end
end
local function applyLighting(preset: Preset, tweenInfo: TweenInfo?)
cancelActiveTweens()
local terrain = workspace.Terrain
local camera = workspace.CurrentCamera
local lightingPool = buildPool(Lighting, CONTAINER_POOL_CLASSES.Lighting)
local terrainPool = buildPool(terrain, CONTAINER_POOL_CLASSES.Terrain)
local cameraPool = buildPool(camera, CONTAINER_POOL_CLASSES.Camera)
local containerPools: { [Instance]: { Instance } } = {
[Lighting] = lightingPool,
[camera] = cameraPool,
}
applyProperties(Lighting, preset.Lighting, tweenInfo)
setupObject(Lighting, lightingPool, "Sky", preset.Sky, tweenInfo)
setupObject(Lighting, lightingPool, "Atmosphere", preset.Atmosphere, tweenInfo)
setupObject(terrain, terrainPool, "Clouds", preset.Clouds, tweenInfo)
if preset.PostEffects ~= nil then
applyPostEffects(preset.PostEffects, containerPools, camera, tweenInfo)
end
cleanupPool(lightingPool)
cleanupPool(terrainPool)
cleanupPool(cameraPool)
end
return applyLighting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment