Day-Night Cycles With Different Noon-Night Durations

Hi, I wanted to make a simple module that makes the celestials move while I can modify how much time to spend at day time and how much time to spend at night time. I wanted to sync the time with server for timed spawnables that spawns based on day time. So, for that, functions include workspace:GetServerTimeNow().

Currently module includes:

  • A function to get day time
  • A function to calculate how much time is left to Daytime from current time, including next day, while keeping in mind the durations of noon-night.
    -Current time can also be given as an argument so you can calculate time left to daytime from your given current time
Module
local clientFunctions = {}

--[[
	* a function that calculate how much time is left to reach the daytime
	* @param daytime: daytime in seconds
	* @param currentDaytime: a param to overwrite current daytime
	* @returns how much time is left until daytime (Including Different Noon - Night Lengths)
]]
clientFunctions.getTimeLeftUntilDayTimeInSeconds = function(daytime: number, currentDaytime: number?): number
	assert(daytime <= 86400, "Day Time cannot exceed 86400 seconds.")
	assert(daytime > 0, "Day Time must be bigger than 0 seconds.")
	assert(daytime == math.round(daytime), "Day Time should be an integer.")
	if currentDaytime then
		assert(currentDaytime <= 86400, "Day Time cannot exceed 86400 seconds.")
		assert(currentDaytime > 0, "Day Time must be bigger than 0 seconds.")
	end

	local noonLengthInSeconds = workspace:GetAttribute("NoonLengthInSeconds")
	local nightLengthInSeconds = workspace:GetAttribute("NightLengthInSeconds")

	local defaultNightLengthInSeconds = 43200 --(between 18 - 6)
	local defaultNoonLengthInSeconds = 43200 --(between 6 - 18)

	local currentDayTimeInSeconds = currentDaytime or clientFunctions.getDayTime() * 60

	local timeLeft = 0
	if currentDayTimeInSeconds > daytime then --daytime is in the next day
		timeLeft +=
			if daytime > 64800 then --next day goes beyond noon times
				(nightLengthInSeconds / 2) -- night time after midnight
				+ noonLengthInSeconds -- noon time
				+ ((daytime - 64800) / (defaultNightLengthInSeconds / 2)) * (nightLengthInSeconds / 2) -- night time after noon, to daytime
			elseif daytime >= 21600 then --next day goes to noon times
				(nightLengthInSeconds / 2) -- night time after midnight
				+ ((daytime - 21600) / defaultNoonLengthInSeconds) * noonLengthInSeconds -- noon time, to daytime
			else (daytime / (defaultNightLengthInSeconds / 2)) * (nightLengthInSeconds / 2) -- night time after midnight, to daytime

		--now calculate today
		timeLeft +=
			if currentDayTimeInSeconds > 64800 then --currently at night time after noon
				(((defaultNightLengthInSeconds / 2) - (currentDayTimeInSeconds - 64800)) / (defaultNightLengthInSeconds / 2)) * (nightLengthInSeconds / 2) -- current time to midnight
			elseif currentDayTimeInSeconds >= 21600 then --next day goes to noon times
				((defaultNoonLengthInSeconds - (currentDayTimeInSeconds - 21600)) / defaultNoonLengthInSeconds) * noonLengthInSeconds -- noon time, to daytime
				+ (nightLengthInSeconds / 2) -- night time after noon
			else --currently at night time after midnight
				(((defaultNightLengthInSeconds / 2) - currentDayTimeInSeconds) / (defaultNightLengthInSeconds / 2)) * (nightLengthInSeconds / 2) -- current time to noon
				+ noonLengthInSeconds -- noon time
				+ (nightLengthInSeconds / 2) -- night time after noon
	else --daytime is in current day
		local timeLeftFromCurrentTimeToMidnight =
			if currentDayTimeInSeconds > 64800 then
				(((defaultNightLengthInSeconds / 2) - (currentDayTimeInSeconds - 64800)) / (defaultNightLengthInSeconds / 2)) * (nightLengthInSeconds / 2)
			elseif currentDayTimeInSeconds >= 21600 then
				((defaultNoonLengthInSeconds - (currentDayTimeInSeconds - 21600)) / defaultNoonLengthInSeconds) * noonLengthInSeconds
				+ (nightLengthInSeconds / 2)
			else
				(((defaultNightLengthInSeconds / 2) - currentDayTimeInSeconds) / (defaultNightLengthInSeconds / 2)) * (nightLengthInSeconds / 2)
				+ noonLengthInSeconds
				+ (nightLengthInSeconds / 2)
		local timeLeftFromDayTimeToMidnight =
			if daytime > 64800 then
				(((defaultNightLengthInSeconds / 2) - (daytime - 64800)) / (defaultNightLengthInSeconds / 2)) * (nightLengthInSeconds / 2)
			elseif daytime >= 21600 then
				((defaultNoonLengthInSeconds - (daytime - 21600)) / defaultNoonLengthInSeconds) * noonLengthInSeconds
				+ (nightLengthInSeconds / 2)
			else
				(((defaultNightLengthInSeconds / 2) - daytime) / (defaultNightLengthInSeconds / 2)) * (nightLengthInSeconds / 2)
				+ noonLengthInSeconds
				+ (nightLengthInSeconds / 2)

		timeLeft += timeLeftFromCurrentTimeToMidnight - timeLeftFromDayTimeToMidnight
	end

	return timeLeft
end

--returns current day time in MINUTES
clientFunctions.getDayTime = function()
	local noonLengthInSeconds = workspace:GetAttribute("NoonLengthInSeconds")
	local nightLengthInSeconds = workspace:GetAttribute("NightLengthInSeconds")
	local totalLengthInSeconds = noonLengthInSeconds + nightLengthInSeconds
	local defaultLengthInSeconds = 86400

	local noonNightLengthDifference = noonLengthInSeconds / nightLengthInSeconds
	local differenceBetweenTotalLengthAndDefaultLength = totalLengthInSeconds / defaultLengthInSeconds

	local nightStartTimeInHours = 24 - ((24 / (1 + noonNightLengthDifference)) / 2)
	local nightEndTimeInHours = 24 - nightStartTimeInHours --also refers to night Length / 2
	local nightLengthInHours = nightEndTimeInHours * 2

	local currentTimeInSecondsWithoutWeight = workspace:GetServerTimeNow() % totalLengthInSeconds

	local alpha
	if currentTimeInSecondsWithoutWeight > nightStartTimeInHours * 3600 * differenceBetweenTotalLengthAndDefaultLength then --night time between 18-24
		local timePassedSinceNightStart = currentTimeInSecondsWithoutWeight - nightStartTimeInHours * 3600 * differenceBetweenTotalLengthAndDefaultLength
		local nightLengthInSecondsRelativeToGameTime = ((nightLengthInHours) * 3600 * differenceBetweenTotalLengthAndDefaultLength)

		alpha = timePassedSinceNightStart / nightLengthInSecondsRelativeToGameTime * 2

		return (18 + (alpha * 6)) * 60
	elseif currentTimeInSecondsWithoutWeight < nightEndTimeInHours * 3600 * differenceBetweenTotalLengthAndDefaultLength then --night time between 0-6
		--you can change it if you want, currently time passed is negative because we are in next day. So I add stuff to alpha to fix it but you can modify timePassedSinceNightStart if this looks weird/complicated
		local timePassedSinceNightStart = currentTimeInSecondsWithoutWeight - nightStartTimeInHours * 3600 * differenceBetweenTotalLengthAndDefaultLength
		local nightLengthInSecondsRelativeToGameTime = ((nightLengthInHours) * 3600 * differenceBetweenTotalLengthAndDefaultLength)

		alpha = 1 + ((timePassedSinceNightStart / nightLengthInSecondsRelativeToGameTime) + noonNightLengthDifference) * 2

		return (alpha * 6) * 60
	else --day time
		alpha = 1 - ((nightStartTimeInHours * 3600 * differenceBetweenTotalLengthAndDefaultLength - currentTimeInSecondsWithoutWeight) / ((24 - nightLengthInHours) * 3600 * differenceBetweenTotalLengthAndDefaultLength))
		return (6 + alpha * 12) * 60
	end
end

return clientFunctions

For usage you should set some attributes to workspace like this:
image

After that, this is how I use it client-sided:

local Lighting = game:GetService("Lighting")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

--name it whatever you want
local clientFunctions = require(ReplicatedStorage.Modules.ClientFunctions) --path to the module

local dayNightCycle = function()
    Lighting:SetMinutesAfterMidnight(clientFunctions.getDayTime())
end

RunService:BindToRenderStep("Day-Night Cycle", 1000, dayNightCycle)

--clientFunctions.isDayNightCycleActive = true
--i recommend saving it to a module that you store informations at
  --(both the function and boolean)
--so it is usable by stuff like ZonePlus, day night tweening for effects etc. which most likely at another script.
--That will allow you to unbind from render and bind it again when necessary

And on server-side, if you need, you can use this system to sync some spawnables / map design changes etc with daytimes like me.

This is my first open-sourced module, thanks for reading. Lemme know if there is any mistakes. Feel free to modify / fix (if any) / use it.

Just wanted to share if you want to do something similar or just check it out.

6 Likes

I think it’s very useful module, thank you for your contribution :+1:

1 Like

Great work! I’d always wanted to be able to customize how long night time lasts in a day cycle.

Would it be possible to provide a demo place file (rbxl) with the module already set up? It calls for a missing function called “BetterFunctions” as well as having a particular folder set-up in ReplicatedStorage.

Thanks!

oh, mb. I modified it for devforum. I think I forgot to modify a single part. Fixed the issue.
Here is the demo:
Day-Night Cycle.rbxl (56.3 KB)

1 Like

Here is one example of getTimeLeftUntilDayTimeInSeconds, as it is the powerful one and i didn’t put any information.

I won’t make any templates, it should be easy to understand with some knowledge.

task.spawn(function()
    repeat --we wait until day-night cycle loads and changes our basic ClockTime, which is 14.5 for me (just to be sure)
        task.wait()
    until clientFunctions.isDayNightCycleActive == true and Lighting.ClockTime ~= 14.5

    while true do
        local dayTime = clientFunctions.getDayTime()
        local isCurrentlyDayTime = math.round(dayTime / 60) >= 6 and math.round(dayTime / 60) < 18
        informations.DefaultAtmosphere = isCurrentlyDayTime and { --day time
            Atmosphere = {
                --desired daytime atmosphere properties
            },
            Lighting = {
                --desired daytime lighting properties
            },
            ColorCorrection = {
                --desired daytime color correction properties
            },
            DepthOfField = {
                --desired daytime depth of field properties
            },
            Clouds = {
                --desired daytime cloud properties
            }
        } or { --night time
            Atmosphere = {
                --desired nighttime atmosphere properties
            },
            Lighting = {
                --desired nighttime lighting properties
            },
            ColorCorrection = {
                --desired nighttime color correction properties
            },
            DepthOfField = {
                --desired nighttime depth of field properties
            },
            Clouds = {
                --desired nighttime cloud properties
            }
        }
        if information.isPlayerInsideDefaultZone then
            --i included this section to showcase how do i do it, as an example
            --set lighting
            TweenService:Create(Lighting.Atmosphere, TweenInfo.new(.5), informations.DefaultAtmosphere.Atmosphere):Play()
            TweenService:Create(Lighting, TweenInfo.new(.5), { --we don't want to touch clock time, as some zones might stop it
                Brightness = informations.DefaultAtmosphere.Lighting.Brightness, ExposureCompensation = informations.DefaultAtmosphere.Lighting.ExposureCompensation
            }):Play()
            TweenService:Create(Lighting.ColorCorrection, TweenInfo.new(.5), informations.DefaultAtmosphere.ColorCorrection):Play()
            TweenService:Create(Lighting.DepthOfField, TweenInfo.new(.5), {FocusDistance = informations.DefaultAtmosphere.DepthOfField.FocusDistance, FarIntensity = informations.DefaultAtmosphere.DepthOfField.FarIntensity, InFocusRadius = informations.DefaultAtmosphere.DepthOfField.InFocusRadius}):Play()
            Lighting.DepthOfField.Enabled = informations.DefaultAtmosphere.DepthOfField.Enabled
            --set terrain
            TweenService:Create(workspace.Terrain.Clouds, TweenInfo.new(.5), informations.DefaultAtmosphere.Clouds):Play()

            handleAreaNotifications({ --also something like this can be made for rpg type games for more effect
                Name = isCurrentlyDayTime and "Day Time" or "Night Time",
                Description = isCurrentlyDayTime and "Sunlight dances on the mountain peak." or "Dark clouds swirl with a menacing aura.",
                NotificationTime = 3
            })
        end
        --waits until 6 if it is night time, or 18 if it is daytime
        task.wait(clientFunctions.getTimeLeftUntilDayTimeInSeconds(isCurrentlyDayTime and 64800 or 21600))
    end
end)

This way you can easily customize how your world looks like on client side. Maybe you want to make nights brighter etc., this should help you.

But real power it has that you can use the same ability server-sided.

1 Like