Syncing the sun position to the in-game time

sun
I was browsing Reddit and someone needed some help with the position of the sun in the sky based on in-game time: https://www.reddit.com/r/godot/comments/1onthst/need_a_hand_writing_a_function_for_daynight_cycle/

Someone recommended using Curves, and I thought it a nice enough little project to try to implement in 2D using _draw():

extends Control

const _SECONDS_IN_ONE_DAY: int = 86400
const _TIME_SCALE: int = 60 * 60

const _SUN_RADIUS: float = 50.0
const _SUN_COLOR := Color.WHEAT

@export var sun_path: Curve

var _sun_position := Vector2(-_SUN_RADIUS, -_SUN_RADIUS):
	set(new_value):
		_sun_position = new_value
		queue_redraw()

var _time: float:
	set(new_value):
		if new_value > _SECONDS_IN_ONE_DAY:
			_time = new_value as int % _SECONDS_IN_ONE_DAY
		elif new_value < 0 or is_equal_approx(new_value, _SECONDS_IN_ONE_DAY):
			_time = 0.0
		else:
			_time = new_value
		if _time as int % 60 == 0: # Once every in-game minute
			_calculate_sun_position()

func _physics_process(delta: float) -> void:
	_time += delta * _TIME_SCALE

func _draw() -> void:
	draw_circle(_sun_position, _SUN_RADIUS, _SUN_COLOR)

func _calculate_sun_position() -> void:
	var normalized_time := _time / _SECONDS_IN_ONE_DAY
	var canvas_size := Vector2(size.x + _SUN_RADIUS * 2, size.y - _SUN_RADIUS * 2)
	var new_position := Vector2(
			canvas_size.x * normalized_time - _SUN_RADIUS,
			canvas_size.y - (canvas_size.y * sun_path.sample(normalized_time)) + _SUN_RADIUS
	)
	_sun_position = new_position

Feel free to copy-paste this into a Control node and mess around with it. Don’t forget to add a Curve in the inspector. One in-game hour passes every second.

Anything obviously wrong with this? How would you’ve gone about it? I was wondering about reducing calls to redraw, but I thought it’s good enough as a proof of concept.

2 Likes

I have not tried it, but I will when I get some time. For a proof of concept it is great.

I am uncomfortable with multiple queue_redraws being called in the _calculate_sun_position, which is called every time the _time is set, which is called in your physics_process. I would put that stuff into a _process function so it is calculated only once per frame, and remove the set function from the _time variable.

Although all that probably gets put into one draw call, it is a lot of re-calculating in physics process for something that only is needed once per draw call, or per frame.

2 Likes

Thanks. I went over it again and, without getting rid of the setter, I simply added a check to update the position once every however long one thinks they’ll need. For example once every hour:

var _time: float:
	set(new_value):
		if new_value > _SECONDS_IN_ONE_DAY:
			_time = new_value as int % _SECONDS_IN_ONE_DAY
		elif new_value < 0 or is_equal_approx(new_value, _SECONDS_IN_ONE_DAY):
			_time = 0.0
		else:
			_time = new_value
		if _time as int % (60 * 60) == 0:
			_calculate_sun_position()

With it being that slow, I also found a bug. The first circle is drawn at (0, 0) since that’s what an uninitialized Vector2 initializes to. So this should be changed as well, to make it display off screen:

var _sun_position := Vector2(-_SUN_RADIUS, -_SUN_RADIUS)

This wasn’t entirely necessary though, since _queue_redraw() only redraws once per frame anyway. I kinda missed that as well. From the docs:

Queues the CanvasItem to redraw. During idle time, if CanvasItem is visible, NOTIFICATION_DRAW is sent and _draw() is called. This only occurs once per frame, even if this method has been called multiple times.

Will add the modifications to the original post. It will at least avoid making pointless function calls.

1 Like

True, and I mentioned that, and I did say I was uncomfortable with it, not that it would not work.

I am not sure about drawing the sun every hour either, but it is only a proof of concept. If the sun was visible on screen at the hour change, the sun would suddenly change position. I am wondering if you need the _physics_process at all, and it could just be in an _process.

Anyway, it is still very nice. In my game I am planning on having a day/night sequence (at least I am going to be prototyping it at a later date) and having a moving sun would actually be really nice, so I might be using something like this in the near future. Thanks for sharing!

2 Likes

It could, although using _physics_process will update the delta 60 times per second, no matter the framerate. In a situation where you disable vsync and you don’t have a frame cap, putting that in _process could update the delta thousands of times per second.

In an ideal scenario, you’d simply update it once a second (since you just want to calculate in-game seconds by using real life seconds).

You could actually pre-calculate all the positions in advance and turf the process calculations altogether.

2 Likes