Skip to content

Comments

Prototype: Time of day#1913

Draft
manuq wants to merge 6 commits intomainfrom
timeofday-and-weather
Draft

Prototype: Time of day#1913
manuq wants to merge 6 commits intomainfrom
timeofday-and-weather

Conversation

@manuq
Copy link
Collaborator

@manuq manuq commented Feb 11, 2026

Here is a quick prototype for how a day/night transition can be added to Threadbare. I added it to Fray's End but this is reusable. This could either be a global that is turned off in special levels (like Stealth) or a node to add to every single scene that needs it. The time of day can also be persisted in the game state.

While doing this I did a quick research and remembered my knowledge in photography, filmmaking, and what other games do.

Days depend a lot on the season and the time of the year, and the geographic position. I think it is fine to simplify and make all days the same. At least for now. Also it is better to simplify and make the change bolder, more evident for aesthetics, than making it realistically accurate. For example: making sunrise/sunset happen for a longer time.

I tried to make sunrise and sunset significantly different. Usually sunrise has cooler tones due to atmosphere conditions, while sunsets are more dramatic and turning gold.

About night: Games (or movies) don't usually have a completely dark night (no moon or stars) because players need to see what's going on! So it's an "american night" as it's called in filmmaking. I used exactly the same darkness as the Stealth game, I only tinted it a bit blue. This is a top-down game so we don't see the sky anyways. Although we see its color reflected in the water. We could certainly add a shader to the water, to make its color vary during the time of day.

Night really needs some artificial lights for better effect. Maybe the player should have a spot light too? I made these lights turn on/off with the same time-of-day cycle.

This is only visual, but shouldn't be! A morning is not a morning without the sign of birds. Ambient sound at night is very different: insects, frogs, the sudden scream of an owl.

Next: Improve it a bit. For example, I don't like how artificial lights pop up all at once. Also I would like to play with some simple weather conditions (rain, fog).

recording.webm

On the technical side, the time-of-day cycle is just an animation that tweaks values from a CanvasModulate and WorldEnvironment nodes. For convenience and easy editing it, it's a 24 seconds animation that is then scaled. The animation also changes a property to turn lights on/off.

image

@github-actions
Copy link

Play this branch at https://play.threadbare.game/branches/endlessm/timeofday-and-weather.

(This launches the game from the start, not directly at the change(s) in this pull request.)

@manuq manuq force-pushed the timeofday-and-weather branch 3 times, most recently from b5c3322 to 8d04009 Compare February 11, 2026 17:30
@manuq
Copy link
Collaborator Author

manuq commented Feb 11, 2026

Update: I tweaked a bit the animation and made the lightspots transition. Also added a Ligth2D to the player to be give it a vignette effect during night (as seen in Silksong and many other games).

recording.webm

@manuq manuq force-pushed the timeofday-and-weather branch from c1ed82d to 40b94c5 Compare February 12, 2026 20:19
@manuq
Copy link
Collaborator Author

manuq commented Feb 12, 2026

I added 3 random weather events: fog (during night or early morning), rain, and cloud shadows (during daytime). To simplify they are mutually exclusive and happen very often in the branch, for testing.

recording.webm

For fog and cloud shadows I used the existing ones as reference: Shadow Journey "neblina" and the old clouds overlay. Looking at them, it looks like the tricky part is how to pass the camera offset to the canvas shader. The old clouds overlay does it through a shader parameter, with an offset that needs to be constantly calculated and updated (every process loop, from GDScript to the shader). Shadow Journey does it through repeating the ColorRect with a parallax effect. I went with the parallax effect (updating it to the new Parallax2D node) mainly because the old clouds were removed for performance reasons. Another difference is that the old clouds overlay has a huge noise texture (1920x1080, the old game resolution), while Shadow Journey's has a small one (320x180 px) that is then upscaled. So maybe the performance issue was the huge texture? In any case, I also went with smaller textures: 256x256 for the fog and 170x256 for the cloud shadows. The shadow height is 2/3 its width, to look like it's in perspective. These textures are upscaled to 1024x1024 and then tiled by the Parallax2D node. Both shaders are the same, except the fog uses "add" render mode and the shadows uses "sub", and currently the render mode can't be set conditionally in a canvas shader (there is a bug open for Godot).

Note that the cloud shadows goes above everything (as the old one did). Is not realistically considering elevations. I also randomized these noise textures by changing their seed, and waiting for the texture resource to have changed before showing it. There are more things that can be randomized in the textures. But I already spent too much time with these effects for a prototype!

For the rain I used a particle system, after looking at other games. The nice thing about using this and not a shader is that the amount of particles can be animated, so it can start as a drizzle and become a storm. But the problem of a particle solution is that I couldn't manage to make it scroll well with the camera. I also wanted to add a splat (like in Stardew Valley, they use the same regardless of the rain drop falling in terrain or water!) and Godot has sub emitters for it. But unfortunately particle sub emitters don't work in the Compatibility renderer mode :( . I spent far less time with rain than with the other two.

These effects need to be kept subtle. Because they are only cosmetic. It could be fun to add a small chance to have a very foggy morning, so foggy that nothing can be seen. But it would interfere with gameplay.

Thanks @pablitar and the @endlessm/suenos-nocturnos for the initial efforts.

@manuq manuq force-pushed the timeofday-and-weather branch 4 times, most recently from e8163be to 6a91db2 Compare February 17, 2026 13:08
@manuq manuq mentioned this pull request Feb 17, 2026
@manuq
Copy link
Collaborator Author

manuq commented Feb 17, 2026

Last week I discussed this prototype a bit with @wjt. Time passing and weather changing seems viable for the main areas of the world. Currently, just Fray's End. The proposal is to add time passing in these main areas, while keeping the levels inside quests (either lore or StoryQuests) standstill. The rationale is:

  • It is costly and too much requirement to design every level in a quest to support day, night and weather.
  • Levels may need a specific time of day or weather to work, either for the storytelling, or for puzzle reasons. For example, a stealth level needs to be dark. A StoryQuest may need fog or rain as part of the story.

Even if levels don't have automatic time-passing or weather changes, they can use the individual components. For example, we can promote the fog node in Sueños Nocturnos storyquest to be in the main game as a reusable node.

@wjt
Copy link
Member

wjt commented Feb 18, 2026

My £0.05:

I love this and I think it makes the world seem much more alive.

Suppose we have a second scene connected to Fray's End by a walkable route. I think that the time-of-day and weather should persist between those. So it's dawn & raining when you walk down a path away from Fray's End, it should still be dawn & raining immediately after the scene transition.

I think it matters less whether the time of day and weather persist if you quit the game and immediately reload it.

Personally I would love for us to not have any HUD element that tells you the time, but instead have clocks on buildings in the world that show the time; e.g. a clock in the spire of whatever equivalents of a church the Threadbare world's religions have; or a clock nailed to the side of a tree, or a signpost. I don't know whether it would really be possible to have a legible clock at that size though. :( Maybe we don't need to show the time at all.

Let's indeed make it possible to apply a specific weather effect to a scene. e.g. maybe I want it always to be raining in the musician rock puzzle.

We should document how to:

  • How to apply a specific weather effect to your scene
  • How to apply the full time-of-day/random-weather effect to your scene
  • Why you might want to do the former rather than the latter - and why we have not made time-of-day/weather global.

@wjt
Copy link
Member

wjt commented Feb 18, 2026

Oh, and while I don't think it's necessary for a first iteration, this is crying out for sound effects like a dawn chorus. A great space to leave open to contributors: give each time of day/weather a AudioStreamRandomizer of sound effects, and contributors can add more, e.g. the sound of a typical evening insect in your region.

@manuq manuq force-pushed the timeofday-and-weather branch from 6a91db2 to 46cbe63 Compare February 19, 2026 21:02
@manuq
Copy link
Collaborator Author

manuq commented Feb 20, 2026

Thanks for your £0.05 @wjt. So to conclude this exploration:

  • Make it possible to continue with the same time-of-day and weather in the next scene.
  • Add rain overlay in the main game (I already reinstated the cloud shadows and there is for the fog). I'm not happy with it but it's a start.
  • Add the TimeAndWeather node to Fray's End, using the weather effects. For this I need to find a good time scale. Maybe one hour in game is 24 hours?
  • Add artificial lights (off by default) in house components and in player. So the TimeAndWeather node can animate them. For this I could try the other way round, these components connecting to a signal rather than TimeAndWeather node changing all nodes in a group.
  • Document all as below (time & weather museum?):
  • How to apply a specific weather effect to your scene
  • How to apply the full time-of-day/random-weather effect to your scene
  • Why you might want to do the former rather than the latter - and why we have not made time-of-day/weather global.

@manuq manuq force-pushed the timeofday-and-weather branch from ddd2f1d to afb21de Compare February 23, 2026 18:34
@manuq manuq force-pushed the timeofday-and-weather branch from afb21de to 1e83c34 Compare February 24, 2026 17:15
Comment on lines 109 to 112
var unix_time := Time.get_unix_time_from_system()
var system_seconds := fmod(unix_time, 24 * 60 * 60)
var system_hour := system_seconds / 60.0 / 60.0
var scaled_hour := fmod((system_hour / 24) * 24 * time_scale, 24)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could consider:

var system_hour := Time.get_datetime_dict_from_system()["hour"]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

		var scaled_hour := fmod((system_hour / 24) * 24 * time_scale, 24)

This looks suspicious to me, you're dividing by 24 and immediately multiplying by 24.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am! Thanks

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could consider:

var system_hour := Time.get_datetime_dict_from_system()["hour"]

Oh but gives me the total hour, eg. 15. For the AnimationPlayer etc I need a floating point number between 0 and 24.0, for example 18.9613.

Copy link
Member

@wjt wjt Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. So the current UTC time as I type this is 09:24:00. You want to get (24 * 60) / (60 * 60), i.e. the fraction of the current real world hour, and then multiply this by 24 to get the in-game time, so that one hour of real-world time is one day of in-game time, with xx:00:00 being midnight (0.0) in game and xx:30:00 being noon (12.0) in game.

In that case I think you want:

const SECONDS_PER_MINUTE := 60.0
const MINUTES_PER_HOUR := 60.0
const SECONDS_PER_HOUR := SECONDS_PER_MINUTE * MINUTES_PER_HOUR

var seconds_of_real_world_hour := fmod(Time.get_unix_time_from_system(), SECONDS_PER_HOUR)
var fraction_of_real_world_hour := seconds_of_real_world_hour / SECONDS_PER_HOUR
var hour_of_in_game_day := 24.0 * fraction_of_real_world_hour

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry that I didn't explain my thinking before asking for advice! I was planning to do it when opening the pull request.

OK. So the current UTC time as I type this is 09:24:00. You want to get (24 * 60) / (60 * 60), i.e. the fraction of the current real world hour, and then multiply this by 24 to get the in-game time, so that one hour of real-world time is one day of in-game time, with xx:00:00 being midnight (0.0) in game and xx:30:00 being noon (12.0) in game.

In this case I need the 09:24:00 datetime converted to a float so it would be 9 + (24 / 60) = 9.4 . And I'm not going with one hour of real-wrld -> one day in game, but I'm making it configurable. I took this image from my notebook:

20260225_120310

Let's suppose it's 15 o'clock. Then if the time scale is 1x, (1 day in game = 1 day in reality) the hour_of_in_game_day is also 15. From the documentation string:

## - 1: 15 (same time in game than in system).
## - 2: 6 (30 hours in game have passed today).
## - 4: 12 (noon in game, exactly 2 and a half days have passed in game today).
## - 8: 0 (midnight in game, exactly 5 days have passed in game today).

For the default time scale, I've been looking at what other games do. In Stardew Valley 14 mins in game is 1 day. Since Threadbare is pretty short (the time you spend today in Fray's End is short), I went for 10 mins in game = 1 day (time scale = 144x). But that can be changed later!

In that case I think you want:

const SECONDS_PER_MINUTE := 60.0
const MINUTES_PER_HOUR := 60.0
const SECONDS_PER_HOUR := SECONDS_PER_MINUTE * MINUTES_PER_HOUR

var seconds_of_real_world_hour := fmod(Time.get_unix_time_from_system(), SECONDS_PER_HOUR)
var fraction_of_real_world_hour := seconds_of_real_world_hour / SECONDS_PER_HOUR
var hour_of_in_game_day := 24.0 * fraction_of_real_world_hour

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants